diff --git a/.circleci/config.yml b/.circleci/config.yml index ddcbd5481..5fcb83145 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,22 +1,25 @@ version: 2.1 orbs: - go: gotest/tools@0.0.9 + aws-cli: circleci/aws-cli@1.3.2 + docker: circleci/docker@2.1.4 executors: golang: docker: - - image: circleci/golang:1.14.2 + # Must match GO_VERSION_MIN in project root + - image: cimg/go:1.19.7 + resource_class: medium+ + golang-2xl: + docker: + # Must match GO_VERSION_MIN in project root + - image: cimg/go:1.19.7 resource_class: 2xlarge ubuntu: docker: - - image: ubuntu:19.10 + - image: ubuntu:20.04 commands: - install-deps: - steps: - - go/install-ssh - - go/install: {package: git} - prepare: + build-platform-specific: parameters: linux: default: true @@ -26,258 +29,376 @@ commands: default: false description: is a darwin build environment? type: boolean + darwin-architecture: + default: "amd64" + description: which darwin architecture is being used? + type: string 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 - run: git submodule sync - run: git submodule update --init + - when: + condition: <> + steps: + - install-ubuntu-deps + - check-go-version + - when: + condition: <> + steps: + - run: + name: Install Go + command: | + curl https://dl.google.com/go/go`cat GO_VERSION_MIN`.darwin-<>.pkg -o /tmp/go.pkg && \ + sudo installer -pkg /tmp/go.pkg -target / + - run: + name: Export Go + command: | + echo 'export GOPATH="${HOME}/go"' >> $BASH_ENV + - run: go version + - run: + name: Install dependencies with Homebrew + command: HOMEBREW_NO_AUTO_UPDATE=1 brew install pkg-config coreutils jq hwloc + - run: + name: Install Rust + command: | + curl https://sh.rustup.rs -sSf | sh -s -- -y + - run: make deps download-params: steps: - restore_cache: name: Restore parameters cache keys: - - 'v25-2k-lotus-params' + - 'v26-2k-lotus-params' paths: - /var/tmp/filecoin-proof-parameters/ - - run: ./lotus fetch-params --proving-params 2048 + - run: ./lotus fetch-params 2048 - save_cache: name: Save parameters cache - key: 'v25-2k-lotus-params' + key: 'v26-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 + curl -O https://dist.ipfs.tech/kubo/v0.16.0/kubo_v0.16.0_linux-amd64.tar.gz + tar -xvzf kubo_v0.16.0_linux-amd64.tar.gz + pushd kubo + sudo bash install.sh + popd + rm -rf kubo + rm kubo_v0.16.0_linux-amd64.tar.gz git_fetch_all_tags: steps: - run: name: fetch all tags command: | git fetch --all + install-ubuntu-deps: + steps: + - run: sudo apt-get update + - run: sudo apt-get install ocl-icd-opencl-dev libhwloc-dev + check-go-version: + steps: + - run: | + v=`go version | { read _ _ v _; echo ${v#go}; }` + if [[ $v != `cat GO_VERSION_MIN` ]]; then + echo "GO_VERSION_MIN file does not match the go version being used." + echo "Please update image to cimg/go:`cat GO_VERSION_MIN` or update GO_VERSION_MIN to $v." + exit 1 + fi jobs: + build: + executor: golang + working_directory: ~/lotus + steps: + - checkout + - git_fetch_all_tags + - run: git submodule sync + - run: git submodule update --init + - install-ubuntu-deps + - check-go-version + - run: make deps lotus + - persist_to_workspace: + root: ~/ + paths: + - "lotus" mod-tidy-check: executor: golang + working_directory: ~/lotus steps: - - install-deps - - prepare - - go/mod-download - - go/mod-tidy-check - - build-all: - executor: golang - steps: - - install-deps - - prepare - - go/mod-download - - run: sudo apt-get update - - run: sudo apt-get install npm - - restore_cache: - name: restore go mod cache - key: v1-go-deps-{{ arch }}-{{ checksum "/home/circleci/project/go.mod" }} + - install-ubuntu-deps + - attach_workspace: + at: ~/ + - run: go mod tidy -v - run: - command: make buildall - - store_artifacts: - path: lotus - - store_artifacts: - path: lotus-storage-miner - - store_artifacts: - path: lotus-seal-worker - - run: mkdir linux && mv lotus lotus-storage-miner lotus-seal-worker linux/ - - persist_to_workspace: - root: "." - paths: - - linux + name: Check git diff + command: | + git --no-pager diff go.mod go.sum + git --no-pager diff --quiet go.mod go.sum - build-debug: - executor: golang - steps: - - install-deps - - prepare - - go/mod-download - - restore_cache: - name: restore go mod cache - key: v1-go-deps-{{ arch }}-{{ checksum "/home/circleci/project/go.mod" }} - - run: - command: make debug - - test: &test + test: description: | Run tests with gotestsum. - parameters: + working_directory: ~/lotus + parameters: &test-params executor: type: executor default: golang go-test-flags: type: string - default: "-timeout 30m" + default: "-timeout 20m" description: Flags passed to go test. - packages: + target: type: string default: "./..." description: Import paths of packages to be tested. - test-suite-name: + proofs-log-test: + type: string + default: "0" + get-params: + type: boolean + default: false + suite: type: string default: unit description: Test suite name to report to CircleCI. - gotestsum-format: - type: string - default: short - 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: false - 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 - - go/mod-download - - restore_cache: - name: restore go mod cache - key: v1-go-deps-{{ arch }}-{{ checksum "/home/circleci/project/go.mod" }} - - run: - command: make deps lotus - no_output_timeout: 30m - - download-params - - go/install-gotestsum: - gobin: $HOME/.local/bin + - install-ubuntu-deps + - attach_workspace: + at: ~/ + - when: + condition: << parameters.get-params >> + steps: + - download-params - run: name: go test environment: - GOTESTSUM_JUNITFILE: /tmp/test-reports/<< parameters.test-suite-name >>/junit.xml - GOTESTSUM_FORMAT: << parameters.gotestsum-format >> + TEST_RUSTPROOFS_LOGS: << parameters.proofs-log-test >> + SKIP_CONFORMANCE: "1" + LOTUS_SRC_DIR: /home/circleci/project command: | - mkdir -p /tmp/test-reports/<< parameters.test-suite-name >> - gotestsum -- \ - << parameters.coverage >> \ - << parameters.go-test-flags >> \ - << parameters.packages >> + mkdir -p /tmp/test-reports/<< parameters.suite >> + mkdir -p /tmp/test-artifacts + gotestsum \ + --format standard-verbose \ + --junitfile /tmp/test-reports/<< parameters.suite >>/junit.xml \ + --jsonfile /tmp/test-artifacts/<< parameters.suite >>.json \ + --packages="<< parameters.target >>" \ + -- << parameters.go-test-flags >> no_output_timeout: 30m - store_test_results: path: /tmp/test-reports - - 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) - - save_cache: - name: save go mod cache - key: v1-go-deps-{{ arch }}-{{ checksum "/home/circleci/project/go.mod" }} - paths: - - "~/go/pkg" - - "~/go/src/github.com" - - "~/go/src/golang.org" + - store_artifacts: + path: /tmp/test-artifacts/<< parameters.suite >>.json - test-short: - <<: *test - - build-macos: - description: build darwin lotus binary - macos: - xcode: "10.0.0" - working_directory: ~/go/src/github.com/filecoin-project/lotus + test-conformance: + working_directory: ~/lotus + 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: - - prepare: + - install-ubuntu-deps + - attach_workspace: + at: ~/ + - 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 >> + - 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-linux-amd64: + executor: golang + steps: + - build-platform-specific + - run: make lotus lotus-miner lotus-worker + - run: + name: check tag and version output match + command: ./scripts/version-check.sh ./lotus + - run: | + mkdir -p /tmp/workspace/linux_amd64_v1 && \ + mv lotus lotus-miner lotus-worker /tmp/workspace/linux_amd64_v1/ + - persist_to_workspace: + root: /tmp/workspace + paths: + - linux_amd64_v1 + + build-darwin-amd64: + description: build darwin lotus binary + working_directory: ~/go/src/github.com/filecoin-project/lotus + macos: + xcode: "13.4.1" + steps: + - build-platform-specific: linux: false darwin: true + darwin-architecture: amd64 + - run: make lotus lotus-miner lotus-worker + - run: otool -hv lotus - run: - name: Install go - command: | - curl -O https://dl.google.com/go/go1.14.2.darwin-amd64.pkg && \ - sudo installer -pkg go1.14.2.darwin-amd64.pkg -target / - - run: - name: Install pkg-config - command: HOMEBREW_NO_AUTO_UPDATE=1 brew install pkg-config - - 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 - - restore_cache: - name: restore go mod and cargo cache - key: v3-go-deps-{{ arch }}-{{ checksum "~/go/src/github.com/filecoin-project/lotus/go.sum" }} - - install-deps - - go/mod-download - - run: - command: make build - no_output_timeout: 30m - - store_artifacts: - path: lotus - - store_artifacts: - path: lotus-storage-miner - - store_artifacts: - path: lotus-seal-worker - - run: mkdir darwin && mv lotus lotus-storage-miner lotus-seal-worker darwin/ + name: check tag and version output match + command: ./scripts/version-check.sh ./lotus + - run: | + mkdir -p /tmp/workspace/darwin_amd64_v1 && \ + mv lotus lotus-miner lotus-worker /tmp/workspace/darwin_amd64_v1/ - persist_to_workspace: - root: "." + root: /tmp/workspace paths: - - darwin - - save_cache: - name: save cargo cache - key: v3-go-deps-{{ arch }}-{{ checksum "~/go/src/github.com/filecoin-project/lotus/go.sum" }} + - darwin_amd64_v1 + + build-darwin-arm64: + description: self-hosted m1 runner + working_directory: ~/go/src/github.com/filecoin-project/lotus + machine: true + resource_class: filecoin-project/self-hosted-m1 + steps: + - run: echo 'export PATH=/opt/homebrew/bin:"$PATH"' >> "$BASH_ENV" + - build-platform-specific: + linux: false + darwin: true + darwin-architecture: arm64 + - run: | + export CPATH=$(brew --prefix)/include && export LIBRARY_PATH=$(brew --prefix)/lib && make lotus lotus-miner lotus-worker + - run: otool -hv lotus + - run: + name: check tag and version output match + command: ./scripts/version-check.sh ./lotus + - run: | + mkdir -p /tmp/workspace/darwin_arm64 && \ + mv lotus lotus-miner lotus-worker /tmp/workspace/darwin_arm64/ + - persist_to_workspace: + root: /tmp/workspace paths: - - "~/.rustup" - - "~/.cargo" + - darwin_arm64 + - run: + command: make clean + when: always + - run: + name: cleanup homebrew + command: HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall pkg-config coreutils jq hwloc + when: always + + release: + executor: golang + parameters: + dry-run: + default: false + description: should this release actually publish it's artifacts? + type: boolean + steps: + - checkout + - run: | + echo 'deb [trusted=yes] https://repo.goreleaser.com/apt/ /' | sudo tee /etc/apt/sources.list.d/goreleaser.list + sudo apt update + sudo apt install goreleaser-pro + - install_ipfs + - attach_workspace: + at: /tmp/workspace + - when: + condition: << parameters.dry-run >> + steps: + - run: goreleaser release --rm-dist --snapshot --debug + - run: ./scripts/generate-checksums.sh + - when: + condition: + not: << parameters.dry-run >> + steps: + - run: goreleaser release --rm-dist --debug + - run: ./scripts/generate-checksums.sh + - run: ./scripts/publish-checksums.sh gofmt: executor: golang + working_directory: ~/lotus steps: - - install-deps - - prepare - - go/mod-download - run: command: "! go fmt ./... 2>&1 | read" - lint: &lint + gen-check: + executor: golang + working_directory: ~/lotus + steps: + - install-ubuntu-deps + - attach_workspace: + at: ~/ + - 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 && git --no-pager diff --quiet + - run: make docsgen-cli + - run: git --no-pager diff && git --no-pager diff --quiet + + docs-check: + executor: golang + working_directory: ~/lotus + steps: + - install-ubuntu-deps + - attach_workspace: + at: ~/ + - 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 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: diff ../pre-openrpc-full ../post-openrpc-full && diff ../pre-openrpc-miner ../post-openrpc-miner && diff ../pre-openrpc-worker ../post-openrpc-worker && git --no-pager diff && git --no-pager diff --quiet + + lint-all: description: | Run golangci-lint. + working_directory: ~/lotus parameters: executor: type: executor default: golang - golangci-lint-version: - type: string - default: 1.23.8 - 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: '' @@ -285,74 +406,728 @@ jobs: Arguments to pass to golangci-lint executor: << parameters.executor >> steps: - - install-deps - - prepare - - go/mod-download - - run: - command: make deps - no_output_timeout: 30m - - go/install-golangci-lint: - gobin: $HOME/.local/bin - version: << parameters.golangci-lint-version >> + - install-ubuntu-deps + - attach_workspace: + at: ~/ - run: name: Lint command: | - $HOME/.local/bin/golangci-lint run -v \ - --concurrency << parameters.concurrency >> << parameters.args >> - lint-changes: - <<: *lint + golangci-lint run -v --timeout 10m \ + --concurrency 4 << parameters.args >> - lint-all: - <<: *lint - - publish: - description: publish binary artifacts - executor: ubuntu + build-docker: + description: > + Publish to Dockerhub + executor: docker/docker + parameters: + image: + type: string + default: lotus + description: > + Passed to the docker build process to determine which image in the + Dockerfile should be built. Expected values are `lotus`, + `lotus-all-in-one` + network: + type: string + default: "mainnet" + description: > + Passed to the docker build process using GOFLAGS+=-tags=<>. + Expected values are `debug`, `2k`, `calibnet`, `butterflynet`, + `interopnet`. + channel: + type: string + default: "" + description: > + The release channel to use for this image. + push: + type: boolean + default: false + description: > + When true, pushes the image to Dockerhub steps: - - run: - name: Install git jq curl - command: apt update && apt install -y git jq curl + - setup_remote_docker - 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 + - run: git submodule sync + - run: git submodule update --init + - docker/check: + docker-username: DOCKERHUB_USERNAME + docker-password: DOCKERHUB_PASSWORD + - when: + condition: + equal: [ mainnet, <> ] + steps: + - when: + condition: <> + steps: + - docker/build: + image: filecoin/<> + extra_build_args: --target <> + tag: <> + - run: + name: Docker push + command: | + docker push filecoin/<>:<> + if [[ ! -z $CIRCLE_SHA ]]; then + docker image tag filecoin/<>:<> filecoin/<>:"${CIRCLE_SHA:0:7}" + docker push filecoin/<>:"${CIRCLE_SHA:0:7}" + fi + if [[ ! -z $CIRCLE_TAG ]]; then + docker image tag filecoin/<>:<> filecoin/<>:"${CIRCLE_TAG}" + docker push filecoin/<>:"${CIRCLE_TAG}" + fi + - unless: + condition: <> + steps: + - docker/build: + image: filecoin/<> + extra_build_args: --target <> + - when: + condition: + not: + equal: [ mainnet, <> ] + steps: + - when: + condition: <> + steps: + - docker/build: + image: filecoin/<> + extra_build_args: --target <> --build-arg GOFLAGS=-tags=<> + tag: <>-<> + - run: + name: Docker push + command: | + docker push filecoin/<>:<>-<> + if [[ ! -z $CIRCLE_SHA ]]; then + docker image tag filecoin/<>:<>-<> filecoin/<>:"${CIRCLE_SHA:0:7}"-<> + docker push filecoin/<>:"${CIRCLE_SHA:0:7}"-<> + fi + if [[ ! -z $CIRCLE_TAG ]]; then + docker image tag filecoin/<>:<>-<> filecoin/<>:"${CIRCLE_TAG}"-<> + docker push filecoin/<>:"${CIRCLE_TAG}"-<> + fi + - unless: + condition: <> + steps: + - docker/build: + image: filecoin/<> + extra_build_args: --target <> --build-arg GOFLAGS=-tags=<> workflows: - version: 2.1 ci: jobs: - - lint-changes: - args: "--new-from-rev origin/master" + - build + - lint-all: + requires: + - build + - mod-tidy-check: + requires: + - build + - gofmt: + requires: + - build + - gen-check: + requires: + - build + - docs-check: + requires: + - build - test: - codecov-upload: true - - mod-tidy-check - - gofmt - - test-short: - go-test-flags: "--timeout 10m --short" + name: test-itest-api + requires: + - build + suite: itest-api + target: "./itests/api_test.go" + - test: + name: test-itest-batch_deal + requires: + - build + suite: itest-batch_deal + target: "./itests/batch_deal_test.go" + - test: + name: test-itest-ccupgrade + requires: + - build + suite: itest-ccupgrade + target: "./itests/ccupgrade_test.go" + - test: + name: test-itest-cli + requires: + - build + suite: itest-cli + target: "./itests/cli_test.go" + - test: + name: test-itest-deadlines + requires: + - build + suite: itest-deadlines + target: "./itests/deadlines_test.go" + - test: + name: test-itest-deals_512mb + requires: + - build + suite: itest-deals_512mb + target: "./itests/deals_512mb_test.go" + - test: + name: test-itest-deals_anycid + requires: + - build + suite: itest-deals_anycid + target: "./itests/deals_anycid_test.go" + - test: + name: test-itest-deals_concurrent + requires: + - build + suite: itest-deals_concurrent + target: "./itests/deals_concurrent_test.go" + executor: golang-2xl + - test: + name: test-itest-deals_invalid_utf8_label + requires: + - build + suite: itest-deals_invalid_utf8_label + target: "./itests/deals_invalid_utf8_label_test.go" + - test: + name: test-itest-deals_max_staging_deals + requires: + - build + suite: itest-deals_max_staging_deals + target: "./itests/deals_max_staging_deals_test.go" + - test: + name: test-itest-deals_offline + requires: + - build + suite: itest-deals_offline + target: "./itests/deals_offline_test.go" + - test: + name: test-itest-deals_padding + requires: + - build + suite: itest-deals_padding + target: "./itests/deals_padding_test.go" + - test: + name: test-itest-deals_partial_retrieval_dm-level + requires: + - build + suite: itest-deals_partial_retrieval_dm-level + target: "./itests/deals_partial_retrieval_dm-level_test.go" + - test: + name: test-itest-deals_partial_retrieval + requires: + - build + suite: itest-deals_partial_retrieval + target: "./itests/deals_partial_retrieval_test.go" + - test: + name: test-itest-deals_power + requires: + - build + suite: itest-deals_power + target: "./itests/deals_power_test.go" + - test: + name: test-itest-deals_pricing + requires: + - build + suite: itest-deals_pricing + target: "./itests/deals_pricing_test.go" + - test: + name: test-itest-deals_publish + requires: + - build + suite: itest-deals_publish + target: "./itests/deals_publish_test.go" + - test: + name: test-itest-deals_remote_retrieval + requires: + - build + suite: itest-deals_remote_retrieval + target: "./itests/deals_remote_retrieval_test.go" + - test: + name: test-itest-deals_retry_deal_no_funds + requires: + - build + suite: itest-deals_retry_deal_no_funds + target: "./itests/deals_retry_deal_no_funds_test.go" + - test: + name: test-itest-deals + requires: + - build + suite: itest-deals + target: "./itests/deals_test.go" + - test: + name: test-itest-decode_params + requires: + - build + suite: itest-decode_params + target: "./itests/decode_params_test.go" + - test: + name: test-itest-dup_mpool_messages + requires: + - build + suite: itest-dup_mpool_messages + target: "./itests/dup_mpool_messages_test.go" + - test: + name: test-itest-eth_account_abstraction + requires: + - build + suite: itest-eth_account_abstraction + target: "./itests/eth_account_abstraction_test.go" + - test: + name: test-itest-eth_api + requires: + - build + suite: itest-eth_api + target: "./itests/eth_api_test.go" + - test: + name: test-itest-eth_balance + requires: + - build + suite: itest-eth_balance + target: "./itests/eth_balance_test.go" + - test: + name: test-itest-eth_block_hash + requires: + - build + suite: itest-eth_block_hash + target: "./itests/eth_block_hash_test.go" + - test: + name: test-itest-eth_bytecode + requires: + - build + suite: itest-eth_bytecode + target: "./itests/eth_bytecode_test.go" + - test: + name: test-itest-eth_config + requires: + - build + suite: itest-eth_config + target: "./itests/eth_config_test.go" + - test: + name: test-itest-eth_conformance + requires: + - build + suite: itest-eth_conformance + target: "./itests/eth_conformance_test.go" + - test: + name: test-itest-eth_deploy + requires: + - build + suite: itest-eth_deploy + target: "./itests/eth_deploy_test.go" + - test: + name: test-itest-eth_fee_history + requires: + - build + suite: itest-eth_fee_history + target: "./itests/eth_fee_history_test.go" + - test: + name: test-itest-eth_filter + requires: + - build + suite: itest-eth_filter + target: "./itests/eth_filter_test.go" + - test: + name: test-itest-eth_hash_lookup + requires: + - build + suite: itest-eth_hash_lookup + target: "./itests/eth_hash_lookup_test.go" + - test: + name: test-itest-eth_transactions + requires: + - build + suite: itest-eth_transactions + target: "./itests/eth_transactions_test.go" + - test: + name: test-itest-fevm_address + requires: + - build + suite: itest-fevm_address + target: "./itests/fevm_address_test.go" + - test: + name: test-itest-fevm_events + requires: + - build + suite: itest-fevm_events + target: "./itests/fevm_events_test.go" + - test: + name: test-itest-fevm + requires: + - build + suite: itest-fevm + target: "./itests/fevm_test.go" + - test: + name: test-itest-gas_estimation + requires: + - build + suite: itest-gas_estimation + target: "./itests/gas_estimation_test.go" + - test: + name: test-itest-gateway + requires: + - build + suite: itest-gateway + target: "./itests/gateway_test.go" + - test: + name: test-itest-get_messages_in_ts + requires: + - build + suite: itest-get_messages_in_ts + target: "./itests/get_messages_in_ts_test.go" + - test: + name: test-itest-lite_migration + requires: + - build + suite: itest-lite_migration + target: "./itests/lite_migration_test.go" + - test: + name: test-itest-lookup_robust_address + requires: + - build + suite: itest-lookup_robust_address + target: "./itests/lookup_robust_address_test.go" + - test: + name: test-itest-mempool + requires: + - build + suite: itest-mempool + target: "./itests/mempool_test.go" + - test: + name: test-itest-migration + requires: + - build + suite: itest-migration + target: "./itests/migration_test.go" + - test: + name: test-itest-mpool_msg_uuid + requires: + - build + suite: itest-mpool_msg_uuid + target: "./itests/mpool_msg_uuid_test.go" + - test: + name: test-itest-mpool_push_with_uuid + requires: + - build + suite: itest-mpool_push_with_uuid + target: "./itests/mpool_push_with_uuid_test.go" + - test: + name: test-itest-msgindex + requires: + - build + suite: itest-msgindex + target: "./itests/msgindex_test.go" + - test: + name: test-itest-multisig + requires: + - build + suite: itest-multisig + target: "./itests/multisig_test.go" + - test: + name: test-itest-net + requires: + - build + suite: itest-net + target: "./itests/net_test.go" + - test: + name: test-itest-nonce + requires: + - build + suite: itest-nonce + target: "./itests/nonce_test.go" + - test: + name: test-itest-path_detach_redeclare + requires: + - build + suite: itest-path_detach_redeclare + target: "./itests/path_detach_redeclare_test.go" + - test: + name: test-itest-path_type_filters + requires: + - build + suite: itest-path_type_filters + target: "./itests/path_type_filters_test.go" + - test: + name: test-itest-paych_api + requires: + - build + suite: itest-paych_api + target: "./itests/paych_api_test.go" + - test: + name: test-itest-paych_cli + requires: + - build + suite: itest-paych_cli + target: "./itests/paych_cli_test.go" + - test: + name: test-itest-pending_deal_allocation + requires: + - build + suite: itest-pending_deal_allocation + target: "./itests/pending_deal_allocation_test.go" + - test: + name: test-itest-raft_messagesigner + requires: + - build + suite: itest-raft_messagesigner + target: "./itests/raft_messagesigner_test.go" + - test: + name: test-itest-remove_verifreg_datacap + requires: + - build + suite: itest-remove_verifreg_datacap + target: "./itests/remove_verifreg_datacap_test.go" + - test: + name: test-itest-sdr_upgrade + requires: + - build + suite: itest-sdr_upgrade + target: "./itests/sdr_upgrade_test.go" + - test: + name: test-itest-sealing_resources + requires: + - build + suite: itest-sealing_resources + target: "./itests/sealing_resources_test.go" + - test: + name: test-itest-sector_finalize_early + requires: + - build + suite: itest-sector_finalize_early + target: "./itests/sector_finalize_early_test.go" + - test: + name: test-itest-sector_import_full + requires: + - build + suite: itest-sector_import_full + target: "./itests/sector_import_full_test.go" + - test: + name: test-itest-sector_import_simple + requires: + - build + suite: itest-sector_import_simple + target: "./itests/sector_import_simple_test.go" + - test: + name: test-itest-sector_make_cc_avail + requires: + - build + suite: itest-sector_make_cc_avail + target: "./itests/sector_make_cc_avail_test.go" + - test: + name: test-itest-sector_miner_collateral + requires: + - build + suite: itest-sector_miner_collateral + target: "./itests/sector_miner_collateral_test.go" + - test: + name: test-itest-sector_numassign + requires: + - build + suite: itest-sector_numassign + target: "./itests/sector_numassign_test.go" + - test: + name: test-itest-sector_pledge + requires: + - build + suite: itest-sector_pledge + target: "./itests/sector_pledge_test.go" + - test: + name: test-itest-sector_prefer_no_upgrade + requires: + - build + suite: itest-sector_prefer_no_upgrade + target: "./itests/sector_prefer_no_upgrade_test.go" + - test: + name: test-itest-sector_revert_available + requires: + - build + suite: itest-sector_revert_available + target: "./itests/sector_revert_available_test.go" + - test: + name: test-itest-sector_terminate + requires: + - build + suite: itest-sector_terminate + target: "./itests/sector_terminate_test.go" + - test: + name: test-itest-sector_unseal + requires: + - build + suite: itest-sector_unseal + target: "./itests/sector_unseal_test.go" + - test: + name: test-itest-self_sent_txn + requires: + - build + suite: itest-self_sent_txn + target: "./itests/self_sent_txn_test.go" + - test: + name: test-itest-splitstore + requires: + - build + suite: itest-splitstore + target: "./itests/splitstore_test.go" + - test: + name: test-itest-tape + requires: + - build + suite: itest-tape + target: "./itests/tape_test.go" + - test: + name: test-itest-verifreg + requires: + - build + suite: itest-verifreg + target: "./itests/verifreg_test.go" + - test: + name: test-itest-wdpost_config + requires: + - build + suite: itest-wdpost_config + target: "./itests/wdpost_config_test.go" + - test: + name: test-itest-wdpost_dispute + requires: + - build + suite: itest-wdpost_dispute + target: "./itests/wdpost_dispute_test.go" + - test: + name: test-itest-wdpost_no_miner_storage + requires: + - build + suite: itest-wdpost_no_miner_storage + target: "./itests/wdpost_no_miner_storage_test.go" + - test: + name: test-itest-wdpost + requires: + - build + suite: itest-wdpost + target: "./itests/wdpost_test.go" + get-params: true + + - test: + name: test-itest-wdpost_worker_config + requires: + - build + suite: itest-wdpost_worker_config + target: "./itests/wdpost_worker_config_test.go" + executor: golang-2xl + - test: + name: test-itest-worker + requires: + - build + suite: itest-worker + target: "./itests/worker_test.go" + executor: golang-2xl + - test: + name: test-itest-worker_upgrade + requires: + - build + suite: itest-worker_upgrade + target: "./itests/worker_upgrade_test.go" + - test: + name: test-unit-cli + requires: + - build + suite: utest-unit-cli + target: "./cli/... ./cmd/... ./api/..." + get-params: true + - test: + name: test-unit-node + requires: + - build + suite: utest-unit-node + target: "./node/..." + + - test: + name: test-unit-rest + requires: + - build + suite: utest-unit-rest + target: "./api/... ./blockstore/... ./build/... ./chain/... ./cli/... ./cmd/... ./conformance/... ./extern/... ./gateway/... ./journal/... ./lib/... ./markets/... ./node/... ./paychmgr/... ./storage/... ./tools/..." + executor: golang-2xl + - test: + name: test-unit-storage + requires: + - build + suite: utest-unit-storage + target: "./storage/... ./extern/..." + + - test: + go-test-flags: "-run=TestMulticoreSDR" + requires: + - build + suite: multicore-sdr-check + target: "./storage/sealer/ffiwrapper" + proofs-log-test: "1" + - test-conformance: + requires: + - build + suite: conformance + target: "./conformance" + + release: + jobs: + - build-linux-amd64: + name: "Build ( linux / amd64 )" filters: + branches: + only: + - /^release\/v\d+\.\d+\.\d+(-rc\d+)?$/ + - /^ci\/.*$/ tags: only: - - /^v\d+\.\d+\.\d+$/ - - build-debug - - build-all: - requires: - - test-short + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - build-darwin-amd64: + name: "Build ( darwin / amd64 )" filters: + branches: + only: + - /^release\/v\d+\.\d+\.\d+(-rc\d+)?$/ + - /^ci\/.*$/ tags: only: - - /^v\d+\.\d+\.\d+$/ - - build-macos: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - build-darwin-arm64: + name: "Build ( darwin / arm64 )" + filters: + branches: + only: + - /^release\/v\d+\.\d+\.\d+(-rc\d+)?$/ + - /^ci\/.*$/ + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - release: + name: "Release" requires: - - test-short + - "Build ( darwin / amd64 )" + - "Build ( linux / amd64 )" + - "Build ( darwin / arm64 )" + filters: + branches: + ignore: + - /^.*$/ + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - release: + name: "Release (dry-run)" + dry-run: true + requires: + - "Build ( darwin / amd64 )" + - "Build ( linux / amd64 )" + - "Build ( darwin / arm64 )" + filters: + branches: + only: + - /^release\/v\d+\.\d+\.\d+(-rc\d+)?$/ + - /^ci\/.*$/ + - build-docker: + name: "Docker push (lotus-all-in-one / stable / mainnet)" + image: lotus-all-in-one + channel: stable + network: mainnet + push: true filters: branches: ignore: @@ -360,10 +1135,44 @@ workflows: tags: only: - /^v\d+\.\d+\.\d+$/ - - publish: - requires: - - build-all - - build-macos + - build-docker: + name: "Docker push (lotus-all-in-one / candidate / mainnet)" + image: lotus-all-in-one + channel: candidate + network: mainnet + push: true + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+-rc\d+$/ + - build-docker: + name: "Docker push (lotus-all-in-one / edge / mainnet)" + image: lotus-all-in-one + channel: master + network: mainnet + push: true + filters: + branches: + only: + - master + - build-docker: + name: "Docker build (lotus-all-in-one / mainnet)" + image: lotus-all-in-one + network: mainnet + push: false + filters: + branches: + only: + - /^release\/v\d+\.\d+\.\d+(-rc\d+)?$/ + - build-docker: + name: "Docker push (lotus-all-in-one / stable / butterflynet)" + image: lotus-all-in-one + channel: stable + network: butterflynet + push: true filters: branches: ignore: @@ -371,3 +1180,204 @@ workflows: tags: only: - /^v\d+\.\d+\.\d+$/ + - build-docker: + name: "Docker push (lotus-all-in-one / candidate / butterflynet)" + image: lotus-all-in-one + channel: candidate + network: butterflynet + push: true + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+-rc\d+$/ + - build-docker: + name: "Docker push (lotus-all-in-one / edge / butterflynet)" + image: lotus-all-in-one + channel: master + network: butterflynet + push: true + filters: + branches: + only: + - master + - build-docker: + name: "Docker build (lotus-all-in-one / butterflynet)" + image: lotus-all-in-one + network: butterflynet + push: false + filters: + branches: + only: + - /^release\/v\d+\.\d+\.\d+(-rc\d+)?$/ + - build-docker: + name: "Docker push (lotus-all-in-one / stable / calibnet)" + image: lotus-all-in-one + channel: stable + network: calibnet + push: true + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+$/ + - build-docker: + name: "Docker push (lotus-all-in-one / candidate / calibnet)" + image: lotus-all-in-one + channel: candidate + network: calibnet + push: true + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+-rc\d+$/ + - build-docker: + name: "Docker push (lotus-all-in-one / edge / calibnet)" + image: lotus-all-in-one + channel: master + network: calibnet + push: true + filters: + branches: + only: + - master + - build-docker: + name: "Docker build (lotus-all-in-one / calibnet)" + image: lotus-all-in-one + network: calibnet + push: false + filters: + branches: + only: + - /^release\/v\d+\.\d+\.\d+(-rc\d+)?$/ + - build-docker: + name: "Docker push (lotus-all-in-one / stable / debug)" + image: lotus-all-in-one + channel: stable + network: debug + push: true + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+$/ + - build-docker: + name: "Docker push (lotus-all-in-one / candidate / debug)" + image: lotus-all-in-one + channel: candidate + network: debug + push: true + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+-rc\d+$/ + - build-docker: + name: "Docker push (lotus-all-in-one / edge / debug)" + image: lotus-all-in-one + channel: master + network: debug + push: true + filters: + branches: + only: + - master + - build-docker: + name: "Docker build (lotus-all-in-one / debug)" + image: lotus-all-in-one + network: debug + push: false + filters: + branches: + only: + - /^release\/v\d+\.\d+\.\d+(-rc\d+)?$/ + - build-docker: + name: "Docker push (lotus / stable / mainnet)" + image: lotus + channel: stable + network: mainnet + push: true + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+$/ + - build-docker: + name: "Docker push (lotus / candidate / mainnet)" + image: lotus + channel: candidate + network: mainnet + push: true + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+-rc\d+$/ + - build-docker: + name: "Docker push (lotus / master / mainnet)" + image: lotus + channel: master + network: mainnet + push: true + filters: + branches: + only: + - master + - build-docker: + name: "Docker build (lotus / mainnet)" + image: lotus + network: mainnet + push: false + filters: + branches: + only: + - /^release\/v\d+\.\d+\.\d+(-rc\d+)?$/ + + nightly: + triggers: + - schedule: + cron: "0 0 * * *" + filters: + branches: + only: + - master + jobs: + - build-docker: + name: "Docker (lotus-all-in-one / nightly / mainnet)" + image: lotus-all-in-one + channel: nightly + network: mainnet + push: true + - build-docker: + name: "Docker (lotus-all-in-one / nightly / butterflynet)" + image: lotus-all-in-one + channel: nightly + network: butterflynet + push: true + - build-docker: + name: "Docker (lotus-all-in-one / nightly / calibnet)" + image: lotus-all-in-one + channel: nightly + network: calibnet + push: true + - build-docker: + name: "Docker (lotus-all-in-one / nightly / debug)" + image: lotus-all-in-one + channel: nightly + network: debug + push: true diff --git a/.circleci/gen.go b/.circleci/gen.go new file mode 100644 index 000000000..5d951027a --- /dev/null +++ b/.circleci/gen.go @@ -0,0 +1,138 @@ +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 { + Networks []string + ItestFiles []string + UnitSuites map[string]string + } + in := data{ + Networks: []string{"mainnet", "butterflynet", "calibnet", "debug"}, + 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..cd8aeb663 --- /dev/null +++ b/.circleci/template.yml @@ -0,0 +1,742 @@ +version: 2.1 +orbs: + aws-cli: circleci/aws-cli@1.3.2 + docker: circleci/docker@2.1.4 + +executors: + golang: + docker: + # Must match GO_VERSION_MIN in project root + - image: cimg/go:1.19.7 + resource_class: medium+ + golang-2xl: + docker: + # Must match GO_VERSION_MIN in project root + - image: cimg/go:1.19.7 + resource_class: 2xlarge + ubuntu: + docker: + - image: ubuntu:20.04 + +commands: + build-platform-specific: + parameters: + linux: + default: true + description: is a linux build environment? + type: boolean + darwin: + default: false + description: is a darwin build environment? + type: boolean + darwin-architecture: + default: "amd64" + description: which darwin architecture is being used? + type: string + steps: + - checkout + - git_fetch_all_tags + - run: git submodule sync + - run: git submodule update --init + - when: + condition: <> + steps: + - install-ubuntu-deps + - check-go-version + - when: + condition: <> + steps: + - run: + name: Install Go + command: | + curl https://dl.google.com/go/go`cat GO_VERSION_MIN`.darwin-<>.pkg -o /tmp/go.pkg && \ + sudo installer -pkg /tmp/go.pkg -target / + - run: + name: Export Go + command: | + echo 'export GOPATH="${HOME}/go"' >> $BASH_ENV + - run: go version + - run: + name: Install dependencies with Homebrew + command: HOMEBREW_NO_AUTO_UPDATE=1 brew install pkg-config coreutils jq hwloc + - run: + name: Install Rust + command: | + curl https://sh.rustup.rs -sSf | sh -s -- -y + - run: make deps + download-params: + steps: + - restore_cache: + name: Restore parameters cache + keys: + - 'v26-2k-lotus-params' + paths: + - /var/tmp/filecoin-proof-parameters/ + - run: ./lotus fetch-params 2048 + - save_cache: + name: Save parameters cache + key: 'v26-2k-lotus-params' + paths: + - /var/tmp/filecoin-proof-parameters/ + install_ipfs: + steps: + - run: | + curl -O https://dist.ipfs.tech/kubo/v0.16.0/kubo_v0.16.0_linux-amd64.tar.gz + tar -xvzf kubo_v0.16.0_linux-amd64.tar.gz + pushd kubo + sudo bash install.sh + popd + rm -rf kubo + rm kubo_v0.16.0_linux-amd64.tar.gz + git_fetch_all_tags: + steps: + - run: + name: fetch all tags + command: | + git fetch --all + install-ubuntu-deps: + steps: + - run: sudo apt-get update + - run: sudo apt-get install ocl-icd-opencl-dev libhwloc-dev + check-go-version: + steps: + - run: | + v=`go version | { read _ _ v _; echo ${v#go}; }` + if [["[[ $v != `cat GO_VERSION_MIN` ]]"]]; then + echo "GO_VERSION_MIN file does not match the go version being used." + echo "Please update image to cimg/go:`cat GO_VERSION_MIN` or update GO_VERSION_MIN to $v." + exit 1 + fi + +jobs: + build: + executor: golang + working_directory: ~/lotus + steps: + - checkout + - git_fetch_all_tags + - run: git submodule sync + - run: git submodule update --init + - install-ubuntu-deps + - check-go-version + - run: make deps lotus + - persist_to_workspace: + root: ~/ + paths: + - "lotus" + mod-tidy-check: + executor: golang + working_directory: ~/lotus + steps: + - install-ubuntu-deps + - attach_workspace: + at: ~/ + - run: go mod tidy -v + - run: + name: Check git diff + command: | + git --no-pager diff go.mod go.sum + git --no-pager diff --quiet go.mod go.sum + + test: + description: | + Run tests with gotestsum. + working_directory: ~/lotus + parameters: &test-params + executor: + type: executor + default: golang + go-test-flags: + type: string + default: "-timeout 20m" + 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" + get-params: + type: boolean + default: false + suite: + type: string + default: unit + description: Test suite name to report to CircleCI. + executor: << parameters.executor >> + steps: + - install-ubuntu-deps + - attach_workspace: + at: ~/ + - when: + condition: << parameters.get-params >> + steps: + - download-params + - run: + name: go test + environment: + TEST_RUSTPROOFS_LOGS: << parameters.proofs-log-test >> + SKIP_CONFORMANCE: "1" + LOTUS_SRC_DIR: /home/circleci/project + command: | + mkdir -p /tmp/test-reports/<< parameters.suite >> + mkdir -p /tmp/test-artifacts + gotestsum \ + --format standard-verbose \ + --junitfile /tmp/test-reports/<< parameters.suite >>/junit.xml \ + --jsonfile /tmp/test-artifacts/<< parameters.suite >>.json \ + --packages="<< parameters.target >>" \ + -- << parameters.go-test-flags >> + no_output_timeout: 30m + - store_test_results: + path: /tmp/test-reports + - store_artifacts: + path: /tmp/test-artifacts/<< parameters.suite >>.json + + test-conformance: + working_directory: ~/lotus + 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-ubuntu-deps + - attach_workspace: + at: ~/ + - 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 >> + - 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-linux-amd64: + executor: golang + steps: + - build-platform-specific + - run: make lotus lotus-miner lotus-worker + - run: + name: check tag and version output match + command: ./scripts/version-check.sh ./lotus + - run: | + mkdir -p /tmp/workspace/linux_amd64_v1 && \ + mv lotus lotus-miner lotus-worker /tmp/workspace/linux_amd64_v1/ + - persist_to_workspace: + root: /tmp/workspace + paths: + - linux_amd64_v1 + + build-darwin-amd64: + description: build darwin lotus binary + working_directory: ~/go/src/github.com/filecoin-project/lotus + macos: + xcode: "13.4.1" + steps: + - build-platform-specific: + linux: false + darwin: true + darwin-architecture: amd64 + - run: make lotus lotus-miner lotus-worker + - run: otool -hv lotus + - run: + name: check tag and version output match + command: ./scripts/version-check.sh ./lotus + - run: | + mkdir -p /tmp/workspace/darwin_amd64_v1 && \ + mv lotus lotus-miner lotus-worker /tmp/workspace/darwin_amd64_v1/ + - persist_to_workspace: + root: /tmp/workspace + paths: + - darwin_amd64_v1 + + build-darwin-arm64: + description: self-hosted m1 runner + working_directory: ~/go/src/github.com/filecoin-project/lotus + machine: true + resource_class: filecoin-project/self-hosted-m1 + steps: + - run: echo 'export PATH=/opt/homebrew/bin:"$PATH"' >> "$BASH_ENV" + - build-platform-specific: + linux: false + darwin: true + darwin-architecture: arm64 + - run: | + export CPATH=$(brew --prefix)/include && export LIBRARY_PATH=$(brew --prefix)/lib && make lotus lotus-miner lotus-worker + - run: otool -hv lotus + - run: + name: check tag and version output match + command: ./scripts/version-check.sh ./lotus + - run: | + mkdir -p /tmp/workspace/darwin_arm64 && \ + mv lotus lotus-miner lotus-worker /tmp/workspace/darwin_arm64/ + - persist_to_workspace: + root: /tmp/workspace + paths: + - darwin_arm64 + - run: + command: make clean + when: always + - run: + name: cleanup homebrew + command: HOMEBREW_NO_AUTO_UPDATE=1 brew uninstall pkg-config coreutils jq hwloc + when: always + + release: + executor: golang + parameters: + dry-run: + default: false + description: should this release actually publish it's artifacts? + type: boolean + steps: + - checkout + - run: | + echo 'deb [trusted=yes] https://repo.goreleaser.com/apt/ /' | sudo tee /etc/apt/sources.list.d/goreleaser.list + sudo apt update + sudo apt install goreleaser-pro + - install_ipfs + - attach_workspace: + at: /tmp/workspace + - when: + condition: << parameters.dry-run >> + steps: + - run: goreleaser release --rm-dist --snapshot --debug + - run: ./scripts/generate-checksums.sh + - when: + condition: + not: << parameters.dry-run >> + steps: + - run: goreleaser release --rm-dist --debug + - run: ./scripts/generate-checksums.sh + - run: ./scripts/publish-checksums.sh + + gofmt: + executor: golang + working_directory: ~/lotus + steps: + - run: + command: "! go fmt ./... 2>&1 | read" + + gen-check: + executor: golang + working_directory: ~/lotus + steps: + - install-ubuntu-deps + - attach_workspace: + at: ~/ + - 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 && git --no-pager diff --quiet + - run: make docsgen-cli + - run: git --no-pager diff && git --no-pager diff --quiet + + docs-check: + executor: golang + working_directory: ~/lotus + steps: + - install-ubuntu-deps + - attach_workspace: + at: ~/ + - 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 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: diff ../pre-openrpc-full ../post-openrpc-full && diff ../pre-openrpc-miner ../post-openrpc-miner && diff ../pre-openrpc-worker ../post-openrpc-worker && git --no-pager diff && git --no-pager diff --quiet + + lint-all: + description: | + Run golangci-lint. + working_directory: ~/lotus + parameters: + executor: + type: executor + default: golang + args: + type: string + default: '' + description: | + Arguments to pass to golangci-lint + executor: << parameters.executor >> + steps: + - install-ubuntu-deps + - attach_workspace: + at: ~/ + - run: + name: Lint + command: | + golangci-lint run -v --timeout 10m \ + --concurrency 4 << parameters.args >> + + build-docker: + description: > + Publish to Dockerhub + executor: docker/docker + parameters: + image: + type: string + default: lotus + description: > + Passed to the docker build process to determine which image in the + Dockerfile should be built. Expected values are `lotus`, + `lotus-all-in-one` + network: + type: string + default: "mainnet" + description: > + Passed to the docker build process using GOFLAGS+=-tags=<>. + Expected values are `debug`, `2k`, `calibnet`, `butterflynet`, + `interopnet`. + channel: + type: string + default: "" + description: > + The release channel to use for this image. + push: + type: boolean + default: false + description: > + When true, pushes the image to Dockerhub + steps: + - setup_remote_docker + - checkout + - git_fetch_all_tags + - run: git submodule sync + - run: git submodule update --init + + - docker/check: + docker-username: DOCKERHUB_USERNAME + docker-password: DOCKERHUB_PASSWORD + - when: + condition: + equal: [ mainnet, <> ] + steps: + - when: + condition: <> + steps: + - docker/build: + image: filecoin/<> + extra_build_args: --target <> + tag: <> + - run: + name: Docker push + command: | + docker push filecoin/<>:<> + if [["[[ ! -z $CIRCLE_SHA ]]"]]; then + docker image tag filecoin/<>:<> filecoin/<>:"${CIRCLE_SHA:0:7}" + docker push filecoin/<>:"${CIRCLE_SHA:0:7}" + fi + if [["[[ ! -z $CIRCLE_TAG ]]"]]; then + docker image tag filecoin/<>:<> filecoin/<>:"${CIRCLE_TAG}" + docker push filecoin/<>:"${CIRCLE_TAG}" + fi + - unless: + condition: <> + steps: + - docker/build: + image: filecoin/<> + extra_build_args: --target <> + - when: + condition: + not: + equal: [ mainnet, <> ] + steps: + - when: + condition: <> + steps: + - docker/build: + image: filecoin/<> + extra_build_args: --target <> --build-arg GOFLAGS=-tags=<> + tag: <>-<> + - run: + name: Docker push + command: | + docker push filecoin/<>:<>-<> + if [["[[ ! -z $CIRCLE_SHA ]]"]]; then + docker image tag filecoin/<>:<>-<> filecoin/<>:"${CIRCLE_SHA:0:7}"-<> + docker push filecoin/<>:"${CIRCLE_SHA:0:7}"-<> + fi + if [["[[ ! -z $CIRCLE_TAG ]]"]]; then + docker image tag filecoin/<>:<>-<> filecoin/<>:"${CIRCLE_TAG}"-<> + docker push filecoin/<>:"${CIRCLE_TAG}"-<> + fi + - unless: + condition: <> + steps: + - docker/build: + image: filecoin/<> + extra_build_args: --target <> --build-arg GOFLAGS=-tags=<> + +workflows: + ci: + jobs: + - build + - lint-all: + requires: + - build + - mod-tidy-check: + requires: + - build + - gofmt: + requires: + - build + - gen-check: + requires: + - build + - docs-check: + requires: + - build + + [[- range $file := .ItestFiles -]] + [[ with $name := $file | stripSuffix ]] + - test: + name: test-itest-[[ $name ]] + requires: + - build + suite: itest-[[ $name ]] + target: "./itests/[[ $file ]]" + [[- if or (eq $name "worker") (eq $name "deals_concurrent") (eq $name "wdpost_worker_config")]] + executor: golang-2xl + [[- end]] + [[- if (eq $name "wdpost")]] + get-params: true + [[end]] + [[- end ]][[- end]] + + [[- range $suite, $pkgs := .UnitSuites]] + - test: + name: test-[[ $suite ]] + requires: + - build + suite: utest-[[ $suite ]] + target: "[[ $pkgs ]]" + [[if eq $suite "unit-cli"]]get-params: true[[end]] + [[- if eq $suite "unit-rest"]]executor: golang-2xl[[end]] + [[- end]] + - test: + go-test-flags: "-run=TestMulticoreSDR" + requires: + - build + suite: multicore-sdr-check + target: "./storage/sealer/ffiwrapper" + proofs-log-test: "1" + - test-conformance: + requires: + - build + suite: conformance + target: "./conformance" + + release: + jobs: + - build-linux-amd64: + name: "Build ( linux / amd64 )" + filters: + branches: + only: + - /^release\/v\d+\.\d+\.\d+(-rc\d+)?$/ + - /^ci\/.*$/ + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - build-darwin-amd64: + name: "Build ( darwin / amd64 )" + filters: + branches: + only: + - /^release\/v\d+\.\d+\.\d+(-rc\d+)?$/ + - /^ci\/.*$/ + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - build-darwin-arm64: + name: "Build ( darwin / arm64 )" + filters: + branches: + only: + - /^release\/v\d+\.\d+\.\d+(-rc\d+)?$/ + - /^ci\/.*$/ + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - release: + name: "Release" + requires: + - "Build ( darwin / amd64 )" + - "Build ( linux / amd64 )" + - "Build ( darwin / arm64 )" + filters: + branches: + ignore: + - /^.*$/ + tags: + only: + - /^v\d+\.\d+\.\d+(-rc\d+)?$/ + - release: + name: "Release (dry-run)" + dry-run: true + requires: + - "Build ( darwin / amd64 )" + - "Build ( linux / amd64 )" + - "Build ( darwin / arm64 )" + filters: + branches: + only: + - /^release\/v\d+\.\d+\.\d+(-rc\d+)?$/ + - /^ci\/.*$/ + [[- range .Networks]] + - build-docker: + name: "Docker push (lotus-all-in-one / stable / [[.]])" + image: lotus-all-in-one + channel: stable + network: [[.]] + push: true + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+$/ + - build-docker: + name: "Docker push (lotus-all-in-one / candidate / [[.]])" + image: lotus-all-in-one + channel: candidate + network: [[.]] + push: true + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+-rc\d+$/ + - build-docker: + name: "Docker push (lotus-all-in-one / edge / [[.]])" + image: lotus-all-in-one + channel: master + network: [[.]] + push: true + filters: + branches: + only: + - master + - build-docker: + name: "Docker build (lotus-all-in-one / [[.]])" + image: lotus-all-in-one + network: [[.]] + push: false + filters: + branches: + only: + - /^release\/v\d+\.\d+\.\d+(-rc\d+)?$/ + [[- end]] + - build-docker: + name: "Docker push (lotus / stable / mainnet)" + image: lotus + channel: stable + network: mainnet + push: true + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+$/ + - build-docker: + name: "Docker push (lotus / candidate / mainnet)" + image: lotus + channel: candidate + network: mainnet + push: true + filters: + branches: + ignore: + - /.*/ + tags: + only: + - /^v\d+\.\d+\.\d+-rc\d+$/ + - build-docker: + name: "Docker push (lotus / master / mainnet)" + image: lotus + channel: master + network: mainnet + push: true + filters: + branches: + only: + - master + - build-docker: + name: "Docker build (lotus / mainnet)" + image: lotus + network: mainnet + push: false + filters: + branches: + only: + - /^release\/v\d+\.\d+\.\d+(-rc\d+)?$/ + + nightly: + triggers: + - schedule: + cron: "0 0 * * *" + filters: + branches: + only: + - master + jobs: + [[- range .Networks]] + - build-docker: + name: "Docker (lotus-all-in-one / nightly / [[.]])" + image: lotus-all-in-one + channel: nightly + network: [[.]] + push: true + [[- end]] diff --git a/.codecov.yml b/.codecov.yml deleted file mode 100644 index cf409a6b6..000000000 --- a/.codecov.yml +++ /dev/null @@ -1,3 +0,0 @@ -comment: off -ignore: - - "cbor_gen.go" diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..cf5b209d7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +chain/actors/builtin/*/v* linguist-generated=true +chain/actors/builtin/*/message* linguist-generated=true diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..b8ec66f00 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,6 @@ +# Reference +# https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-repository-on-github/about-code-owners + +# Global owners +# Ensure maintainers team is a requested reviewer for non-draft PRs +* @filecoin-project/lotus-maintainers diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index a1baede0d..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: '' -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. Run '...' -2. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Version (run `lotus --version`):** - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000..e4c0c7f26 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,85 @@ +name: "Bug Report" +description: "File a bug report to help us improve" +labels: [need/triage, kind/bug] +body: +- type: checkboxes + attributes: + label: Checklist + description: Please check off the following boxes before continuing to file a bug report! + options: + - label: This is **not** a security-related bug/issue. If it is, please follow please follow the [security policy](https://github.com/filecoin-project/lotus/security/policy). + required: true + - label: I **have** searched on the [issue tracker](https://github.com/filecoin-project/lotus/issues) and the [lotus forum](https://github.com/filecoin-project/lotus/discussions), and there is no existing related issue or discussion. + required: true + - label: I am running the [`Latest release`](https://github.com/filecoin-project/lotus/releases), the most recent RC(release canadiate) for the upcoming release or the dev branch(master), or have an issue updating to any of these. + required: true + - label: I did not make any code changes to lotus. + required: false +- type: checkboxes + attributes: + label: Lotus component + description: Please select the lotus component you are filing a bug for + options: + - label: lotus daemon - chain sync + required: false + - label: lotus fvm/fevm - Lotus FVM and FEVM interactions + required: false + - label: lotus miner/worker - sealing + required: false + - label: lotus miner - proving(WindowPoSt/WinningPoSt) + required: false + - label: lotus JSON-RPC API + required: false + - label: lotus message management (mpool) + required: false + - label: Other + required: false +- type: textarea + id: version + attributes: + label: Lotus Version + render: text + description: Enter the output of `lotus version` and `lotus-miner version` if applicable. + placeholder: | + e.g. + Daemon: 1.19.0+mainnet+git.64059ca87+api1.5.0 + Local: lotus-miner version 1.19.0+mainnet+git.64059ca87 + validations: + required: true +- type: textarea + id: ReproSteps + attributes: + label: Repro Steps + description: "Steps to reproduce the behavior" + value: | + 1. Run '...' + 2. Do '...' + 3. See error '...' + ... + validations: + required: false +- type: textarea + id: Description + attributes: + label: Describe the Bug + description: | + This is where you get to tell us what went wrong, when doing so, please try to provide a clear and concise description of the bug with all related information: + * What you were doing when you experienced the bug? + * Any *error* messages you saw, *where* you saw them, and what you believe may have caused them (if you have any ideas). + * What is the expected behaviour? + * For sealing issues, include the output of `lotus-miner sectors status --log ` for the failed sector(s). + * For proving issues, include the output of `lotus-miner proving` info. + validations: + required: true +- type: textarea + id: extraInfo + attributes: + label: Logging Information + render: text + description: | + Please provide debug logs of the problem, remember you can get set log level control for: + * lotus: use `lotus log list` to get all log systems available and set level by `lotus log set-level`. An example can be found [here](https://lotus.filecoin.io/lotus/configure/defaults/#log-level-control). + * lotus-miner:`lotus-miner log list` to get all log systems available and set level by `lotus-miner log set-level + If you don't provide detailed logs when you raise the issue it will almost certainly be the first request we make before furthur diagnosing the problem. + validations: + required: true \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..e5ae608b3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: true +contact_links: + - name: Ask a question about Lotus or get support + url: https://github.com/filecoin-project/lotus/discussions/new/choose + about: Ask a question or request support for using Lotus + - name: Filecoin protocol feature or enhancement + url: https://github.com/filecoin-project/FIPs/discussions/new/choose + about: Write a discussion in the Filecoin Improvement Proposal repo diff --git a/.github/ISSUE_TEMPLATE/enhancement.yml b/.github/ISSUE_TEMPLATE/enhancement.yml new file mode 100644 index 000000000..d367feeac --- /dev/null +++ b/.github/ISSUE_TEMPLATE/enhancement.yml @@ -0,0 +1,47 @@ +name: Enhancement +description: Suggest an improvement to an existing lotus feature. +labels: [need/triage, kind/enhancement] +body: +- type: checkboxes + attributes: + label: Checklist + description: Please check off the following boxes before continuing to create an improvement suggestion! + options: + - label: I **have** a specific, actionable, and well motivated improvement to an existing lotus feature. + required: true +- type: checkboxes + attributes: + label: Lotus component + description: Please select the lotus component you are filing an improvement request for + options: + - label: lotus daemon - chain sync + required: false + - label: lotus fvm/fevm - Lotus FVM and FEVM interactions + required: false + - label: lotus miner/worker - sealing + required: false + - label: lotus miner - proving(WindowPoSt/WinningPoSt) + required: false + - label: lotus JSON-RPC API + required: false + - label: lotus message management (mpool) + required: false + - label: Other + required: false +- type: textarea + id: request + attributes: + label: Enhancement Suggestion + description: A clear and concise description of the suggested enhancement? + placeholder: Ex. Currently lotus... However it would be great if [enhancement] was implemented... With the ability to... + validations: + required: true +- type: textarea + id: request + attributes: + label: Use-Case + description: How would this enhancement help you? + placeholder: Ex. With the [enhancement] node operators would be able to... For Storage Providers it would enable... + validations: + required: true + diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 000000000..76493e9c1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,61 @@ +name: Feature request +description: Suggest an idea for lotus +labels: [need/triage, kind/feature] +body: +- type: checkboxes + attributes: + label: Checklist + description: Please check off the following boxes before continuing to create a new feature request! + options: + - label: This is **not** brainstorming ideas. If you have an idea you'd like to discuss, please open a new discussion on [the lotus forum](https://github.com/filecoin-project/lotus/discussions/categories/ideas) and select the category as `Ideas`. + required: true + - label: I **have** a specific, actionable, and well motivated feature request to propose. + required: true +- type: checkboxes + attributes: + label: Lotus component + description: Please select the lotus component you are filing a new feature request for + options: + - label: lotus daemon - chain sync + required: false + - label: lotus fvm/fevm - Lotus FVM and FEVM interactions + required: false + - label: lotus miner/worker - sealing + required: false + - label: lotus miner - proving(WindowPoSt/WinningPoSt) + required: false + - label: lotus JSON-RPC API + required: false + - label: lotus message management (mpool) + required: false + - label: Other + required: false +- type: textarea + id: request + attributes: + label: What is the motivation behind this feature request? Is your feature request related to a problem? Please describe. + description: A clear and concise description of what the motivation or the problem is. + placeholder: Ex. I'm always frustrated when [...] + validations: + required: true +- type: textarea + id: solution + attributes: + label: Describe the solution you'd like + description: A clear and concise description of what you want to happen. + validations: + required: true +- type: textarea + id: alternatives + attributes: + label: Describe alternatives you've considered + description: A clear and concise description of any alternative solutions or features you've considered. + validations: + required: false +- type: textarea + id: extra + attributes: + label: Additional context + description: Add any other context, design docs or screenshots about the feature request here. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/sealingfailed.md b/.github/ISSUE_TEMPLATE/sealingfailed.md deleted file mode 100644 index d58664415..000000000 --- a/.github/ISSUE_TEMPLATE/sealingfailed.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -name: Sealing Issues -about: Create a report for help with sealing (commit) failures. -title: '' -labels: 'sealing' -assignees: '' - ---- - -Please provide all the information requested here to help us troubleshoot "commit failed" issues. -If the information requested is missing, we will probably have to just ask you to provide it anyway, -before we can help debug. - -**Describe the problem** - -A brief description of the problem you encountered while proving (sealing) a sector. - -Including what commands you ran, and a description of your setup, is very helpful. - -**Sectors list** - -The output of `./lotus-storage-miner sectors list`. - -**Sectors status** - -The output of `./lotus-storage-miner sectors status --log ` for the failed sector(s). - -**Lotus storage miner logs** - -Please go through the logs of your storage miner, and include screenshots of any error-like messages you find. - -**Version** - -The output of `./lotus --version`. diff --git a/.github/ISSUE_TEMPLATE/service_developer_bug_report.yml b/.github/ISSUE_TEMPLATE/service_developer_bug_report.yml new file mode 100644 index 000000000..8174d13f5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/service_developer_bug_report.yml @@ -0,0 +1,83 @@ +name: "Bug Report - developer/service provider" +description: "Bug report template about FEVM/FVM for developers/service providers" +labels: [need/triage, kind/bug, area/fevm] +body: +- type: checkboxes + attributes: + label: Checklist + description: Please check off the following boxes before continuing to file a bug report! + options: + - label: This is **not** a security-related bug/issue. If it is, please follow please follow the [security policy](https://github.com/filecoin-project/lotus/security/policy). + required: true + - label: I **have** searched on the [issue tracker](https://github.com/filecoin-project/lotus/issues) and the [lotus forum](https://github.com/filecoin-project/lotus/discussions), and there is no existing related issue or discussion. + required: true + - label: I did not make any code changes to lotus. + required: false +- type: checkboxes + attributes: + label: Lotus component + description: Please select the lotus component you are filing a bug for + options: + - label: lotus Ethereum RPC + required: false + - label: lotus FVM - Lotus FVM interactions + required: false + - label: FEVM tooling + required: false + - label: Other + required: false +- type: textarea + id: version + attributes: + label: Lotus Version + render: text + description: Enter the output of `lotus version` if applicable. + placeholder: | + e.g. + Daemon: 1.19.0+mainnet+git.64059ca87+api1.5.0 + Local: lotus-miner version 1.19.0+mainnet+git.64059ca87 + validations: + required: true +- type: textarea + id: repro + attributes: + label: Repro Steps + description: "Steps to reproduce the behavior" + value: | + 1. Run '...' + 2. Do '...' + 3. See error '...' + ... + validations: + required: false +- type: textarea + id: Description + attributes: + label: Describe the Bug + description: | + This is where you get to tell us what went wrong, when doing so, please try to provide a clear and concise description of the bug with all related information: + * What you were doing when you experienced the bug? What are you trying to build? + * Any *error* messages and logs you saw, *where* you saw them, and what you believe may have caused them (if you have any ideas). + * What is the expected behaviour? Links to the actual code? + validations: + required: true +- type: textarea + id: toolingInfo + attributes: + label: Tooling + render: text + description: | + What kind of tooling are you using: + * Are you using ether.js, Alchemy, Hardhat, etc. + validations: + required: true +- type: textarea + id: extraInfo + attributes: + label: Configuration Options + render: text + description: | + Please provide your updated FEVM related configuration options, or custome enviroment variables related to Lotus FEVM + * lotus: use `lotus config updated` to get your configuration options, and copy the [FEVM] section + validations: + required: true diff --git a/.github/labels.yml b/.github/labels.yml new file mode 100644 index 000000000..7102f1311 --- /dev/null +++ b/.github/labels.yml @@ -0,0 +1,248 @@ +### +### Special magic GitHub labels +### https://help.github.com/en/github/building-a-strong-community/encouraging-helpful-contributions-to-your-project-with-labels +# +- name: "good first issue" + color: 7057ff + description: "Good for newcomers" +- name: "help wanted" + color: 008672 + description: "Extra attention is needed" + +### +### Goals +# +- name: goal/incentives + color: ff004d + description: "Incentinet" + +### +### Areas +# +- name: area/ux + color: 00A4E0 + description: "Area: UX" +- name: area/chain/vm + color: 00A4E2 + description: "Area: Chain/VM" +- name: area/chain/sync + color: 00A4E4 + description: "Area: Chain/Sync" +- name: area/chain/misc + color: 00A4E6 + description: "Area: Chain/Misc" +- name: area/markets + color: 00A4E8 + description: "Area: Markets" +- name: area/sealing/fsm + color: 0bb1ed + description: "Area: Sealing/FSM" +- name: area/sealing/storage + color: 0EB4F0 + description: "Area: Sealing/Storage" +- name: area/proving + color: 0EB4F0 + description: "Area: Proving" +- name: area/mining + color: 10B6F2 + description: "Area: Mining" +- name: area/client/storage + color: 13B9F5 + description: "Area: Client/Storage" +- name: area/client/retrieval + color: 15BBF7 + description: "Area: Client/Retrieval" +- name: area/wallet + color: 15BBF7 + description: "Area: Wallet" +- name: area/payment-channel + color: ff6767 + description: "Area: Payment Channel" +- name: area/multisig + color: fff0ff + description: "Area: Multisig" +- name: area/networking + color: 273f8a + description: "Area: Networking" + +### +### Kinds +# +- name: kind/bug + color: c92712 + description: "Kind: Bug" +- name: kind/chore + color: fcf0b5 + description: "Kind: Chore" +- name: kind/feature + color: FFF3B8 + description: "Kind: Feature" +- name: kind/improvement + color: FFF5BA + description: "Kind: Improvement" +- name: kind/test + color: FFF8BD + description: "Kind: Test" +- name: kind/question + color: FFFDC2 + description: "Kind: Question" +- name: kind/enhancement + color: FFFFC5 + description: "Kind: Enhancement" +- name: kind/discussion + color: FFFFC7 + description: "Kind: Discussion" + +### +### Difficulties +# +- name: dif/trivial + color: b2b7ff + description: "Can be confidently tackled by newcomers, who are widely unfamiliar with lotus" +- name: dif/easy + color: 7886d7 + description: "An existing lotus user should be able to pick this up" +- name: dif/medium + color: 6574cd + description: "Prior development experience with lotus is likely helpful" +- name: dif/hard + color: 5661b3 + description: "Suggests that having worked on the specific component affected by this issue is important" +- name: dif/expert + color: 2f365f + description: "Requires extensive knowledge of the history, implications, ramifications of the issue" + +### +### Efforts +# +- name: effort/minutes + color: e8fffe + description: "Effort: Minutes" +- name: effort/hours + color: a0f0ed + description: "Effort: Hours" +- name: effort/day + color: 64d5ca + description: "Effort: One Day" +- name: effort/days + color: 4dc0b5 + description: "Effort: Multiple Days" +- name: effort/week + color: 38a89d + description: "Effort: One Week" +- name: effort/weeks + color: 20504f + description: "Effort: Multiple Weeks" + +### +### Impacts +# +- name: impact/regression + color: f1f5f8 + description: "Impact: Regression" +- name: impact/api-breakage + color: ECF0F3 + description: "Impact: API Breakage" +- name: impact/quality + color: E7EBEE + description: "Impact: Quality" +- name: impact/dx + color: E2E6E9 + description: "Impact: Developer Experience" +- name: impact/test-flakiness + color: DDE1E4 + description: "Impact: Test Flakiness" +- name: impact/consensus + color: b20014 + description: "Impact: Consensus" + +### +### Topics +# +- name: topic/interoperability + color: bf0f73 + description: "Topic: Interoperability" +- name: topic/specs + color: CC1C80 + description: "Topic: Specs" +- name: topic/docs + color: D9298D + description: "Topic: Documentation" +- name: topic/architecture + color: E53599 + description: "Topic: Architecture" + +### +### Priorities +### +- name: P0 + color: dd362a + description: "P0: Critical Blocker" +- name: P1 + color: ce8048 + description: "P1: Must be resolved" +- name: P2 + color: dbd81a + description: "P2: Should be resolved" +- name: P3 + color: 9fea8f + description: "P3: Might get resolved" + +### +### Hints +# +#- name: hint/good-first-issue +# color: 7057ff +# description: "Hint: Good First Issue" +#- name: hint/help-wanted +# color: 008672 +# description: "Hint: Help Wanted" +- name: hint/needs-decision + color: 33B9A5 + description: "Hint: Needs Decision" +- name: hint/needs-triage + color: 1AA08C + description: "Hint: Needs Triage" +- name: hint/needs-analysis + color: 26AC98 + description: "Hint: Needs Analysis" +- name: hint/needs-author-input + color: 33B9A5 + description: "Hint: Needs Author Input" +- name: hint/needs-team-input + color: 40C6B2 + description: "Hint: Needs Team Input" +- name: hint/needs-community-input + color: 4DD3BF + description: "Hint: Needs Community Input" +- name: hint/needs-review + color: 5AE0CC + description: "Hint: Needs Review" + +### +### Statuses +# +- name: status/done + color: edb3a6 + description: "Status: Done" +- name: status/deferred + color: E0A699 + description: "Status: Deferred" +- name: status/in-progress + color: D49A8D + description: "Status: In Progress" +- name: status/blocked + color: C78D80 + description: "Status: Blocked" +- name: status/inactive + color: BA8073 + description: "Status: Inactive" +- name: status/waiting + color: AD7366 + description: "Status: Waiting" +- name: status/rotten + color: 7A4033 + description: "Status: Rotten" +- name: status/discarded + color: 6D3326 + description: "Status: Discarded / Won't fix" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..b6ef5fa3c --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,23 @@ +## Related Issues + + +## Proposed Changes + + +## Additional Info + + +## Checklist + +Before you mark the PR ready for review, please make sure that: + +- [ ] Commits have a clear commit message. +- [ ] PR title is in the form of of `: : ` + - example: ` fix: mempool: Introduce a cache for valid signatures` + - `PR type`: fix, feat, build, chore, ci, docs, perf, refactor, revert, style, test + - `area`, e.g. api, chain, state, market, mempool, multisig, networking, paych, proving, sealing, wallet, deps +- [ ] New features have usage guidelines and / or documentation updates in + - [ ] [Lotus Documentation](https://lotus.filecoin.io) + - [ ] [Discussion Tutorials](https://github.com/filecoin-project/lotus/discussions/categories/tutorials) +- [ ] Tests exist for new functionality or change in behavior +- [ ] CI is green diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 000000000..0cba5457e --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,73 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: + - master + - 'release/*' + pull_request: + # The branches below must be a subset of the branches above + branches: + - master + - 'release/*' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + language: [ 'go' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - uses: actions/setup-go@v3 + with: + go-version: '1.18.8' + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: go + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/label-syncer.yml b/.github/workflows/label-syncer.yml new file mode 100644 index 000000000..a94b0edb6 --- /dev/null +++ b/.github/workflows/label-syncer.yml @@ -0,0 +1,17 @@ + +name: Label syncer +on: + push: + paths: + - '.github/labels.yml' + branches: + - master +jobs: + build: + name: Sync labels + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@1.0.0 + - uses: micnncim/action-label-syncer@v1.0.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000..35b97e369 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,33 @@ +name: Close and mark stale issue + +on: + schedule: + - cron: '0 12 * * *' + +jobs: + stale: + + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + + steps: + - uses: actions/stale@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: 'Oops, seems like we needed more information for this issue, please comment with more details or this issue will be closed in 24 hours.' + close-issue-message: 'This issue was closed because it is missing author input.' + stale-pr-message: 'Thank you for submitting the PR and contributing to lotus! Lotus maintainers need more of your input before merging it, please address the suggested changes or reply to the comments or this PR will be closed in 48 hours. You are always more than welcome to reopen the PR later as well!' + close-pr-message: 'This PR was closed because it is missing author input. Please feel free to reopen the PR when you get to it! Thank you for your interest in contributing to lotus!' + stale-issue-label: 'kind/stale' + stale-pr-label: 'kind/stale' + any-of-labels: 'need/author-input ' + days-before-issue-stale: 3 + days-before-issue-close: 1 + days-before-pr-stale: 5 + days-before-pr-close: 2 + remove-stale-when-updated: true + enable-statistics: true + + diff --git a/.github/workflows/sync-master-main.yaml b/.github/workflows/sync-master-main.yaml new file mode 100644 index 000000000..a55454eff --- /dev/null +++ b/.github/workflows/sync-master-main.yaml @@ -0,0 +1,14 @@ +name: sync-master-main +on: + push: + branches: + - master +jobs: + sync: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: update remote branch main + run: | + # overrides the remote branch (origin:github) `main` + git push origin --force master:main diff --git a/.github/workflows/testground-on-push.yml b/.github/workflows/testground-on-push.yml new file mode 100644 index 000000000..8e749bfae --- /dev/null +++ b/.github/workflows/testground-on-push.yml @@ -0,0 +1,29 @@ +--- +name: Testground PR Checker + +on: [push] + +jobs: + testground: + runs-on: ubuntu-latest + name: ${{ matrix.composition_file }} + strategy: + matrix: + include: + - backend_addr: ci.testground.ipfs.team + backend_proto: https + plan_directory: testplans/lotus-soup + composition_file: testplans/lotus-soup/_compositions/baseline-k8s-3-1.toml + - backend_addr: ci.testground.ipfs.team + backend_proto: https + plan_directory: testplans/lotus-soup + composition_file: testplans/lotus-soup/_compositions/paych-stress-k8s.toml + steps: + - uses: actions/checkout@v2 + - name: testground run + uses: testground/testground-github-action@v1 + with: + backend_addr: ${{ matrix.backend_addr }} + backend_proto: ${{ matrix.backend_proto }} + plan_directory: ${{ matrix.plan_directory }} + composition_file: ${{ matrix.composition_file }} diff --git a/.gitignore b/.gitignore index 6aaaf9240..23a0631c3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,19 +1,25 @@ /lotus -/lotus-storage-miner -/lotus-seal-worker +/lotus-miner +/lotus-worker /lotus-seed /lotus-health +/lotus-chainwatch /lotus-shed -/pond -/townhall -/fountain -/stats -/bench +/lotus-sim +/lotus-townhall +/lotus-fountain +/lotus-stats +/lotus-bench +/lotus-gateway +/lotus-pcr +/lotus-wallet +/lotus-keygen +/docgen-md +/docgen-openrpc /bench.json -/lotuspond/front/node_modules -/lotuspond/front/build /cmd/lotus-townhall/townhall/node_modules /cmd/lotus-townhall/townhall/build +/cmd/lotus-townhall/townhall/package-lock.json extern/filecoin-ffi/rust/target **/*.a **/*.pc @@ -24,14 +30,26 @@ build/paramfetch.sh /vendor /blocks.dot /blocks.svg -/chainwatch /chainwatch.db /bundle /darwin /linux +*.snap *-fuzz.zip /chain/types/work_msg/ bin/ipget bin/tmp/* .idea +scratchpad + +build/builtin-actors/v* +build/builtin-actors/*.car + +dist/ + + +# The following files are checked into git and result +# in dirty git state if removed from the docker context +!extern/filecoin-ffi/rust/filecoin.pc +!extern/test-vectors diff --git a/.gitmodules b/.gitmodules index 709a28003..cdee35ce3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,7 +1,9 @@ [submodule "extern/filecoin-ffi"] path = extern/filecoin-ffi url = https://github.com/filecoin-project/filecoin-ffi.git - branch = master [submodule "extern/serialization-vectors"] path = extern/serialization-vectors - url = https://github.com/filecoin-project/serialization-vectors + url = https://github.com/filecoin-project/serialization-vectors.git +[submodule "extern/test-vectors"] + path = extern/test-vectors + url = https://github.com/filecoin-project/test-vectors.git diff --git a/.golangci.yml b/.golangci.yml index 396a1c2ac..fe663ef7b 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -16,22 +16,63 @@ linters: - deadcode - scopelint +# We don't want to skip builtin/ +skip-dirs-use-default: false +skip-dirs: + - vendor$ + - testdata$ + - examples$ issues: exclude: - - "func name will be used as test\\.Test.* by other packages, and that stutters; consider calling this" + - "by other packages, and that stutters; consider calling this" - "Potential file inclusion via variable" - "should have( a package)? comment" + - "Error return value of `logging.SetLogLevel` is not checked" + - "comment on exported" + - "(func|method) \\w+ should be \\w+" + - "(type|var|struct field|(method|func) parameter) `\\w+` should be `\\w+`" + - "(G306|G301|G307|G108|G302|G204|G104)" + - "don't use ALL_CAPS in Go names" + - "string .* has .* occurrences, make it a constant" + - "a blank import should be only in a main or test package, or have a comment justifying it" + - "package comment should be of the form" + - "Potential hardcoded credentials" + - "Use of weak random number generator" + - "xerrors.* is deprecated" exclude-use-default: false exclude-rules: + - path: node/modules/lp2p linters: - golint - - path: ".*_test.go" + + - path: build/params_.*\.go + linters: + - golint + + - path: api/apistruct/struct.go + linters: + - golint + + - path: .*_test.go linters: - gosec + - path: chain/vectors/gen/.* + linters: + - gosec + + - path: cmd/lotus-bench/.* + linters: + - gosec + + - path: api/test/.* + text: "context.Context should be the first parameter" + linters: + - golint + linters-settings: goconst: min-occurrences: 6 diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 000000000..766f4f30a --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,109 @@ +project_name: lotus + +universal_binaries: + - id: lotus + replace: true + name_template: lotus + - id: lotus-miner + replace: true + name_template: lotus-miner + - id: lotus-worker + replace: true + name_template: lotus-worker + +builds: + - id: lotus + binary: lotus + builder: prebuilt + goos: + - darwin + - linux + goarch: + - amd64 + - arm64 + goamd64: + - v1 + ignore: + - goos: linux + goarch: arm64 + prebuilt: + path: /tmp/workspace/{{ .Os }}_{{ .Arch }}{{ with .Amd64 }}_{{ . }}{{ end }}/lotus + - id: lotus-miner + binary: lotus-miner + builder: prebuilt + goos: + - darwin + - linux + goarch: + - amd64 + - arm64 + goamd64: + - v1 + ignore: + - goos: linux + goarch: arm64 + prebuilt: + path: /tmp/workspace/{{ .Os }}_{{ .Arch }}{{ with .Amd64 }}_{{ . }}{{ end }}/lotus-miner + - id: lotus-worker + binary: lotus-worker + builder: prebuilt + goos: + - darwin + - linux + goarch: + - amd64 + - arm64 + goamd64: + - v1 + ignore: + - goos: linux + goarch: arm64 + prebuilt: + path: /tmp/workspace/{{ .Os }}_{{ .Arch }}{{ with .Amd64 }}_{{ . }}{{ end }}/lotus-worker + +archives: + - id: primary + format: tar.gz + wrap_in_directory: true + name_template: "{{ .ProjectName }}_v{{ .Version }}_{{ .Os }}_{{ .Arch }}" + files: + # this is a dumb but required hack so it doesn't include the default files + # https://github.com/goreleaser/goreleaser/issues/602 + - _n_o_n_e_* + +release: + github: + owner: filecoin-project + name: lotus + prerelease: auto + name_template: "v{{.Version}}" + +brews: + - tap: + owner: filecoin-project + name: homebrew-lotus + branch: master + ids: + - primary + install: | + bin.install "lotus" + bin.install "lotus-miner" + bin.install "lotus-worker" + test: | + system "#{bin}/lotus --version" + system "#{bin}/lotus-miner --version" + system "#{bin}/lotus-worker --version" + folder: Formula + homepage: "https://filecoin.io" + description: "A homebrew cask for installing filecoin-project/lotus on MacOS" + license: MIT + skip_upload: auto + dependencies: + - name: hwloc + +# produced manually so we can include cid checksums +checksum: + disable: true + +snapshot: + name_template: "{{ .Version }}" diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ba83a258..17cf7a8c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,4941 @@ -# lotus changelog +# Lotus changelog -## 0.1.0 / 2019-12-11 +# UNRELEASED -We are very excited to release **lotus** 0.1.0. This is our testnet release. To install lotus and join the testnet, please visit [docs.lotu.sh](docs.lotu.sh). Please file bug reports as [issues](https://github.com/filecoin-project/lotus/issues). +## New features +- feat: Added new environment variable `LOTUS_EXEC_TRACE_CACHE_SIZE` to configure execution trace cache size ([filecoin-project/lotus#10585](https://github.com/filecoin-project/lotus/pull/10585)) + - If unset, we default to caching 16 most recent execution traces. Node operatores may want to set this to 0 while exchanges may want to crank it up. -A huge thank you to all contributors for this testnet release! \ No newline at end of file +# v1.23.0 / 2023-04-21 + +This is the stable feature release for the upcoming MANDATORY network upgrade at `2023-04-27T13:00:00Z`, epoch `2809800`. This feature release delivers the nv19 Lighting and nv20 Thunder network upgrade for mainnet, and includes numerous improvements and enhancements for node operators, ETH RPC-providers and storage providers. + +## ☢️ Upgrade Warnings ☢️ + +Please read carefully through the **upgrade warnings** section if you are upgrading from a v1.20.X release, or the v1.22.0 release. If you are upgrading from a v1.21.0-rcX these warnings should be familiar to you. + +- Starting from this release, the SplitStore feature is automatically activated on new nodes. However, for existing Lotus users, you need to explicitly configure SplitStore by uncommenting the `EnableSplitstore` option in your `config.toml` file. To enable SplitStore, set `EnableSplitstore=true`, and to disable it, set `EnableSplitstore=false`. **It's important to note that your Lotus node will not start unless this configuration is properly set. Set it to false if you are running a full archival node!** +- This feature release requires a **minimum Go version of v1.19.7 or higher to successfully build Lotus**. Additionally, Go version v1.20 and higher is now also supported. +- **Storage Providers:** The proofs libraries now have CUDA enabled by default, which requires you to install (CUDA)[https://lotus.filecoin.io/tutorials/lotus-miner/cuda/] if you haven't already done so. If you prefer to use OpenCL on your GPUs instead, you can use the `FFI_USE_OPENCL=1` flag when building from source. On the other hand, if you want to disable GPUs altogether, you can use the `FFI_NO_GPU=1` environment variable when building from source. +- **Storage Providers:** The `lotus-miner sectors extend` command has been refactored to the functionality of `lotus-miner sectors renew`. +- **Exchanges/Node operators/RPC-providers::** Execution traces (returned from `lotus state exec-trace`, `lotus state replay`, etc.), has changed to account for changes introduced by the by the FVM. **Please make sure to read the `Execution trace format change` section carefully, as these are interface breaking changes** +- **Syncing issues:** If you have been struggling with syncing issues in normal operations you can try to adjust the amount of threads used for more concurrent FMV execution through via the `LOTUS_FVM_CONCURRENCY` enviroment variable. It is set to 4 threads by default. Recommended formula for concurrency == YOUR_RAM/4 , but max during a network upgrade is 24. If you are a Storage Provider and are pushing many messages within a short period of time, exporting `LOTUS_SKIP_APPLY_TS_MESSAGE_CALL_WITH_GAS=1` will also help with keeping in sync. +- **Catching up from a Snapshot:** Users have noticed that catching up sync from a snapshot is taking a lot longer these day. This is largely related to the built-in market actor consuming a lot of computational demand for block validation. A FIP for a short-term mitigation for this is currently in Last Call and will be included network version 19 upgrade if accepted. You [can read the FIP here.](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0060.md) + +## Highlights + +### Execution Trace Format Changes + +Execution traces (returned from `lotus state exec-trace`, `lotus state replay`, etc.), has changed to account for changes introduced by the FVM. Specifically: + +- The `Msg` field no longer matches the Filecoin message format as many of the message fields didn't make sense in on-chain sub-calls. Instead, it now has the fields `To`, `From`, `Value`, `Method`, `Params`, and `ParamsCodec` where `ParamsCodec` is a new field indicating the IPLD codec of the parameters. + - Importantly, the `Msg.CID` field has been removed. This field is still present in top-level invocation results, just not inside the execution trace itself. +- The `MsgRct` field no longer includes a `GasUsed` field and now has a `ReturnCodec` field to indicating the IPLD codec of the return value. +- The `Error` and `Duration` fields have been removed as these are not set by the FVM. The top-level message "invocation result" retains the `Error` and `Duration` fields, they've only been removed from the trace itself. +- Gas Charges no longer include "virtual" gas fields (those starting with `v...`) or source location information (`loc`) as neither field is set by the FVM. + +A note on "codecs": FVM parameters and return values are IPLD blocks where the "codec" specifies the data encoding. The codec will generally be one of: + +- `0x51`, `0x71` - CBOR or DagCBOR. You should generally treat these as equivalent. +- `0x55` - Raw bytes. +- `0x00` - Nothing. If the codec is `0x00`, the parameter and/or return value should be empty and should be treated as "void" (not specified). + +
+ +Old ExecutionTrace: + + +```json +{ + "Msg": { + "Version": 0, + "To": "f01234", + "From": "f04321", + "Nonce": 1, + "Value": "0", + "GasLimit": 0, + "GasFeeCap": "1234", + "GasPremium": "1234", + "Method": 42, + "Params": "", + "CID": { + "/": "bafyxyz....." + }, + }, + "MsgRct": { + "ExitCode": 0, + "Return": "", + "GasUsed": 12345, + }, + "Error": "", + "Duration": 568191845, + "GasCharges": [ + { + "Name": "OnMethodInvocation", + "loc": null, + "tg": 23856, + "cg": 23856, + "sg": 0, + "vtg": 0, + "vcg": 0, + "vsg": 0, + "tt": 0 + }, + { + "Name": "wasm_exec", + "loc": null, + "tg": 1764, + "cg": 1764, + "sg": 0, + "vtg": 0, + "vcg": 0, + "vsg": 0, + "tt": 0 + }, + { + "Name": "OnSyscall", + "loc": null, + "tg": 14000, + "cg": 14000, + "sg": 0, + "vtg": 0, + "vcg": 0, + "vsg": 0, + "tt": 0 + }, + ], + "Subcalls": [ + { + "Msg": { }, + "MsgRct": { }, + "Error": "", + "Duration": 1235, + "GasCharges": [], + "Subcalls": [], + }, + ] +} +``` +
+ +
+ +New ExecutionTrace: + + +```json +{ + "Msg": { + "To": "f01234", + "From": "f04321", + "Value": "0", + "Method": 42, + "Params": "", + "ParamsCodec": 81 + }, + "MsgRct": { + "ExitCode": 0, + "Return": "", + "ReturnCodec": 81 + }, + "GasCharges": [ + { + "Name": "OnMethodInvocation", + "loc": null, + "tg": 23856, + "cg": 23856, + "tt": 0 + }, + { + "Name": "wasm_exec", + "loc": null, + "tg": 1764, + "cg": 1764, + "sg": 0, + "tt": 0 + }, + { + "Name": "OnSyscall", + "loc": null, + "tg": 14000, + "cg": 14000, + "sg": 0, + "tt": 0 + }, + ], + "Subcalls": [ + { + "Msg": { }, + "MsgRct": { }, + "GasCharges": [], + "Subcalls": [], + }, + ] +} +``` + +
+ +**SplitStore** + +This feature release introduces numerous improvements and fixes to tackle SplitStore related issues that has been reported. With this feature release SplitStore is automatically activated by default on new nodes. However, for existing Lotus users, you need to explicitly configure SplitStore by uncommenting the `EnableSplitstore` option in your `config.toml` file. To enable SplitStore, set `EnableSplitstore=true`, and to disable it, set `EnableSplitstore=false`. **It's important to note that your Lotus node will not start unless this configuration is properly set. Set it to false if you are running a full archival node!** + +SplitStore also has some new configuration settings that you can set in your config.toml file: +- `HotstoreMaxSpaceTarget` suggests the max allowed space (in bytes) the hotstore can take. +- `HotstoreMaxSpaceThreshold` a moving GC will be triggered when total moving size exceeds this threshold (in bytes). +- `HotstoreMaxSpaceSafetyBuffer` a safety buffer to prevent moving GC from an overflowing disk. + +The SplitStore also has two new commands: + +- `lotus chain prune hot` is a much less resource-intensive GC and is best suited for situations where you don't have the spare disk space for a full GC. +- `lotus chain prune hot-moving` will run a full moving garbage collection of the hotstore. This commands create a new hotstore before deleting the old one so you need working room in the hotstore directory. The current size of a fully GC'd hotstore is around 295 GiB so you need to make sure you have at least that available. + +You can read more about the new SplitStore commands in [the documentation](https://lotus.filecoin.io/lotus/configure/splitstore/#manual-chain-store-garbage-collection). + +**RPC API improvements** + +This feature release includes all the RPC API improvements made in the Lotus v1.20.x patch releases. It includes an updated FFI that sets the FVM parallelism to 4 by default. + +Node operators with higher memory specs can experiment with setting LOTUS_FVM_CONCURRENCY to higher values, up to 48, to allow for more concurrent FVM execution. + +**Experimental scheduler assigners** + +In this release there are four new expirmental scheduler assigners: + +- The `experiment-spread-qcount` - similar to the spread assigner but also takes into account task counts which are in running/preparing/queued states. +- The `experiment-spread-tasks` - similar to the spread assigner, but counts running tasks on a per-task-type basis +- The `experiment-spread-tasks-qcount` - similar to the spread assigner, but also takes into account task counts which are in running/preparing/queued states, as well as counting running tasks on a per-task-type basis. Check the results for this assigner on ([storage-only lotus-workers here](https://github.com/filecoin-project/lotus/issues/8566#issuecomment-1446978856)). +- The `experiment-random` - In each schedule loop the assinger figures a set of all workers which can handle the task and then picks a random one. Check the results for this assigner on ([storage-only lotus-workers here](https://github.com/filecoin-project/lotus/issues/8566#issuecomment-1447064218)). + +**Graceful shutdown of lotus-workers** +We have cleaned up some commands in the `lotus-worker` to make it less confusing how to gracefully shutting down a `lotus-worker` while there are incoming sealing tasks in the pipeline. To shut down a `lotus-worker` gracefully: + +1. `lotus-worker tasks disable --all` and wait for the worker to finish processing its current tasks. +2. `lotus-worker stop` to detach it and do maintenance/upgrades. + +**CLI speedups** + +The `lotus-miner sector list` is now running in parallel - which should speed up the process from anywhere between 2x-10x+. You can tune it additionally with the `check-parallelism` option in the command. The `Lotus-Miner info` command also has a large speed improvement, as calls to the lotus legacy market has been removed. + +## New features +- feat: splitstore: Pause compaction when out of sync ([filecoin-project/lotus/#10641](https://github.com/filecoin-project/lotus/pull/10641)) + - Pause the SplitStore compaction if the node is out of sync. Resumes the compation when its back in sync. +- feat: splitstore: limit moving gc threads (#10621) ([filecoin-project/lotus/#10621](https://github.com/filecoin-project/lotus/pull/10621)) + - Makes moving gc less likely to cause node falling out of sync. +- feat: splitstore: Update config default value (#10605) ([filecoin-project/lotus/#10605](https://github.com/filecoin-project/lotus/pull/10605)) + - Sets Splitstore HotStoreMaxSpaceTarget config to 650GB as default +- feat: splitstore: Splitstore enabled by default (#10429) ([filecoin-project/lotus#10429](https://github.com/filecoin-project/lotus/pull/10429)) + - Enables SplitStore by default on new Lotus nodes. Existing Lotus users need to explicitly configure +- feat: splitstore: Configure max space used by hotstore and GC makes best effort to respect ([filecoin-project/lotus#10391](https://github.com/filecoin-project/lotus/pull/10391)) + - Adds three new configs for setting the maximum allowed space the hotstore can take. +- feat: splitstore: Badger GC of hotstore command ([filecoin-project/lotus#10387](https://github.com/filecoin-project/lotus/pull/10387)) + - Adds a `lotus chain prune hot` command, to run the garbage collection of the hotstore in a user driven way. +- feat: sched: Assigner experiments ([filecoin-project/lotus#10356](https://github.com/filecoin-project/lotus/pull/10356)) + - Introduces experimental scheduler assigners that works better for setups that uses storage-only lotus-workers. +- fix: wdpost: disabled post worker handling ([filecoin-project/lotus#10394](https://github.com/filecoin-project/lotus/pull/10394)) + - Improved scheduling logic for Proof-of-SpaceTime workers. +- feat: cli: list claims and remove expired claims ([filecoin-project/lotus#9875](https://github.com/filecoin-project/lotus/pull/9875)) + - Adds a command to list claims made by a provider `lotus filplus list-claims`. And `lotus filplus remove-expired-claims` to remove expired claims. +- feat: cli: make sectors list much faster ([filecoin-project/lotus#10202](https://github.com/filecoin-project/lotus/pull/10202)) + - Makes `lotus-miner sector list` checks run in parallel. +- feat: cli: Add an EVM command to fetch a contract's bytecode ([filecoin-project/lotus#10443](https://github.com/filecoin-project/lotus/pull/10443)) + - Adds an `lotus evm bytecode` command to fetch a contract's bytecode. +- feat: mempool: Reduce minimum replace fee from 1.25x to 1.1x (#10416) ([filecoin-project/lotus#10416](https://github.com/filecoin-project/lotus/pull/10416)) + - Reduces replacement message fee logic to help include update message replacements from developers using Ethereum tools like MetaMask. +- feat: update renew-sectors with FIP-0045 logic ([filecoin-project/lotus#10328](https://github.com/filecoin-project/lotus/pull/10328)) + - Updates the `lotus-miner sectors extend` with FIP-0045 logic to include the ability to drop claims and set the maximum number of messages contained in a message. +- feat: IPC: Abstract common consensus functions and consensus interface ([filecoin-project/lotus#9481](https://github.com/filecoin-project/lotus/pull/9481)) + - Add eudico's consensus interface to Lotus and implement EC behind that interface. This abstraction is the stepping-stone for Mir's integration. +- fix: worker: add all tasks flag ([filecoin-project/lotus#10232](https://github.com/filecoin-project/lotus/pull/10232)) + - Adds an `all` flag for the `lotus-worker tasks enable/disable` cmds. +- feat:shed:add cid to cbor serialization command ([filecoin-project/lotus#10032](https://github.com/filecoin-project/lotus/pull/10032)) + - Adds two `lotus-shed` commands, `lotus-shed cid bytes` and `lotus-shed cid cbor` to serialize cid to cbor and cid to bytes. +- feat: add toolshed commands to inspect statetree size ([filecoin-project/lotus#9982](https://github.com/filecoin-project/lotus/pull/9982)) + - Adds two commands, `lotus-shed stat-actor` and `lotus-shed stat-obj` that work with an offline lotus repo to report dag size stats. +- feat: shed: encode address to bytes ([filecoin-project/lotus#10105](https://github.com/filecoin-project/lotus/pull/10105)) + - Adds a `lotus-shed address encode` for encoding a filecoin address to hex bytes. +- feat: chain: export-range ([filecoin-project/lotus#10145](https://github.com/filecoin-project/lotus/pull/10145)) + - Adds a `lotus chain export-range` command that can create archival-grade ranged exports of the chain as quickly as possible. +- feat: stmgr: cache migrated stateroots ([filecoin-project/lotus#10282](https://github.com/filecoin-project/lotus/pull/10282)) + - Cache network migration results to avoid running migrations twice. +- feat: shed: Add a tool to read data from sectors ([filecoin-project/lotus#10169](https://github.com/filecoin-project/lotus/pull/10169)) + - Adds a lotus-shed sectors read command that extract data from sectors from a running lotus-miner deployment. +- feat: cli: Refactor renew and remove extend ([filecoin-project/lotus#9920](https://github.com/filecoin-project/lotus/pull/9920)) + - Refactors the `lotus-miner sectors extend` command to have the functionality of `lotus-miner sectors renew`. The `lotus-miner sectors renew` command has been deprecated. +- feat: shed: Add beneficiary commands ([filecoin-project/lotus#10037](https://github.com/filecoin-project/lotus/pull/10037)) + - Adds the beneficiary address command to `lotus-shed`. You can now use `lotus-shed actor propose-change-beneficiary` and `lotus-shed actor confirm-change-beneficiary` to change beneficiary addresses. + +## Improvements + +- backport: fix: miner: correctly count sector extensions (10555) ([filecoin-project/lotus#10555](https://github.com/filecoin-project/lotus/pull/10555)) + - Fixes the issue with sector extensions. +- fix: proving: Initialize slice with with same length as partition (#10574) ([filecoin-project/lotus#10574])(https://github.com/filecoin-project/lotus/pull/10574) + - Fixes an issue where `lotus-miner proving compute window-post` paniced when trying to make skipped sectors human readable. +- feat: stmgr: speed up calculation of genesis circ supply (#10553) ([filecoin-project/lotus#10553])(https://github.com/filecoin-project/lotus/pull/10553) +- perf: eth: gas estimate set applyTsMessages false (#10546) ([filecoin-project/lotus#10456](https://github.com/filecoin-project/lotus/pull/10546)) +- feat: config: Force existing users to opt into new defaults (#10488) ([filecoin-project/lotus#10488](https://github.com/filecoin-project/lotus/pull/10488)) + - Force existing users to opt into the new SplitStore defaults. +- fix: splitstore: Demote now common logs (#10516) ([filecoin-project/lotus#10516](https://github.com/filecoin-project/lotus/pull/10516)) +- fix: splitstore: Don't enforce walking receipt tree during compaction ([filecoin-project/lotus#10502](https://github.com/filecoin-project/lotus/pull/10502)) +- fix: splitstore: Fix the overzealous fix (#10366) ([filecoin-project/lotus#10366](https://github.com/filecoin-project/lotus/pull/10366)) +- fix: splitstore: Two fixes, better logging and comments (#10332) ([filecoin-project/lotus#10332](https://github.com/filecoin-project/lotus/pull/10332)) +- fix: fsm: shutdown removed sectors FSMs ([filecoin-project/lotus#10363](https://github.com/filecoin-project/lotus/pull/10363)) + - Fixes an issue where removed sectors still got state machine events. +- fix: rpcenc: Don't hang when source dies ([filecoin-project/lotus#10116](https://github.com/filecoin-project/lotus/pull/10116)) + - Fixes an issue where AddPiece tasks could get stuck if the Boost process was abruptly lost. +- fix: make debugging windowPoSt-failures human readable ([filecoin-project/lotus#10390](https://github.com/filecoin-project/lotus/pull/10390)) + - Makes the skipped sector list in `lotus-miner proving compute window-post` human readable. +- fix: cli: Hide `lotus-worker set` command ([filecoin-project/lotus#10384](https://github.com/filecoin-project/lotus/pull/10384)) + - Hides the `lotus-worker set` command. This command will be deprecated later. +- fix: worker: Hide `wait-quiet` cmd ([filecoin-project/lotus#10331](https://github.com/filecoin-project/lotus/pull/10331)) + - Hides the `lotus-worker wait-quiet` command. This command will be deprecated later. +- fix: post: Tune down default post-parallel-reads ([filecoin-project/lotus#10365](https://github.com/filecoin-project/lotus/pull/10365)) + - Tuning down the default post-parallel-reads to a more conservative number to prevent sectors from being skipped due to network timeouts. +- fix: cli: error if backup file already exists ([filecoin-project/lotus#10209](https://github.com/filecoin-project/lotus/pull/10209)) + - Error out if a backup file with the same name already exists when using the `lotus-miner backup` or `lotus backup` command +- fix: cli: option to set-seal-delay in seconds ([filecoin-project/lotus#10208](https://github.com/filecoin-project/lotus/pull/10208)) + - Adds the option to specify `lotus-miner sectors set-seal-delay` in seconds +- fix: cli: extend cmd to get the right sector number ([filecoin-project/lotus#10182](https://github.com/filecoin-project/lotus/pull/10182)) + - Making sure the `lotus-miner sectors extend` command gets the correct sector number. +- feat: wdpost: Emit more detailed errors ([filecoin-project/lotus#10121](https://github.com/filecoin-project/lotus/pull/10121)) + - Emits more detailed windowPoSt error messages, making it easier to debug PoSt issues. +- fix: Lotus Gateway: Add missing methods - master ([filecoin-project/lotus#10420](https://github.com/filecoin-project/lotus/pull/10420)) + - Adds `StateNetworkName`, `MpoolGetNonce`, `StateCall` and `StateDecodeParams` methods to Lotus Gateway. +- fix: stmgr: don't attempt to lookup genesis state (#10472) ([filecoin-project/lotus#10472](https://github.com/filecoin-project/lotus/pull/10472)) +- feat: gateway: export StateVerifierStatus ([filecoin-project/lotus#10477](https://github.com/filecoin-project/lotus/pull/10477)) +- fix: gateway: correctly apply the fee history lookback max ([filecoin-project/lotus#10464](https://github.com/filecoin-project/lotus/pull/10464)) +- fix: gateway: drop overzealous guard on MsigGetVested ([filecoin-project/lotus#10451](https://github.com/filecoin-project/lotus/pull/10451)) +- feat: apply gateway lookback limit to eth API lookback ([filecoin-project/lotus#10467](https://github.com/filecoin-project/lotus/pull/10467)) +- fix: revert "Eth API: drop support for 'pending' block parameter." ([filecoin-project/lotus#10474](https://github.com/filecoin-project/lotus/pull/10474)) +- fix: Eth API: make net_version return the chain ID ([filecoin-project/lotus#10456](https://github.com/filecoin-project/lotus/pull/10456)) +- fix: eth: handle a potential divide by zero in receipt handling ([filecoin-project/lotus#10495](https://github.com/filecoin-project/lotus/pull/10495)) +- fix: ethrpc: Don't lock up when eth subscriber goes away ([filecoin-project/lotus#10485](https://github.com/filecoin-project/lotus/pull/10485)) +- feat: eth: Avoid StateCompute in EthTxnReceipt lookup (#10460) ([filecoin-project/lotus#10460](https://github.com/filecoin-project/lotus/pull/10460)) +- feat: eth: optimize eth block loading + eth_feeHistory ([filecoin-project/lotus#10446](https://github.com/filecoin-project/lotus/pull/10446)) +- feat: state: skip tipset execution when possible ([filecoin-project/lotus#10445](https://github.com/filecoin-project/lotus/pull/10445)) +- feat: eth API: reject masked ID addresses embedded in f410f payloads ([filecoin-project/lotus#10440](https://github.com/filecoin-project/lotus/pull/10440)) +- fix: Eth API: make block parameter parsing sounder. ([filecoin-project/lotus#10427](https://github.com/filecoin-project/lotus/pull/10427)) +- fix: eth API: return correct txIdx around null blocks (#10419) ([filecoin-project/lotus#10419](https://github.com/filecoin-project/lotus/pull/10419)) +- fix: EthAPI: use StateCompute for feeHistory; apply minimum gas premium (#10413) ([filecoin-project/lotus#10413](https://github.com/filecoin-project/lotus/pull/10413)) +- refactor: EthAPI: Drop unnecessary param from newEthTxReceipt ([filecoin-project/lotus#10411](https://github.com/filecoin-project/lotus/pull/10411)) +- fix: eth API: correct gateway restrictions, drop unimplemented methods ([filecoin-project/lotus#10409](https://github.com/filecoin-project/lotus/pull/10409)) +- fix: EthAPI: Correctly get parent hash ([filecoin-project/lotus#10389](https://github.com/filecoin-project/lotus/pull/10389)) +- fix: EthAPI: Make newEthBlockFromFilecoinTipSet faster and correct ([filecoin-project/lotus#10380](https://github.com/filecoin-project/lotus/pull/10380)) +- fix: eth: incorrect struct tags (#10309) ([filecoin-project/lotus#10309](https://github.com/filecoin-project/lotus/pull/10309)) +- refactor: update cache to the new generic version (#10463) ([filecoin-project/lotus#10463](https://github.com/filecoin-project/lotus/pull/10463)) +- feat: consensus: log ApplyBlock timing/gas stats ([filecoin-project/lotus#10470](https://github.com/filecoin-project/lotus/pull/10470)) +- feat: chain: make chain tipset fetching 1000x faster ([filecoin-project/lotus#10423](https://github.com/filecoin-project/lotus/pull/10423)) +- chain: explicitly check that gasLimit is above zero ([filecoin-project/lotus#10198](https://github.com/filecoin-project/lotus/pull/10198)) +- feat: blockstore: Envvar can adjust badger compaction worker poolsize ([filecoin-project/lotus#9973](https://github.com/filecoin-project/lotus/pull/9973)) +- feat: stmgr: add env to disable premigrations ([filecoin-project/lotus#10283](https://github.com/filecoin-project/lotus/pull/10283)) +- chore: Remove legacy market info from lotus-miner info ([filecoin-project/lotus#10364](https://github.com/filecoin-project/lotus/pull/10364)) + - Removes the legacy market info in the `Lotus-Miner info`. Speeds up the command significantly. +- chore: blockstore: Plumb through a proper Flush() method on all blockstores ([filecoin-project/lotus#10465](https://github.com/filecoin-project/lotus/pull/10465)) +- fix: extend LOTUS_CHAIN_BADGERSTORE_DISABLE_FSYNC to the markset ([filecoin-project/lotus#10172](https://github.com/filecoin-project/lotus/pull/10172)) +- feat: vm: switch to the new exec trace format (#10372) ([filecoin-project/lotus#10372](https://github.com/filecoin-project/lotus/pull/10372)) +- fix: Remove workaround that is no longer needed ([filecoin-project/lotus#9995](https://github.com/filecoin-project/lotus/pull/9995)) +- feat: Check for allocation expiry when waiting to seal sectors ([filecoin-project/lotus#9878](https://github.com/filecoin-project/lotus/pull/9878)) +- feat: Allow libp2p user agent to be overriden ([filecoin-project/lotus#10149](https://github.com/filecoin-project/lotus/pull/10149)) +- feat: cli: Add global color flag ([filecoin-project/lotus#10022](https://github.com/filecoin-project/lotus/pull/10022)) +- fix: should not serve non v0 api in v0 ([filecoin-project/lotus#10066](https://github.com/filecoin-project/lotus/pull/10066)) +- fix: build: drop drand incentinet servers ([filecoin-project/lotus#10476](https://github.com/filecoin-project/lotus/pull/10476)) +- fix: sealing: stub out the FileSize function on Windows ([filecoin-project/lotus#10035](https://github.com/filecoin-project/lotus/pull/10035)) + +## Dependencies +- github.com/filecoin-project/go-dagaggregator-unixfs (v0.2.0 -> v0.3.0): +- github.com/filecoin-project/go-fil-markets (v1.25.2 -> v1.27.0-rc1): +- github.com/filecoin-project/go-jsonrpc (v0.2.1 -> v0.2.3): +- github.com/filecoin-project/go-statemachine (v1.0.2 -> v1.0.3): +- github.com/filecoin-project/go-state-types (v0.10.0 -> v0.11.0-alpha-3) +- github.com/ipfs/go-cid (v0.3.2 -> v0.4.0): +- github.com/ipfs/go-libipfs (v0.5.0 -> v0.7.0): +- github.com/ipfs/go-path (v0.3.0 -> v0.3.1): +- chore: deps: update to go-state-types v0.11.0-alpha-3 (([filecoin-project/lotus#10606](https://github.com/filecoin-project/lotus/pull/10606)) +- deps: update go-libp2p-pubsub to v0.9.3 ([filecoin-project/lotus#10483](https://github.com/filecoin-project/lotus/pull/10483)) +- deps: Update go-jsonrpc to v0.2.2 ([filecoin-project/lotus#10395](https://github.com/filecoin-project/lotus/pull/10395)) +- Update to go-data-transfer v2 and libp2p, still wip ([filecoin-project/lotus#10382](https://github.com/filecoin-project/lotus/pull/10382)) +- dep: ipld: update ipld prime to v0.20.0 ([filecoin-project/lotus#10247](https://github.com/filecoin-project/lotus/pull/10247)) +- chore: node: migrate go-bitswap to go-libipfs/bitswap ([filecoin-project/lotus#10138](https://github.com/filecoin-project/lotus/pull/10138)) +- chore: all: bump go-libipfs to replace go-block-format ([filecoin-project/lotus#10126](https://github.com/filecoin-project/lotus/pull/10126)) +- chore: market: Upgrade to index-provider 0.10.0 ([filecoin-project/lotus#9981](https://github.com/filecoin-project/lotus/pull/9981)) +- chore: all: bump go-libipfs ([filecoin-project/lotus#10563](https://github.com/filecoin-project/lotus/pull/10563)) + +## Others +- Update service_developer_bug_report.yml ([filecoin-project/lotus#10321](https://github.com/filecoin-project/lotus/pull/10321)) +- Update service_developer_bug_report.yml ([filecoin-project/lotus#10321](https://github.com/filecoin-project/lotus/pull/10321)) +- chore: github: Service-provider/dev bug template ([filecoin-project/lotus#10321](https://github.com/filecoin-project/lotus/pull/10321)) +- chore: github: update enhancement and feature templates ([filecoin-project/lotus#10291](https://github.com/filecoin-project/lotus/pull/10291)) +- chore: github: Update bug_report template ([filecoin-project/lotus#10289](https://github.com/filecoin-project/lotus/pull/10289)) +- fix: itest: avoid failing the test when we race the miner ([filecoin-project/lotus#10461](https://github.com/filecoin-project/lotus/pull/10461)) +- fix: github: Discussion and FIP links in `New Issue` ([filecoin-project/lotus#10268](https://github.com/filecoin-project/lotus/pull/10268)) +- fix: state: short-circuit genesis state computation ([filecoin-project/lotus#10397](https://github.com/filecoin-project/lotus/pull/10397)) +- fix: rpcenc: deflake TestReaderRedirectDrop ([filecoin-project/lotus#10406](https://github.com/filecoin-project/lotus/pull/10406)) +- fix: tests: Fix TestMinerAllInfo test ([filecoin-project/lotus#10319](https://github.com/filecoin-project/lotus/pull/10319)) +- fix: tests: Make TestWorkerKeyChange not flaky ([filecoin-project/lotus#10320](https://github.com/filecoin-project/lotus/pull/10320)) +- test: eth: make sure we can deploy a new placeholder on transfer (#10281) ([filecoin-project/lotus#10281](https://github.com/filecoin-project/lotus/pull/10281)) +- fix: itests: Fix flaky paych test ([filecoin-project/lotus#10100](https://github.com/filecoin-project/lotus/pull/10100)) +- fix: cli: add ArgsUsage ([filecoin-project/lotus#10147](https://github.com/filecoin-project/lotus/pull/10147)) +- chore: cli: cleanup cli ([filecoin-project/lotus#10114](https://github.com/filecoin-project/lotus/pull/10114)) +- chore: cli: Remove unneeded individual color flags ([filecoin-project/lotus#10028](https://github.com/filecoin-project/lotus/pull/10028)) +- fix: cli: remove requirements in helptext ([filecoin-project/lotus#9969](https://github.com/filecoin-project/lotus/pull/9969)) +- chore: build: release v1.21.0-rc1 prep ([filecoin-project/lotus#10524](https://github.com/filecoin-project/lotus/pull/10524)) +- chore: merge release/v1.20.0 into master ([filecoin-project/lotus#10308](https://github.com/filecoin-project/lotus/pull/10308)) +- chore: merge release branch into master ([filecoin-project/lotus#10272](https://github.com/filecoin-project/lotus/pull/10272)) +- chore: merge release/v1.20.0 into master ([filecoin-project/lotus#10238](https://github.com/filecoin-project/lotus/pull/10238)) +- chore: releases to master ([filecoin-project/lotus#10490](https://github.com/filecoin-project/lotus/pull/10490)) +- chore: merge releases into master ([filecoin-project/lotus#10377](https://github.com/filecoin-project/lotus/pull/10377)) +- chore: merge release/v1.20.0 into master ([filecoin-project/lotus#10030](https://github.com/filecoin-project/lotus/pull/10030)) +- chore: update ffi to increase execution parallelism (#10480) ([filecoin-project/lotus#10480](https://github.com/filecoin-project/lotus/pull/10480)) +- chore: update the FFI for release (#10435) ([filecoin-project/lotus#10444](https://github.com/filecoin-project/lotus/pull/10444)) +- build: bump version to v1.21.0-dev ([filecoin-project/lotus#10249](https://github.com/filecoin-project/lotus/pull/10249)) +- build: docker: Update GO-version (#10591) ([filecoin-project/lotus#10249](https://github.com/filecoin-project/lotus/pull/10591)) +- chore: merge release/v1.20.0 into master ([filecoin-project/lotus#10184](https://github.com/filecoin-project/lotus/pull/10184)) +- docs: API Gateway: patch documentation note about make gen command ([filecoin-project/lotus#10422](https://github.com/filecoin-project/lotus/pull/10422)) +- chore: docs: fix docs typos ([filecoin-project/lotus#10155](https://github.com/filecoin-project/lotus/pull/10155)) +- chore: docker: Add back <> parameter for docker push ([filecoin-project/lotus#10096](https://github.com/filecoin-project/lotus/pull/10096)) +- chore: docker: Properly balance <> in circleci docker config ([filecoin-project/lotus#10088](https://github.com/filecoin-project/lotus/pull/10088)) +- chore: ci: Fix dirty git state when building docker images ([filecoin-project/lotus#10125](https://github.com/filecoin-project/lotus/pull/10125)) +- chore: build: Remove AppImage and Snapcraft build automation ([filecoin-project/lotus#10003](https://github.com/filecoin-project/lotus/pull/10003)) +- chore: ci: Update codeql to v2 ([filecoin-project/lotus#10120](https://github.com/filecoin-project/lotus/pull/10120)) +- feat: ci: make ci more efficient ([filecoin-project/lotus#9910](https://github.com/filecoin-project/lotus/pull/9910)) +- feat: scripts: go.mod dep diff script ([filecoin-project/lotus#9711](https://github.com/filecoin-project/lotus/pull/9711)) + +## Contributors + +| Contributor | Commits | Lines ± | Files Changed | +|-------------|---------|---------|---------------| +| Hannah Howard | 2 | +2909/-6026 | 84 | +| Łukasz Magiera | 42 | +2967/-1848 | 95 | +| Steven Allen | 20 | +1703/-1345 | 88 | +| Alfonso de la Rocha | 17 | +823/-1808 | 86 | +| Peter Rabbitson | 9 | +1957/-219 | 34 | +| Geoff Stuart | 12 | +818/-848 | 29 | +| hannahhoward | 5 | +507/-718 | 36 | +| Hector Sanjuan | 6 | +443/-726 | 35 | +| Kevin Li | 1 | +1124/-14 | 22 | +| zenground0 | 30 | +791/-269 | 88 | +| frrist | 1 | +992/-16 | 13 | +| Travis Person | 4 | +837/-53 | 24 | +| Phi | 20 | +622/-254 | 34 | +| Ian Davis | 7 | +35/-729 | 20 | +| Aayush | 10 | +378/-177 | 40 | +| Raúl Kripalani | 15 | +207/-138 | 19 | +| Arsenii Petrovich | 7 | +248/-94 | 30 | +| ZenGround0 | 5 | +238/-39 | 15 | +| Neel Virdy | 1 | +109/-107 | 58 | +| ychiao | 1 | +135/-39 | 3 | +| Jorropo | 2 | +87/-82 | 67 | +| Marten Seemann | 8 | +69/-64 | 17 | +| Rod Vagg | 1 | +55/-16 | 3 | +| Masih H. Derkani | 3 | +39/-27 | 12 | +| raulk | 2 | +30/-29 | 5 | +| dependabot[bot] | 4 | +37/-17 | 8 | +| beck | 2 | +38/-2 | 2 | +| Jennifer Wang | 4 | +20/-19 | 19 | +| Richard Guan | 3 | +28/-8 | 5 | +| omahs | 7 | +14/-14 | 7 | +| dirkmc | 2 | +19/-7 | 6 | +| David Choi | 2 | +16/-5 | 2 | +| Mike Greenberg | 1 | +18/-1 | 1 | +| Adin Schmahmann | 1 | +19/-0 | 2 | +| Phi-rjan | 5 | +12/-4 | 5 | +| Dirk McCormick | 2 | +6/-6 | 3 | +| Aayush Rajasekaran | 2 | +9/-3 | 2 | +| Jiaying Wang | 5 | +6/-4 | 5 | +| Anjor Kanekar | 1 | +5/-5 | 1 | +| vyzo | 1 | +3/-3 | 2 | +| 0x5459 | 1 | +1/-1 | 1 | + +# v1.22.1 / 2023-04-23 + +## Important Notice + +This is a MANDATORY hotfix release that fixes a consensus-critical bug that was in v1.22.0 -- the necessary fix is https://github.com/filecoin-project/ref-fvm/pull/1750 and it is integrated into lotus via https://github.com/filecoin-project/lotus/pull/10735. +You can NOT use 1.22.0 for the nv19 upgrade, you MUST be on 1.22.1 or higher. + +## About This Release + +This is the stable release of Lotus v1.22.1 for the upcoming MANDATORY network upgrade at `2023-04-27T13:00:00Z`, epoch `2809800`. This release delivers the nv19 Lighting and nv20 Thunder network upgrade for mainnet. + +Note that you must be on a go version higher than Go 1.18.8, but lower than Go v1.20.0. We would recommend Go 1.19.7. + +The Lighting and Thunder upgrade introduces the following Filecoin Improvement Proposals (FIPs), delivered by builtin-actors v11 (see actors [v11.0.0](https://github.com/filecoin-project/builtin-actors/releases/tag/v11.0.0-rc2)): + +- [FIP 0060](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0060.md) - Thirty day market deal maintenance interval +- [FIP 0061](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0061.md) - WindowPoSt grindability fix +- [FIP 0062](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0062.md) - Fallback method handler for multisig actor + +## Expedited nv19 Lightning ⚡️ rollout + +In light of the recent degraded chain quality on the mainnet [an expedited nv19 upgrade has been proposed and accepted](https://github.com/filecoin-project/core-devs/discussions/123#discussioncomment-5642909) to roll out the market cron mitigation ([FIP0060](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0060.md)) that will improve block validation times, and with that the delay in block production that is causing a decrease in the chain quality currently. + +With this expedited roll out we want to inform you of some **key changes and important dates:** + +- Accelerate the nv19-upgrade on **mainnet** from May 11th to **April 27th**. +- Derisk nv19 by descoping the sector info migration, activation epoch fixes and drop [[FIP0052 - Extend sector/deal max duration to 3.5 year.](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0052.md)](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0052.md) + - By descoping these changes we can greatly derisk the network upgrade itself by removing a heavy migration that could cause instability for storage providers and node operators during the network upgrade. +- Increase the rollover period for [[FIP0061](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0052.md)](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0052.md) from 1 week to 3 weeks on mainnet. The rollover period is the duration between nv19 and nv20 which both old proofs (v1) and the new proofs (v1_1) proofs will be accepted by the network. + +The Lighting and Thunder upgrade now implements the following Filecoin Improvement Proposals (FIPs), delivered by builtin-actors v11 (see actors [v11.0.0](https://github.com/filecoin-project/builtin-actors/releases/tag/v11.0.0)): + +- [FIP 0060](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0060.md) - Thirty day market deal maintenance interval +- [FIP 0061](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0061.md) - WindowPoSt grindability fix +- [FIP 0062](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0062.md) - Fallback method handler for multisig actor + +## v11 Builtin Actor Bundles + +Make sure that your lotus actor bundle matches the v11 actors manifest by running after upgrading: + +``` +lotus state actor-cids --network-version 19 +Network Version: 19 +Actor Version: 11 +Manifest CID: bafy2bzacecnhaiwcrpyjvzl4uv4q3jzoif26okl3m66q3cijp3dfwlcxwztwo + +Actor CID +datacap bafk2bzacebslykoyrb2hm7aacjngqgd5n2wmeii2goadrs5zaya3pvdf6pdnq +init bafk2bzaceckwf3w6n2nw6eh77ktmsxqgsvshonvgnyk5q5syyngtetxvasfxg +reward bafk2bzacebwjw2vxkobs7r2kwjdqqb42h2kucyuk6flbnyzw4odg5s4mogamo +cron bafk2bzacebpewdvvgt6tk2o2u4rcovdgym67tadiis5usemlbejg7k3kt567o +ethaccount bafk2bzaceclkmc4yidxc6lgcjpfypbde2eddnevcveo4j5kmh4ek6inqysz2k +evm bafk2bzacediwh6etwzwmb5pivtclpdplewdjzphouwqpppce6opisjv2fjqfe +storagemarket bafk2bzaceazu2j2zu4p24tr22btnqzkhzjvyjltlvsagaj6w3syevikeb5d7m +storagepower bafk2bzaceaxgloxuzg35vu7l7tohdgaq2frsfp4ejmuo7tkoxjp5zqrze6sf4 +system bafk2bzaced7npe5mt5nh72jxr2igi2sofoa7gedt4w6kueeke7i3xxugqpjfm +account bafk2bzacealnlr7st6lkwoh6wxpf2hnrlex5sknaopgmkr2tuhg7vmbfy45so +placeholder bafk2bzacedfvut2myeleyq67fljcrw4kkmn5pb5dpyozovj7jpoez5irnc3ro +eam bafk2bzaceaelwt4yfsfvsu3pa3miwalsvy3cfkcjvmt4sqoeopsppnrmj2mf2 +multisig bafk2bzaceafajceqwg5ybiz7xw6rxammuirkgtuv625gzaehsqfprm4bazjmk +paymentchannel bafk2bzaceb4e6cnsnviegmqvsmoxzncruvhra54piq7bwiqfqevle6oob2gvo +storageminer bafk2bzacec24okjqrp7c7rj3hbrs5ez5apvwah2ruka6haesgfngf37mhk6us +verifiedregistry bafk2bzacedej3dnr62g2je2abmyjg3xqv4otvh6e26du5fcrhvw7zgcaaez3a +``` + +## Changelog + +- feat: build: set Lightning and Thunder upgrade epochs [filecoin-project/lotus#10716](https://github.com/filecoin-project/lotus/pull/10707) +- fix: PoSt worker: use go-state-types for proof policies [filecoin-project/lotus#10716](https://github.com/filecoin-project/lotus/pull/10716) +- chore: deps: update to actors v11.0.0 [filecoin-project/lotus#10718](https://github.com/filecoin-project/lotus/pull/10718) +- chore: deps: update to go-state-types v0.11.1 [filecoin-project/lotus#10720](https://github.com/filecoin-project/lotus/pull/10720) +- feat: upgrade: expedite nv19 [filecoin-project/lotus#10681](https://github.com/filecoin-project/lotus/pull/10681) + - Update changelog build version (commit: [67d419e](https://github.com/filecoin-project/lotus/commit/67d419e1623e6b9f5b871d6157a3096378477c3b)) + - Update actors v11 (commit: [5df4f75](https://github.com/filecoin-project/lotus/commit/5df4f75dc22318fd304313714d5c4f4cfeed22c9)) + - Correct epoch to match specified date (commit: [a28fcea](https://github.com/filecoin-project/lotus/commit/a28fceaa559b6c7e1b5df09383af56a5c2f51caa)) + - Fast butterfly migration to validate migration (commit: [37a0dca](https://github.com/filecoin-project/lotus/commit/37a0dca11ebfadebad3920a337b4f1b2fba08a7b)) + - Make docsgen (commit: [daba4ff](https://github.com/filecoin-project/lotus/commit/daba4ff5f0e97ab6ed444a34f61499a64b92a220)) + - Update go-state-types (commit: [244ca0b](https://github.com/filecoin-project/lotus/commit/244ca0b5f32a2af684f3f9586b92861a06bb8833)) + - Revert FIP0052 (commit: [68ed494](https://github.com/filecoin-project/lotus/commit/68ed494a6e497ac556eb93b28b2536c881dc9a4c)) + - Modify upgrade schedule and params (commit: [fa0dfdf](https://github.com/filecoin-project/lotus/commit/fa0dfdfd9f89fab8491f3e613909782ab9bb7cee)) + - Update go-state-types (commit: [19ae05f](https://github.com/filecoin-project/lotus/commit/19ae05f3b3a589e28efe4690c5816dfc1c7866a6)) + +### Dependencies +github.com/filecoin-project/go-state-types (v0.11.0-rc1 -> v0.11.1): + +# v1.22.0 / 2023-04-21 + +EDIT: Do NOT use this release for nv19, you MUST use v1.22.1 or higher. + +This is the stable release of Lotus v1.22.0 for the upcoming MANDATORY network upgrade at `2023-04-27T13:00:00Z`, epoch `2809800`. This release delivers the nv19 Lighting and nv20 Thunder network upgrade for mainnet. + +Note that you must be on a go version higher then Go 1.18.8, but lower then Go v1.20.0. We would recommend Go 1.19.7. + +The Lighting and Thunder upgrade introduces the following Filecoin Improvement Proposals (FIPs), delivered by builtin-actors v11 (see actors [v11.0.0](https://github.com/filecoin-project/builtin-actors/releases/tag/v11.0.0-rc2)): + +- [FIP 0060](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0060.md) - Thirty day market deal maintenance interval +- [FIP 0061](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0061.md) - WindowPoSt grindability fix +- [FIP 0062](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0062.md) - Fallback method handler for multisig actor + +## Expedited nv19 Lightning ⚡️ rollout + +In light of the recent degraded chain quality on the mainnet [an expedited nv19 upgrade has been proposed and accepted](https://github.com/filecoin-project/core-devs/discussions/123#discussioncomment-5642909) to roll out the market cron mitigation ([FIP0060](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0060.md)) that will improve block validation times, and with that the delay in block production that is causing a decrease in the chain quality currently. + +With this expedited roll out we want to inform you of some **key changes and important dates:** + +- Accelerate the nv19-upgrade on **mainnet** from May 11th to **April 27th**. +- Derisk nv19 by descoping the sector info migration, activation epoch fixes and drop [[FIP0052 - Extend sector/deal max duration to 3.5 year.](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0052.md)](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0052.md) + - By descoping these changes we can greatly derisk the network upgrade itself by removing a heavy migration that could cause instability for storage providers and node operators during the network upgrade. +- Increase the rollover period for [[FIP0061](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0052.md)](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0052.md) from 1 week to 3 weeks on mainnet. The rollover period is the duration between nv19 and nv20 which both old proofs (v1) and the new proofs (v1_1) proofs will be accepted by the network. + +The Lighting and Thunder upgrade now implements the following Filecoin Improvement Proposals (FIPs), delivered by builtin-actors v11 (see actors [v11.0.0](https://github.com/filecoin-project/builtin-actors/releases/tag/v11.0.0)): + +- [FIP 0060](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0060.md) - Thirty day market deal maintenance interval +- [FIP 0061](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0061.md) - WindowPoSt grindability fix +- [FIP 0062](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0062.md) - Fallback method handler for multisig actor + +## v11 Builtin Actor Bundles + +Make sure that your lotus actor bundle matches the v11 actors manifest by running after upgrading: + +``` +lotus state actor-cids --network-version 19 +Network Version: 19 +Actor Version: 11 +Manifest CID: bafy2bzacecnhaiwcrpyjvzl4uv4q3jzoif26okl3m66q3cijp3dfwlcxwztwo + +Actor CID +datacap bafk2bzacebslykoyrb2hm7aacjngqgd5n2wmeii2goadrs5zaya3pvdf6pdnq +init bafk2bzaceckwf3w6n2nw6eh77ktmsxqgsvshonvgnyk5q5syyngtetxvasfxg +reward bafk2bzacebwjw2vxkobs7r2kwjdqqb42h2kucyuk6flbnyzw4odg5s4mogamo +cron bafk2bzacebpewdvvgt6tk2o2u4rcovdgym67tadiis5usemlbejg7k3kt567o +ethaccount bafk2bzaceclkmc4yidxc6lgcjpfypbde2eddnevcveo4j5kmh4ek6inqysz2k +evm bafk2bzacediwh6etwzwmb5pivtclpdplewdjzphouwqpppce6opisjv2fjqfe +storagemarket bafk2bzaceazu2j2zu4p24tr22btnqzkhzjvyjltlvsagaj6w3syevikeb5d7m +storagepower bafk2bzaceaxgloxuzg35vu7l7tohdgaq2frsfp4ejmuo7tkoxjp5zqrze6sf4 +system bafk2bzaced7npe5mt5nh72jxr2igi2sofoa7gedt4w6kueeke7i3xxugqpjfm +account bafk2bzacealnlr7st6lkwoh6wxpf2hnrlex5sknaopgmkr2tuhg7vmbfy45so +placeholder bafk2bzacedfvut2myeleyq67fljcrw4kkmn5pb5dpyozovj7jpoez5irnc3ro +eam bafk2bzaceaelwt4yfsfvsu3pa3miwalsvy3cfkcjvmt4sqoeopsppnrmj2mf2 +multisig bafk2bzaceafajceqwg5ybiz7xw6rxammuirkgtuv625gzaehsqfprm4bazjmk +paymentchannel bafk2bzaceb4e6cnsnviegmqvsmoxzncruvhra54piq7bwiqfqevle6oob2gvo +storageminer bafk2bzacec24okjqrp7c7rj3hbrs5ez5apvwah2ruka6haesgfngf37mhk6us +verifiedregistry bafk2bzacedej3dnr62g2je2abmyjg3xqv4otvh6e26du5fcrhvw7zgcaaez3a +``` + +## Changelog + + +- feat: build: set Lightning and Thunder upgrade epochs [filecoin-project/lotus#10716](https://github.com/filecoin-project/lotus/pull/10707) +- fix: PoSt worker: use go-state-types for proof policies [filecoin-project/lotus#10716](https://github.com/filecoin-project/lotus/pull/10716) +- chore: deps: update to actors v11.0.0 [filecoin-project/lotus#10718](https://github.com/filecoin-project/lotus/pull/10718) +- chore: deps: update to go-state-types v0.11.1 [filecoin-project/lotus#10720](https://github.com/filecoin-project/lotus/pull/10720) +- feat: upgrade: expedite nv19 [filecoin-project/lotus#10681](https://github.com/filecoin-project/lotus/pull/10681) + - Update changelog build version (commit: [67d419e](https://github.com/filecoin-project/lotus/commit/67d419e1623e6b9f5b871d6157a3096378477c3b)) + - Update actors v11 (commit: [5df4f75](https://github.com/filecoin-project/lotus/commit/5df4f75dc22318fd304313714d5c4f4cfeed22c9)) + - Correct epoch to match specified date (commit: [a28fcea](https://github.com/filecoin-project/lotus/commit/a28fceaa559b6c7e1b5df09383af56a5c2f51caa)) + - Fast butterfly migration to validate migration (commit: [37a0dca](https://github.com/filecoin-project/lotus/commit/37a0dca11ebfadebad3920a337b4f1b2fba08a7b)) + - Make docsgen (commit: [daba4ff](https://github.com/filecoin-project/lotus/commit/daba4ff5f0e97ab6ed444a34f61499a64b92a220)) + - Update go-state-types (commit: [244ca0b](https://github.com/filecoin-project/lotus/commit/244ca0b5f32a2af684f3f9586b92861a06bb8833)) + - Revert FIP0052 (commit: [68ed494](https://github.com/filecoin-project/lotus/commit/68ed494a6e497ac556eb93b28b2536c881dc9a4c)) + - Modify upgrade schedule and params (commit: [fa0dfdf](https://github.com/filecoin-project/lotus/commit/fa0dfdfd9f89fab8491f3e613909782ab9bb7cee)) + - Update go-state-types (commit: [19ae05f](https://github.com/filecoin-project/lotus/commit/19ae05f3b3a589e28efe4690c5816dfc1c7866a6)) + +### Dependencies +github.com/filecoin-project/go-state-types (v0.11.0-rc1 -> v0.11.1): + +# v1.20.4 / 2023-03-17 + +This is a patch release intended to alleviate performance issues reported by some users since the nv18 upgrade. +The primary change is to update the FFI to allow for FVM parallelism of 4 by default, and make this user-configurable. +through the `LOTUS_FVM_CONCURRENCY` env var. + +Users with higher memory specs can experiment with setting `LOTUS_FVM_CONCURRENCY` to higher values, up to 48, to allow for more concurrent FVM execution. + +## Bug fixes + +- Splitstore: Don't enforce walking receipt tree during compaction #10505 +- fix: build: drop drand incentinet servers #10506 + +## Improvement + +- chore: update ffi to increase execution parallelism #10503 + +# v1.20.3 / 2023-03-09 + +A 🐈 stepped on the ⌨️ and made a mistake while resolving conflicts 😨. This releases only includes #10439 to fix that mistake. v1.20.2 is retracted - Please skip v1.20.2 and **only** update to v1.20.3!!! + +## Changelog +> compare to v1.20.1 + +This is a HIGHLY RECOMMENDED patch release for node operators/API service providers that run ETH RPC service and an optional release for Storage Providers. + +## Bug fixes +- fix: EthAPI: use StateCompute for feeHistory; apply minimum gas premium #10413 +- fix: eth API: return correct txIdx around null blocks #10419 +- fix: Eth API: make block parameter parsing sounder. #10427 + +## Improvement +- feat: Lotus Gateway: Add missing methods - master #10420 +- feat: mempool: Reduce minimum replace fee from 1.25x to 1.1x #10416 +- We recommend storage providers to update your nodes to this patch, that will help improve developers who use Ethereum tooling's experience. + +# v1.20.2 / 2023-03-09 + +DO NOT USE: Use 1.20.3 instead! + +This is a HIGHLY RECOMMENDED patch release for node operators/API service providers that run ETH RPC service and an optional release for Storage Providers. + +## Bug fixes +- fix: EthAPI: use StateCompute for feeHistory; apply minimum gas premium #10413 +- fix: eth API: return correct txIdx around null blocks #10419 +- fix: Eth API: make block parameter parsing sounder. #10427 + +## Improvement +- feat: Lotus Gateway: Add missing methods - master #10420 +- feat: mempool: Reduce minimum replace fee from 1.25x to 1.1x #10416 + - We recommend storage providers to update your nodes to this patch, that will help improve developers who use Ethereum tooling's experience. + +# v1.20.1 / 2023-03-06 + +This an optional patch releases for node operators/API service providers that run ETH RPC service. + +## Bug fixes +- fix: EthAPI: Correctly get parent hash #10389 +- fix: EthAPI: Make newEthBlockFromFilecoinTipSet faster and correct #10380 +- fix: state: short-circuit genesis state computation + +# 1.20.0 / 2023-02-28 + +This is a MANDATORY release of Lotus that delivers the [Hygge network upgrade](https://github.com/filecoin-project/community/discussions/74?sort=top#discussioncomment-4313888), introducing Filecoin network version 18. The centerpiece of the upgrade is the introduction of the [Filecoin Virtual Machine (FVM)’s Milestone 2.1](https://fvm.filecoin.io/), which will allow for EVM-compatible contracts to be deployed on the Filecoin network. This upgrade delivers user-programmablity to the Filecoin network for the first time! + +The Filecoin mainnet is scheduled to upgrade to nv18 at epoch 2683348, on March 14th at 2023-03-14T15:14:00Z. All node operators, including storage providers, must upgrade to this release before that time. Storage providers must update their daemons, miners, market and worker(s)/boost. +At the upgrade, a short migration will run that converts code actors v9 code CIDs to v10 CIDs, and installs the new Ethereum Address Manager singleton (see below). This is expected to be a lightweight migration that causes no service disruption. + +The Hygge upgrade introduces the following Filecoin Improvement Proposals (FIPs), delivered in FVM3 (see FVM [v3.0.0](https://github.com/filecoin-project/ref-fvm/pull/1683)) and builtin-actors v10 (see actors [v10.0.0](https://github.com/filecoin-project/builtin-actors/releases/tag/v10.0.0)): + +- [FIP-0048](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0048.md): f4 Address Class +- [FIP-0049](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0049.md): Actor events +- [FIP-0050](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0050.md): API between user-programmed actors and built-in actors +- [FIP-0054](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0054.md): Filecoin EVM runtime (FEVM) +- [FIP-0055](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0055.md): Supporting Ethereum Accounts, Addresses, and Transactions +- [FIP-0057](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0057.md): Update gas charging schedule and system limits for FEVM + +## Filecoin Ethereum Virtual Machine (FEVM) + +The Filecoin Ethereum Virtual Machine (FEVM) is built on top of the WASM-based execution environment introduced in the Skyr v16 upgrade. The chief feature introduced is the ability for anyone participating in the Filecoin network to deploy their own EVM-compatible contracts onto the blockchain, and invoke them as appropriate. + +## New Built-in Actors + +The FEVM is principally delivered through the introduction of **the new [EVM actor](https://github.com/filecoin-project/builtin-actors/tree/master/actors/evm)**. This actor “represents” smart contracts on the Filecoin network, and includes an interpreter that implements all EVM opcodes as their Filecoin equivalents, and translates state I/O operations to be compatible with Filecoin’s IPLD-based data model. For more on the EVM actors, please see [FIP-0054](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0054.md). + +The creation of EVM actors is managed by **the new** [Ethereum Address Manager actor (EAM)](https://github.com/filecoin-project/builtin-actors/tree/master/actors/eam), a singleton that is invoked in order to deploy EVM actors. In order to make usage of the FEVM as seamless as possible for users familiar with the Ethereum ecosystem, this upgrades also introduces **a dedicated actor to serve as “[Ethereum Accounts](https://github.com/filecoin-project/builtin-actors/tree/master/actors/ethaccount)”**. This actor exists to allow for secp keys to be used in the Ethereum addressing scheme. **The last new built-in actor introduced is [the Placeholder actor](https://github.com/filecoin-project/builtin-actors/tree/master/actors/placeholder)**, a thin “shell” of an actor that can transform into either EVM or EthAccount actors. For more on the EAM, EthAccount, and Placeholder actors, please see [FIP-0055](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0055.md). + +### v10 Built-in actor bundles + +Bundles for all networks (mainnet, calibnet, etc.) are included in the lotus source tree (`build/actors/`) and embedded on build, for v10 actors you can find it [here](https://github.com/filecoin-project/lotus/blob/master/build/actors/v10.tar.zst). +Reminder: Lotus verifies that the bundle CIDs are the right ones upon build & upgrade against the values in `build/builtin_actors_gen.go`, according to the network you are building. You may also check the bundle manifest CID matches the bundle gen-ed values by running `lotus state actor-cids --network-version 18`. + +The manifest CID & full list of actor code CIDs for nv18 using [actor v10](https://github.com/filecoin-project/builtin-actors/releases/tag/v10.0.0) is: + + "_manifest": "bafy2bzacecsuyf7mmvrhkx2evng5gnz5canlnz2fdlzu2lvcgptiq2pzuovos" + "account": "bafk2bzaceampw4romta75hyz5p4cqriypmpbgnkxncgxgqn6zptv5lsp2w2bo" + "cron": "bafk2bzacedcbtsifegiu432m5tysjzkxkmoczxscb6hqpmrr6img7xzdbbs2g" + "datacap": "bafk2bzacealj5uk7wixhvk7l5tnredtelralwnceafqq34nb2lbylhtuyo64u" + "eam": "bafk2bzacedrpm5gbleh4xkyo2jvs7p5g6f34soa6dpv7ashcdgy676snsum6g" + "ethaccount": "bafk2bzaceaqoc5zakbhjxn3jljc4lxnthllzunhdor7sxhwgmskvc6drqc3fa" + "evm": "bafk2bzaceahmzdxhqsm7cu2mexusjp6frm7r4kdesvti3etv5evfqboos2j4g" + "init": "bafk2bzaced2f5rhir3hbpqbz5ght7ohv2kgj42g5ykxrypuo2opxsup3ykwl6" + "multisig": "bafk2bzaceduf3hayh63jnl4z2knxv7cnrdenoubni22fxersc4octlwpxpmy4" + "paymentchannel": "bafk2bzaceartlg4mrbwgzcwric6mtvyawpbgx2xclo2vj27nna57nxynf3pgc" + "placeholder": "bafk2bzacedfvut2myeleyq67fljcrw4kkmn5pb5dpyozovj7jpoez5irnc3ro" + "reward": "bafk2bzacebnhtaejfjtzymyfmbdrfmo7vgj3zsof6zlucbmkhrvcuotw5dxpq" + "storagemarket": "bafk2bzaceclejwjtpu2dhw3qbx6ow7b4pmhwa7ocrbbiqwp36sq5yeg6jz2bc" + "storageminer": "bafk2bzaced4h7noksockro7glnssz2jnmo2rpzd7dvnmfs4p24zx3h6gtx47s" + "storagepower": "bafk2bzacec4ay4crzo73ypmh7o3fjendhbqrxake46bprabw67fvwjz5q6ixq" + "system": "bafk2bzacedakk5nofebyup4m7nvx6djksfwhnxzrfuq4oyemhpl4lllaikr64" + "verifiedregistry": "bafk2bzacedfel6edzqpe5oujno7fog4i526go4dtcs6vwrdtbpy2xq6htvcg6" + +## Node Operators + +FVM has been running in lotus since v1.16.0 and up, and the new FEVM does not increase any node hardware spec requirement. + +With FEVM on Filecoin, we aim to provide full compatibility with the existing EVM ecosystem and its tooling out of the box. +Consequently, lotus now provides a full set of [Ethereum-styled APIs](https://github.com/filecoin-project/lotus/blob/release/v1.20.0/node/impl/full/eth.go) for developers and token holders to interact with the Filecoin network as well. +For full documentation on this new tooling, please see the [Lotus docs website](https://lotus.filecoin.io/lotus/configure/ethereum-rpc/). + +**Enabling Ethereum JSON RPC API** + +Note that Ethereum APIs are only supported in the lotus v1 API, meaning that any node operator who wants to enable Eth API services must be using the v1 API, instead of the v0 API. To enable Eth RPC, simply set `EnableEthRPC` to `true` in your node config.toml file; or set env var `LOTUS_FEVM_ENABLEETHRPC` to `1` before starting your lotus node. + +**Eth tx hash and Filecoin message CID** + +Most of the Eth APIs take Eth accounts and tx has as an input, and they start with `0x` , and that is what Ethereum tooling support. However, in Filecoin, we have Filecoin account formats where things start with `f` (`f410` specifically for eth accounts on Filecoin) and the messages are in the format of CIDs. To enable a smooth developer experience, Lotus internally converts between Ethereum address and Filecoin account address as needed. In addition, lotus also keeps a Eth tx hash <> Filecoin message CID map and stores them in a SQLite database as node sees a FEVM messages. The database is initiated and the maps are populated automatically in `~//sqlite/txhash.db` for any node that as Eth RPC enabled. Node operators can configure how many historical mappings they wanna store by configuring `EthTxHashMappingLifetimeDays` . + +**Events** + +[FIP-0049 introduces actor events](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0049.md) that can be emitted and externally observable during message execution. An `events.db` is created automatically under `~//sqlite` to store these events if the node has Eth RPC enabled. Node operators can configure the events support base on their needs by configuration `Events` configurations. + +Note: All three features are new, and we welcome user feedback, please create an issue if you have any enhancements that you’d like to see! + +# 1.19.0 / 2022-12-07 + +This is an optional feature release of Lotus. This feature release includes the SplitStore beta, the experimental Lotus node cluster feature, as well as numerous enhancments and bugfixes. + +## Highlights +### 🟢 SplitStore v2(Beta) 🟢 + +Splitstore aims to reduce the node performance impact that's caused by the Filecoin's very large, and continuously growing chain datastore by having a hot and cold blockstore. You can find more about the Splitstore implementation [here](https://github.com/filecoin-project/lotus/blob/master/blockstore/splitstore/README.md). +Splitstore has three basic modes for node operators to configure according to your needs: +- `discard`: hotstore only, automatically archive out-of-scope objects that are beyond 4 finalities(3600 epochs). +- `universal`: stores all chain data that's beyond 4 finalities into coldstore. +- `messages`: only stores on-chain messages into coldstore. + +The `EnableColdStoreAutoPrune=` configuration is being deprecated in this release, as there is only ever one compaction running. We welcome all node operators to try the new feature and let us know [here](https://github.com/filecoin-project/lotus/discussions/9179) if you have any feedback! +There are more configuration one may set, you can read the full documentation about the SplitStoreV2 here: https://lotus.filecoin.io/lotus/configure/splitstore/. + +### 🧪 Node Cluster (*EXPERIMENTAL.*) 🧪 +The Lotus HA node cluster feature allows you to run multiple Lotus daemons for the same lotus-miner increasing resiliency. We welcome all Lotus users to join the early testing for this feature and provide your feedback. Please note that this feature is targeted towards more enterprise users of Lotus and requires at least 3 lotus nodes to be set up in a cluster. +Check out the documentation here: https://lotus.filecoin.io/lotus/configure/clusters/ + +### ⭐️ SnapDeals Enhancements ⭐️ +Numerous SnapDeals related improvements and fixes made it into this release before the code freeze. Some the highlights of the issues that has been fixed in this feature release are: + +- *Unable to snap-up a sector again if something went wrong.* - This has now been fixed ✅ +- *Error messages on loop during an open deadline.* - This has now been fixed ✅ + +## New features +- feat:splitstore:single compaction that can handle prune aka two marksets one compaction (#9571) ([filecoin-project/lotus#9571](https://github.com/filecoin-project/lotus/pull/9571)) + - Introduces a new SplitStore-mode, `messages`, which will only store on-chain messages. Fixes previously issues with regards to `AutoPrune` not compacting the coldstore. [Link to documentation](https://lotus.filecoin.io/lotus/configure/splitstore/) +- feat: Raft consensus for lotus nodes in a cluster ([filecoin-project/lotus#9294](https://github.com/filecoin-project/lotus/pull/9294)) + - Adds the experimental node cluster feature. +- feat: storage: Force exit GenerateSingleVanillaProof on cancelled context ([filecoin-project/lotus#9613](https://github.com/filecoin-project/lotus/pull/9613)) + - `GenerateSingleVanillaProof` now respects context, which means that it will skip slow to read sectors :snail: and return a context error. Instead of being blocked forever if storage reads where blocked (e.g disconnected NFS). +- feat: wdpost: Configurable pre-check timeouts ([filecoin-project/lotus#9680](https://github.com/filecoin-project/lotus/pull/9680)) + - Adds configuration knobs for setting custom amount of time a proving pre-check can take before a sector and partition will be skipped. [Link to documentation](https://lotus.filecoin.io/storage-providers/advanced-configurations/proving/#pre-check-sector-timeout) +- feat: chain: future proof the from & to address protocols ([filecoin-project/lotus#9515](https://github.com/filecoin-project/lotus/pull/9515)) + - This lets us add new address protocols to go-address without implicitly accepting them in messages on the network. +- feat: Retrieval into remote blockstores ([filecoin-project/lotus#9565](https://github.com/filecoin-project/lotus/pull/9565)) + - Makes it possible to point retrievals at a network-backed blockstore. +- feat: Add node uptime rpc / output in info command ([filecoin-project/lotus#9436](https://github.com/filecoin-project/lotus/pull/9436)) + - Adds node uptime stats to the `lotus-miner info` and `lotus info` commands +- feat: wdpost: Add ability to only have single partition per msg for partitions with… ([filecoin-project/lotus#9413](https://github.com/filecoin-project/lotus/pull/9413)) + - Adds a configuration option to have a single partition per PoSt Message for partitions containing recovering sectors. +- feat: miner paramfetch: Don't fetch param files when not needed ([filecoin-project/lotus#9391](https://github.com/filecoin-project/lotus/pull/9391)) + - A Lotus-Miner processes that has disabled local PoSt / C2 / PR2 does not need the param-files. This makes node startup much faster, reducing downtime by a lot when restarts are needed. +- feat: client: Add retrieval deal ID and bytes transferred to retrieval output ([filecoin-project/lotus#9398](https://github.com/filecoin-project/lotus/pull/9398)) + - Appends retrieval deal ID and bytes transferred to the retrieval output. +- feat: dealpublisher: check for duplicate deals before adding ([filecoin-project/lotus#9365](https://github.com/filecoin-project/lotus/pull/9365)) +- feat: Drop active retrieval check (#764) ([filecoin-project/go-fil-markets#764](https://github.com/filecoin-project/go-fil-markets/pull/764)) +- feat(retrievalmarkets): expose GetDynamicAsk (#748) ([filecoin-project/go-fil-markets#748](https://github.com/filecoin-project/go-fil-markets/pull/748)) +- feat: handle retrieval queries for unindexed identity payload CIDs (#747) ([filecoin-project/go-fil-markets#747](https://github.com/filecoin-project/go-fil-markets/pull/747)) +- feat: add a method for validating an address for a network version (#115) ([filecoin-project/go-state-types#115](https://github.com/filecoin-project/go-state-types/pull/115)) + +## Improvements +- fix: miner-cli: Fix lotus-miner proving check ([filecoin-project/lotus#9643](https://github.com/filecoin-project/lotus/pull/9643)) + - Fixes the issue where the `lotus-miner proving check` command always outputted `Error: rg is nil` +- fix: sealing pipeline: Clear CreationTime when starting sector upgrade ([filecoin-project/lotus#9642](https://github.com/filecoin-project/lotus/pull/9642)) + - Fixes the issue where an aborted SnapDeal upgrade could no longer be retried with SnapDeals. +- fix:sealing-fsm:wait mutable fsm state for immutable sector upgrade error ([filecoin-project/lotus#9598](https://github.com/filecoin-project/lotus/pull/9598)) + - Creating a new WaitMutable state - now if the deadline is open and the sectors are trying finalize they will wait on the worker until the deadline has closed. Important to note that they will not finalize as soon as the deadline closes, they will wait 1h before continuing. Fixes the previous issue where upgraded Snap-sectors for an open deadline cause a lot of `error-messages` and `p_aux` issues +- fix: cli: add beneficiary info to lotus-miner actor control list ([filecoin-project/lotus#9632](https://github.com/filecoin-project/lotus/pull/9632)) + - Adds the beneficiary address to the `lotus-miner actor control list` output. +- fix: sealing pipeine: Release assigned deals on snapdeals abort ([filecoin-project/lotus#9601](https://github.com/filecoin-project/lotus/pull/9601)) +- fix: docker: make compatible with arm platform ([filecoin-project/lotus#9363](https://github.com/filecoin-project/lotus/pull/9363)) + - Makes the `Dockerfile.lotus` compatible with ARM-platforms (e.g Mac M1). +- fix: post worker sched: Don't check worker session in a busy loop ([filecoin-project/lotus#9495](https://github.com/filecoin-project/lotus/pull/9495)) + - Fixes a looping pattern which could result in a flood of requests between `lotus-miner`<->`lotus-worker`, potentially exhausting resources needed to make http requests, that lead to all sorts of random RPC-related issues. +- fix: miner: init miner's with 32GiB sectors by default ([filecoin-project/lotus#9364](https://github.com/filecoin-project/lotus/pull/9364)) + - Makes the `lotus-miner init` defualt to 32GiB sectors. +- fix: store identity CIDs in CARs for online deals (#749) ([filecoin-project/go-fil-markets#749](https://github.com/filecoin-project/go-fil-markets/pull/749)) +- fix: cliutil: Fix URL-based API endpoint parsing + +## Dependencies +- deps: upgrade go-merkledag to 0.8.1 (#9717) +- deps: Update go-fil-markets to v1.25.0 ([filecoin-project/lotus#9633](https://github.com/filecoin-project/lotus/pull/9633)) +- deps: upgrade go-merkledag to 0.8.0 ([filecoin-project/lotus#9455](https://github.com/filecoin-project/lotus/pull/9455)) + +## Others +- fix/build: Update Zondax/hid to 0.9.1 +- refactor: sealing: minor refactor of FinalizeReplicaUpdate ([filecoin-project/lotus#9614](https://github.com/filecoin-project/lotus/pull/9614)) +- update ffi to 280c4f8b94fd46dc (#9608) ([filecoin-project/lotus#9608](https://github.com/filecoin-project/lotus/pull/9608)) +- fix: tvx: make it work with the FVM ([filecoin-project/lotus#9604](https://github.com/filecoin-project/lotus/pull/9604)) +- fix: autobatch: remove potential deadlock when a block is missing ([filecoin-project/lotus#9602](https://github.com/filecoin-project/lotus/pull/9602)) +- feat: shed: set control address: add dump bytes option ([filecoin-project/lotus#9572](https://github.com/filecoin-project/lotus/pull/9572)) +- feat: shed: Online export-car ([filecoin-project/lotus#9590](https://github.com/filecoin-project/lotus/pull/9590)) +- fix: chain: Update chain.go ([filecoin-project/lotus#9373](https://github.com/filecoin-project/lotus/pull/9373)) +- fix: fvm: Allow setting local bundles for Debug FVM for av 9+ ([filecoin-project/lotus#9509](https://github.com/filecoin-project/lotus/pull/9509)) +- fix: ux: Add outputs to wallet commands ([filecoin-project/lotus#9416](https://github.com/filecoin-project/lotus/pull/9416)) +- fix: ux: specify arg in `net ping` cmd ([filecoin-project/lotus#9459](https://github.com/filecoin-project/lotus/pull/9459)) +- fix: cli: renew --only-cc with sectorfile ([filecoin-project/lotus#9402](https://github.com/filecoin-project/lotus/pull/9402)) +- fix: bstore: Handle codecs correctly in membstore Get ([filecoin-project/lotus#9471](https://github.com/filecoin-project/lotus/pull/9471)) +- fix: not multiplied by the number of seconds ([filecoin-project/lotus#9460](https://github.com/filecoin-project/lotus/pull/9460)) +- Makefile: Drop rarely used binaries from build-devnets (#9513) ([filecoin-project/lotus#9513](https://github.com/filecoin-project/lotus/pull/9513)) +- _ci_ Remove unneeded homebrew deps ([filecoin-project/lotus#9559](https://github.com/filecoin-project/lotus/pull/9559)) +- _ci_: Have apt-get wait until it can get a lock in packer builds ([filecoin-project/lotus#9534](https://github.com/filecoin-project/lotus/pull/9534)) +- _ci_ Appimage go1.18.1 fix ([filecoin-project/lotus#9496](https://github.com/filecoin-project/lotus/pull/9496)) +- _ci_: Fix failing Digital Ocean and Amazon Machine Image release process ([filecoin-project/lotus#9425](https://github.com/filecoin-project/lotus/pull/9425)) +- _ci_: Don't publish new homebrew releases for RC builds ([filecoin-project/lotus#9350](https://github.com/filecoin-project/lotus/pull/9350)) +- _ci_: Use golang 1.18.1 to build appimage ([filecoin-project/lotus#9386](https://github.com/filecoin-project/lotus/pull/9386)) +- _ci_ Refactor release pipeline to better support m1 builds ([filecoin-project/lotus#9600](https://github.com/filecoin-project/lotus/pull/9600)) +- _ci_: Rely on local env varibale instead of context ([filecoin-project/lotus#9740](https://github.com/filecoin-project/lotus/pull/9740)) +- feat: shed: FIP0036 post poll result processing ([filecoin-project/lotus#9387](https://github.com/filecoin-project/lotus/pull/9387)) +- misc: github: Cleanup PR template ([filecoin-project/lotus#9472](https://github.com/filecoin-project/lotus/pull/9472)) +- docs: release template: Mention codegen in release prep ([filecoin-project/lotus#9430](https://github.com/filecoin-project/lotus/pull/9430)) +- chore: merge releases (v1.17.2) into master ([filecoin-project/lotus#9433](https://github.com/filecoin-project/lotus/pull/9433)) +- chore: merge release/v1.18.0 into master ([filecoin-project/lotus#9597](https://github.com/filecoin-project/lotus/pull/9597)) +- chore:shed: Teach shed/sim to understand --tipset=@nnn notation ([filecoin-project/lotus#9434](https://github.com/filecoin-project/lotus/pull/9434)) +- _chore_: Upgrade `hid` ([filecoin-project/lotus#9406](https://github.com/filecoin-project/lotus/pull/9406)) +- chore: release: Update `release_issue_template` ([filecoin-project/lotus#9150](https://github.com/filecoin-project/lotus/pull/9150)) +- chore: update lotus version to 1.19.0-rc1 +- chore: merge release into master ([filecoin-project/lotus#9657](https://github.com/filecoin-project/lotus/pull/9657)) +- Backport: #9061 rpc errors ([filecoin-project/lotus#9384](https://github.com/filecoin-project/lotus/pull/9384)) +- shed: util: get all msig ([filecoin-project/lotus#9322](https://github.com/filecoin-project/lotus/pull/9322)) +- fix: test: simplify TestDeadlineToggling ([filecoin-project/lotus#9356](https://github.com/filecoin-project/lotus/pull/9356)) +- fix: build: set PropagationDelaySecs correctly ([filecoin-project/lotus#9358](https://github.com/filecoin-project/lotus/pull/9358)) +- fix: test: flaky TestDeadlineToggling around nulls (#9354) ([filecoin-project/lotus#9354](https://github.com/filecoin-project/lotus/pull/9354)) +- fix: retrievals: price-per-byte calculation fix ([filecoin-project/lotus#9353](https://github.com/filecoin-project/lotus/pull/9353)) +- fix: docs: update Go-badge in readme ([filecoin-project/lotus#9347](https://github.com/filecoin-project/lotus/pull/9347)) +- docs: release template: clarify location of version.go ([filecoin-project/lotus#9338](https://github.com/filecoin-project/lotus/pull/9338)) +- build: Bump version to v1.17.3-dev ([filecoin-project/lotus#9337](https://github.com/filecoin-project/lotus/pull/9337)) +- github.com/filecoin-project/go-fil-markets (v1.24.0-v17 -> v1.25.0): +- Merge branch 'release/v1.24.3' +- Update ffi and update markets to v9 (#751) (#761) ([filecoin-project/go-fil-markets#761](https://github.com/filecoin-project/go-fil-markets/pull/761)) +- chore: extract Provider piece logic to separate file (#750) ([filecoin-project/go-fil-markets#750](https://github.com/filecoin-project/go-fil-markets/pull/750)) +- Merge branch 'release/v1.24.2' +- Feat/support custom metadata (#759) ([filecoin-project/go-fil-markets#759](https://github.com/filecoin-project/go-fil-markets/pull/759)) +- Revert "Update ffi and update markets to v9 (#751)" (#755) ([filecoin-project/go-fil-markets#755](https://github.com/filecoin-project/go-fil-markets/pull/755)) +- Update ffi and update markets to v9 (#751) ([filecoin-project/go-fil-markets#751](https://github.com/filecoin-project/go-fil-markets/pull/751)) +- release: v1.24.0 ([filecoin-project/go-fil-markets#745](https://github.com/filecoin-project/go-fil-markets/pull/745)) +- github.com/filecoin-project/go-state-types (v0.9.8 -> v0.9.9): + +## lotus-market EOL notice + +As mentioned in [lotus v1.17.0 release notes](https://github.com/filecoin-project/lotus/releases/tag/v1.17.0), markets related features, enhancements and fixes is now lower priority for Lotus. We recommend our users to migrate to other deal making focused software, like [boost](https://boost.filecoin.io/) as soon as possible. That being said, the lotus maintainers will be: +- Lotus maintainers will stop supporting lotus-market subcomponent/**storage** deal making related issues or enhancements on Jan 31, 2023. +- In Q2 2023, we will be deprecating/removing lotus-market related code from this repository. + +If you have any questions or concerns, please raise them in [Lotus discussion](https://github.com/filecoin-project/lotus/discussions/categories/market)! + +## Contributors + +| Contributor | Commits | Lines ± | Files Changed | +|-------------|---------|---------|---------------| +| Geoff Stuart | 69 | +4745/-19478 | 405 | +| Shrenuj Bansal | 39 | +5257/-2183 | 243 | +| Łukasz Magiera | 32 | +2763/-730 | 169 | +| Aayush | 47 | +1439/-1138 | 157 | +| Ian Davis | 21 | +556/-1065 | 41 | +| Rod Vagg | 5 | +657/-320 | 18 | +| jennijuju | 4 | +632/-317 | 6 | +| Aayush Rajasekaran | 13 | +700/-135 | 18 | +| Jennifer Wang | 14 | +740/-54 | 25 | +| ZenGround0 | 1 | +193/-195 | 14 | +| Hannah Howard | 4 | +138/-122 | 52 | +| Steven Allen | 4 | +105/-24 | 11 | +| zenground0 | 9 | +109/-16 | 14 | +| Peter Rabbitson | 1 | +27/-23 | 3 | +| hannahhoward | 2 | +49/-0 | 2 | +| Airenas Vaičiūnas | 2 | +31/-16 | 2 | +| simlecode | 6 | +19/-10 | 12 | +| Phi | 5 | +16/-10 | 7 | +| sectrgt | 2 | +18/-0 | 2 | +| Jiaying Wang | 2 | +4/-4 | 3 | +| Rob Quist | 1 | +3/-1 | 1 | +| Jakub Sztandera | 1 | +1/-1 | 1 | + +# 1.18.2 / 2022-12-10 + +This is an OPTIONAL patch release that fixes a recently reported bug, where the miner process crashes due to a panic during an AddPiece process. More details can be found [here](https://github.com/filecoin-project/lotus/pull/9822). + + +# 1.18.1 / 2022-11-28 + +This is a small OPTIONAL patch release for the mandatory v1.18.0 release that supports the Filecoin nv17 Shark Upgrade. +We highly recommend you to read the full [v1.18.0 release note](https://github.com/filecoin-project/lotus/releases/tag/v1.18.0) if you haven't already. + +Note to SPs: +If you are running into issue with updating your miner node from an earlier release and is failing to restart your miner, check your `journalctl` and see if you noticed the following error: +``` +New sector storage: <%d> +Nov 19 15:03:43 g0lotus01 lotus-miner[]: ERROR: creating node: starting node: could not build arguments for function "reflect".makeFuncStub (/usr/local/go/src/reflect/asm_amd64.s:28): failed to build *paths.Local: received non-nil error from function "reflect".makeFuncStub (/usr/local/go/src/reflect/asm_amd64.s:28): opening path /media/data1/lotusstorage: path with ID <%d> already opened: '' +``` +If so, this check is introduced via [#9032](https://github.com/filecoin-project/lotus/pull/9032), precisely this [line](https://github.com/filecoin-project/lotus/blame/master/storage/paths/local.go#L164-L166 +). It's added to prevents double-attaching paths given it's now possible to manipulate paths at runtime. Verify storage.json configs if you encounter this error or remove the undesired depulicated path as you see fit accordingly. + +## Bug Fixes + +- fix: cli: check found before dereferencing SectorInfo #9703 + +# 1.18.0 / 2022-11-15 + +> ⚠️ **Please note that from Lotus v1.17.2&^ will require a Go-version of v1.18.1&^** + +This is the final release of the upcoming MANDATORY release of Lotus that introduces [Filecoin network v17, +codenamed the Shark upgrade](https://github.com/filecoin-project/community/discussions/74?sort=top#discussioncomment-3825422). Shark upgrade delivers a wave of protocol refinements that will allow for useful smart contracts to be written using the FVM (eg. programmable markets, lending contracts, etc.). + +**The Filecoin mainnet is scheduled to upgrade to nv17 at epoch 2383680, on Nov 30th on 2022-11-30T14:00:00Z. All node operators, including storage providers, must upgrade to this release before that time. Storage providers must update their daemons, miners, market and worker(s)/boost.** + +The Shark upgrade introduces the following FIPs, delivered in [actors v9](https://github.com/filecoin-project/builtin-actors/releases/tag/v9.0.3): +- [FIP0029 Beneficiary Address for Storage Providers](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0029.md): step towards better lending market for SP +- [FIP0034 Fix PreCommit Deposit Independent of Sector Content](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0034.md): resolves a significant weakening of Filecoin PoRep’s security guarantees + - ❗Pre-commit deposit will be calculated as the 20-day projection of expected reward earned by a sector with **a sector quality of 10 (i.e. full of verified deals)**, regardless of sector content. The Initial Pledge value, due when the sector is proven, is left **unchanged**. +- [FIP0041 Forward Compatibility for PreCommit](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0041.md): enables a cleaner and easier transition to Programmable Storage Markets +- [FIP0044 Standard Message Authentication](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0044.md): enable metadata authentication for user defined actor +- [FIP0045 Decoupling Fil+ from Marketplace](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0045.md): DataCap and the 10x QAP is now only associated with how long DATA is wanted to be stored on the network. + - This is a transitional state to enabling far more efficient and dynamic storage markets on Filecoin network in the future. + - ⭐️ First Fungible Token Contract - Datacap Actor, on Filecoin! ([fungible token standard](https://github.com/filecoin-project/FIPs/blob/master/FRCs/frc-0046.md), [token contract library](https://github.com/helix-onchain/filecoin/tree/5455f4f831e0f3f20005a9a789623d7ad6dada15/frc46_token)). + - For storage deal participants (clients and storage providers): + - `PublishStorageDeals`/`ProveCommit(Aggregate)`/`ProveReplicaUpdates` message that includes verified deals will see a gas usage increase, more details can be found [here](https://github.com/filecoin-project/FIPs/blob/385f069b3b146c5fef4fdc1109a0e2f35f399e48/FIPS/fip-0045.md?plain=1#L784) + - `Term` is introduced for defining how long the DataCap is assigned to a piece of data. Anyone who cares about that piece of data may extend the _term_, which incentives SPs to store the data longer on the network without a new deal/resealing. + - There is no more diluted verified deal QAP due to deal/sector space time for new sectors that contains verified deals after this upgrade. + - SPs may enjoy 90 days of extra QAP than deal duration by default, given `term_max` is always `deal duration + 90 days`. +❗ We highly recommend all lotus users, especially storage providers, developers and clients to read the FIPs in detail to understand the protocol changes and potential impact to network participants! + +## Snapshots + +The [#fil-infra](https://filecoinproject.slack.com/archives/C039RBG3RPC) team at PL has launched a brand new Lightweight Filecoin Chain Snapshots Service to support chain management needs for the node operators, check [here](https://www.notion.so/pl-strflt/Lightweight-Filecoin-Chain-Snapshots-17e4c386f35c44548f5863afb7b5e024) for the full detail. +We are planning to switch [the snapshot service listed in lotus docs](https://lotus.filecoin.io/lotus/manage/chain-management/#lightweight-snapshot) to the new Lightweight Filecoin Chain Snapshots Service by EOY, and deprecate public support of the current snapshots production. We recommend all users to test and switch the new service ASAP, and if you run into any issue, please report them [here](https://github.com/filecoin-project/filecoin-chain-archiver/discussions/new?category=feedback) and the team would be happy to support you! For the main differences between the old & the new service, checkout the FAQ section [here](https://www.notion.so/pl-strflt/Lightweight-Filecoin-Chain-Snapshots-17e4c386f35c44548f5863afb7b5e024) + +## Migration + +We are expecting a heavier than normal state migration for this upgrade due to the amount of the state changes introduced. +All node operators, including storage providers, should be aware that two pre-migrations are being scheduled. The first pre-migration will begin at 2022-11-30T12:00:00Z (120 minutes before the real upgrade), the second pre-migration will begin at 2022-11-30T13:45:00Z (7.5 minutes before the real upgrade). +The first pre-migration will take up to 1.5hr, depending on the amount of the historical state in the node blockstore and the hardware specs the node is running on. During this time, expect slower block validation times, increased CPU and memory usage, and longer delays for API queries. +We recommend node operators (who haven't enbabled splistore `universal` mode) that do not care about historical chain states, to prune the chain blockstore by syncing from a snapshot 1-2 days before the upgrade. +Note to full archival node operators: you may expect a migration that takes up to 20 min upon the upgrade, during this period your node will fall out of sync and your chain service may have some disruption. However, you can expect the node to catch up soon after the migration completes. + +### v9 Built-in actor bundles + +Bundles for all networks(mainnet, calibnet, and etc) are included in the lotus source tree (`build/actors/`) and embedded on build, for v9 actors you can find it [here](https://github.com/filecoin-project/lotus/blob/master/build/actors/v9.tar.zst). +Reminder: Lotus verifies that the bundle CIDs are the right ones upon build & upgrade against the values in `build/builtin_actors_gen.go`, according to the network you are building. You may also check the bundle manifest CID matches the bundle gen-ed values by running `lotus state actor-cids --network-version 17`. + +The manifest CID & full list of actor code CIDs for nv17 using [actor v9](https://github.com/filecoin-project/builtin-actors/releases/tag/v9.0.3) is: + +``` +"_manifest": "bafy2bzaceb6j6666h36xnhksu3ww4kxb6e25niayfgkdnifaqi6m6ooc66i6i" +"account": "bafk2bzacect2p7urje3pylrrrjy3tngn6yaih4gtzauuatf2jllk3ksgfiw2y" +"cron": "bafk2bzacebcec3lffmos3nawm5cvwehssxeqwxixoyyfvejy7viszzsxzyu26" +"datacap": "bafk2bzacebb6uy2ys7tapekmtj7apnjg7oyj4ia5t7tlkvbmwtxwv74lb2pug" +"init": "bafk2bzacebtdq4zyuxk2fzbdkva6kc4mx75mkbfmldplfntayhbl5wkqou33i" +"multisig": "bafk2bzacec4va3nmugyqjqrs3lqyr2ij67jhjia5frvx7omnh2isha6abxzya" +"paymentchannel": "bafk2bzacebhdvjbjcgupklddfavzef4e4gnkt3xk3rbmgfmk7xhecszhfxeds" +"reward": "bafk2bzacebezgbbmcm2gbcqwisus5fjvpj7hhmu5ubd37phuku3hmkfulxm2o" +"storagemarket": "bafk2bzacec3j7p6gklk64stax5px3xxd7hdtejaepnd4nw7s2adihde6emkcu" +"storageminer": "bafk2bzacedyux5hlrildwutvvjdcsvjtwsoc5xnqdjl73ouiukgklekeuyfl4" +"storagepower": "bafk2bzacedsetphfajgne4qy3vdrpyd6ekcmtfs2zkjut4r34cvnuoqemdrtw" +"system": "bafk2bzaceagvlo2jtahj7dloshrmwfulrd6e2izqev32qm46eumf754weec6c" +"verifiedregistry": "bafk2bzacecf3yodlyudzukumehbuabgqljyhjt5ifiv4vetcfohnvsxzynwga" +``` + +## New Features +- Integrate actor v9: + - test: Add invariance checks to v17 migration test ([filecoin-project/lotus#9454](https://github.com/filecoin-project/lotus/pull/9454)) +- Implement and support [FIP0045 Decoupling Fil+ from Marketplace](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0045.md): + - fix: state: add datacap actor to actors registry ([filecoin-project/lotus#9476](https://github.com/filecoin-project/lotus/pull/9476)) + - feat: actors: Integrate builtin-actors changes for FIP-0045 ([filecoin-project/lotus#9355](https://github.com/filecoin-project/lotus/pull/9355)) + - feat: actors: Integrate datacap actor into lotus (#9348) ([filecoin-project/lotus#9348](https://github.com/filecoin-project/lotus/pull/9348)) + - feat: cli: Add commands for listing allocations and removing expired allocations ([filecoin-project/lotus#9468](https://github.com/filecoin-project/lotus/pull/9468)) + - feat: sealing pipeline: Prepare deal assigning logic for FIP-45 ([filecoin-project/lotus#9412](https://github.com/filecoin-project/lotus/pull/9412)) + - feat: add API methods to get allocations and claims ([filecoin-project/lotus#9437](https://github.com/filecoin-project/lotus/pull/9437)) +- Implement and support [FIP0029 Beneficiary Address for Storage Providers](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0029.md) + - feat: api/cli: beneficiary withdraw api and cli #9296 + - feat: api/cli: change beneficiary propose and confirm for actors and multisigs. #9307 + +## Improvements +- feat: wdpost: Add ability to only have single partition per msg for partitions with recovery sectors ([filecoin-project/lotus#9427](https://github.com/filecoin-project/lotus/pull/9427)) +- feat: API: support typed errors over RPC ([filecoin-project/lotus#9061](https://github.com/filecoin-project/lotus/pull/9061)) +- feat: refactor: remove NewestNetworkVersion ([filecoin-project/lotus#9351](https://github.com/filecoin-project/lotus/pull/9351)) +- chore: actors: Allow builtin-actors to return a map of methods (#9342) ([filecoin-project/lotus#9342](https://github.com/filecoin-project/lotus/pull/9342)) + +## Dependencies +- Update FFI ([filecoin-project/lotus#9484](https://github.com/filecoin-project/lotus/pull/9484)) +- chore: deps: update go-state-types and builtin-actors for v9 release ([filecoin-project/lotus#9485](https://github.com/filecoin-project/lotus/pull/9485)) +- deps: backport: #9455 ([filecoin-project/lotus#9463](https://github.com/filecoin-project/lotus/pull/9463)) +- Deps: Update go-fil-markets to 1.24.0-v17 ([filecoin-project/lotus#9450](https://github.com/filecoin-project/lotus/pull/9450)) +- github.com/filecoin-project/go-jsonrpc (v0.1.7 -> v0.1.8) +- github.com/filecoin-project/go-state-types (v0.1.12-beta -> v0.9.0): + +## Others +- fix: upgrade: no splash banner for nv17 :( ([filecoin-project/lotus#9486](https://github.com/filecoin-project/lotus/pull/9486)) +- chore: build: add calib upgrade param for shark ([filecoin-project/lotus#9483](https://github.com/filecoin-project/lotus/pull/9483)) +- chore: update butterfly artifacts ([filecoin-project/lotus#9467](https://github.com/filecoin-project/lotus/pull/9467)) +- chore: butterfly: update assets ([filecoin-project/lotus#9462](https://github.com/filecoin-project/lotus/pull/9462)) +- Delete lotus-pond (#9352) ([filecoin-project/lotus#9352](https://github.com/filecoin-project/lotus/pull/9352)) +- build: set version to v1.18.0-dev + +## lotus-market EOL notice + +As mentioned in [lotus v1.17.0 release notes](https://github.com/filecoin-project/lotus/releases/tag/v1.17.0), markets related features, enhancements and fixes is now lower priority for Lotus. We recommend our users to migrate to other deal making focused software, like [boost](https://boost.filecoin.io/) as soon as possible. That being said, the lotus maintainers will be: +- Lotus maintainers will stop supporting lotus-market subcomponent/**storage** deal making related issues or enhancements on Jan 31, 2023. +- In Q2 2023, we will be deprecating/removing lotus-market related code from this repository. +If you have any questions or concerns, please raise them in [Lotus discussion](https://github.com/filecoin-project/lotus/discussions/categories/market)! + + +## Contributors + +| Contributor | Commits | Lines ± | Files Changed | +|-------------|---------|---------|---------------| +| @geoff-vball | 73 | +14533/-19712 | 509 | +| @arajasek | 16 | +2230/-303 | 49 | +| @arajasek | 29 | +701/-297 | 117 | +| @magik6k | 5 | +429/-135 | 45 | +| @Frrist | 1 | +246/-203 | 25 | +| @stebalien | 2 | +323/-2 | 6 | +| @shrenujbansal | 3 | +176/-61 | 10 | +| @ZenGround0 | 2 | +78/-38 | 5 | +| @jennijuju | 8 | +97/-18 | 16 | +| @simlecode | 5 | +18/-9 | 11 | +| Kevin Li | 1 | +7/-0 | 1 | +| @zenground0 | 2 | +3/-3 | 3 | +| @jennijuju | 1 | +3/-3 | 2 | +| Rod Vagg | 1 | +3/-2 | 2 | +| @jennijuju | 1 | +2/-2 | 2 | +| Peter Rabbitson | 1 | +3/-0 | 1 | +| Jakub Sztandera | 1 | +1/-1 | 1 | + +# v1.17.2 / 2022-10-05 + +This is an OPTIONAL release of Lotus. This feature release introduces new sector number management APIs in Lotus that enables all the Sealing-as-a-Service and Lotus interactions needed to function. The default propagation delay setting for storage providers has also been changed, as well as numerous other features and enhancements. Check out the sub-bullet points in the feature and enhancement section to get a short description about each feature and enhancements. + +### Highlights + +🦭 **SaaS** 🦭 +New sector management APIs makes it possible to import partially sealed sectors into Lotus. This release implements all SaaS<->Lotus interactions needed for such services to work. Deep dive into the new APIs here: https://github.com/filecoin-project/lotus/discussions/9079#discussioncomment-3652044 + +⏳ **Propagation delay** ⌛️ +In v1.17.2 the default PropagationDelay has been raised from 6 seconds -> 10 seconds, and you can tune this yourself with the `PROPAGATION_DELAY_SECS` environment variable. This means you will now wait for 10 seconds for other blocks to arrive from the network before computing a winningPoSt (if eligible). In your `lotus-miner` logs that means you will see this "baseDeltaSeconds": 10 as default. + +⚠️ **Please note that Lotus v1.17.2 will require a Go-version of v1.18.1 or higher!** + +## New features +- feat: sealing: Partially sealed sector import ([filecoin-project/lotus#9210](https://github.com/filecoin-project/lotus/pull/9210)) + - Implements support for importing (partially) sealed sectors which is needed for enabling external sealing services. +- feat: sealing: Use bitfields to manage sector numbers ([filecoin-project/lotus#9183](https://github.com/filecoin-project/lotus/pull/9183)) + - Needed for Sealing-as-a-Service. Move sector number assigning logic to use stored bitfields instead of stored counters. Makes it possible to reserve ranges of sector numbers, see the sector assigner state and view sector number reservations. +- feat: env: propagation delay ([filecoin-project/lotus#9290](https://github.com/filecoin-project/lotus/pull/9290)) + - The default propagation delay is raised to 10 seconds from 6 seconds. Ability to set it yourself with the `PROPAGATION_DELAY_SECS` environment variable. +- feat: cli: lotus info cmd ([filecoin-project/lotus#9233](https://github.com/filecoin-project/lotus/pull/9233)) + - A new `lotus info` command that prints useful node information in one place. +- feat: proving: Introduce manual sector fault recovery (#9144) ([filecoin-project/lotus#9144](https://github.com/filecoin-project/lotus/pull/9144)) + - Allow users to declare fault recovery messages manually with the `lotus-miner proving recover-faults` command, rather than waiting for it to happen automatically before windowPost. +- feat: api: Reintroduce StateActorManifestCID ([filecoin-project/lotus#9201](https://github.com/filecoin-project/lotus/pull/9201)) + - Adds ability to retrieve the Actor Manifest CID through the api. +- feat: message: Add uuid to mpool message sent to chain node from miner ([filecoin-project/lotus#9174](https://github.com/filecoin-project/lotus/pull/9174)) + - Adds a UUID to each message sent by the `lotus-miner` to the daemon. A requirement needed for https://github.com/filecoin-project/lotus/issues/9130 +- feat: message: Add retries to mpool push message from lotus-miner ([filecoin-project/lotus#9177](https://github.com/filecoin-project/lotus/pull/9177)) + - Retries to mpool push message API in case of unavailability of the lotus chain node. + +**Network 17 related features :** +- feat: network: add nv17 and integrate the corresponding go state type ([filecoin-project/lotus#9267](https://github.com/filecoin-project/lotus/pull/9267)) +- feat: cli: print beneficiary info in state miner-info ([filecoin-project/lotus#9308](https://github.com/filecoin-project/lotus/pull/9308)) +- feat: api/cli: change beneficiary propose and confirm for actors and multisigs. ([filecoin-project/lotus#9307](https://github.com/filecoin-project/lotus/pull/9307)) +- feat: api/cli: beneficiary withdraw api and cli ([filecoin-project/lotus#9296](https://github.com/filecoin-project/lotus/pull/9296)) + +## Enhancements +- feat: sectors renew --only-cc ([filecoin-project/lotus#9184](https://github.com/filecoin-project/lotus/pull/9184)) + - Exlude extending deal-related sectors with the `--only-cc` option when using the `lotus-miner sectors renew` +- feat: miner: display updated & update-cache for storage list ([filecoin-project/lotus#9323](https://github.com/filecoin-project/lotus/pull/9323)) + - Show amount of `updated` & `update-cache` sectors in each storage path in the `lotus-miner storage list` output +- feat: add descriptive errors to markets event handler ([filecoin-project/lotus#9326](https://github.com/filecoin-project/lotus/pull/9326)) + - More descriptive market error logs +- feat: cli: Add option to terminate sectors from worker address ([filecoin-project/lotus#9291](https://github.com/filecoin-project/lotus/pull/9291)) + - Adds a flag to allow either owner address or worker address to send terminate sectors message. +- fix: cli: actor-cids cli command now defaults to current network ([filecoin-project/lotus#9321](https://github.com/filecoin-project/lotus/pull/9321)) + - Makes the command defaults to the current network. +- fix: ux: Output bytes in `lotus client commP` cmd ([filecoin-project/lotus#9189](https://github.com/filecoin-project/lotus/pull/9189)) + - Adds an additional line that outputs bytes in the `lotus client commP` command. +- fix: sealing: Add information on what worker a job was assigned to in logs ([filecoin-project/lotus#9151](https://github.com/filecoin-project/lotus/pull/9151)) + - Adds the worker hostname into the assignment logs. +- refactor: sealing pipeline: Remove useless storage adapter code ([filecoin-project/lotus#9142](https://github.com/filecoin-project/lotus/pull/9142) + - Remove proxy code in `storage/miner.go` / `storage/miner_sealing.go`, call the pipeline directly instead. + +## Bug fixes +- fix: ffiwrapper: Close readers in AddPiece ([filecoin-project/lotus#9328](https://github.com/filecoin-project/lotus/pull/9328)) +- fix: sealing: Drop unused PreCommitInfo from pipeline.SectorInfo ([filecoin-project/lotus#9325](https://github.com/filecoin-project/lotus/pull/9325)) +- fix: cli: fix panic in `lotus-miner actor control list` ([filecoin-project/lotus#9241](https://github.com/filecoin-project/lotus/pull/9241)) +- fix: sealing: Abort upgrades in sectors with no deals ([filecoin-project/lotus#9310](https://github.com/filecoin-project/lotus/pull/9310)) +- fix: sealing: Make DataCid resource env vars make more sense ([filecoin-project/lotus#9231](https://github.com/filecoin-project/lotus/pull/9231)) +- fix: cli: Option to specify --from msg sender ([filecoin-project/lotus#9237](https://github.com/filecoin-project/lotus/pull/9237)) +- fix: ux: better ledger rejection error ([filecoin-project/lotus#9242](https://github.com/filecoin-project/lotus/pull/9242)) +- fix: ux: msg receipt for actor withdrawal ([filecoin-project/lotus#9155](https://github.com/filecoin-project/lotus/pull/9155)) +- fix: ux: exclude negative available balance from spendable amount ([filecoin-project/lotus#9182](https://github.com/filecoin-project/lotus/pull/9182)) +- fix: sealing: Avoid panicking in handleUpdateActivating on startup ([filecoin-project/lotus#9331](https://github.com/filecoin-project/lotus/pull/9331)) +- fix: api: DataCid - ensure reader is closed ([filecoin-project/lotus#9230](https://github.com/filecoin-project/lotus/pull/9230)) +- fix: verifreg: serialize RmDcProposalID as int, not tuple ([filecoin-project/lotus#9206](https://github.com/filecoin-project/lotus/pull/9206)) +- fix: api: Ignore uuid check for messages with uuid not set ([filecoin-project/lotus#9303](https://github.com/filecoin-project/lotus/pull/9303)) +- fix: cgroupV1: memory.memsw.usage_in_bytes: no such file or directory ([filecoin-project/lotus#9202](https://github.com/filecoin-project/lotus/pull/9202)) +- fix: miner: init miner's with 32GiB sectors by default ([filecoin-project/lotus#9364](https://github.com/filecoin-project/lotus/pull/9364)) +- fix: worker: Close all storage paths on worker shutdown ([filecoin-project/lotus#9153](https://github.com/filecoin-project/lotus/pull/9153)) +- fix: build: set PropagationDelaySecs correctly ([filecoin-project/lotus#9358](https://github.com/filecoin-project/lotus/pull/9358)) +- fix: renew --only-cc with sectorfile ([filecoin-project/lotus#9428](https://github.com/filecoin-project/lotus/pull/9428)) + +## Dependency updates +- github.com/filecoin-project/go-fil-markets (v1.23.1 -> v1.24.0) +- github.com/filecoin-project/go-jsonrpc (v0.1.5 -> v0.1.7) +- github.com/filecoin-project/go-state-types (v0.1.10 -> v0.1.12-beta) +- github.com/filecoin-project/go-commp-utils/nonffi (null -> v0.0.0-20220905160352-62059082a837) +- deps: go-libp2p-pubsub v0.8.0 ([filecoin-project/lotus#9229](https://github.com/filecoin-project/lotus/pull/9229)) +- deps: libp2p v0.22 ([filecoin-project/lotus#9216](https://github.com/filecoin-project/lotus/pull/9216)) +- deps: Use latest cbor-gen ([filecoin-project/lotus#9335](https://github.com/filecoin-project/lotus/pull/9335)) +- chore: update bitswap and some libp2p packages ([filecoin-project/lotus#9279](https://github.com/filecoin-project/lotus/pull/9279)) + +## Others +- chore: merge releases into master after v1.17.1 release ([filecoin-project/lotus#9283](https://github.com/filecoin-project/lotus/pull/9283)) +- chore: docs: Fix dead links to docs.filecoin.io ([filecoin-project/lotus#9304](https://github.com/filecoin-project/lotus/pull/9304)) +- chore: deps: update FFI ([filecoin-project/lotus#9330](https://github.com/filecoin-project/lotus/pull/9330)) +- chore: seed: add cmd for adding signers to rkh to genesis ([filecoin-project/lotus#9198](https://github.com/filecoin-project/lotus/pull/9198)) +- chore: fix typo in comment ([filecoin-project/lotus#9161](https://github.com/filecoin-project/lotus/pull/9161)) +- chore: cli: cleanup and standardize cli ([filecoin-project/lotus#9317](https://github.com/filecoin-project/lotus/pull/9317)) +- chore: versioning: Bump version to v1.17.2-dev ([filecoin-project/lotus#9147](https://github.com/filecoin-project/lotus/pull/9147)) +- chore: release: v1.17.2-rc1 ([filecoin-project/lotus#9339](https://github.com/filecoin-project/lotus/pull/9339)) +- feat: shed: add a --max-size flag to vlog2car ([filecoin-project/lotus#9212](https://github.com/filecoin-project/lotus/pull/9212)) +- fix: docsgen: revert rename of API Name to Num ([filecoin-project/lotus#9315](https://github.com/filecoin-project/lotus/pull/9315)) +- fix: ffi: Revert accidental filecoin-ffi downgrade from #9144 ([filecoin-project/lotus#9277](https://github.com/filecoin-project/lotus/pull/9277)) +- fix: miner: Call SyncBasefeeCheck from `lotus info` ([filecoin-project/lotus#9281](https://github.com/filecoin-project/lotus/pull/9281)) +- fix: mock sealer: grab lock in ReadPiece ([filecoin-project/lotus#9207](https://github.com/filecoin-project/lotus/pull/9207)) +- refactor: use `os.ReadDir` for lightweight directory reading ([filecoin-project/lotus#9282](https://github.com/filecoin-project/lotus/pull/9282)) +- tests: cli: Don't panic with no providers in client retrieve ([filecoin-project/lotus#9232](https://github.com/filecoin-project/lotus/pull/9232)) +- build: artifacts: butterfly ([filecoin-project/lotus#9027](https://github.com/filecoin-project/lotus/pull/9027)) +- build: Use lotus snap (and fix typo) for packer builds ([filecoin-project/lotus#9152](https://github.com/filecoin-project/lotus/pull/9152)) +- build: Update xcode version for macos builds ([filecoin-project/lotus#9170](https://github.com/filecoin-project/lotus/pull/9170)) +- ci: build: Snap daemon autorun disable ([filecoin-project/lotus#9167](https://github.com/filecoin-project/lotus/pull/9167)) +- ci: Use golang 1.18.1 to build appimage ([filecoin-project/lotus#9389](https://github.com/filecoin-project/lotus/pull/9389)) +- ci: Don't publish new homebrew releases for RC builds ([filecoin-project/lotus#9350](https://github.com/filecoin-project/lotus/pull/9350)) +- Merge branch 'deps/go-libp2p-v0.21' + +Contributors + +| Contributor | Commits | Lines ± | Files Changed | +|-------------|---------|---------|---------------| +| Aayush Rajasekaran | 8 | +23010/-2122 | 109 | +| Aayush | 15 | +6168/-2679 | 360 | +| Łukasz Magiera | 69 | +6462/-2137 | 606 | +| Geoff Stuart | 19 | +3080/-1177 | 342 | +| Marco Munizaga | 16 | +543/-424 | 41 | +| Shrenuj Bansal | 30 | +485/-419 | 88 | +| LexLuthr | 3 | +498/-12 | 19 | +| Phi | 15 | +330/-70 | 17 | +| Jennifer Wang | 7 | +132/-12 | 11 | +| TippyFlitsUK | 1 | +43/-45 | 12 | +| Steven Allen | 1 | +18/-28 | 2 | +| Frrist | 1 | +19/-11 | 2 | +| Eng Zer Jun | 1 | +14/-11 | 6 | +| Dirk McCormick | 2 | +23/-1 | 3 | +| Ian Davis | 3 | +7/-9 | 3 | +| Masih H. Derkani | 1 | +11/-0 | 1 | +| Anton Evangelatov | 1 | +11/-0 | 1 | +| Yu | 2 | +4/-4 | 5 | +| Hannah Howard | 1 | +4/-4 | 1 | +| Phi-rjan | 1 | +1/-2 | 1 | +| Jiaying Wang | 1 | +3/-0 | 1 | +| nujz | 1 | +1/-1 | 1 | +| Rob Quist | 1 | +1/-1 | 1 | + +# v1.17.1 / 2022-09-06 + +This is an optional release of Lotus. This release introduces the [Splitstore v2 - beta](https://github.com/filecoin-project/lotus/blob/master/blockstore/splitstore/README.md)(beta). Splitstore aims to reduce the node performance impact that's caused by the Filecoin's very large, and continuously growing datastore. Splitstore v2 introduces the coldstore auto prune/GC feature & some improvements for the hotstore. We welcome all lotus users to join the early testers and try the new Splitstore out, you can leave any feedback or report issues in [this discussion](https://github.com/filecoin-project/lotus/discussions/9179) or create an issue. As always, multiple small bug fixes, new features & improvements are also included in this release. + + +## New features + +- feat:chain:splitstore auto prune ([filecoin-project/lotus#9123](https://github.com/filecoin-project/lotus/pull/9123)) + - Trigger SplitStore chain prune on head events. [Link to the documentation](https://lotus.filecoin.io/lotus/manage/chain-management/#cold-store-garbage-collection) +- feat:chain:splitstore chain prune ([filecoin-project/lotus#9056](https://github.com/filecoin-project/lotus/pull/9056)) + - Adds `chain prune` command to trigger manual garbage collection. [Link to the documentation](https://lotus.filecoin.io/lotus/manage/chain-management/#cold-store-garbage-collection) +- feat: storage: Path type filters ([filecoin-project/lotus#9013](https://github.com/filecoin-project/lotus/pull/9013)) + - Adds new fields to `sectorstore.json` to allow file type filtering. [Link to the documentation](https://lotus.filecoin.io/storage-providers/operate/custom-storage-layout/#filter-sector-types-1) +- feat: sealing: storage redeclare/detach ([filecoin-project/lotus#9032](https://github.com/filecoin-project/lotus/pull/9032)) + - Adds new Lotus commands to detach and redeclare storage paths. [Link to the documentation](https://lotus.filecoin.io/storage-providers/operate/custom-storage-layout/#detach-storage-paths) +- feat: worker: Add stop cmd for lotus worker ([filecoin-project/lotus#9101](https://github.com/filecoin-project/lotus/pull/9101)) + - Adds new `lotus-worker stop` command. [Link to the documentation](https://lotus.filecoin.io/storage-providers/seal-workers/seal-workers/#stop-the-worker) +- feat: market: Add lotus-shed cmd to get total active deal storage ([filecoin-project/lotus#9113](https://github.com/filecoin-project/lotus/pull/9113)) + - `get-deals-total-storage` - View the total storage available in all active market deals +- feat: wdpost: Envvar for limiting recovering sectors ([filecoin-project/lotus#9106](https://github.com/filecoin-project/lotus/pull/9106)) + - Adds new envvar to limit the number of sectors declared in the recover message + + +## Improvements + +- feat: sealing: Allow overriding worker hostname ([filecoin-project/lotus#9116](https://github.com/filecoin-project/lotus/pull/9116)) +- feat: build: run fiximports on make actors-gen ([filecoin-project/lotus#9114](https://github.com/filecoin-project/lotus/pull/9114)) +- feat: FVM: always enable tracing for user-triggered executions ([filecoin-project/lotus#9036](https://github.com/filecoin-project/lotus/pull/9036)) +- feat: miner cli: proving deadline command enchantments ([filecoin-project/lotus#9109](https://github.com/filecoin-project/lotus/pull/9109)) +- FVM: Use MaxInt64 for Implicit Message gas limits ([filecoin-project/lotus#9037](https://github.com/filecoin-project/lotus/pull/9037)) +- lotus shed addr decode +- push lotus-gateway to docker hub ([filecoin-project/lotus#8969](https://github.com/filecoin-project/lotus/pull/8969)) +- Review Response +- test: net: net and conngater tests ([filecoin-project/lotus#8084](https://github.com/filecoin-project/lotus/pull/8084)) +- Update FFI ([filecoin-project/lotus#9139](https://github.com/filecoin-project/lotus/pull/9139)) + +## Bug Fixes + +- backport: 9153: detach storage on worker shutdown ([filecoin-project/lotus#9127](https://github.com/filecoin-project/lotus/pull/9165)) +- fix makegen +- fix: build: use GOCC when building lotus-fountain ([filecoin-project/lotus#9127](https://github.com/filecoin-project/lotus/pull/9127)) +- fix: ci: Forgot a .sh on the end of a the new publish script ([filecoin-project/lotus#9088](https://github.com/filecoin-project/lotus/pull/9088)) +- fix: cli: ./lotus-miner actor control list, if the owner is not account ([filecoin-project/lotus#9072](https://github.com/filecoin-project/lotus/pull/9072)) +- fix: deps: update FFI to fix a slow memory leak ([filecoin-project/lotus#9042](https://github.com/filecoin-project/lotus/pull/9042)) +- fix: FVM: record message applied metrics ([filecoin-project/lotus#9052](https://github.com/filecoin-project/lotus/pull/9052)) +- fix: gas: estimate gas with a zero base-fee ([filecoin-project/lotus#8991](https://github.com/filecoin-project/lotus/pull/8991)) +- fix: post: restrict recoveries per deadline ([filecoin-project/lotus#9111](https://github.com/filecoin-project/lotus/pull/9111)) +- fix: sealing: Workaround for sealing bug ([filecoin-project/lotus#9043](https://github.com/filecoin-project/lotus/pull/9043)) +- fix: storage: don't panic in getCommitCutoff when precommit is not found ([filecoin-project/lotus#9141](https://github.com/filecoin-project/lotus/pull/9141)) +- fix: test: deflake TestQuotePriceForUnsealedRetrieval ([filecoin-project/lotus#9084](https://github.com/filecoin-project/lotus/pull/9084)) + +## Dependency Updates + +- github.com/multiformats/go-multibase (v0.0.3 -> v0.1.1) + +## Others + +- chore: ci: Update xcode version for macos builds ([filecoin-project/lotus#9164)](https://github.com/filecoin-project/lotus/pull/9164)) +- Merge branch 'docs/ysrotciv-desc' +- Merge branch 'feat/f8-worker-env' +- Merge branch 'LexLuthr-feat/minerWithdrawBalanceAPI' +- Merge branch 'LexLuthr-feat/SchedRemoveRequest' +- base256emoji ([filecoin-project/lotus#9038)](https://github.com/filecoin-project/lotus/pull/9038)) +- chore: interop: update interop assets ([filecoin-project/lotus#9093)](https://github.com/filecoin-project/lotus/pull/9093)) +- chore: merge: releases (v1.17.0) to master ([filecoin-project/lotus#9146)](https://github.com/filecoin-project/lotus/pull/9146)) +- chore: sealer: Fixup typos ([filecoin-project/lotus#9040)](https://github.com/filecoin-project/lotus/pull/9040)) +- chore:docs:remove readme reference to deprecated specs-actors ([filecoin-project/lotus#8984)](https://github.com/filecoin-project/lotus/pull/8984)) +- ci : Change default shell options for snapcraft publish ([filecoin-project/lotus#9122)](https://github.com/filecoin-project/lotus/pull/9122)) +- ci: More tweaks to snapcraft release process ([filecoin-project/lotus#9090)](https://github.com/filecoin-project/lotus/pull/9090)) +- ci: Publish to both lotus and lotus-filecoin for snap ([filecoin-project/lotus#9119)](https://github.com/filecoin-project/lotus/pull/9119)) +- ci: Run snap builds for lotus and lotus-filecoin in parallel ([filecoin-project/lotus#9133)](https://github.com/filecoin-project/lotus/pull/9133)) +- ci: Switches goreleaser notes back to default (keep-existing) ([filecoin-project/lotus#9120)](https://github.com/filecoin-project/lotus/pull/9120)) +- ci: update snapcraft and release flow logic ([filecoin-project/lotus#8994)](https://github.com/filecoin-project/lotus/pull/8994)) +- ci: Use goreleaser to build macos universal binaries (including M1 macs) ([filecoin-project/lotus#9096)](https://github.com/filecoin-project/lotus/pull/9096)) +- ci:testing:remove codecov ([filecoin-project/lotus#9062)](https://github.com/filecoin-project/lotus/pull/9062)) + + +Contributors + +| Contributor | Commits | Lines ± | Files Changed | +|-------------|---------|---------|---------------| +| Łukasz Magiera | 34 | +2329/-317 | 163 | +| ZenGround0 | 2 | +1527/-89 | 38 | +| Ian Davis | 14 | +751/-232 | 30 | +| LexLuthr | 17 | +480/-225 | 63 | +| TheMenko | 4 | +323/-61 | 5 | +| Aayush | 10 | +285/-92 | 30 | +| beck | 3 | +143/-93 | 3 | +| Steven Allen | 4 | +95/-75 | 9 | +| zenground0 | 5 | +44/-116 | 9 | +| Shrenuj Bansal | 7 | +136/-7 | 16 | +| Patrick Deuse | 3 | +76/-57 | 3 | +| Jennifer Wang | 3 | +6/-52 | 11 | +| zl | 2 | +20/-16 | 2 | +| Aayush Rajasekaran | 2 | +6/-6 | 2 | +| Clint Armstrong | 1 | +7/-3 | 1 | +| Cory Schwartz | 2 | +9/-0 | 2 | +| Jorropo | 1 | +3/-2 | 2 | +| Geoff Stuart | 1 | +5/-0 | 1 | +| Frank Y | 1 | +2/-2 | 2 | +| Aloxaf | 1 | +2/-2 | 1 | + + +# Lotus changelog + +# v1.17.0 / 2022-08-02 + +This is an optional release of Lotus. This feature release introduces a lot of new sealing and scheduler improvements, and many other functionalities and bug fixes. + +PSA: Markets related features, enhancements and fixes is now lower priority for Lotus, and is going to be in the hands of [Boost](https://boost.filecoin.io), built by the amazing [Bedrock team](https://www.notion.so/pl-strflt/Bedrock-2e956d5d8143432080a1d84435ccf0ff). You can find Lotus mission scope [here](https://www.notion.so/Lotus-8352bbb6c321431abd8790a7a3401ed3#805c645f592840ad893c272723362d3d) + +## New features + +- feat: worker: lotus-worker run --no-default ([filecoin-project/lotus#8672](https://github.com/filecoin-project/lotus/pull/8672)) + - Makes it very easy to spin up workers that make the compute tasks opt-in, instead of the default opt-out. Also makes it very easy to create storage-only workers. [Link to the documentation](https://lotus.filecoin.io/storage-providers/seal-workers/seal-workers/#run-the-worker) +- feat: sched: Per worker concurrent task count limits ([filecoin-project/lotus#8725](https://github.com/filecoin-project/lotus/pull/8725)) + - Set the maximum number of tasks running it parallel on workers by exporting env-variables: `[short task type]_[sector size]_MAX_CONCURRENT=[limit]`. [Link to documentation](https://lotus.filecoin.io/storage-providers/seal-workers/seal-workers/#limit-tasks-run-in-parallel) +- feat: sched: Finalize* move selectors ([filecoin-project/lotus#8710](https://github.com/filecoin-project/lotus/pull/8710)) + - Allows you to force all Finalize tasks to run on workers with local access to both long-term storage and the sealing path containing the sector. +- feat: sched: Add scheduler interfaces, configurable assigner ([filecoin-project/lotus#8700](https://github.com/filecoin-project/lotus/pull/8700)) + - Introduce a new simpler worker assigning logic which will attempt to assign tasks to as many workers as possible and ignore worker utilization. [Link to documentation](https://lotus.filecoin.io/storage-providers/advanced-configurations/sealing/#worker-assigning-logic) +- feat: bench: simple sealing operations commands ([filecoin-project/lotus#8373](https://github.com/filecoin-project/lotus/pull/8373)) + - Allows you to only test the performance of a single task. [Read the documentation](https://lotus.filecoin.io/storage-providers/operate/benchmarks/#single-task-benchmark). +- feat: miner cli: sealing data-cid command ([filecoin-project/lotus#8715](https://github.com/filecoin-project/lotus/pull/8715)) + - Makes it possible to compute data CIDs on lotus workers. +- feat: precommits info ([filecoin-project/lotus#8696](https://github.com/filecoin-project/lotus/pull/8696)) + - Check the on-chain precommit info with the `lotus-miner sectors precommits` command. +- feat: dagstore: add dagstore register-shard command ([filecoin-project/lotus#8645](https://github.com/filecoin-project/lotus/pull/8645)) + - If you have broken indexes in your market’s dagstore, you can try to recover it by using `lotus-miner dagstore register-shard`. [Link to documentation](https://lotus.filecoin.io/kb/dagstore-register/) +- feat: Implement cli command for compactPartitions ([filecoin-project/lotus#8637](https://github.com/filecoin-project/lotus/pull/8637)) + - Remove dead sectors from partitions and reduce the number of partitions used if possible with the `lotus-miner sectors compact-partitions`command. [Link to documentation](https://lotus.filecoin.io/storage-providers/operate/daily-chores/#compacting-partitions) +- feat: recovery: Config for maximum partition count per message ([filecoin-project/lotus#8986](https://github.com/filecoin-project/lotus/pull/8986) + - Adds config for setting the maximum amount of partitions to declare in a DeclareFaultsRecovered message [Link to documentation](https://lotus.filecoin.io/storage-providers/seal-workers/post-workers/#multiple-partitions) +- feat: wdpost: Config for maximum partition count per message ([filecoin-project/lotus#8982](https://github.com/filecoin-project/lotus/pull/8982)) + - Adds config for setting the maximum amount of partitions to prove in a SubmitWindowPoSt message [Link to documentation](https://lotus.filecoin.io/storage-providers/seal-workers/post-workers/#multiple-partitions) +- feat: sealer: Config for disabling builtin PoSt / PoSt pre-checks ([filecoin-project/lotus#8959](https://github.com/filecoin-project/lotus/pull/8959)) + - Adds the ability to fully disable PoSt tasks on the `lotus-miner` and disabling windowPoSt pre-checks. [Link to documentation](https://lotus.filecoin.io/storage-providers/advanced-configurations/workers/#post-computations) +- feat: add create ledger wallet address by account index command ([filecoin-project/lotus#8657](https://github.com/filecoin-project/lotus/pull/8657)) + +## Improvements + +- feat: wdpost: Ignore faults in lotus-miner proving compute window-post ([filecoin-project/lotus#8737](https://github.com/filecoin-project/lotus/pull/8737)) +- feat: cli: Nicer net stat ([filecoin-project/lotus#8797](https://github.com/filecoin-project/lotus/pull/8797)) +- feat: networking: add healthz and livez endpoints ([filecoin-project/lotus#8692](https://github.com/filecoin-project/lotus/pull/8692)) +- feat: Snap Deals full unseal ([filecoin-project/lotus#8478](https://github.com/filecoin-project/lotus/pull/8478)) +- feat: cli: Hide sector nums in 'proving deadline' by default ([filecoin-project/lotus#8952](https://github.com/filecoin-project/lotus/pull/8952)) +- feat: Add rate limiting to the lotus gateway ([filecoin-project/lotus#8517](https://github.com/filecoin-project/lotus/pull/8517)) +- feat: networking: disconnect cmd ([filecoin-project/lotus#8955](https://github.com/filecoin-project/lotus/pull/8955)) +- feat: dagstore: Add DestroyShard() in dagstore wrapper ([filecoin-project/lotus#9010](https://github.com/filecoin-project/lotus/pull/9010)) +- feat: shed: report the "user version" ([filecoin-project/lotus#8864](https://github.com/filecoin-project/lotus/pull/8864)) +- feat: lotus-shed get remote peer hello message ([filecoin-project/lotus#8787](https://github.com/filecoin-project/lotus/pull/8787)) +- feat: only enable rcmgr by default in full nodes ([filecoin-project/lotus#8769](https://github.com/filecoin-project/lotus/pull/8769)) +- feat: refactor: actor bundling system (#8838) ([filecoin-project/lotus#8838](https://github.com/filecoin-project/lotus/pull/8838)) +- feat: migration: Implement function to migrate actors with only code changes ([filecoin-project/lotus#8843](https://github.com/filecoin-project/lotus/pull/8843)) +- feat: conformance & tvx: support ReportConsensusFault messages ([filecoin-project/lotus#8302](https://github.com/filecoin-project/lotus/pull/8302)) +- feat: wdpost: Envvar for limiting recovering sectors ([filecoin-project/lotus#9106](https://github.com/filecoin-project/lotus/pull/9106)) +- fix: post: restrict recoveries per deadline ([filecoin-project/lotus#9111](https://github.com/filecoin-project/lotus/pull/9111)) +- ux: print absolute time for proving period start in proving cli ([filecoin-project/lotus#8954](https://github.com/filecoin-project/lotus/pull/8954)) +- chore: storage refactors part 1 ([filecoin-project/lotus#8858](https://github.com/filecoin-project/lotus/pull/8858)) +- chore: sealing pipeline: Remove adapter code (storage refactors part 2) ([filecoin-project/lotus#8871](https://github.com/filecoin-project/lotus/pull/8871)) +- refactor: remove old BlockSyncProtocolID ([filecoin-project/lotus#8820](https://github.com/filecoin-project/lotus/pull/8820)) + +## Bug Fixes + +- fix: format error log ([filecoin-project/lotus#8854](https://github.com/filecoin-project/lotus/pull/8854)) +- fix: build: really make macos compatible (#8853) ([filecoin-project/lotus#8853](https://github.com/filecoin-project/lotus/pull/8853)) +- fix: build: fix pack script and add calibrationnet to bundle ([filecoin-project/lotus#8852](https://github.com/filecoin-project/lotus/pull/8852)) +- fix: build: fix 2k build params ([filecoin-project/lotus#8835](https://github.com/filecoin-project/lotus/pull/8835)) +- Fix: PaychGetRestartAfterAddFundsMsg may stuck in forever waiting ([filecoin-project/lotus#8829](https://github.com/filecoin-project/lotus/pull/8829)) +- fix: paych: Print "waiting for confirmation.." ([filecoin-project/lotus#8823](https://github.com/filecoin-project/lotus/pull/8823)) +- fix: build: genesis miner network version ([filecoin-project/lotus#8756](https://github.com/filecoin-project/lotus/pull/8756)) +- fix: bench: consistency in description ([filecoin-project/lotus#8777](https://github.com/filecoin-project/lotus/pull/8777)) +- fix: worker: don't log normal storage stat calls ([filecoin-project/lotus#8744](https://github.com/filecoin-project/lotus/pull/8744)) +- fix: worker: don't check params with --no-default when not needed ([filecoin-project/lotus#8741](https://github.com/filecoin-project/lotus/pull/8741)) +- fix: Deduplicate parallel stat requests ([filecoin-project/lotus#8589](https://github.com/filecoin-project/lotus/pull/8589)) +- fix: Delegate storage auth on market nodes ([filecoin-project/lotus#8978](https://github.com/filecoin-project/lotus/pull/8978)) +- fix: post workers: check proving params on startup ([filecoin-project/lotus#8736](https://github.com/filecoin-project/lotus/pull/8736)) +- fix: rpc: readd rpc.discover aliases; lotus-gateway openrpc ([filecoin-project/lotus#8738](https://github.com/filecoin-project/lotus/pull/8738)) +- fix: change 1475 bootstrap peer ([filecoin-project/lotus#9008](https://github.com/filecoin-project/lotus/pull/9008)) +- fix: verifreg: update deprecation log ([filecoin-project/lotus#8690](https://github.com/filecoin-project/lotus/pull/8690)) +- fix: vm: support raw blocks in chain export ([filecoin-project/lotus#8691](https://github.com/filecoin-project/lotus/pull/8691)) +- fix: deps: restore butterfly network genesis from v1.14.4 ([filecoin-project/lotus#8708](https://github.com/filecoin-project/lotus/pull/8708)) +- fix: improve error message when maxPrice is too low ([filecoin-project/lotus#8818](https://github.com/filecoin-project/lotus/pull/8818)) +- fix: msig cli: Check for existing signers in add-propose ([filecoin-project/lotus#8833](https://github.com/filecoin-project/lotus/pull/8833)) +- Add new proofs version type ([filecoin-project/lotus#8848](https://github.com/filecoin-project/lotus/pull/8848)) +- fix: Make cli deal command get Block Delay specific to build ([filecoin-project/lotus#8896](https://github.com/filecoin-project/lotus/pull/8896)) +- fix: cli: Break out of retrieval if provider cancels ([filecoin-project/lotus#8912](https://github.com/filecoin-project/lotus/pull/8912)) +- fix: appimage build ([filecoin-project/lotus#8931](https://github.com/filecoin-project/lotus/pull/8931)) +- fix: incorrect usage of peer.IDFromString (should be peer.Decode) ([filecoin-project/lotus#8993](https://github.com/filecoin-project/lotus/pull/8993)) +- fix: deps: update FFI to fix a slow memory leak ([filecoin-project/lotus#9044](https://github.com/filecoin-project/lotus/pull/9044)) +- fix: sealing: hacky sealing fix backport ([filecoin-project/lotus#9055](https://github.com/filecoin-project/lotus/pull/9055)) + +## Dependency Updates + +- github.com/filecoin-project/go-address (v0.0.6 -> v1.0.0) +- github.com/filecoin-project/go-fil-markets (v1.20.1 -> v1.23.1) +- github.com/filecoin-project/go-indexer-core (v0.2.8 -> v0.2.9) +- github.com/filecoin-project/go-data-transfer (v1.15.1 -> v1.15.2) +- github.com/filecoin-project/go-legs (v0.3.7 -> v0.4.4) +- github.com/filecoin-project/go-state-types (v0.1.8 -> v0.1.10) +- github.com/filecoin-project/index-provider (v0.5.0 -> v0.8.1) +- github.com/filecoin-project/specs-actors (v0.9.14 -> v0.9.15) +- github.com/filecoin-project/specs-actors/v3 (v3.1.1 -> v3.1.2) +- github.com/filecoin-project/specs-actors/v4 (v4.0.1 -> v4.0.2) +- github.com/filecoin-project/specs-actors/v5 (v5.0.4 -> v5.0.6) +- github.com/filecoin-project/specs-actors/v6 (v6.0.1 -> v6.0.2) +- github.com/filecoin-project/specs-actors/v7 (v7.0.0 -> v7.0.1) +- github.com/filecoin-project/specs-actors/v8 (null -> v8.0.1) +- github.com/filecoin-project/specs-storage (v0.2.4 -> v0.4.1) +- github.com/filecoin-project/storetheindex (v0.3.5 -> v0.4.17) +- deps: libp2p: update to the latest golibp2p tag ([filecoin-project/lotus#8704](https://github.com/filecoin-project/lotus/pull/8704)) +- chore: update and fix libp2p ([filecoin-project/lotus#8996](https://github.com/filecoin-project/lotus/pull/8996)) + +## Others + +- chore: fix imports conflict ([filecoin-project/lotus#8863](https://github.com/filecoin-project/lotus/pull/8863)) +- chore: Fix imports ([filecoin-project/lotus#8859](https://github.com/filecoin-project/lotus/pull/8859)) +- chore: backport: release v1.16.0 back to master ([filecoin-project/lotus#8855](https://github.com/filecoin-project/lotus/pull/8855)) +- chore: bundle: remove wrongly committed bundle cars ([filecoin-project/lotus#8762](https://github.com/filecoin-project/lotus/pull/8762)) +- docs:sealing:fix default miner config comments ([filecoin-project/lotus#8689](https://github.com/filecoin-project/lotus/pull/8689)) +- ci: deps: Use testground-github-action from testground org ([filecoin-project/lotus#8490](https://github.com/filecoin-project/lotus/pull/8490)) + +Contributors + +| Contributor | Commits | Lines ± | Files Changed | +|-------------|---------|---------|---------------| +| Masih H. Derkani | 153 | +15515/-16832 | 660 | +| Łukasz Magiera | 92 | +10429/-8024 | 1580 | +| Andrew Gillis | 43 | +4149/-1765 | 208 | +| Jennifer Wang | 10 | +1441/-1138 | 34 | +| Geoff Stuart | 18 | +1348/-859 | 113 | +| dirkmc | 11 | +1827/-210 | 70 | +| Aayush | 21 | +1134/-894 | 69 | +| Steven Allen | 9 | +743/-889 | 66 | +| Marco Munizaga | 15 | +990/-252 | 36 | +| gammazero | 47 | +681/-411 | 104 | +| Will | 4 | +514/-246 | 29 | +| web3-bot | 15 | +409/-348 | 20 | +| Steven Fraser | 1 | +671/-0 | 36 | +| Cory Schwartz | 27 | +520/-89 | 36 | +| Hannah Howard | 3 | +318/-105 | 8 | +| Piotr Galar | 2 | +337/-59 | 7 | +| swift-mx | 14 | +264/-131 | 17 | +| vyzo | 7 | +357/-15 | 16 | +| Petar Maymounkov | 6 | +221/-23 | 14 | +| LexLuthr | 7 | +182/-21 | 14 | +| Aayush Rajasekaran | 5 | +97/-70 | 33 | +| Raúl Kripalani | 5 | +87/-45 | 7 | +| unknown | 1 | +114/-0 | 8 | +| sti-bot | 44 | +54/-60 | 44 | +| Aarsh Shah | 2 | +61/-50 | 8 | +| Lucas Molas | 1 | +74/-27 | 3 | +| zenground0 | 8 | +80/-18 | 14 | +| Dirk McCormick | 3 | +52/-33 | 8 | +| frank | 3 | +73/-7 | 3 | +| Will Scott | 4 | +45/-11 | 5 | +| kaola526 | 5 | +44/-11 | 5 | +| dependabot[bot] | 3 | +16/-10 | 8 | +| zl | 1 | +15/-4 | 4 | +| Phi | 5 | +12/-6 | 6 | +| Marcin Rataj | 1 | +11/-7 | 1 | +| github-actions[bot] | 7 | +8/-8 | 7 | +| Anton Evangelatov | 2 | +13/-0 | 4 | +| Nicolas Gimenez | 1 | +12/-0 | 1 | +| Marten Seemann | 2 | +5/-7 | 5 | +| Chris Harden | 1 | +10/-0 | 2 | +| jennijuju | 1 | +4/-4 | 7 | +| Travis Person | 2 | +2/-6 | 2 | +| Rod Vagg | 1 | +3/-3 | 2 | +| Rob Quist | 1 | +3/-3 | 1 | +| Jiaying Wang | 1 | +2/-3 | 2 | +| zengroundumbass | 1 | +3/-1 | 1 | +| lifei | 1 | +1/-1 | 1 | +| Mike | 1 | +2/-0 | 1 | +| Hubert | 1 | +1/-1 | 1 | +| Daniel N | 1 | +1/-1 | 1 | +| BMZ | 1 | +1/-1 | 1 | + +# 1.16.1 / 2022-07-07 + +This is an OPTIONAL PATCH releases for storage providers who have failed to publish `SubmitWindowedPoSt` due to out of gas error. The error log looks like `/wdpost_run.go:xxx estimating gas {"error": "estimating gas used: message execution failed: exit SysErrOutOfGas(7)...`. + +## New Features + +- feat: declare fault recovery: Config for maximum partition count per message (#8988 / #8986) + - configure `MaxPartitionsPerRecoveryMessage` in miner configuration setting. +- feat: wdpost: Config for maximum partition count per message (#8982 / #8986) + - configure `MaxPartitionsPerPoStMessage` in miner configuration setting. + +# 1.16.0 / 2022-06-24 + +This is a MANDATORY release of Lotus that introduces [Filecoin network v16, +codenamed the Skyr upgrade](https://github.com/filecoin-project/community/discussions/74?sort=new#discussioncomment-2392151). + +The network is scheduled to upgrade to nv16 at epoch 1960320, on July 6th at 2022-07-06T14:00:00Z. All node operators, including storage providers, must upgrade to this release (or a later release) before that time. Storage providers must update their daemons, miners, market and worker(s). + +Your lotus node will switch from the Legacy VM (that depended on go-based specs-actors) to Filecoin Virtual Machine FVM (that uses Rust-based builtin-actors) atomically upon the upgrade. + +The Skyr upgrade introduces the following FIPs, enhancements and bug fixes, delivered in [built-actors v8](https://github.com/filecoin-project/builtin-actors/releases/tag/v8.0.0) and [ref-fvm v1.0.0](https://github.com/filecoin-project/ref-fvm/releases/tag/fvm%40v1.0.0):, + +- [FIP-0030](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0030.md) +- [FIP-0031](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0031.md) +- [FIP-0032](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0032.md)) +- [FIP-0027](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0027.md) +- [Bug fix for the ProveReplicaUpdates method](https://github.com/filecoin-project/builtin-actors/pull/138) +- [New proofs version for SnarkPack](https://github.com/filecoin-project/builtin-actors/pull/474/commits/3027c365f516e1cba6f156d4fb9dbd8c893d5b62) + + +## 🆕 Things you may wanna know + +### Actor Code CIDs + +As stated in [FIP-0031](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0031.md)- [structure of the code cid](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0031.md#structure-of-code-cids), system actors' code CIDs will be real content-addressing + +For lotus users, we are making the change minimal for you. This means the `CODE` output when you run `lotus state get-actor` will now be the actual CID that represents the executable code for the actor, followed by wrapped synthetic id like the ones you've got before, i.e `fil/8/system`. + +Moreover, this also means that in the future, whenever the actor code changes, the CID changes accordingly, and a network upgrade is needed for the network participants to have consensus over what executable code we should use for each system actor. + +### Built-in actor bundles + +As the network introduces FVM, it's also switching from spec-actor (written in GoLang) to [built-in actor](https://github.com/filecoin-project/builtin-actors) (written in rust), in which the latter comes with[ importable bundles](https://github.com/filecoin-project/builtin-actors#importable-bundle). This means, like filecoin proof parameters, node operators now also need to fetch the actor bundles according to the network versions for the nodes to remain operational. + +Bundles for all networks(mainnet, calibnet, and etc) are included in the lotus source tree (`build/actors/`) and embedded on build. Lotus verifies that the bundle CIDs are the right ones upon build & upgrade against the values in `build/builtin_actors_gen.go`, according to the network you are building. You may also check the bundle manifest CID matches the bundle gen-ed values by running `lotus state actor-cids --network-version 16`. + +The manifest CID & full list of actor code CIDs for nv16 using v8.0.0 is: + +``` + "_manifest": "bafy2bzacebogjbpiemi7npzxchgcjjki3tfxon4ims55obfyfleqntteljsea" + "account": "bafk2bzacedudbf7fc5va57t3tmo63snmt3en4iaidv4vo3qlyacbxaa6hlx6y" + "cron": "bafk2bzacecqb3eolfurehny6yp7tgmapib4ocazo5ilkopjce2c7wc2bcec62" + "init": "bafk2bzaceaipvjhoxmtofsnv3aj6gj5ida4afdrxa4ewku2hfipdlxpaektlw" + "multisig": "bafk2bzacebhldfjuy4o5v7amrhp5p2gzv2qo5275jut4adnbyp56fxkwy5fag" + "paymentchannel": "bafk2bzacebalad3f72wyk7qyilvfjijcwubdspytnyzlrhvn73254gqis44rq" + "reward": "bafk2bzacecwzzxlgjiavnc3545cqqil3cmq4hgpvfp2crguxy2pl5ybusfsbe" + "storagemarket": "bafk2bzacediohrxkp2fbsl4yj4jlupjdkgsiwqb4zuezvinhdo2j5hrxco62q" + "storageminer": "bafk2bzacecgnynvd3tene3bvqoknuspit56canij5bpra6wl4mrq2mxxwriyu" + "storagepower": "bafk2bzacebjvqva6ppvysn5xpmiqcdfelwbbcxmghx5ww6hr37cgred6dyrpm" + "system": "bafk2bzacedwq5uppsw7vp55zpj7jdieizirmldceehu6wvombw3ixq2tcq57w" + "verifiedregistry": "bafk2bzaceb3zbkjz3auizmoln2unmxep7dyfcmsre64vnqfhdyh7rkqfoxlw4" +``` + +All bundles are also available at https://github.com/filecoin-project/builtin-actors/releases, thus you can also manually download the bundles and place them in the right path. + +Note: use customized bundle will risk you to lose sync with the network! + +To get Code CIDs: +- api:`StateActorCodeCIDs` +- cli: `lotus state actor-cids` +- cli: `lotus-shed cid inspect-bundle` + +### Execution Trace + +For developers that are dependent on lotus execution trace, you will need to enable `LOTUS_VM_ENABLE_TRACING` envvar to get the exact execution trace response as before. Without the envvar enabled, `Subcall` details, Duration` and `GasCharges` fields will be missing from the new FVM trace. + +### Deal Proposal Migration + +All deal proposals with non-utf8 string as the label in the metadata store will perform a light migration to new format as defined in [v1.1.1](https://github.com/filecoin-project/go-fil-markets/pull/721). + +## Others +- Resource manager is now only enabled by default on full daemon node. You can enable it manually for other nodes by setting env var `LOTUS_RCMGR` to `1`. +- Fix: drand: calculation of round from Filecoin epochs ([filecoin-project/lotus#8606](https://github.com/filecoin-project/lotus/pull/8606)) + +## Dependency Update +- chore: deps: update to go-libp2p v0.19.4 ([filecoin-project/lotus#8801](https://github.com/filecoin-project/lotus/pull/8801)) +- github.com/filecoin-project/go-fil-markets (v1.20.1 -> v1.20.1-v16-2): +- github.com/filecoin-project/go-legs (v0.3.7 -> v0.3.10): +- github.com/filecoin-project/go-state-types (v0.1.8 -> v0.1.10): +- github.com/filecoin-project/specs-storage (v0.2.4 -> v0.4.1): + +## Contributors + +| Contributor | Commits | Lines ± | Files Changed | +|-------------|---------|---------|---------------| +| @geoff-vball | 37 | +10565/-8454 | 150 | +| @arajasek | 33 | +7964/-6340 | 473 | +| @arajasek | 37 | +6220/-6976 | 457 | +| @vyzo | 135 | +7287/-5380 | 546 | +| @ZenGround0 | 19 | +5958/-2920 | 226 | +| @stebalien | 18 | +1566/-1101 | 116 | +| Alex | 5 | +323/-2304 | 32 | +| @zenground0 | 9 | +583/-358 | 56 | +| @jennijuju | 5 | +853/-27 | 19 | +| @jennijuju | 24 | +392/-201 | 60 | +| Marco Munizaga | 3 | +236/-83 | 5 | +| @raulk| 9 | +93/-15 | 15 | +| @travisperson | 3 | +37/-37 | 12 | +| @Kubuxu | 1 | +41/-5 | 1 | +| @koalacxr | 1 | +29/-13 | 3 | +| @gammazero | 2 | +18/-10 | 4 | +| Peter Rabbitson | 1 | +5/-3 | 1 | +| Steve Loeppky | 1 | +6/-0 | 1 | +| @masih | 1 | +3/-3 | 2 | +| @magik6k | 1 | +4/-0 | 1 | +| @jennijuju | 1 | +2/-2 | 1 | +| tian zhou | 1 | +1/-1 | 1 | + +# 1.15.3 / 2022-05-31 + +This is an optional release of lotus that include new APIs, some improvements and bug fixes. + +## New Features +- feat: api: add StateGetNetworkParams api ([filecoin-project/lotus#8546](https://github.com/filecoin-project/lotus/pull/8546)) +- feat: api: Implement StateLookupRobustAddress ([filecoin-project/lotus#8486](https://github.com/filecoin-project/lotus/pull/8486)) +- sealing: DataCid on workers ([filecoin-project/lotus#8557](https://github.com/filecoin-project/lotus/pull/8557)) +- feat: FVM: Support exectraces ([filecoin-project/lotus#8514](https://github.com/filecoin-project/lotus/pull/8514)) + +## Improvements +- ux: wallet: update delete usage ([filecoin-project/lotus#8442](https://github.com/filecoin-project/lotus/pull/8442)) +- chore: config: default-disable kvlog ([filecoin-project/lotus#8477](https://github.com/filecoin-project/lotus/pull/8477)) +- chore: cli: Alias cli commands for uniformity ([filecoin-project/lotus#8587](https://github.com/filecoin-project/lotus/pull/8587)) + +## Bug Fixes +- fix: Make deal making logs much less noisy ([filecoin-project/lotus#8622](https://github.com/filecoin-project/lotus/pull/8622)) +- fix: mpool: avoid deadlock on unsubscribe ([filecoin-project/lotus#8635](https://github.com/filecoin-project/lotus/pull/8635)) +- fix: update StatApplied when fvm apply message ([filecoin-project/lotus#8545](https://github.com/filecoin-project/lotus/pull/8545)) +- fix: ci: build macos and linux assets on tagged releases ([filecoin-project/lotus#8597](https://github.com/filecoin-project/lotus/pull/8597)) +- fix: Clean up vanilla proof fetching errors for proper display ([filecoin-project/lotus#8564](https://github.com/filecoin-project/lotus/pull/8564)) +- fix: Make markets logger less noisy when doing retrievals ([filecoin-project/lotus#8604](https://github.com/filecoin-project/lotus/pull/8604)) +- fix: test: Fix flaky TestGatewayWalletMsig ([filecoin-project/lotus#8601](https://github.com/filecoin-project/lotus/pull/8601)) +- fix: lotus-wallet: correct network in version ([filecoin-project/lotus#8563](https://github.com/filecoin-project/lotus/pull/8563)) +- fix: worker: Download proofs if PRU2 is enabled ([filecoin-project/lotus#8555](https://github.com/filecoin-project/lotus/pull/8555)) +- fix: ux: update `lotus-wallet run` description ([filecoin-project/lotus#8528](https://github.com/filecoin-project/lotus/pull/8528)) +- fix: market: Infer index provider topic from network name by default ([filecoin-project/lotus#8526](https://github.com/filecoin-project/lotus/pull/8526)) +- fix: storiface: Make FSOverhead numbers more accurate ([filecoin-project/lotus#8481](https://github.com/filecoin-project/lotus/pull/8481)) +- fix: node: fix comment for IndexProvider ([filecoin-project/lotus#8480](https://github.com/filecoin-project/lotus/pull/8480)) +- fix: sealing: fail to add expired precommits to a batch ([filecoin-project/lotus#8479](https://github.com/filecoin-project/lotus/pull/8479)) +- fix:sealing:check index out of bounds against correct param length not return length ([filecoin-project/lotus#8475](https://github.com/filecoin-project/lotus/pull/8475)) +- fix: sealing: Finalize snap sectors before submitting proofs ([filecoin-project/lotus#8582](https://github.com/filecoin-project/lotus/pull/8582)) +- chore: fix lint issue ([filecoin-project/lotus#8531](https://github.com/filecoin-project/lotus/pull/8531)) +- feat: vm: add actor error backtraces to FVM ([filecoin-project/lotus#8524](https://github.com/filecoin-project/lotus/pull/8524)) + +## Dependency Updates +- github.com/filecoin-project/specs-storage (v0.2.2 -> v0.2.3-0.20220426183226-1a0a63c5990f): +- deps: update go-libp2p@v0.19 ([filecoin-project/lotus#8511](https://github.com/filecoin-project/lotus/pull/8511)) +- chore: deps: Use tagged version of specs-storage #8764 + +## Others +- chore: merge releases back into master ([filecoin-project/lotus#8638](https://github.com/filecoin-project/lotus/pull/8638)) +- chore:ci:make codecov quiet ([filecoin-project/lotus#8600](https://github.com/filecoin-project/lotus/pull/8600)) +- chore: version: bump the version to v1.15.3-dev ([filecoin-project/lotus#8473](https://github.com/filecoin-project/lotus/pull/8473)) +- chore: .gitignore: ignore built in actor bundles ([filecoin-project/lotus#8590](https://github.com/filecoin-project/lotus/pull/8590)) +- ci: deps: macos build deps ([filecoin-project/lotus#8581](https://github.com/filecoin-project/lotus/pull/8581)) +- chore:chore:chore remove the wrong TODO ([filecoin-project/lotus#8586](https://github.com/filecoin-project/lotus/pull/8586)) +- add 1475 bootstrapper ([filecoin-project/lotus#8495](https://github.com/filecoin-project/lotus/pull/8495)) +- Bump version for xtools ([filecoin-project/lotus#8494](https://github.com/filecoin-project/lotus/pull/8494)) +- chore: bundle: remove wrongly committed bundle cars #8763 +## Contributors + +| Contributor | Commits | Lines ± | Files Changed | +|-------------|---------|---------|---------------| +| @stebalien | 4 | +607/-95 | 19 | +| @magik6k | 9 | +550/-37 | 43 | +| @geoff-vball | 5 | +279/-219 | 27 | +| @simlecode | 1 | +306/-39 | 20 | +| @arajasek | 1 | +256/-34 | 10 | +| @zenground0 | 11 | +214/-66 | 31 | +| @arajasek | 2 | +149/-99 | 8 | +| @vyzo | 3 | +125/-81 | 4 | +| @Masih| 1 | +134/-15 | 7 | +| @travisperson | 3 | +24/-32 | 6 | +| @Rjan | 6 | +16/-16 | 9 | +| @jennijuju | 3 | +9/-8 | 15 | +| Rob Quist | 3 | +12/-4 | 3 | +| @Icarus9913 | 1 | +3/-3 | 3 | +| @swift-mx | 1 | +3/-0 | 1 | +| @Phi-rjan | 1 | +1/-1 | 1 | +| @lifei | 1 | +1/-0 | 1 | + + +# 1.15.2 / 2022-05-06 + +This is a highly recommended feature lotus release v1.15.2. This feature release introduces many new features and for SPs, including PoSt workers, sealing scheduler, snap deal queue and so on. + +Note: You need to be using go v1.18.1&up from this release onwards. + +## Highlights +### ❣️❣️❣️ PoSt Workers ❣️❣️❣️ +‼️️Attention - the long-awaited yet highly requested PoSt workers, they are here! And they come in as a combo: you may setup PoSt workers for both winningPoSt or/and windowPoSt worker. You can also setup any number of PoSt workers as long as you have the hardware resources! +For more details and learn how to set it up, see the docs [here](https://lotus.filecoin.io/storage-providers/seal-workers/post-workers/). You can also find early result of the PoSt workers performance by the community [here](https://github.com/filecoin-project/lotus/discussions/8375). +- feat: PoSt workers ([filecoin-project/lotus#7971](https://github.com/filecoin-project/lotus/pull/7971)) +- feat: storage: Parallel proving checks ([filecoin-project/lotus#8391](https://github.com/filecoin-project/lotus/pull/8391)) + - adjust `ParallelCheckLimit` according to your resource setup to get pre-post checkers run faster! + +In addition, we also added some handy toolings: +- feat: miner: API/CLI to compute window-post ([filecoin-project/lotus#8389](https://github.com/filecoin-project/lotus/pull/8389)) + - run `lotus-miner proving compute window-post` to manually trigger a full windowPoSt computation for a specific deadline for full sanity checks. +- feat: miner cli: Separate proving workers command ([filecoin-project/lotus#8379](https://github.com/filecoin-project/lotus/pull/8379)) + - run `lotus-miner proving workers` to list all the PoSt workers that's attached +- feat: miner cli: proving check --faulty, faults in storage list sectors ([filecoin-project/lotus#8349](https://github.com/filecoin-project/lotus/pull/8349)) + - run `lotus-miner proving check --faulty` to identify the sectors that might be bad. + +### 🔥🔥🔥 Sealing Scheduler Enhancement 🔥🔥🔥 + +Have you ever got a couple workers but only a few of them are super packed, while the rest are idling waiting for jobs? Now the task can be distributed more evenly with: +- feat: sched: Improve worker assigning logic ([filecoin-project/lotus#8447](https://github.com/filecoin-project/lotus/pull/8447)) + +### 🌟🌟🌟 Snap Deal Enhancements 🌟🌟🌟 + +The Filecoin Network introduced Snap Deal with the network v15 OhSnap upgrade, and lotus shipped v1.14.0 with basic snappy support for SP to use this feature. Since then, we have received good ux feedbacks and bug reports from the community, and we are introducing a couple enhancement for SPs to better leverage this feature. + +- feat: sealing: Sector upgrade queue ([filecoin-project/lotus#8330](https://github.com/filecoin-project/lotus/pull/8330)) + - Snap up CCs to be ready for deals automatically, learn more [here](https://lotus.filecoin.io/storage-providers/operate/snap-deals/#snap-deal-queue) +- feat: sealing: More SnapDeals config knobs ([filecoin-project/lotus#8343](https://github.com/filecoin-project/lotus/pull/8343)) + - SPs can now set `PreferNewSectorsForDeals` and `MaxUpgradingSectors` to better manage their sealing/dealing pipeline. +- feat: config: Move MakeNewSectorForDeals config into the Sealing sectoin ([filecoin-project/lotus#8378](https://github.com/filecoin-project/lotus/pull/8378)) +- fix: sealing: Release unsealed sector files after snapdeals abort ([filecoin-project/lotus#8438](https://github.com/filecoin-project/lotus/pull/8438)) +- fix: sealing: always do cooldown in handleSubmitReplicaUpdateFailed ([filecoin-project/lotus#8353](https://github.com/filecoin-project/lotus/pull/8353)) +- fix: storagefsm: Fix error loop on bad event ([filecoin-project/lotus#8338](https://github.com/filecoin-project/lotus/pull/8338)) +- fix: sealing: Remove sector copies from workers after snapdeals ([filecoin-project/lotus#8329](https://github.com/filecoin-project/lotus/pull/8329)) + +## New Features +- enable rcmgr by default ([filecoin-project/lotus#8470](https://github.com/filecoin-project/lotus/pull/8470)) +- feat: cli: lotus client list-asks --protocols ([filecoin-project/lotus#8464](https://github.com/filecoin-project/lotus/pull/8464)) +- feat: shed: Multi-file vlog2car ([filecoin-project/lotus#8426](https://github.com/filecoin-project/lotus/pull/8426)) +- feat: worker: check fd limit on startup ([filecoin-project/lotus#8390](https://github.com/filecoin-project/lotus/pull/8390)) +- feat: lotus-shed: add command to compute state over a range of tipsets. ([filecoin-project/lotus#8371](https://github.com/filecoin-project/lotus/pull/8371)) +- feat: cli/net: implement 'net ping' command ([filecoin-project/lotus#8357](https://github.com/filecoin-project/lotus/pull/8357)) +- feat: stmgr: call: use a buffered concurrent-access blockstore ([filecoin-project/lotus#8358](https://github.com/filecoin-project/lotus/pull/8358)) +- feat: infra/ci: add `lotus-test` image as CI build step ([filecoin-project/lotus#7956](https://github.com/filecoin-project/lotus/pull/7956)) +- feat: multisig: lotus-sheed miner-multisig change-worker command. ([filecoin-project/lotus#8281](https://github.com/filecoin-project/lotus/pull/8281)) +- feat: Add additional test annotations (#8272) ([filecoin-project/lotus#8272](https://github.com/filecoin-project/lotus/pull/8272)) + +## Improvements +- Revert appimage removal ([filecoin-project/lotus#8439](https://github.com/filecoin-project/lotus/pull/8439)) +- sealing: Don't panic in ReleaseUnsealed with no ranges ([filecoin-project/lotus#8461](https://github.com/filecoin-project/lotus/pull/8461)) +- testkit: give up on waiting for the RPC server to shutdown after 1s ([filecoin-project/lotus#8450](https://github.com/filecoin-project/lotus/pull/8450)) +- chore: events: implement event observer deregister method ([filecoin-project/lotus#8441](https://github.com/filecoin-project/lotus/pull/8441)) +- Thread safe piecereader ([filecoin-project/lotus#8397](https://github.com/filecoin-project/lotus/pull/8397)) +- VM: Refactor pricelist to be based on network versions ([filecoin-project/lotus#8369](https://github.com/filecoin-project/lotus/pull/8369)) +- --max-piece-size in set-ask is no longer required ([filecoin-project/lotus#8361](https://github.com/filecoin-project/lotus/pull/8361)) +- refactor: convert RepoType from int to interface ([filecoin-project/lotus#8086](https://github.com/filecoin-project/lotus/pull/8086)) +- Shed: fix error message ([filecoin-project/lotus#8340](https://github.com/filecoin-project/lotus/pull/8340)) + +## Bug Fixes +- fix: FVM: add finality check for consensus faults ([filecoin-project/lotus#8452](https://github.com/filecoin-project/lotus/pull/8452)) +- fix: market: Reuse the market `PubSub` in index provider ([filecoin-project/lotus#8443](https://github.com/filecoin-project/lotus/pull/8443)) +- fix: market: set all index provider options based on lotus config ([filecoin-project/lotus#8444](https://github.com/filecoin-project/lotus/pull/8444)) +- release worker tracker lock when call cb func ([filecoin-project/lotus#8206](https://github.com/filecoin-project/lotus/pull/8206)) +- fix: node: Fix market node startup ([filecoin-project/lotus#8425](https://github.com/filecoin-project/lotus/pull/8425)) +- fix: sealing: Fix PR1 worker selection ([filecoin-project/lotus#8420](https://github.com/filecoin-project/lotus/pull/8420)) +- fix: go: make Go 1.18 builds work ([filecoin-project/lotus#8410](https://github.com/filecoin-project/lotus/pull/8410)) +- fix: sealing: Added error checking ([filecoin-project/lotus#8404](https://github.com/filecoin-project/lotus/pull/8404)) +- fix: ux: Change Propose-worker msg ([filecoin-project/lotus#8384](https://github.com/filecoin-project/lotus/pull/8384)) +- fix: miner: dead loop on removing sector ([filecoin-project/lotus#8386](https://github.com/filecoin-project/lotus/pull/8386)) +- fix: worker: Fix default value handling ([filecoin-project/lotus#8380](https://github.com/filecoin-project/lotus/pull/8380)) +- Revert "Update params for interopnet for fvm" ([filecoin-project/lotus#8374](https://github.com/filecoin-project/lotus/pull/8374)) +- fix: cli: Reset miner/ask lists in interactive deal 'miner' step (#8155) ([filecoin-project/lotus#8155](https://github.com/filecoin-project/lotus/pull/8155)) +- fix:snapup: Rename error message ([filecoin-project/lotus#8370](https://github.com/filecoin-project/lotus/pull/8370)) +- fix: multisig: Print "waiting for confirmation.." ([filecoin-project/lotus#8368](https://github.com/filecoin-project/lotus/pull/8368)) +- fix: lotus-wallet: pass correct repo type to repo.Init ([filecoin-project/lotus#8356](https://github.com/filecoin-project/lotus/pull/8356)) +- fix: avoid racy memstores when estimating gas ([filecoin-project/lotus#8351](https://github.com/filecoin-project/lotus/pull/8351)) +- fix: itests: Don't hang on exit in MineBlocksMustPost ([filecoin-project/lotus#8345](https://github.com/filecoin-project/lotus/pull/8345)) +- fix: miner cli: Estimate deal weight in sector list when upgrading ([filecoin-project/lotus#8336](https://github.com/filecoin-project/lotus/pull/8336)) +- fix: sealing: FinalizeSector doesn't need sealed replica access ([filecoin-project/lotus#8337](https://github.com/filecoin-project/lotus/pull/8337)) +- fix: cli: add ArgsUsage field to clientGetDealCmd ([filecoin-project/lotus#8241](https://github.com/filecoin-project/lotus/pull/8241)) +- fix: market: Infer index provider topic from network name by default #8533 +- fix: deps: Update to FFI with logger bump #8588 +- fix: sealing: Finalize snap sectors before submitting proofs #8588 + +## Dependency Updates +- deps: update go-libp2p@v0.19 #8533 +- deps: ffi: update ffi that includes the log fix #8577 +- deps: ffi: pull ffi that includes the latest fvm ([filecoin-project/lotus#8424](https://github.com/filecoin-project/lotus/pull/8424)) +- Update to go-log 2.5.1 ([filecoin-project/lotus#8422](https://github.com/filecoin-project/lotus/pull/8422)) +- chore(deps): update go-data-transfer with fixes (master edition) ([filecoin-project/lotus#8411](https://github.com/filecoin-project/lotus/pull/8411)) +- deps: update ffi with actor v7.1.0 and fvm that uses the bundle that includes the new manifest ([filecoin-project/lotus#8402](https://github.com/filecoin-project/lotus/pull/8402)) +- Update to specs-storage v0.2.2 ([filecoin-project/lotus#8400](https://github.com/filecoin-project/lotus/pull/8400)) +- chore: ffi: the latest fvm release ([filecoin-project/lotus#8381](https://github.com/filecoin-project/lotus/pull/8381)) +- Update params for interopnet for fvm ([filecoin-project/lotus#8119](https://github.com/filecoin-project/lotus/pull/8119)) +- github.com/filecoin-project/specs-storage (v0.2.0 -> v0.2.2): +- ci: deps: macos build deps #8588 + +## Others +- chore: merge releases back to master ([filecoin-project/lotus#8468](https://github.com/filecoin-project/lotus/pull/8468)) +- Packer publish copy orb ([filecoin-project/lotus#8413](https://github.com/filecoin-project/lotus/pull/8413)) +- chore: Remove temp gomock reflect file ([filecoin-project/lotus#8372](https://github.com/filecoin-project/lotus/pull/8372)) +- chore: FVM: log when fvm is used ([filecoin-project/lotus#8363](https://github.com/filecoin-project/lotus/pull/8363)) +- lib: extract unixfs filestore into lib ([filecoin-project/lotus#8354](https://github.com/filecoin-project/lotus/pull/8354)) +- test: use `T.TempDir` to create temporary test directory ([filecoin-project/lotus#8295](https://github.com/filecoin-project/lotus/pull/8295)) +- Update Dockerfile.lotus +- chore:sealing:remove endpoint from cli ([filecoin-project/lotus#8215](https://github.com/filecoin-project/lotus/pull/8215)) +- chore: build: bump the master version to v1.15.2-dev ([filecoin-project/lotus#8322](https://github.com/filecoin-project/lotus/pull/8322)) +- chore: fix lint issue #8533 + +## Contributors + +| Contributor | Commits | Lines ± | Files Changed | +|-------------|---------|---------|---------------| +| @magik6k | 95 | +5147/-2922 | 401 | +| @mz-sirius | 3 | +1789/-546 | 48 | +| @nonsense | 11 | +777/-567 | 121 | +| @arajasek | 11 | +336/-231 | 28 | +| Darko Brdareski | 1 | +463/-13 | 95 | +| @coryschwartz | 11 | +147/-217 | 13 | +| spark8899 | 2 | +300/-0 | 2 | +| @zenground0 | 2 | +6/-193 | 7 | +| Eng Zer Jun | 1 | +31/-158 | 11 | +| Kevin Li | 2 | +174/-0 | 14 | +| @arajasek | 5 | +85/-86 | 18 | +| @jennijuju | 1 | +0/-119 | 3 | +| @jennijuju | 1 | +0/-98 | 6 | +| @raulk | 1 | +60/-1 | 1 | +| @frrist | 1 | +56/-0 | 2 | +| @vyzo | 3 | +18/-16 | 5 | +| @Masih | 3 | +29/-4 | 3 | +| @jennijuju | 4 | +18/-11 | 11 | +| @hannahhoward | 1 | +13/-10 | 2 | +| @dirkmc | 1 | +21/-1 | 1 | +| koalacxr | 1 | +10/-11 | 4 | +| Aarsh Shah | 1 | +19/-1 | 1 | +| @Rjan | 6 | +10/-8 | 7 | +| @zl | 1 | +7/-1 | 1 | +| KAYUII | 1 | +3/-2 | 1 | +| @simlecode | 1 | +4/-0 | 1 | +| @dirkmc | 1 | +1/-3 | 1 | +| Jerry | 1 | +3/-0 | 1 | +| @steblian | 1 | +1/-1 | 1 | +| Geoff Stuart | 1 | +1/-0 | 1 | +| Florian Ruen | 1 | +0/-1 | 1 | + +# 1.15.1 / 2022-04-07 + +This is a *HIGHLY recommended* feature release v1.15.1, especially for node operators and storage providers who want to be a part of the content addressing network of Filecoin and IPFS. +This feature release introduces Index Provider, GraphSync v2, and many other latest functionalities, improvements and bug fixes. More importantly, node operator can now enable the FVM(experimental) to sync mainnet!! + +## Highlights + +### 🔥🔥🔥 FVM (Experimental) 🔥🔥🔥 +- feat: fvm: FVM integration ([filecoin-project/lotus#8332](https://github.com/filecoin-project/lotus/pull/8332)) + The lotus team is excited to announce the launch of experimental non-programmable FVM on mainnet. By enabling `"LOTUS_USE_FVM_EXPERIMENTAL=1` envvar, the lotus daemon will be running the [WASM-compiled built-in actors](https://github.com/filecoin-project/builtin-actors) that is compatible with the existing chain(Network v15 OhSnap). If you are trying it out and having any questions or feedbacks, please leave a comment [here](https://github.com/filecoin-project/lotus/discussions/8334)! + - chore: FVM: log when fvm is used([filecoin-project/lotus#8363](https://github.com/filecoin-project/lotus/pull/8363)) + - chore: ffi: the latest fvm release([filecoin-project/lotus#8382](https://github.com/filecoin-project/lotus/pull/8382)) + +### 🌟🌟🌟 Index Provider (Production Ready!) 🌟🌟🌟 +- feat: markets: Integrate index ingest protocol and retrieve by any CID ([filecoin-project/lotus#7313](https://github.com/filecoin-project/lotus/pull/7313)) + +More and more useful data is being stored on Filecoin via deals made by clients to Storage Providers. The goal is that this content is discoverable when people need them. To achieve that goal, one of the projects [the Bedrock team](https://www.notion.so/pl-strflt/Bedrock-2e956d5d8143432080a1d84435ccf0ff) is working on is building an Indexer Ecosystem, a project that's focus on content addressing on Filecoin, then potentially have interoperability with IPFS in the future and eventually serve the retrieval market. The Indexer Ecosystem high level architecture overview diagram can be found [here](https://github.com/filecoin-project/storetheindex/blob/main/doc/indexer_ecosys.png) and a detailed write up about can be found [here](https://www.notion.so/pl-strflt/Introducing-Indexer-to-SP-90bf296794174a8281c121d4ce6747a0). + +That being said, with this release, lotus Storage Providers can easily become an Index Provider and serve the Indexer Content Addressing System. Index Providers generate advertisements from the deals made by a storage provider and announces the data to the indexer nodes for further processing: +- To learn more about *what is an Index Provider and how to be an Index Provider*, read it [here](https://lotus.filecoin.io/storage-providers/operate/index-provider/) in lotus docuementation. +- An [one-off migration](https://lotus.filecoin.io/storage-providers/operate/index-provider/#first-time-migration) is needed in order for a Storage Provider to become an Index Provider and announce the proper formatted index. It's *highly recommended* for all Index Provider to do a [force bulk initialization](https://lotus.filecoin.io/storage-providers/configure/dagstore/#forcing-bulk-initialization) to enable index announcement on all existing deals. + - Note that the Initialization places IO workload on your storage system. SP should set a proper `concurrency` based on your hardware or can stop/start initialization at their wish/convenience as proving deadlines approach and elapse, to avoid IOPS starvation or competition with window PoSt. +- After the first one-time migration, being an Index Provider barely puts any extra usage on SP's market system. + - You can find the testing result by SPX fellows [here](https://github.com/filecoin-project/lotus/discussions/8087). + +We recommend all Storage Providers that are serving deals in the Filecoin network to become a Index Provider, make the data you are storing discoverable for the retrieval market and retrieval clients! +- If you have any questions about becoming an index provider, or the indexer system in general, leave a comment [here](https://github.com/filecoin-project/lotus/discussions/8341). +- Follow the indexer project at https://github.com/filecoin-project/go-indexer-core. +- If you have any feature request or bug reports of running an index provider, create an issue in https://github.com/filecoin-project/index-provider. +- You may also join the #storetheindex channel in the Filecoin Slack to engage with the team & the community! + +### ❗️❗️❗️ Dag Migration For New CAR index format in DagStore ❗️❗️❗️ +The index provider leverages the latest CARv2 indexing format `MultihashIndexSorted`, which stores the multihash code as well as the digest of all CIDs in a CAR file. Thus, all Storage Providers SHOULD perform an one-off DAG mirgation to regenerate DagStore CARv2 indices. You have to do it to become an index provider, failing to do so may also impact your future deal making. +Follow the instruction [here](https://lotus.filecoin.io/storage-providers/operate/index-provider/) to perform the migration. + +## New Features +- feat: sealing: Sector upgrade queue ([filecoin-project/lotus#8333](https://github.com/filecoin-project/lotus/pull/8333)) + - see more details in docs: [here](https://lotus.filecoin.io/storage-providers/operate/snap-deals/#snap-deal-queue) +- feat: market utils: Support unixfsnode in TraverseDag ([filecoin-project/lotus#8168](https://github.com/filecoin-project/lotus/pull/8168)) +- feat: config: enable indexer providing by default ([filecoin-project/lotus#8314](https://github.com/filecoin-project/lotus/pull/8314)) +- feat: api: Make ClientCalcCommP multithreaded ([filecoin-project/lotus#8276](https://github.com/filecoin-project/lotus/pull/8276)) +- feat: config: Persistent subsystem log level config ([filecoin-project/lotus#8283](https://github.com/filecoin-project/lotus/pull/8283)) +- feat: shed: blockstore/vlog to car export cmds ([filecoin-project/lotus#8265](https://github.com/filecoin-project/lotus/pull/8265)) +- feat: shed: ItestD ([filecoin-project/lotus#8290](https://github.com/filecoin-project/lotus/pull/8290)) +- feat: Make add piece idempotent ([filecoin-project/lotus#8160](https://github.com/filecoin-project/lotus/pull/8160)) +- feat: paychmgr: Support paych funding (a.k.a. fast paid retrieval) ([filecoin-project/lotus#7883](https://github.com/filecoin-project/lotus/pull/7883)) +- feat: ci: packer snap ([filecoin-project/lotus#7819](https://github.com/filecoin-project/lotus/pull/7819)) +- feat: #6147: Include worker name in sealing errors ([filecoin-project/lotus#7844](https://github.com/filecoin-project/lotus/pull/7844)) +- Feat: cli: Remove verified data cap ([filecoin-project/lotus#8175](https://github.com/filecoin-project/lotus/pull/8175)) +- feat: gateway: add MsigGetVestingSchedule to gateway api ([filecoin-project/lotus#8104](https://github.com/filecoin-project/lotus/pull/8104)) +- feat: itests: add itests ensemble mocknet getter ([filecoin-project/lotus#8157](https://github.com/filecoin-project/lotus/pull/8157)) +- feat: lotus-miner sectors list --initial-pledge ([filecoin-project/lotus#8098](https://github.com/filecoin-project/lotus/pull/8098)) +- Resource Manager Metrics ([filecoin-project/lotus#8089](https://github.com/filecoin-project/lotus/pull/8089)) +- feat: cli: set current network version from params ([filecoin-project/lotus#8111](https://github.com/filecoin-project/lotus/pull/8111)) +- feat: Snapdeals support in `storage find` CLI ([filecoin-project/lotus#8130](https://github.com/filecoin-project/lotus/pull/8130)) + +## Improvements +- improve resource manager integration ([filecoin-project/lotus#8318](https://github.com/filecoin-project/lotus/pull/8318)) +- add check manual-stateless-deal with interactive deal making ([filecoin-project/lotus#7560](https://github.com/filecoin-project/lotus/pull/7560)) +- test: cli: adding wallet tests ([filecoin-project/lotus#8079](https://github.com/filecoin-project/lotus/pull/8079)) +- test: chain: unit tests for the syncer & sync manager ([filecoin-project/lotus#8072](https://github.com/filecoin-project/lotus/pull/8072)) +- test: cli: unit tests for sync related commands ([filecoin-project/lotus#8080](https://github.com/filecoin-project/lotus/pull/8080)) +- misc: wallet: wallet tests with annotations for system test matrix ([filecoin-project/lotus#7928](https://github.com/filecoin-project/lotus/pull/7928)) +- test: Cli: add mempool tests ([filecoin-project/lotus#8162](https://github.com/filecoin-project/lotus/pull/8162)) +- add a state-tree diff command to lotus shed ([filecoin-project/lotus#8081](https://github.com/filecoin-project/lotus/pull/8081)) +- test: mempool: Add unit and integration tests ([filecoin-project/lotus#8017](https://github.com/filecoin-project/lotus/pull/8017)) +- splistore cold object reification redux ([filecoin-project/lotus#8029](https://github.com/filecoin-project/lotus/pull/8029)) +- test: cli: chain category unit tests ([filecoin-project/lotus#8048](https://github.com/filecoin-project/lotus/pull/8048)) +- feat: config: Move MakeNewSectorForDeals config into the Sealing section([filecoin-project/lotus#8382](https://github.com/filecoin-project/lotus/pull/8382)) + +## Bug Fixes +- fix: FVM: add finality check for consensus faults #8452 +- fix: market: Reuse the market PubSub in index provider #8451 +- fix: market: set all index provider options based on lotus config #8444 +- fix: sealing: Fix PR1 worker selection (#8421) +- fix: miner: dead loop on removing sector (#8421) +- fix: sealing: Remove sector copies from workers after snapdeals ([filecoin-project/lotus#8331](https://github.com/filecoin-project/lotus/pull/8331)) +- fix: storagefsm: Fix error loop on bad event ([filecoin-project/lotus#8339](https://github.com/filecoin-project/lotus/pull/8339)) +- fix: sealing: FinalizeSector doesn't need sealed replica access ([filecoin-project/lotus#8339](https://github.com/filecoin-project/lotus/pull/8339)) +- fix: sealing: always do cooldown in handleSubmitReplicaUpdateFailed ([filecoin-project/lotus#8353](https://github.com/filecoin-project/lotus/pull/8353)) +- fix: storage cli: Output primary sector status correctly ([filecoin-project/lotus#8320](https://github.com/filecoin-project/lotus/pull/8320)) +- fix: sealing fsm: Handle inputLk correctly ([filecoin-project/lotus#8291](https://github.com/filecoin-project/lotus/pull/8291)) +- fix: piece provider: Don't log CIDs as binary ([filecoin-project/lotus#8287](https://github.com/filecoin-project/lotus/pull/8287)) +- fix:sealing:Log instead of error normal shutdown of state machine ([filecoin-project/lotus#8232](https://github.com/filecoin-project/lotus/pull/8232)) +- fix:sealing:Handle finalize replica update failures in fsm ([filecoin-project/lotus#8229](https://github.com/filecoin-project/lotus/pull/8229)) +- ci: appimage: re-install appimage CI ([filecoin-project/lotus#7943](https://github.com/filecoin-project/lotus/pull/7943)) +- fix: sealing: PRU insufficient collateral ([filecoin-project/lotus#8219](https://github.com/filecoin-project/lotus/pull/8219)) +- fix: shed: diff command ([filecoin-project/lotus#8202](https://github.com/filecoin-project/lotus/pull/8202)) +- Make `--lite` option visible in the lotus daemon help text ([filecoin-project/lotus#8207](https://github.com/filecoin-project/lotus/pull/8207)) +- fix:sealing:Less verbose sector manager logging ([filecoin-project/lotus#8213](https://github.com/filecoin-project/lotus/pull/8213)) +- avoid panic ([filecoin-project/lotus#8205](https://github.com/filecoin-project/lotus/pull/8205)) +- A package is vulnerable to Exposure of Sensitive Information ([filecoin-project/lotus#8204](https://github.com/filecoin-project/lotus/pull/8204)) +- fix: sealing: add flag usage ([filecoin-project/lotus#8190](https://github.com/filecoin-project/lotus/pull/8190)) +- Fix the epoch used for gas in the message pool & validation ([filecoin-project/lotus#8163](https://github.com/filecoin-project/lotus/pull/8163)) +- fix:sealing:really-do-it flag for abort upgrade ([filecoin-project/lotus#8181](https://github.com/filecoin-project/lotus/pull/8181)) +- fix:proving:post check sector handles snap deals replica faults ([filecoin-project/lotus#8177](https://github.com/filecoin-project/lotus/pull/8177)) +- fix: client: calculate commps for pieces bigger than 32GB ([filecoin-project/lotus#8179](https://github.com/filecoin-project/lotus/pull/8179)) +- fix:cli:Continue instead of return error if no valid value is filled ([filecoin-project/lotus#8131](https://github.com/filecoin-project/lotus/pull/8131)) +- fix: limit reification sizes ([filecoin-project/lotus#8149](https://github.com/filecoin-project/lotus/pull/8149)) +- fix: state: Allow lotus-miner info to complete without admin permission ([filecoin-project/lotus#8057](https://github.com/filecoin-project/lotus/pull/8057)) +- fix:paychan:deflake integration test ([filecoin-project/lotus#8088](https://github.com/filecoin-project/lotus/pull/8088)) +- fix: worker: allow enable/disabling ReplicaUpdate tasks ([filecoin-project/lotus#8090](https://github.com/filecoin-project/lotus/pull/8090)) +- don't fail reification on missing references ([filecoin-project/lotus#8128](https://github.com/filecoin-project/lotus/pull/8128)) +- sealer: fix error message ([filecoin-project/lotus#8121](https://github.com/filecoin-project/lotus/pull/8121)) +- don't track peer ids in rcmgr metrics ([filecoin-project/lotus#8099](https://github.com/filecoin-project/lotus/pull/8099)) +- temporarily disable reification ([filecoin-project/lotus#8132](https://github.com/filecoin-project/lotus/pull/8132)) +- [Describe]: when excute cmd "lotus-bench sealing" without "benchmark-… ([filecoin-project/lotus#8173](https://github.com/filecoin-project/lotus/pull/8173)) + +## Dependency Updates +- deps: update go-libp2p and go-libp2p-resource-manager ([filecoin-project/lotus#8289](https://github.com/filecoin-project/lotus/pull/8289)) +- feat(deps): update to graphsync v0.13.0 with 2.0 protocol ([filecoin-project/lotus#8273](https://github.com/filecoin-project/lotus/pull/8273)) +- dep: actor: get v7 ([filecoin-project/lotus#8194](https://github.com/filecoin-project/lotus/pull/8194)) +- deps: update go-libp2p to v0.18 release ([filecoin-project/lotus#8355](https://github.com/filecoin-project/lotus/pull/8355)) +- github.com/filecoin-project/go-data-transfer (v1.14.1 -> v1.15.0): +- github.com/filecoin-project/go-fil-markets (v1.19.2 -> v1.20.1): +- deps: update go-libp2p to v0.18.0-rc5 ([filecoin-project/lotus#8169](https://github.com/filecoin-project/lotus/pull/8169)) + +## Others +- chore: build: backport releases ([filecoin-project/lotus#8192](https://github.com/filecoin-project/lotus/pull/8192)) +- feat: build: bump the version to v1.15.1-dev ([filecoin-project/lotus#8073](https://github.com/filecoin-project/lotus/pull/8073)) +- makefile: add make jen ([filecoin-project/lotus#8122](https://github.com/filecoin-project/lotus/pull/8122)) +- chore: Merge releases into master ([filecoin-project/lotus#8156](https://github.com/filecoin-project/lotus/pull/8156)) +- chore: ci: disable publish-packer #8451 + +# 1.15.0 / 2022-03-09 + +This is an optional release with retrieval improvements(client side), SP ux with unsealing, snap deals and regular deal making and many other new features, improvements and bug fixes. + +## Highlights +- feat:sealing: StartEpochSealingBuffer triggers packing on time([filecoin-project/lotus#7905](https://github.com/filecoin-project/lotus/pull/7905)) + - use the `StartEpochSealingBuffer` configuration variable as a way to enforce that sectors are packed for sealing / updating no matter how many deals they have if the nearest deal start date is close enough to the present. +- feat: #6017 market: retrieval ask CLI command ([filecoin-project/lotus#7814](https://github.com/filecoin-project/lotus/pull/7814)) +- feat(graphsync): allow setting of per-peer incoming requests for miners ([filecoin-project/lotus#7578](https://github.com/filecoin-project/lotus/pull/7578)) + - by setting `SimultaneousTransfersForStoragePerClient` in deal making configuration. +- Make retrieval even faster ([filecoin-project/lotus#7746](https://github.com/filecoin-project/lotus/pull/7746)) +- feat: #7747 sealing: Adding conf variable for capping number of concurrent unsealing jobs (#7884) ([filecoin-project/lotus#7884](https://github.com/filecoin-project/lotus/pull/7884)) + - by setting `MaxConcurrentUnseals` in `DAGStoreConfig` + +## New Features +- feat: mpool: Cache state nonces ([filecoin-project/lotus#8005](https://github.com/filecoin-project/lotus/pull/8005)) +- chore: build: make the OhSnap epoch configurable by an envvar for devnets ([filecoin-project/lotus#7995](https://github.com/filecoin-project/lotus/pull/7995)) +- Shed: Add a util to send a batch of messages ([filecoin-project/lotus#7667](https://github.com/filecoin-project/lotus/pull/7667)) +- Add api for transfer diagnostics ([filecoin-project/lotus#7759](https://github.com/filecoin-project/lotus/pull/7759)) +- Shed: Add a util to list terminated deals ([filecoin-project/lotus#7774](https://github.com/filecoin-project/lotus/pull/7774)) +- Expose EnableGasTracing as an env_var ([filecoin-project/lotus#7750](https://github.com/filecoin-project/lotus/pull/7750)) +- Command to list active sector locks ([filecoin-project/lotus#7735](https://github.com/filecoin-project/lotus/pull/7735)) +- Initial switch to OpenTelemetry ([filecoin-project/lotus#7725](https://github.com/filecoin-project/lotus/pull/7725)) + +## Improvements +- splitstore sortless compaction ([filecoin-project/lotus#8008](https://github.com/filecoin-project/lotus/pull/8008)) +- perf: chain: Make drand logs in daemon less noisy (#7955) ([filecoin-project/lotus#7955](https://github.com/filecoin-project/lotus/pull/7955)) +- chore: shed: storage stats 2.0 ([filecoin-project/lotus#7941](https://github.com/filecoin-project/lotus/pull/7941)) +- misc: api: Annotate lotus tests according to listed behaviors ([filecoin-project/lotus#7835](https://github.com/filecoin-project/lotus/pull/7835)) +- some basic splitstore refactors ([filecoin-project/lotus#7999](https://github.com/filecoin-project/lotus/pull/7999)) +- chore: sealer: quieten a log ([filecoin-project/lotus#7998](https://github.com/filecoin-project/lotus/pull/7998)) +- tvx: supply network version when extracting messages. ([filecoin-project/lotus#7996](https://github.com/filecoin-project/lotus/pull/7996)) +- chore: remove inaccurate comment in sealtasks ([filecoin-project/lotus#7977](https://github.com/filecoin-project/lotus/pull/7977)) +- Refactor: VM: Remove the NetworkVersionGetter ([filecoin-project/lotus#7818](https://github.com/filecoin-project/lotus/pull/7818)) +- refactor: state: Move randomness versioning out of the VM ([filecoin-project/lotus#7816](https://github.com/filecoin-project/lotus/pull/7816)) +- updating to new datastore/blockstore code with contexts ([filecoin-project/lotus#7646](https://github.com/filecoin-project/lotus/pull/7646)) +- Mempool msg selection should respect block message limits ([filecoin-project/lotus#7321](https://github.com/filecoin-project/lotus/pull/7321)) +- Minor improvement for OpenTelemetry ([filecoin-project/lotus#7760](https://github.com/filecoin-project/lotus/pull/7760)) +- Sort lotus-miner retrieval-deals by dealId ([filecoin-project/lotus#7749](https://github.com/filecoin-project/lotus/pull/7749)) +- dagstore pieceReader: Always read full in ReadAt ([filecoin-project/lotus#7737](https://github.com/filecoin-project/lotus/pull/7737)) + +## Bug Fixes +- fix: sealing: Stop recovery attempts after fault ([filecoin-project/lotus#8014](https://github.com/filecoin-project/lotus/pull/8014)) +- fix:snap: pay for the collateral difference needed if the miner available balance is insufficient ([filecoin-project/lotus#8234](https://github.com/filecoin-project/lotus/pull/8234)) +- sealer: fix error message ([filecoin-project/lotus#8136](https://github.com/filecoin-project/lotus/pull/8136)) +- typo in variable name ([filecoin-project/lotus#8134](https://github.com/filecoin-project/lotus/pull/8134)) +- fix: sealer: allow enable/disabling ReplicaUpdate tasks ([filecoin-project/lotus#8093](https://github.com/filecoin-project/lotus/pull/8093)) +- chore: chain: fix log ([filecoin-project/lotus#7993](https://github.com/filecoin-project/lotus/pull/7993)) +- Fix: chain: create a new VM for each epoch ([filecoin-project/lotus#7966](https://github.com/filecoin-project/lotus/pull/7966)) +- fix: doc generation struct slice example value ([filecoin-project/lotus#7851](https://github.com/filecoin-project/lotus/pull/7851)) +- fix: returned error not be accept correctly ([filecoin-project/lotus#7852](https://github.com/filecoin-project/lotus/pull/7852)) +- fix: #7577 markets: When retrying Add Piece, first seek to start of reader ([filecoin-project/lotus#7812](https://github.com/filecoin-project/lotus/pull/7812)) +- misc: n/a sealing: Fix grammatical error in a log warning message ([filecoin-project/lotus#7831](https://github.com/filecoin-project/lotus/pull/7831)) +- sectors update-state checks if sector exists before changing its state ([filecoin-project/lotus#7762](https://github.com/filecoin-project/lotus/pull/7762)) +- SplitStore: supress compaction near upgrades ([filecoin-project/lotus#7734](https://github.com/filecoin-project/lotus/pull/7734)) + +## Dependency Updates +- github.com/filecoin-project/go-commp-utils (v0.1.2 -> v0.1.3): +- github.com/filecoin-project/dagstore (v0.4.3 -> v0.4.4): +- github.com/filecoin-project/go-fil-markets (v1.13.4 -> v1.19.2): +- github.com/filecoin-project/go-statestore (v0.1.1 -> v0.2.0): +- github.com/filecoin-project/go-storedcounter (v0.0.0-20200421200003-1c99c62e8a5b -> v0.1.0): +- github.com/filecoin-project/specs-actors/v2 (v2.3.5 -> v2.3.6): + - feat(deps): update markets stack ([filecoin-project/lotus#7959](https://github.com/filecoin-project/lotus/pull/7959)) + - Use go-libp2p-connmgr v0.3.1 ([filecoin-project/lotus#7957](https://github.com/filecoin-project/lotus/pull/7957)) + - dep/fix 7701 Dependency: update to ipld-legacy to v0.1.1 ([filecoin-project/lotus#7751](https://github.com/filecoin-project/lotus/pull/7751)) + +## Others +- chore: backport: release ([filecoin-project/lotus#8245](https://github.com/filecoin-project/lotus/pull/8245)) +- Lotus release v1.15.0-rc3 ([filecoin-project/lotus#8236](https://github.com/filecoin-project/lotus/pull/8236)) +- Lotus release v1.15.0-rc2 ([filecoin-project/lotus#8211](https://github.com/filecoin-project/lotus/pull/8211)) +- Merge branch 'releases' into release/v1.15.0 +- chore: build: backport releases ([filecoin-project/lotus#8193](https://github.com/filecoin-project/lotus/pull/8193)) +- Merge branch 'releases' into release/v1.15.0 +- bump the version to v1.15.0-rc1 +- chore: build: v1.14.0 -> master ([filecoin-project/lotus#8053](https://github.com/filecoin-project/lotus/pull/8053)) +- chore: merge release/v1.14.0 PRs into master ([filecoin-project/lotus#7979](https://github.com/filecoin-project/lotus/pull/7979)) +- chore: update PR template ([filecoin-project/lotus#7918](https://github.com/filecoin-project/lotus/pull/7918)) +- build: release: bump master version to v1.15.0-dev ([filecoin-project/lotus#7922](https://github.com/filecoin-project/lotus/pull/7922)) +- misc: docs: remove issue number from the pr title ([filecoin-project/lotus#7902](https://github.com/filecoin-project/lotus/pull/7902)) +- Snapcraft grade no develgrade ([filecoin-project/lotus#7802](https://github.com/filecoin-project/lotus/pull/7802)) +- chore: create pull_request_template.md ([filecoin-project/lotus#7726](https://github.com/filecoin-project/lotus/pull/7726)) +- Disable appimage ([filecoin-project/lotus#7707](https://github.com/filecoin-project/lotus/pull/7707)) + +## Contributors +| Contributor | Commits | Lines ± | Files Changed | +|-------------|---------|---------|---------------| +| @arajasek | 73 | +7232/-2778 | 386 | +| @zenground0 | 27 | +5604/-1049 | 219 | +| @vyzo | 118 | +4356/-1470 | 253 | +| @zl | 1 | +3725/-309 | 8 | +| @dirkmc | 7 | +1392/-1110 | 61 | +| arajasek | 37 | +221/-1329 | 90 | +| @magik6k | 33 | +1138/-336 | 101 | +| @whyrusleeping | 2 | +483/-585 | 28 | +| Darko Brdareski | 14 | +725/-276 | 154 | +| @rvagg | 2 | +43/-947 | 10 | +| @hannahhoward | 5 | +436/-335 | 31 | +| @hannahhoward | 12 | +507/-133 | 37 | +| @jennijuju | 27 | +333/-178 | 54 | +| @TheMenko | 8 | +237/-179 | 17 | +| c r | 2 | +227/-45 | 12 | +| @dirkmck | 12 | +188/-40 | 27 | +| @ribasushi | 3 | +128/-62 | 3 | +| @raulk | 6 | +128/-49 | 9 | +| @Whyrusleeping | 1 | +76/-70 | 8 | +| @Stebalien | 1 | +55/-37 | 1 | +| @jennijuju | 11 | +29/-16 | 11 | +| @aarshkshah1992 | 1 | +23/-19 | 5 | +| @travisperson | 1 | +0/-18 | 2 | +| @gstuart | 3 | +12/-1 | 3 | +| @coryschwartz | 4 | +5/-6 | 4 | +| @pefish | 1 | +4/-3 | 1 | +| @Kubuxu | 1 | +5/-2 | 2 | +| Colin Kennedy | 1 | +4/-2 | 1 | +| Rob Quist | 1 | +2/-2 | 1 | +| @shotcollin | 1 | +1/-1 | 1 | + + +# 1.14.4 / 2022-03-03 + +This is a *highly recommended* optional release for storage providers that are doing snap deals. This fix the bug +that causes some snap deal sectors are stuck in `FinalizeReplicaUpdate`. In addition, SPs should be able to force +update sectors status without getting blocked by `normal shutdown of state machine`. + +# v1.14.3 / 2022-02-28 + +This is an **optional** release, that includes a fix to properly register the `--really-do-it` flag for abort-upgrade. + +# 1.14.2 / 2022-02-24 + +This is an **optional** release of lotus, that's had a couple more improvements w.r.t Snap experience for storage providers in preparation of the[upcoming OhSnap upgrade](https://github.com/filecoin-project/community/discussions/74?sort=new#discussioncomment-1922550). + +Note that the network is STILL scheduled to upgrade to v15 on March 1st at 2022-03-01T15:00:00Z. All node operators, including storage providers, must upgrade to at least Lotus v1.14.0 before that time. Storage providers must update their daemons, miners, and worker(s). + +Wanna know how to Snap your deal? Check [this](https://github.com/filecoin-project/lotus/discussions/8141) out! + +## Bug Fixes +- fix lotus-bench for sealing jobs (#8173) +- fix:sealing:really-do-it flag for abort upgrade (#8181) +- fix:proving:post check sector handles snap deals replica faults (#8177) +- fix: sealing: missing file type (#8180) + +## Others +- Retract force-pushed v1.14.0 to work around stale gomod caches (#8159): We originally tagged v1.14.0 off the wrong + commit and fixed that by a force push, in which is a really bad practise since it messes up the go mod. Therefore, + we want to retract it and users may use v1.14.1&^. + +## Contributors + +| Contributor | Commits | Lines ± | Files Changed | +|-------------|---------|---------|---------------| +| @zenground0 | 2 | +73/-58 | 12 | +| @eben.xie | 1 | +7/-0 | 1 | +| @jennijuju | 1 | +4/-0 | 1 | +| @jennijuju | 1 | +2/-1 | 1 | +| @ribasushi | 1 | +2/-0 | 1 | + +# 1.14.1 / 2022-02-18 + +This is an **optional** release of lotus, that fixes the incorrect *comment* of network v15 OhSnap upgrade **date**. Note the actual upgrade epoch in [v1.14.0](https://github.com/filecoin-project/lotus/releases/tag/v1.14.0) was correct. + +# 1.14.0 / 2022-02-17 + +This is a MANDATORY release of Lotus that introduces [Filecoin network v15, +codenamed the OhSnap upgrade](https://github.com/filecoin-project/community/discussions/74?sort=new#discussioncomment-1922550). + +The network is scheduled to upgrade to v15 on March 1st at 2022-03-01T15:00:00Z. All node operators, including storage providers, must upgrade to this release (or a later release) before that time. Storage providers must update their daemons, miners, and worker(s). + +The OhSnap upgrade introduces the following FIPs, delivered in [actors v7](https://github.com/filecoin-project/specs-actors/releases/tag/v7.0.0): +- [FIP-0019 Snap Deals](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0019.md) +- [FIP-0028 Remove Datacap from Verified clients](https://github.com/filecoin-project/FIPs/pull/226) + +It is recommended that storage providers download the new params before updating their node, miner, and workers. To do so: + +- Download Lotus v1.14.0 or later +- run `make lotus-shed` +- run `./lotus-shed fetch-params` with the appropriate `proving-params` flag +- Upgrade the Lotus daemon and miner **when the previous step is complete** + +All node operators, including storage providers, should be aware that a pre-migration will begin at 2022-03-01T13:30:00Z (90 minutes before the real upgrade). The pre-migration will take between 20 and 50 minutes, depending on hardware specs. During this time, expect slower block validation times, increased CPU and memory usage, and longer delays for API queries. + +## New Features and Changes +- Integrate actor v7-rc1: + - Integrate v7 actors ([#7617](https://github.com/filecoin-project/lotus/pull/7617)) + - feat: state: Fast migration for v15 ([#7933](https://github.com/filecoin-project/lotus/pull/7933)) + - fix: blockstore: Add missing locks to autobatch::Get() [#7939](https://github.com/filecoin-project/lotus/pull/7939)) + - correctness fixes for the autobatch blockstore ([#7940](https://github.com/filecoin-project/lotus/pull/7940)) +- Implement and support [FIP-0019 Snap Deals](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0019.md) + - chore: deps: Integrate proof v11.0.0 ([#7923](https://github.com/filecoin-project/lotus/pull/7923)) + - Snap Deals Lotus Integration: FSM Posting and integration test ([#7810](https://github.com/filecoin-project/lotus/pull/7810)) + - Feat/sector storage unseal ([#7730](https://github.com/filecoin-project/lotus/pull/7730)) + - Feat/snap deals storage ([#7615](https://github.com/filecoin-project/lotus/pull/7615)) + - fix: sealing: Add more deal expiration checks during PRU pipeline ([#7871](https://github.com/filecoin-project/lotus/pull/7871)) + - chore: deps: Update go-paramfetch ([#7917](https://github.com/filecoin-project/lotus/pull/7917)) + - feat: #7880 gas: add gas charge for VerifyReplicaUpdate ([#7897](https://github.com/filecoin-project/lotus/pull/7897)) + - enhancement: sectors: disable existing cc upgrade path 2 days before the upgrade epoch ([#7900](https://github.com/filecoin-project/lotus/pull/7900)) + +## Improvements +- updating to new datastore/blockstore code with contexts ([#7646](https://github.com/filecoin-project/lotus/pull/7646)) +- reorder transfer checks so as to ensure sending 2B FIL to yourself fails if you don't have that amount ([#7637](https://github.com/filecoin-project/lotus/pull/7637)) +- VM: Circ supply should be constant per epoch ([#7811](https://github.com/filecoin-project/lotus/pull/7811)) + +## Bug Fixes +- Fix: state: circsuypply calc around null blocks ([#7890](https://github.com/filecoin-project/lotus/pull/7890)) +- Mempool msg selection should respect block message limits ([#7321](https://github.com/filecoin-project/lotus/pull/7321)) + SplitStore: supress compaction near upgrades ([#7734](https://github.com/filecoin-project/lotus/pull/7734)) + +## Others +- chore: create pull_request_template.md ([#7726](https://github.com/filecoin-project/lotus/pull/7726)) + +## Contributors + +| Contributor | Commits | Lines ± | Files Changed | +|-------------|---------|---------|---------------| +| Aayush Rajasekaran | 41 | +5538/-1205 | 189 | +| zenground0 | 11 | +3316/-524 | 124 | +| Jennifer Wang | 29 | +714/-599 | 68 | +| ZenGround0 | 3 | +263/-25 | 11 | +| c r | 2 | +198/-30 | 6 | +| vyzo | 4 | +189/-7 | 7 | +| Aayush | 11 | +146/-48 | 49 | +| web3-bot | 10 | +99/-17 | 10 | +| Steven Allen | 1 | +55/-37 | 1 | +| Jiaying Wang | 5 | +30/-8 | 5 | +| Jakub Sztandera | 2 | +8/-3 | 3 | +| Łukasz Magiera | 1 | +3/-3 | 2 | +| Travis Person | 1 | +2/-2 | 2 | +| Rod Vagg | 1 | +2/-2 | 2 | + +# v1.13.2 / 2022-01-09 + +Lotus v1.13.2 is a *highly recommended* feature release with remarkable retrieval improvements, new features like +worker management, schedule enhancements and so on. + +## Highlights +- 🚀🚀🚀Improve retrieval deal experience + - Testing result with MinerX.3 shows the retrieval deal success rate has increased dramatically with faster transfer + speed, you can join or follow along furthur performance testings [here](https://github.com/filecoin-project/lotus/discussions/7874). We recommend application developers to integrate with the new + retrieval APIs to provide a better client experience. + - 🌟🌟🌟 Reduce retrieval Time-To-First-Byte over 100x ([#7693](https://github.com/filecoin-project/lotus/pull/7693)) + - This change makes most free, small retrievals sub-second + - 🌟🌟🌟 Partial retrieval ux improvements ([#7610](https://github.com/filecoin-project/lotus/pull/7610)) + - New retrieval commands for clients: + - `lotus client ls`: retrieve and list desired object links + - `lotus client cat`: retrieve and print the data from the network + - 🌟🌟 The monolith `ClientRetrieve` method was broken into: + - `ClientRetrieve` which retrieves data into the local repo (or into an IPFS node if ipfs integration is enabled) + - `ClientRetrieveWait` which will wait for the retrieval to complete + - `ClientExport` which will export data from the local node + - Note: this change only applies to v1 API. v0 API remains unchanged. + - 🌟 Support for full ipld selectors was added (for example making it possible to only retrieve list of directories in a deal, without fetching any file data) + - To learn more, see [here](https://github.com/filecoin-project/lotus/blob/0523c946f984b22b3f5de8cc3003cc791389527e/api/types.go#L230-L264) +- 🚀🚀 Sealing scheduler enhancements ([#7703](https://github.com/filecoin-project/lotus/pull/7703), + [#7269](https://github.com/filecoin-project/lotus/pull/7269)), [#7714](https://github.com/filecoin-project/lotus/pull/7714) + - Workers are now aware of cgroup memory limits + - Multiple tasks which use a GPU can be scheduled on a single worker + - Workers can override default resource table through env vars + - Default value list: https://gist.github.com/magik6k/c0e1c7cd73c1241a9acabc30bf469a43 +- 🚀🚀 Sector storage groups ([#7453](https://github.com/filecoin-project/lotus/pull/7453)) + - Storage groups allow for better control of data flow between workers, for example, it makes it possible to define that data from PC1 on a given worker has to have it's PC2 step executed on the same worker + - To set it up, follow the instructions under the `Sector Storage Group` section [here](https://lotus.filecoin.io/docs/storage-providers/seal-workers/#lotus-worker-co-location) + +## New Features +- Add RLE dump code ([#7691](https://github.com/filecoin-project/lotus/pull/7691)) +- Shed: Add a util to list miner faults ([#7605](https://github.com/filecoin-project/lotus/pull/7605)) +- lotus-shed msg: Decode submessages/msig proposals ([#7639](https://github.com/filecoin-project/lotus/pull/7639)) +- CLI: Add a lotus multisig cancel command ([#7645](https://github.com/filecoin-project/lotus/pull/7645)) +- shed: simple wallet balancer util ([#7414](https://github.com/filecoin-project/lotus/pull/7414)) + - balancing token balance between multiple accounts + +## Improvements +- Add verbose mode to `lotus-miner pieces list-cids` ([#7699](https://github.com/filecoin-project/lotus/pull/7699)) +- retrieval: Only output matching nodes, MatchPath dagspec ([#7706](https://github.com/filecoin-project/lotus/pull/7706)) +- Cleanup partial retrieval codepaths ( zero functional changes ) ([#7688](https://github.com/filecoin-project/lotus/pull/7688)) +- storage: Use 1M buffers for Tar transfers ([#7681](https://github.com/filecoin-project/lotus/pull/7681)) +- Chore/dm level tests plus merkle proof cars ([#7673](https://github.com/filecoin-project/lotus/pull/7673)) +- Shed: Add a util to create miners more easily ([#7595](https://github.com/filecoin-project/lotus/pull/7595)) +- add timeout flag to wait-api command ([#7592](https://github.com/filecoin-project/lotus/pull/7592)) +- add log for restart windows post scheduler ([#7613](https://github.com/filecoin-project/lotus/pull/7613)) +- remove jaeger envvars ([#7631](https://github.com/filecoin-project/lotus/pull/7631)) +- remove api and jaeger env from docker file ([#7624](https://github.com/filecoin-project/lotus/pull/7624)) +- Wdpost worker: Reduce challenge confidence to 1 epoch ([#7572](https://github.com/filecoin-project/lotus/pull/7572)) +- add additional methods to lotus gateway ([#7644](https://github.com/filecoin-project/lotus/pull/7644)) +- Add caches to lotus-stats and splitcode ([#7329](https://github.com/filecoin-project/lotus/pull/7329)) +- remote store: Remove debug printf ([#7664](https://github.com/filecoin-project/lotus/pull/7664)) +- docsgen-cli: Handle commands with no description correctly ([#7659](https://github.com/filecoin-project/lotus/pull/7659)) + +## Bug Fixes +- fix docker logic error ([#7709](https://github.com/filecoin-project/lotus/pull/7709)) +- add missing NodeType tag ([#7559](https://github.com/filecoin-project/lotus/pull/7559)) +- checkCommit should return SectorCommitFailed ([#7555](https://github.com/filecoin-project/lotus/pull/7555)) +- ffiwrapper: Validate PC2 by calling C1 with random seeds ([#7710](https://github.com/filecoin-project/lotus/pull/7710)) + +## Dependency Updates +- Update go-graphsync v0.10.6 ([#7708](https://github.com/filecoin-project/lotus/pull/7708)) +- update go-libp2p-pubsub to v0.5.6 ([#7581](https://github.com/filecoin-project/lotus/pull/7581)) +- Update go-state-types ([#7591](https://github.com/filecoin-project/lotus/pull/7591)) +- disable mplex stream muxer ([#7689](https://github.com/filecoin-project/lotus/pull/7689)) +- Bump ws from 5.2.2 to 5.2.3 in /lotuspond/front ([#7660](https://github.com/filecoin-project/lotus/pull/7660)) +- Bump color-string from 1.5.3 to 1.6.0 in /lotuspond/front ([#7658](https://github.com/filecoin-project/lotus/pull/7658)) +- Bump postcss from 7.0.17 to 7.0.39 in /lotuspond/front ([#7657](https://github.com/filecoin-project/lotus/pull/7657)) +- Bump path-parse from 1.0.6 to 1.0.7 in /lotuspond/front ([#7656](https://github.com/filecoin-project/lotus/pull/7656)) +- Bump tmpl from 1.0.4 to 1.0.5 in /lotuspond/front ([#7655](https://github.com/filecoin-project/lotus/pull/7655)) +- Bump url-parse from 1.4.7 to 1.5.3 in /lotuspond/front ([#7654](https://github.com/filecoin-project/lotus/pull/7654)) +- github.com/filecoin-project/go-state-types (v0.1.1-0.20210915140513-d354ccf10379 -> v0.1.1): + +## Others +- Update archive script ([#7690](https://github.com/filecoin-project/lotus/pull/7690)) + +## Contributors + +| Contributor | Commits | Lines ± | Files Changed | +|-------------|---------|---------|---------------| +| @magik6k | 89 | +5200/-1818 | 232 | +| Travis Person | 5 | +1473/-953 | 38 | +| @arajasek | 6 | +550/-38 | 19 | +| @clinta | 4 | +393/-123 | 26 | +| @ribasushi | 3 | +334/-68 | 7 | +| @jennijuju| 13 | +197/-120 | 67 | +| @Kubuxu | 10 | +153/-30 | 10 | +| @coryschwartz | 6 | +18/-26 | 6 | +| Marten Seemann | 2 | +6/-34 | 5 | +| @vyzo | 1 | +3/-3 | 2 | +| @hannahhoward | 1 | +3/-3 | 2 | +| @zenground0 | 2 | +2/-2 | 2 | +| @yaohcn | 2 | +2/-2 | 2 | +| @jennijuju | 1 | +1/-1 | 1 | +| @hunjixin | 1 | +1/-0 | 1 | + + + +# v1.13.1 / 2021-11-26 + +This is an optional Lotus v1.13.1 release. + +## New Features +- Shed: Add a util to find miner based on peerid ([filecoin-project/lotus#7544](https://github.com/filecoin-project/lotus/pull/7544)) +- Collect and expose graphsync metrics ([filecoin-project/lotus#7542](https://github.com/filecoin-project/lotus/pull/7542)) +- Shed: Add a util to find the most recent null tipset ([filecoin-project/lotus#7456](https://github.com/filecoin-project/lotus/pull/7456)) + +## Improvements +- Show prepared tasks in sealing jobs ([filecoin-project/lotus#7527](https://github.com/filecoin-project/lotus/pull/7527)) +- To make Deep happy ([filecoin-project/lotus#7546](https://github.com/filecoin-project/lotus/pull/7546)) +- Expose per-state sector counts on the prometheus endpoint ([filecoin-project/lotus#7541](https://github.com/filecoin-project/lotus/pull/7541)) +- Add storage-id flag to proving check ([filecoin-project/lotus#7479](https://github.com/filecoin-project/lotus/pull/7479)) +- FilecoinEC: Improve a log message ([filecoin-project/lotus#7499](https://github.com/filecoin-project/lotus/pull/7499)) +- itests: retry deal when control addr is out of funds ([filecoin-project/lotus#7454](https://github.com/filecoin-project/lotus/pull/7454)) +- Normlize selector use within lotus ([filecoin-project/lotus#7467](https://github.com/filecoin-project/lotus/pull/7467)) +- sealing: Improve scheduling of ready work ([filecoin-project/lotus#7335](https://github.com/filecoin-project/lotus/pull/7335)) +- Remove dead example code + dep ([filecoin-project/lotus#7466](https://github.com/filecoin-project/lotus/pull/7466)) + +## Bug Fixes +- fix the withdrawn amount unit ([filecoin-project/lotus#7563](https://github.com/filecoin-project/lotus/pull/7563)) +- rename vm#make{=>Account}Actor(). ([filecoin-project/lotus#7562](https://github.com/filecoin-project/lotus/pull/7562)) +- Fix used sector space accounting after AddPieceFailed ([filecoin-project/lotus#7530](https://github.com/filecoin-project/lotus/pull/7530)) +- Don't remove sector data when moving data into a shared path ([filecoin-project/lotus#7494](https://github.com/filecoin-project/lotus/pull/7494)) +- fix: support node instantiation in external packages ([filecoin-project/lotus#7511](https://github.com/filecoin-project/lotus/pull/7511)) +- Stop adding Jennifer's $HOME to lotus docs ([filecoin-project/lotus#7477](https://github.com/filecoin-project/lotus/pull/7477)) +- Bugfix: Use correct startup network versions ([filecoin-project/lotus#7486](https://github.com/filecoin-project/lotus/pull/7486)) +- Dep upgrade pass ([filecoin-project/lotus#7478](https://github.com/filecoin-project/lotus/pull/7478)) +- Remove obsolete GS testplan - it now lives in go-graphsync ([filecoin-project/lotus#7469](https://github.com/filecoin-project/lotus/pull/7469)) +- sealing: Recover sectors after failed AddPiece ([filecoin-project/lotus#7444](https://github.com/filecoin-project/lotus/pull/7444)) + +## Dependency Updates +- Update go-graphsync v0.10.1 ([filecoin-project/lotus#7457](https://github.com/filecoin-project/lotus/pull/7457)) +- update to proof v10.1.0 ([filecoin-project/lotus#7564](https://github.com/filecoin-project/lotus/pull/7564)) +- github.com/filecoin-project/specs-actors/v6 (v6.0.0 -> v6.0.1): +- github.com/filecoin-project/go-jsonrpc (v0.1.4-0.20210217175800-45ea43ac2bec -> v0.1.5): +- github.com/filecoin-project/go-fil-markets (v1.13.1 -> v1.13.3): +- github.com/filecoin-project/go-data-transfer (v1.11.1 -> v1.11.4): +- github.com/filecoin-project/go-crypto (v0.0.0-20191218222705-effae4ea9f03 -> v0.0.1): +- github.com/filecoin-project/go-commp-utils (v0.1.1-0.20210427191551-70bf140d31c7 -> v0.1.2): +- github.com/filecoin-project/go-cbor-util (v0.0.0-20191219014500-08c40a1e63a2 -> v0.0.1): +- github.com/filecoin-project/go-address (v0.0.5 -> v0.0.6): +- unpin the yamux dependency ([filecoin-project/lotus#7532](https://github.com/filecoin-project/lotus/pull/7532) +- peerstore@v0.2.9 was withdrawn, let's not depend on it directly ([filecoin-project/lotus#7481](https://github.com/filecoin-project/lotus/pull/7481)) +- chore(deps): use tagged github.com/ipld/go-ipld-selector-text-lite ([filecoin-project/lotus#7464](https://github.com/filecoin-project/lotus/pull/7464)) +- Stop indirectly depending on deprecated github.com/prometheus/common ([filecoin-project/lotus#7473](https://github.com/filecoin-project/lotus/pull/7473)) + +## Others +- fix the changelog ([filecoin-project/lotus#7594](https://github.com/filecoin-project/lotus/pull/7594)) +- v1.13.1-rc2 prep ([filecoin-project/lotus#7593](https://github.com/filecoin-project/lotus/pull/7593)) +- lotus v1.13.1-rc1 ([filecoin-project/lotus#7569](https://github.com/filecoin-project/lotus/pull/7569)) +- misc: back-port v1.13.0 back to master ([filecoin-project/lotus#7537](https://github.com/filecoin-project/lotus/pull/7537)) +- Inline codegen ([filecoin-project/lotus#7495](https://github.com/filecoin-project/lotus/pull/7495)) +- releases -> master ([filecoin-project/lotus#7507](https://github.com/filecoin-project/lotus/pull/7507)) +- Make chocolate back to master ([filecoin-project/lotus#7493](https://github.com/filecoin-project/lotus/pull/7493)) +- restore filters for the build-macos job ([filecoin-project/lotus#7455](https://github.com/filecoin-project/lotus/pull/7455)) +- bump master to v1.13.1-dev ([filecoin-project/lotus#7451](https://github.com/filecoin-project/lotus/pull/7451)) + +Contributors + +| Contributor | Commits | Lines ± | Files Changed | +|-------------|---------|---------|---------------| +| @magik6k | 27 | +1285/-531 | 76 | +| @ribasushi | 7 | +265/-1635 | 21 | +| @raulk | 2 | +2/-737 | 13 | +| @nonsens | 4 | +391/-21 | 19 | +| @arajasek | 6 | +216/-23 | 14 | +| @jennijuju| 8 | +102/-37 | 29 | +| Steven Allen | 2 | +77/-29 | 6 | +| @jennijuju | 4 | +19/-18 | 11 | +| @dirkmc | 2 | +9/-9 | 4 | +| @@coryschwartz | 1 | +16/-2 | 2 | +| @frrist | 1 | +12/-0 | 2 | +| @Kubuxu | 5 | +5/-5 | 5 | +| @hunjixin | 2 | +6/-3 | 2 | +| @vyzo | 1 | +3/-3 | 2 | +| @@rvagg | 1 | +3/-3 | 2 | +| @hannahhoward | 1 | +3/-2 | 2 | +| Marten Seemann | 1 | +3/-0 | 1 | +| @ZenGround0 | 1 | +1/-1 | 1 | + + +# v1.13.0 / 2021-10-18 + +Lotus v1.13.0 is a *highly recommended* feature release for all lotus users(i.e: storage providers, data brokers, application developers and so on) that supports the upcoming +[Network v14 Chocolate upgrade](https://github.com/filecoin-project/lotus/discussions/7431). +This feature release includes the latest functionalities and improvements, like data transfer rate-limiting for both storage and retrieval deals, proof v10 with CUDA support, etc. You can find more details in the Changelog below. + +## Highlights +- Enable separate storage and retrieval transfer limits ([filecoin-project/lotus#7405](https://github.com/filecoin-project/lotus/pull/7405)) + - `SimultaneousTransfer` is now replaced by `SimultaneousTransfersForStorage` and `SimultaneousTransfersForRetrieval`, where users may set the amount of ongoing data transfer for storage and retrieval deals in parallel separately. The default value for both is set to 20. + - If you are using the lotus client, these two configuration variables are under the `Client` section in `./lotus/config.toml`. + - If you are a service provider, these two configuration variables should be set under the `Dealmaking` section in `/.lotusminer/config.toml`. +- Update proofs to v10.0.0 ([filecoin-project/lotus#7420](https://github.com/filecoin-project/lotus/pull/7420)) + - This version supports CUDA. To enable CUDA instead of openCL, build lotus with `FFI_USE_CUDA=1 FFI_BUILD_FROM_SOURCE=1 ...`. + - You can find additional Nvidia driver installation instructions written by MinerX fellows [here](https://github.com/filecoin-project/lotus/discussions/7443#discussioncomment-1425274) and perf improvements result on PC2/C2/WindowPoSt computation on different profiles [here](https://github.com/filecoin-project/lotus/discussions/7443), most people observe a 30-50% decrease in computation time. + +## New Features +- Feat/datamodel selector retrieval ([filecoin-project/lotus#6393](https://github.com/filecoin-project/lotus/pull/66393393)) + - This introduces a new RetrievalOrder-struct field and a CLI option that takes a string representation as understood by [https://pkg.go.dev/github.com/ipld/go-ipld-selector-text-lite#SelectorSpecFromPath](https://pkg.go.dev/github.com/ipld/go-ipld-selector-text-lite#SelectorSpecFromPath). This allows for partial retrieval of any sub-DAG of a deal provided the user knows the exact low-level shape of the deal contents. + - For example, to retrieve the first entry of a UnixFS directory by executing, run `lotus client retrieve --miner f0XXXXX --datamodel-path-selector 'Links/0/Hash' bafyROOTCID ~/output` +- Expose storage stats on the metrics endpoint ([filecoin-project/lotus#7418](https://github.com/filecoin-project/lotus/pull/7418)) +- feat: Catch panic to generate report and reraise ([filecoin-project/lotus#7341](https://github.com/filecoin-project/lotus/pull/7341)) + - Set `LOTUS_PANIC_REPORT_PATH` and `LOTUS_PANIC_JOURNAL_LOOKBACK` to get reports generated when a panic occurs on your daemon miner or workers. +- Add envconfig docs to the config ([filecoin-project/lotus#7412](https://github.com/filecoin-project/lotus/pull/7412)) + - You can now find supported env vars in [default-lotus-miner-config.toml](https://github.com/filecoin-project/lotus/blob/master/documentation/en/default-lotus-miner-config.toml). +- lotus shed: fr32 utils ([filecoin-project/lotus#7355](https://github.com/filecoin-project/lotus/pull/7355)) +- Miner CLI: Allow trying to change owners of any miner actor ([filecoin-project/lotus#7328](https://github.com/filecoin-project/lotus/pull/7328)) +- Add --unproven flag to the sectors list command ([filecoin-project/lotus#7308](https://github.com/filecoin-project/lotus/pull/7308)) + +## Improvements +- check for deal start epoch on SectorAddPieceToAny ([filecoin-project/lotus#7407](https://github.com/filecoin-project/lotus/pull/7407)) +- Verify Voucher locks in VoucherValidUnlocked ([filecoin-project/lotus#5609](https://github.com/filecoin-project/lotus/pull/5609)) +- Add more info to miner allinfo command ([filecoin-project/lotus#7384](https://github.com/filecoin-project/lotus/pull/7384)) +- add `lotus-miner storage-deals list --format=json` with transfers ([filecoin-project/lotus#7312](https://github.com/filecoin-project/lotus/pull/7312)) +- Fix formatting ([filecoin-project/lotus#7383](https://github.com/filecoin-project/lotus/pull/7383)) +- GetCurrentDealInfo err: handle correctly err case ([filecoin-project/lotus#7346](https://github.com/filecoin-project/lotus/pull/7346)) +- fix: Enforce verification key integrity check regardless of TRUST_PARAMS=1 ([filecoin-project/lotus#7327](https://github.com/filecoin-project/lotus/pull/7327)) +- Show more deal states in miner info ([filecoin-project/lotus#7311](https://github.com/filecoin-project/lotus/pull/7311)) +- Prep retrieval for selectors: no functional changes ([filecoin-project/lotus#7306](https://github.com/filecoin-project/lotus/pull/7306)) +- Seed: improve helptext ([filecoin-project/lotus#7304](https://github.com/filecoin-project/lotus/pull/7304)) +- Mempool: reduce size of sigValCache ([filecoin-project/lotus#7305](https://github.com/filecoin-project/lotus/pull/7305)) +- Stop indirectly depending on deprecated github.com/prometheus/common ([filecoin-project/lotus#7474](https://github.com/filecoin-project/lotus/pull/7474)) + +## Bug Fixes +- StateSearchMsg: Correct usage of the allowReplaced flag ([filecoin-project/lotus#7450](https://github.com/filecoin-project/lotus/pull/7450)) +- fix staging area path buildup ([filecoin-project/lotus#7363](https://github.com/filecoin-project/lotus/pull/7363)) +- storagemgr: Cleanup workerLk around worker resources ([filecoin-project/lotus#7334](https://github.com/filecoin-project/lotus/pull/7334)) +- fix: check padSector Cid ([filecoin-project/lotus#7310](https://github.com/filecoin-project/lotus/pull/7310)) +- sealing: Recover sectors after failed AddPiece ([filecoin-project/lotus#7492](https://github.com/filecoin-project/lotus/pull/7492)) +- fix: support node instantiation in external packages ([filecoin-project/lotus#7511](https://github.com/filecoin-project/lotus/pull/7511)) +- Chore/backport cleanup withdrawn dependency ([filecoin-project/lotus#7482](https://github.com/filecoin-project/lotus/pull/7482)) + +## Dependency Updates +- github.com/filecoin-project/go-data-transfer (v1.10.1 -> v1.11.1): +- github.com/filecoin-project/go-fil-markets (v1.12.0 -> v1.13.1): +- github.com/filecoin-project/go-paramfetch (v0.0.2-0.20210614165157-25a6c7769498 -> v0.0.2): +- update go-libp2p to v0.15.0 ([filecoin-project/lotus#7362](https://github.com/filecoin-project/lotus/pull/7362)) +- update to go-graphsync v0.10.1 ([filecoin-project/lotus#7359](https://github.com/filecoin-project/lotus/pull/7359)) + +## Others +- Chocolate to master ([filecoin-project/lotus#7440](https://github.com/filecoin-project/lotus/pull/7440)) +- releases -> master ([filecoin-project/lotus#7403](https://github.com/filecoin-project/lotus/pull/7403)) +- remove nerpanet related code ([filecoin-project/lotus#7373](https://github.com/filecoin-project/lotus/pull/7373)) +- sync branch main with master on updates ([filecoin-project/lotus#7366](https://github.com/filecoin-project/lotus/pull/7366)) +- remove job to install jq ([filecoin-project/lotus#7309](https://github.com/filecoin-project/lotus/pull/7309)) +- restore filters for the build-macos job ([filecoin-project/lotus#7455](https://github.com/filecoin-project/lotus/pull/7455)) +- v1.13.0-rc2 ([filecoin-project/lotus#7458](https://github.com/filecoin-project/lotus/pull/7458)) +- v1.13.0-rc1 ([filecoin-project/lotus#7452](https://github.com/filecoin-project/lotus/pull/7452)) + +## Contributors + +| Contributor | Commits | Lines ± | Files Changed | +|-------------|---------|---------|---------------| +| @dirkmc | 8 | +845/-375 | 55 | +| @magik6k | 10 | +1056/-60 | 26 | +| @aarshkshah1992 | 6 | +813/-259 | 16 | +| @arajasek | 10 | +552/-251 | 43 | +| @ribasushi | 6 | +505/-78 | 22 | +| @jennijuju | 7 | +212/-323 | 34 | +| @nonsense | 10 | +335/-139 | 19 | +| @dirkmc | 8 | +149/-55 | 16 | +| @hannahhoward | 4 | +56/-32 | 17 | +| @rvagg | 4 | +61/-13 | 9 | +| @jennijuju | 2 | +0/-57 | 2 | +| @hannahhoward | 1 | +33/-18 | 7 | +| @Kubuxu | 8 | +27/-16 | 9 | +| @coryschwartz | 1 | +16/-2 | 2 | +| @travisperson | 1 | +14/-0 | 1 | +| @frrist | 1 | +12/-0 | 2 | +| @ognots | 1 | +0/-10 | 2 | +| @lanzafame | 1 | +3/-3 | 1 | +| @jennijuju | 1 | +2/-2 | 1 | +| @swift-mx | 1 | +1/-1 | 1 | + +# v1.12.0 / 2021-10-12 + +This is a mandatory release of Lotus that introduces [Filecoin Network v14](https://github.com/filecoin-project/community/discussions/74#discussioncomment-1398542), codenamed the Chocolate upgrade. The Filecoin mainnet will upgrade at epoch 1231620, on 2021-10-26T13:30:00Z. + +The Chocolate upgrade introduces the following FIPs, delivered in [v6 actors](https://github.com/filecoin-project/specs-actors/releases/tag/v6.0.0) + +- [FIP-0020](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0020.md): Add return value to `WithdrawBalance` +- [FIP-0021](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0021.md): Correct quality calculation on expiration +- [FIP-0022](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0022.md): Bad deals don't fail PublishStorageDeals +- [FIP-0023](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0023.md): Break ties between tipsets of equal weight +- [FIP-0024](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0024.md): BatchBalancer & BatchDiscount Post-HyperDrive Adjustment +- [FIP-0026](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0026.md): Extend sector faulty period from 2 weeks to 6 weeks + +Note that this release is built on top of lotus v1.11.3. Enterprising users like storage providers, data brokers and others are recommended to use lotus v1.13.0 for latest new features, improvements and bug fixes. + +## New Features and Changes +- Implement and support [FIP-0024](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0024.md) BatchBalancer & BatchDiscount Post-HyperDrive Adjustment: + - Precommit batch balancer support/config ([filecoin-project/lotus#7410](https://github.com/filecoin-project/lotus/pull/7410)) + - Set `BatchPreCommitAboveBaseFee` to decide whether sending out a PreCommits in individual messages or in a batch. + - The default value of `BatchPreCommitAboveBaseFee` and `AggregateAboveBaseFee` are now updated to 0.32nanoFIL. +- The amount of FIL withdrawn from `WithdrawBalance` from miner or market via lotus CLI is now printed out upon message landing on the chain. + +## Improvements +- Implement [FIP-0023](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0023.md) (Break ties between tipsets of equal weight) + - ChainStore: Add a tiebreaker rule for tipsets of equal weight ([filecoin-project/lotus#7378](https://github.com/filecoin-project/lotus/pull/7378)) +- Randomness: Move getters from ChainAPI to StateAPI ([filecoin-project/lotus#7322](https://github.com/filecoin-project/lotus/pull/7322)) + +## Bug Fixes +- Fix Drand fetching around null tipsets ([filecoin-project/lotus#7376](https://github.com/filecoin-project/lotus/pull/7376)) + +## Dependency Updates +- Add [v6 actors](https://github.com/filecoin-project/specs-actors/releases/tag/v6.0.0) + - **Protocol changes** + - Multisig Approve only hashes when hash in params + - FIP 0020 WithdrawBalance methods return withdrawn value + - FIP 0021 Fix bug in power calculation when extending verified deals sectors + - FIP 0022 PublishStorageDeals drops errors in batch + - FIP 0024 BatchBalancer update and burn added to PreCommitBatch + - FIP 0026 Add FaultMaxAge extension + - Reduce calls to power and reward actors by passing values from power cron + - Defensive programming hardening power cron against programmer error + - **Implementation changes** + - Move to xerrors + - Improved logging: burn events are not logged with reasons and burned value. +- github.com/filecoin-project/go-state-types (v0.1.1-0.20210810190654-139e0e79e69e -> v0.1.1-0.20210915140513-d354ccf10379): + +## Others +- v1.12.0-rc1 prep ([filecoin-project/lotus#7426](https://github.com/filecoin-project/lotus/pull/7426) +- Extend FaultMaxAge to 6 weeks for actors v6 on test networks only ([filecoin-project/lotus#7421](https://github.com/filecoin-project/lotus/pull/7421)) + +## Contributors + +| Contributor | Commits | Lines ± | Files Changed | +|-------------|---------|---------|---------------| +| @ZenGround0 | 12 | +4202/-2752 | 187 | +| @arajasek | 25 | +4567/-854 | 190 | +| @laudiacay | 4 | +1276/-435 | 37 | +| @laudiacay | 12 | +1350/-209 | 43 | +| @magik6k | 1 | +171/-13 | 8 | +| @Stebalien | 2 | +115/-12 | 6 | +| @jennijuju | 7 | +73/-34 | 26 | +| @travisperson | 2 | +19/-19 | 7 | +| @coryschwartz | 1 | +16/-2 | 2 | +| @Kubuxu | 5 | +5/-5 | 5 | +| @ribasushi | 1 | +5/-3 | 1 | + +# v1.11.3 / 2021-09-29 + +lotus v1.11.3 is a feature release that's **highly recommended to ALL lotus users to upgrade**, including node +operators, storage providers and clients. It includes many improvements and bug fixes that result in perf +improvements in different area, like deal making, sealing and so on. + +## Highlights + +- 🌟🌟Introduce `MaxStagingDealsBytes - reject new deals if our staging deals area is full ([filecoin-project/lotus#7276](https://github.com/filecoin-project/lotus/pull/7276)) + - Set `MaxStagingDealsBytes` under the [Dealmaking] section of the markets' subsystem's `config.toml` to reject new incoming deals when the `deal-staging` directory of market subsystem's repo gets too large. +- 🌟🌟miner: Command to list/remove expired sectors locally ([filecoin-project/lotus#7140](https://github.com/filecoin-project/lotus/pull/7140)) + - run `./lotus-miner sectors expired -h` for more details. +- 🚀update to ffi to update-bellperson-proofs-v9-0-2 ([filecoin-project/lotus#7369](https://github.com/filecoin-project/lotus/pull/7369)) + - MinerX fellows(early testers of lotus releases) have reported faster WindowPoSt computation! +- 🌟dealpublisher: Fully validate deals before publishing ([filecoin-project/lotus#7234](https://github.com/filecoin-project/lotus/pull/7234)) + - This excludes the expired deals before sending out a PSD message which reduces the chances of PSD message failure due to invalid deals. +- 🌟Simple alert system; FD limit alerts ([filecoin-project/lotus#7108](https://github.com/filecoin-project/lotus/pull/7108)) + +## New Features + +- feat(ci): include version/cli checks in tagged releases ([filecoin-project/lotus#7331](https://github.com/filecoin-project/lotus/pull/7331)) +- Show deal sizes is sealing sectors ([filecoin-project/lotus#7261](https://github.com/filecoin-project/lotus/pull/7261)) +- config for disabling NAT port mapping ([filecoin-project/lotus#7204](https://github.com/filecoin-project/lotus/pull/7204)) +- Add optional mined block list to miner info ([filecoin-project/lotus#7202](https://github.com/filecoin-project/lotus/pull/7202)) +- Shed: Create a verifreg command for when VRK isn't a multisig ([filecoin-project/lotus#7099](https://github.com/filecoin-project/lotus/pull/7099)) + +## Improvements + +- build macOS CI ([filecoin-project/lotus#7307](https://github.com/filecoin-project/lotus/pull/7307)) +- itests: remove cid equality comparison ([filecoin-project/lotus#7292](https://github.com/filecoin-project/lotus/pull/7292)) +- Add partition info to the 'sectors status' command ([filecoin-project/lotus#7246](https://github.com/filecoin-project/lotus/pull/7246)) +- chain: Cleanup consensus logic ([filecoin-project/lotus#7255](https://github.com/filecoin-project/lotus/pull/7255)) +- builder: Handle chainstore config in ConfigFullNode ([filecoin-project/lotus#7232](https://github.com/filecoin-project/lotus/pull/7232)) +- gateway: check tipsets in ChainGetPath ([filecoin-project/lotus#7230](https://github.com/filecoin-project/lotus/pull/7230)) +- Refactor events subsystem ([filecoin-project/lotus#7000](https://github.com/filecoin-project/lotus/pull/7000)) +- test: re-enable disabled tests ([filecoin-project/lotus#7211](https://github.com/filecoin-project/lotus/pull/7211)) +- Reduce lotus-miner startup spam ([filecoin-project/lotus#7205](https://github.com/filecoin-project/lotus/pull/7205)) +- Catch deal slashed because sector was terminated ([filecoin-project/lotus#7201](https://github.com/filecoin-project/lotus/pull/7201)) +- Insert miner and network power data as gibibytes to avoid int64 overflows ([filecoin-project/lotus#7194](https://github.com/filecoin-project/lotus/pull/7194)) +- sealing: Check piece CIDs after AddPiece ([filecoin-project/lotus#7185](https://github.com/filecoin-project/lotus/pull/7185)) +- markets: OnDealExpiredOrSlashed - get deal by proposal instead of deal ID ([filecoin-project/lotus#5431](https://github.com/filecoin-project/lotus/pull/5431)) +- Incoming: improve a log message ([filecoin-project/lotus#7181](https://github.com/filecoin-project/lotus/pull/7181)) +- journal: make current log file have a fixed named (#7112) ([filecoin-project/lotus#7112](https://github.com/filecoin-project/lotus/pull/7112)) +- call string.Repeat always with positive int ([filecoin-project/lotus#7104](https://github. com/filecoin-project/lotus/pull/7104)) +- itests: support larger sector sizes; add large deal test. ([filecoin-project/lotus#7148](https://github.com/filecoin-project/lotus/pull/7148)) +- Ignore nil throttler ([filecoin-project/lotus#7169](https://github.com/filecoin-project/lotus/pull/7169)) + +## Bug Fixes + +- fix: escape periods to match actual periods in version +- fix bug for CommittedCapacitySectorLifetime ([filecoin-project/lotus#7337](https://github.com/filecoin-project/lotus/pull/7337)) +- fix a panic in HandleRecoverDealIDs ([filecoin-project/lotus#7336](https://github.com/filecoin-project/lotus/pull/7336)) +- fix index out of range ([filecoin-project/lotus#7273](https://github.com/filecoin-project/lotus/pull/7273)) +- fix: correctly handle null blocks when detecting an expensive fork ([filecoin-project/lotus#7210](https://github.com/filecoin-project/lotus/pull/7210)) +- fix: make lotus soup use the correct dependencies ([filecoin-project/lotus#7221](https://github.com/filecoin-project/lotus/pull/7221)) +- fix: init restore adds empty storage.json ([filecoin-project/lotus#7025](https://github.com/filecoin-project/lotus/pull/7025)) +- fix: disable broken testground integration test ([filecoin-project/lotus#7187](https://github.com/filecoin-project/lotus/pull/7187)) +- fix TestDealPublisher ([filecoin-project/lotus#7173](https://github.com/filecoin-project/lotus/pull/7173)) +- fix: make TestTimedCacheBlockstoreSimple pass reliably ([filecoin-project/lotus#7174](https://github.com/filecoin-project/lotus/pull/7174)) +- Fix throttling bug ([filecoin-project/lotus#7177](https://github.com/filecoin-project/lotus/pull/7177)) +- sealing: Fix sector state accounting with FinalizeEarly ([filecoin-project/lotus#7256](https://github.com/filecoin-project/lotus/pull/7256)) +- docker entrypoint.sh missing variable escape character ([filecoin-project/lotus#7291](https://github.com/filecoin-project/lotus/pull/7291)) +- sealing: Fix retry loop in SubmitCommitAggregate ([filecoin-project/lotus#7245](https://github.com/filecoin-project/lotus/pull/7245)) +- sectors expired: Handle precomitted and unproven sectors correctly ([filecoin-project/lotus#7236](https://github.com/filecoin-project/lotus/pull/7236)) +- stores: Fix reserved disk usage log spam ([filecoin-project/lotus#7233](https://github.com/filecoin-project/lotus/pull/7233)) + + +## Dependency Updates + +- github.com/filecoin-project/go-fil-markets (v1.8.1 -> v1.12.0): +- github.com/filecoin-project/go-data-transfer (v1.7.8 -> v1.10.1): +- update to ffi to update-bellperson-proofs-v9-0-2 ([filecoin-project/lotus#7369](https://github.com/filecoin-project/lotus/pull/7369)) +- fix(deps): use go-graphsync v0.9.3 with hotfix +- Update to unified go-graphsync v0.9.0 ([filecoin-project/lotus#7197](https://github.com/filecoin-project/lotus/pull/7197)) + +## Others + +- v1.11.3-rc2 ([filecoin-project/lotus#7371](https://github.com/filecoin-project/lotus/pull/7371)) +- v1.11.3-rc1 ([filecoin-project/lotus#7299](https://github.com/filecoin-project/lotus/pull/7299)) +- Increase threshold from 0.5% to 1% ([filecoin-project/lotus#7262](https://github.com/filecoin-project/lotus/pull/7262)) +- ci: exclude cruft from code coverage ([filecoin-project/lotus#7189](https://github.com/filecoin-project/lotus/pull/7189)) +- Bump version to v1.11.3-dev ([filecoin-project/lotus#7180](https://github.com/filecoin-project/lotus/pull/7180)) +- test: disable flaky TestBatchDealInput ([filecoin-project/lotus#7176](https://github.com/filecoin-project/lotus/pull/7176)) +- Turn off patch ([filecoin-project/lotus#7172](https://github.com/filecoin-project/lotus/pull/7172)) +- test: disable flaky TestSimultaneousTransferLimit ([filecoin-project/lotus#7153](https://github.com/filecoin-project/lotus/pull/7153)) + + +## Contributors + +| Contributor | Commits | Lines ± | Files Changed | +|-------------|---------|---------|---------------| +| @magik6k | 39 | +3311/-1825 | 179 | +| @Stebalien | 23 | +1935/-1417 | 84 | +| @dirkmc | 12 | +921/-732 | 111 | +| @dirkmc | 12 | +663/-790 | 30 | +| @hannahhoward | 3 | +482/-275 | 46 | +| @travisperson | 1 | +317/-65 | 5 | +| @jennijuju | 11 | +223/-126 | 24 | +| @hannahhoward | 7 | +257/-55 | 16 | +| @nonsense| 9 | +258/-37 | 19 | +| @raulk | 4 | +127/-36 | 13 | +| @raulk | 1 | +43/-60 | 15 | +| @arajasek | 4 | +74/-8 | 10 | +| @Frank | 2 | +68/-8 | 3 | +| @placer14| 2 | +52/-1 | 4 | +| @ldoublewood | 2 | +15/-13 | 3 | +| @lanzafame | 1 | +16/-2 | 1 | +| @aarshkshah1992 | 2 | +11/-6 | 2 | +| @ZenGround0 | 2 | +7/-6 | 2 | +| @ognots | 1 | +0/-10 | 2 | +| @KAYUII | 2 | +4/-4 | 2 | +| @lanzafame | 1 | +6/-0 | 1 | +| @jacobheun | 1 | +3/-3 | 1 | +| @frank | 1 | +4/-0 | 1 | + + +# v1.11.2 / 2021-09-06 + +lotus v1.11.2 is a feature release that's **highly recommended ALL lotus users to upgrade**, including node operators, +storage providers and clients. + +## Highlights +- 🌟🌟🌟 Introduce Dagstore and CARv2 for deal-making (#6671) ([filecoin-project/lotus#6671](https://github.com/filecoin-project/lotus/pull/6671)) + - **[lotus miner markets' Dagstore](https://lotus.filecoin.io/storage-providers/operate/dagstore/)** is a + component of the `markets` subsystem in lotus-miner. It is a sharded store to hold large IPLD graphs efficiently, + packaged as location-transparent attachable CAR files and it replaces the former Badger staging blockstore. It + is designed to provide high efficiency and throughput, and minimize resource utilization during deal-making operations. + The dagstore also leverages the indexing features of [CARv2](https://github.com/ipld/ipld/blob/master/specs/transport/car/carv2/index.md) to enable plan CAR files to act as read and write + blockstores, which are served as the direct medium for data exchanges in markets for both storage and retrieval + deal making without requiring intermediate buffers. + - In the future, lotus will leverage and interact with Dagstore a lot for new features and improvements for deal + making, therefore, it's highly recommended to lotus users to go through [Lotus Miner: About the markets dagstore](https://lotus.filecoin.io/storage-providers/operate/dagstore/) thoroughly to learn more about Dagstore's + conceptual overview, terminology, directory structure, configuration and so on. + - **Note**: + - When you first start your lotus-miner or market subsystem with this release, a one-time/first-time **dagstore migration** will be triggered which replaces the former Badger staging blockstore with dagstore. We highly + recommend storage providers to read this [section](https://lotus.filecoin.io/storage-providers/operate/dagstore/#first-time-migration) to learn more about + what the process does, what to expect and how monitor it. + - It is highly recommended to **wait all ongoing data transfer to finish or cancel inbound storage deals that + are still transferring**, using the `lotus-miner data-transfers cancel` command before upgrade your market nodes. Reason being that the new dagstore changes attributes in the internal deal state objects, and the paths to the staging CARs where the deal data was being placed will be lost. + - ‼️Having your dags initialized will become important in the near feature for you to provide a better storage + and retrieval service. We'd suggest you to start [forced bulk initialization] soon if possible as this process + places relatively high IP workload on your storage system and is better to be carried out gradually and over a + longer timeframe. Read how to do properly perform a force bulk initialization [here](https://lotus.filecoin.io/storage-providers/operate/dagstore/#forcing-bulk-initialization). + - ⏮ Rollback Alert(from v1.11.2-rcX to any version lower): If a storages deal is initiated with M1/v1.11.2(-rcX) + release, it needs to get to the `StorageDealAwaitingPrecommit` state before you can do a version rollback or the markets process may panic. + - 💙 **Special thanks to [MinerX fellows for testing and providing valuable feedbacks](https://github.com/filecoin-project/lotus/discussions/6852) for Dagstore in the past month!** +- 🌟🌟 rpcenc: Support reader redirect ([filecoin-project/lotus#6952](https://github.com/filecoin-project/lotus/pull/6952)) + - This allows market processes to send piece bytes directly to workers involved on `AddPiece`. +- Extending sectors: more practical and flexible tools ([filecoin-project/lotus#6097](https://github.com/filecoin-project/lotus/pull/6097)) + - `lotus-miner sectors check-expire` to inspect expiring sectors. + - `lotus-miner sectors renew` for renewing expiring sectors, see the command help menu for customizable option + like `extension`, `new-expiration` and so on. +- ‼️ MpoolReplaceCmd ( lotus mpool replace`) now takes FIL for fee-limit ([filecoin-project/lotus#6927](https://github.com/filecoin-project/lotus/pull/6927)) +- Drop townhall/chainwatch ([filecoin-project/lotus#6912](https://github.com/filecoin-project/lotus/pull/6912)) + - ChainWatch is no longer supported by lotus. +- Configurable CC Sector Expiration ([filecoin-project/lotus#6803](https://github.com/filecoin-project/lotus/pull/6803)) + - Set `CommittedCapacitySectorLifetime` in lotus-miner/config.toml to specify the default expiration for a new CC + sector, value must be between 180-540 days inclusive. + +## New Features +- api/command for encoding actor params ([filecoin-project/lotus#7150](https://github.com/filecoin-project/lotus/pull/7150)) +- shed: Support raw encoding in cid id ([filecoin-project/lotus#7149](https://github.com/filecoin-project/lotus/pull/7149)) +- feat(miner deals): create subdir to miner repo for staged deals ([filecoin-project/lotus#6853](https://github.com/filecoin-project/lotus/pull/6853)) +- Support --actor in miner actor control list ([filecoin-project/lotus#7027](https://github.com/filecoin-project/lotus/pull/7027)) +- Shed: Include network name in genesis-verify ([filecoin-project/lotus#7019](https://github.com/filecoin-project/lotus/pull/7019)) +- feat: add ChainGetTipSetAfterHeight ([filecoin-project/lotus#6990](https://github.com/filecoin-project/lotus/pull/6990)) +- lotus-shed splitstore clear command ([filecoin-project/lotus#6967](https://github.com/filecoin-project/lotus/pull/6967)) + +## Improvements +- improve get api error messages ([filecoin-project/lotus#7088](https://github.com/filecoin-project/lotus/pull/7088)) +- Strict major minor version checking on v0 and v1 apis ([filecoin-project/lotus#7038](https://github.com/filecoin-project/lotus/pull/7038)) +- make lotus-miner net commands hit markets subsystem. ([filecoin-project/lotus#7042](https://github.com/filecoin-project/lotus/pull/7042)) +- Test with latest actors version ([filecoin-project/lotus#6998](https://github.com/filecoin-project/lotus/pull/6998)) +- Reduce splitstore memory usage during chain walks ([filecoin-project/lotus#6949](https://github.com/filecoin-project/lotus/pull/6949)) +- Remove forgotten non-functioning config from the pre-mainnet days ([filecoin-project/lotus#6970](https://github.com/filecoin-project/lotus/pull/6970)) +- add explicit error msg if repo dir does not exist ([filecoin-project/lotus#6909](https://github.com/filecoin-project/lotus/pull/6909)) +- Test/pledge batching msg prop ([filecoin-project/lotus#6537](https://github.com/filecoin-project/lotus/pull/6537)) +- reasonable max value for initial sector expiration ([filecoin-project/lotus#6099](https://github.com/filecoin-project/lotus/pull/6099)) +- support MARKETS_API_INFO env var, and markets-repo, markets-api-url CLI flags. ([filecoin-project/lotus#6936](https://github.com/filecoin-project/lotus/pull/6936)) +- Improve formatting of workers CLI ([filecoin-project/lotus#6942](https://github.com/filecoin-project/lotus/pull/6942)) +- make: set default GOCC earlier ([filecoin-project/lotus#6932](https://github.com/filecoin-project/lotus/pull/6932)) +- Moving GC Followup ([filecoin-project/lotus#6905](https://github.com/filecoin-project/lotus/pull/6905)) +- Log more call context during errors ([filecoin-project/lotus#6918](https://github.com/filecoin-project/lotus/pull/6918)) +- polish(errors): better state tree errors ([filecoin-project/lotus#6923](https://github.com/filecoin-project/lotus/pull/6923)) +- adding an RuntimeSubsystems API to storage miner; fix `lotus-miner info` ([filecoin-project/lotus#6906](https://github.com/filecoin-project/lotus/pull/6906)) +- Reduce entropy in the chain package ([filecoin-project/lotus#6889](https://github.com/filecoin-project/lotus/pull/6889)) +- make: Allow setting Go compiler with GOCC ([filecoin-project/lotus#6911](https://github.com/filecoin-project/lotus/pull/6911)) + +## Bug Fixes +- sealing: Fix RecoverDealIDs loop with changed PieceCID ([filecoin-project/lotus#7117](https://github.com/filecoin-project/lotus/pull/7117)) +- Fix error handling in SectorAddPieceToAny api impl ([filecoin-project/lotus#7135](https://github.com/filecoin-project/lotus/pull/7135)) +- add rice box to required binaries ([filecoin-project/lotus#7125](https://github.com/filecoin-project/lotus/pull/7125)) +- fix(miner): always create miner deal staging directory (#7098) ([filecoin-project/lotus#7098](https://github.com/filecoin-project/lotus/pull/7098)) +- fix build after merging #6097. (#7096) ([filecoin-project/lotus#7096](https://github.com/filecoin-project/lotus/pull/7096)) +- fix: don't check for t_aux when proving ([filecoin-project/lotus#7011](https://github.com/filecoin-project/lotus/pull/7011)) +- fix: vet actors shims ([filecoin-project/lotus#6999](https://github.com/filecoin-project/lotus/pull/6999)) +- fix: more logging in maybeStartBatch error ([filecoin-project/lotus#6996](https://github.com/filecoin-project/lotus/pull/6996)) +- fix flaky TestDealPublisher and re-enable ([filecoin-project/lotus#6991](https://github.com/filecoin-project/lotus/pull/6991)) +- fix skipCount ([filecoin-project/lotus#6940](https://github.com/filecoin-project/lotus/pull/6940)) +- fix bug in MpoolPending message exclusion ([filecoin-project/lotus#6945](https://github.com/filecoin-project/lotus/pull/6945)) +- PreCommitPolicy: Don't try to align expirations on proving period boundaries ([filecoin-project/lotus#7018](https://github.com/filecoin-project/lotus/pull/7018)) +- make: fix version check when using gotip ([filecoin-project/lotus#6916](https://github.com/filecoin-project/lotus/pull/6916)) +- fix ticket check ([filecoin-project/lotus#6882](https://github.com/filecoin-project/lotus/pull/6882)) + +## Dependency Updates +- github.com/filecoin-project/go-data-transfer (v1.7.2 -> v1.7.8): +- github.com/filecoin-project/go-fil-markets (v1.6.2 -> v1.8.1): +- update go-libp2p-pubsub to v0.5.4 ([filecoin-project/lotus#6958](https://github.com/filecoin-project/lotus/pull/6958)) +- integrate the proof patch: tag proofs-v9-revert-deps-hotfix +- Update markets, dt and graphsync ([filecoin-project/lotus#7160](https://github.com/filecoin-project/lotus/pull/7160)) +- Remove replace directive for multihash dep (#7113) ([filecoin-project/lotus#7113](https://github.com/filecoin-project/lotus/pull/7113)) +- upgrade upstream dependencies. ([filecoin-project/lotus#7115](https://github.com/filecoin-project/lotus/pull/7115)) +- Update to latest FFI ([filecoin-project/lotus#7110](https://github.com/filecoin-project/lotus/pull/7110)) +- Update state machine deps for logging ([filecoin-project/lotus#6941](https://github.com/filecoin-project/lotus/pull/6941)) +- Update deps for more logging in data transfer and markets ([filecoin-project/lotus#6937](https://github.com/filecoin-project/lotus/pull/6937)) +- Update to branches with improved logging ([filecoin-project/lotus#6919](https://github.com/filecoin-project/lotus/pull/6919)) +- update go-libp2p-pubsub to v0.5.3 ([filecoin-project/lotus#6907](https://github.com/filecoin-project/lotus/pull/6907)) + +## Others +- Fix nits and see if codecov works now with auto ([filecoin-project/lotus#7151](https://github.com/filecoin-project/lotus/pull/7151)) +- Codecov Projects ([filecoin-project/lotus#7147](https://github.com/filecoin-project/lotus/pull/7147)) +- remove m1 templates and make area selection multi-optionable ([filecoin-project/lotus#7121](https://github.com/filecoin-project/lotus/pull/7121)) +- release -> master ([filecoin-project/lotus#7105](https://github.com/filecoin-project/lotus/pull/7105)) +- Lotus release process - how we make releases ([filecoin-project/lotus#6944](https://github.com/filecoin-project/lotus/pull/6944)) +- codecov: fix mock name ([filecoin-project/lotus#7039](https://github.com/filecoin-project/lotus/pull/7039)) +- codecov: fix regexes ([filecoin-project/lotus#7037](https://github.com/filecoin-project/lotus/pull/7037)) +- chore: disable flaky test ([filecoin-project/lotus#6957](https://github.com/filecoin-project/lotus/pull/6957)) +- set buildtype in nerpa and butterfly ([filecoin-project/lotus#6085](https://github.com/filecoin-project/lotus/pull/6085)) +- release v1.11.1 backport -> master ([filecoin-project/lotus#6929](https://github.com/filecoin-project/lotus/pull/6929)) +- chore: fixup issue templates ([filecoin-project/lotus#6899](https://github.com/filecoin-project/lotus/pull/6899)) +- bump master version to v1.11.2-dev ([filecoin-project/lotus#6903](https://github.com/filecoin-project/lotus/pull/6903)) +- releases -> master for v1.11.0 ([filecoin-project/lotus#6894](https://github.com/filecoin-project/lotus/pull/6894)) + + +Contributors + +| Contributor | Commits | Lines ± | Files Changed | +|-------------|---------|---------|---------------| +| @magik6k | 23 | +5040/-8389 | 114 | +| @aarshkshah1992 | 11 | +4859/-1078 | 101 | +| @raulk | 5 | +4170/-1662 | 104 | +| @vyzo | 30 | +1092/-702 | 49 | +| @nonsense | 6 | +630/-472 | 19 | +| @ZenGround0 | 31 | +556/-274 | 74 | +| @He Weidong | 16 | +680/-128 | 16 | +| @raulk | 16 | +444/-277 | 49 | +| @Stebalien | 11 | +403/-259 | 48 | +| @jennijuju| 17 | +276/-281 | 42 | +| @dirkmc | 5 | +204/-138 | 20 | +| @placer14 | 7 | +178/-77 | 17 | +| @BlocksOnAChain | 1 | +138/-0 | 1 | +| @Frrist | 1 | +63/-56 | 2 | +| @arajasek | 7 | +74/-42 | 13 | +| @frrist | 2 | +67/-6 | 6 | +| @hannahhoward | 2 | +13/-11 | 3 | +| @coryschwartz | 1 | +16/-6 | 3 | +| @whyrusleeping | 1 | +7/-7 | 1 | +| @hunjixin | 1 | +8/-6 | 1 | +| @aarshkshah1992 | 1 | +6/-6 | 2 | +| @dirkmc | 2 | +8/-0 | 2 | +| @mx | 2 | +6/-1 | 2 | +| @travisperson | 1 | +3/-2 | 1 | +| @jennijuju | 2 | +2/-2 | 2 | +| @ribasushi | 1 | +1/-2 | 2 | + +# 1.11.1 / 2021-08-16 + +> Note: for discussion about this release, please comment [here](https://github.com/filecoin-project/lotus/discussions/6904) + +This is a **highly recommended** but optional Lotus v1.11.1 release that introduces many deal making and datastore improvements and new features along with other bug fixes. + +## Highlights +- ⭐️⭐️⭐️[**lotus-miner market subsystem**](https://lotus.filecoin.io/storage-providers/advanced-configurations/split-markets-miners/) is introduced in this release! It is **highly recommended** for storage providers to run markets processes on a separate machine! Doing so, only this machine needs to exposes public ports for deal making. This also means that the other miner operations can now be completely isolated by from the deal making processes and storage providers can stop and restarts the markets process without affecting an ongoing Winning/Window PoSt! + - More details on the concepts, architecture and how to split the market process can be found [here](https://lotus.filecoin.io/storage-providers/advanced-configurations/split-markets-miners/#concepts). + - Base on your system setup(running on separate machines, same machine and so on), please see the suggested practice by community members [here](https://github.com/filecoin-project/lotus/discussions/7047#discussion-3515335). + - Note: if you are running lotus-worker on a different machine, you will need to set `MARKETS_API_INFO` for certain CLI to work properly. This will be improved by #7072. + - Huge thanks to MinerX fellows for [helping testing the implementation, reporting the issues so they were fixed by now and providing feedbacks](https://github.com/filecoin-project/lotus/discussions/6861) to user docs in the past three weeks! +- Config for collateral from miner available balance ([filecoin-project/lotus#6629](https://github.com/filecoin-project/lotus/pull/6629)) + - Better control your sector collateral payment by setting `CollateralFromMinerBalance`, `AvailableBalanceBuffer` and `DisableCollateralFallback`. + - `CollateralFromMinerBalance`: whether to use available miner balance for sector collateral instead of sending it with each message, default is `false`. + - `AvailableBalanceBuffer`: minimum available balance to keep in the miner actor before sending it with messages, default is 0FIL. + - `DisableCollateralFallback`: whether to send collateral with messages even if there is no available balance in the miner actor, default is `false`. +- Config for deal publishing control addresses ([filecoin-project/lotus#6697](https://github.com/filecoin-project/lotus/pull/6697)) + - Set `DealPublishControl` to set the wallet used for sending `PublishStorageDeals` messages, instructions [here](https://lotus.filecoin.io/storage-providers/operate/addresses/#control-addresses). +- Config UX improvements ([filecoin-project/lotus#6848](https://github.com/filecoin-project/lotus/pull/6848)) + - You can now preview the the default and updated node config by running `lotus/lotus-miner config default/updated` + +## New Features +- ⭐️⭐️⭐️ Support standalone miner-market process ([filecoin-project/lotus#6356](https://github.com/filecoin-project/lotus/pull/6356)) +- **⭐️⭐️ Experimental** [Splitstore]((https://github.com/filecoin-project/lotus/blob/master/blockstore/splitstore/README.md)) (more details coming in v1.11.2! Stay tuned! Join the discussion [here](https://github.com/filecoin-project/lotus/discussions/5788) if you have questions!) : + - Improve splitstore warmup ([filecoin-project/lotus#6867](https://github.com/filecoin-project/lotus/pull/6867)) + - Moving GC for badger ([filecoin-project/lotus#6854](https://github.com/filecoin-project/lotus/pull/6854)) + - splitstore shed utils ([filecoin-project/lotus#6811](https://github.com/filecoin-project/lotus/pull/6811)) + - fix warmup by decoupling state from message receipt walk ([filecoin-project/lotus#6841](https://github.com/filecoin-project/lotus/pull/6841)) + - Splitstore: support on-disk marksets using badger ([filecoin-project/lotus#6833](https://github.com/filecoin-project/lotus/pull/6833)) + - cache loaded block messages ([filecoin-project/lotus#6760](https://github.com/filecoin-project/lotus/pull/6760)) + - Splitstore: add retention policy option for keeping messages in the hotstore ([filecoin-project/lotus#6775](https://github.com/filecoin-project/lotus/pull/6775)) + - Introduce the LOTUS_CHAIN_BADGERSTORE_DISABLE_FSYNC envvar ([filecoin-project/lotus#6817](https://github.com/filecoin-project/lotus/pull/6817)) + - Splitstore: add support for protecting out of chain references in the blockstore ([filecoin-project/lotus#6777](https://github.com/filecoin-project/lotus/pull/6777)) + - Implement exposed splitstore ([filecoin-project/lotus#6762](https://github.com/filecoin-project/lotus/pull/6762)) + - Splitstore code reorg ([filecoin-project/lotus#6756](https://github.com/filecoin-project/lotus/pull/6756)) + - Splitstore: Some small fixes ([filecoin-project/lotus#6754](https://github.com/filecoin-project/lotus/pull/6754)) + - Splitstore Enhanchements ([filecoin-project/lotus#6474](https://github.com/filecoin-project/lotus/pull/6474)) +- lotus-shed: initial export cmd for markets related metadata ([filecoin-project/lotus#6840](https://github.com/filecoin-project/lotus/pull/6840)) +- add a very verbose -vv flag to lotus and lotus-miner. ([filecoin-project/lotus#6888](https://github.com/filecoin-project/lotus/pull/6888)) +- Add allocated sectorid vis ([filecoin-project/lotus#4638](https://github.com/filecoin-project/lotus/pull/4638)) +- add a command for compacting sector numbers bitfield ([filecoin-project/lotus#4640](https://github.com/filecoin-project/lotus/pull/4640)) + - Run `lotus-miner actor compact-allocated` to compact sector number allocations to reduce the size of the allocated sector number bitfield. +- Add ChainGetMessagesInTipset API ([filecoin-project/lotus#6642](https://github.com/filecoin-project/lotus/pull/6642)) +- Handle the --color flag via proper global state ([filecoin-project/lotus#6743](https://github.com/filecoin-project/lotus/pull/6743)) + - Enable color by default only if os.Stdout is a TTY ([filecoin-project/lotus#6696](https://github.com/filecoin-project/lotus/pull/6696)) + - Stop outputing ANSI color on non-TTY ([filecoin-project/lotus#6694](https://github.com/filecoin-project/lotus/pull/6694)) +- Envvar to disable slash filter ([filecoin-project/lotus#6620](https://github.com/filecoin-project/lotus/pull/6620)) +- commit batch: AggregateAboveBaseFee config ([filecoin-project/lotus#6650](https://github.com/filecoin-project/lotus/pull/6650)) +- shed tool to estimate aggregate network fees ([filecoin-project/lotus#6631](https://github.com/filecoin-project/lotus/pull/6631)) + +## Bug Fixes +- Fix padding of deals, which only partially shipped in #5988 ([filecoin-project/lotus#6683](https://github.com/filecoin-project/lotus/pull/6683)) +- fix deal concurrency test failures by upgrading graphsync and others ([filecoin-project/lotus#6724](https://github.com/filecoin-project/lotus/pull/6724)) +- fix: on randomness change, use new rand ([filecoin-project/lotus#6805](https://github.com/filecoin-project/lotus/pull/6805)) - fix: always check if StateSearchMessage returns nil ([filecoin-project/lotus#6802](https://github.com/filecoin-project/lotus/pull/6802)) +- test: fix flaky window post tests ([filecoin-project/lotus#6804](https://github.com/filecoin-project/lotus/pull/6804)) +- wrap close(wait) with sync.Once to avoid panic ([filecoin-project/lotus#6800](https://github.com/filecoin-project/lotus/pull/6800)) +- fixes #6786 segfault ([filecoin-project/lotus#6787](https://github.com/filecoin-project/lotus/pull/6787)) +- ClientRetrieve stops on cancel([filecoin-project/lotus#6739](https://github.com/filecoin-project/lotus/pull/6739)) +- Fix bugs in sectors extend --v1-sectors ([filecoin-project/lotus#6066](https://github.com/filecoin-project/lotus/pull/6066)) +- fix "lotus-seed genesis car" error "merkledag: not found" ([filecoin-project/lotus#6688](https://github.com/filecoin-project/lotus/pull/6688)) +- Get retrieval pricing input should not error out on a deal state fetch ([filecoin-project/lotus#6679](https://github.com/filecoin-project/lotus/pull/6679)) +- Fix more CID double-encoding as hex ([filecoin-project/lotus#6680](https://github.com/filecoin-project/lotus/pull/6680)) +- storage: Fix FinalizeSector with sectors in stoage paths ([filecoin-project/lotus#6653](https://github.com/filecoin-project/lotus/pull/6653)) +- Fix tiny error in check-client-datacap ([filecoin-project/lotus#6664](https://github.com/filecoin-project/lotus/pull/6664)) +- Fix: precommit_batch method used the wrong cfg.CommitBatchWait ([filecoin-project/lotus#6658](https://github.com/filecoin-project/lotus/pull/6658)) +- fix ticket expiration check ([filecoin-project/lotus#6635](https://github.com/filecoin-project/lotus/pull/6635)) +- remove precommit check in handleCommitFailed ([filecoin-project/lotus#6634](https://github.com/filecoin-project/lotus/pull/6634)) +- fix prove commit aggregate send token amount ([filecoin-project/lotus#6625](https://github.com/filecoin-project/lotus/pull/6625)) + +## Improvements +- Eliminate inefficiency in markets logging ([filecoin-project/lotus#6895](https://github.com/filecoin-project/lotus/pull/6895)) +- rename `cmd/lotus{-storage=>}-miner` to match binary. ([filecoin-project/lotus#6886](https://github.com/filecoin-project/lotus/pull/6886)) +- fix racy TestSimultanenousTransferLimit. ([filecoin-project/lotus#6862](https://github.com/filecoin-project/lotus/pull/6862)) +- ValidateBlock: Assert that block header height's are greater than parents ([filecoin-project/lotus#6872](https://github.com/filecoin-project/lotus/pull/6872)) +- feat: Don't panic when api impl is nil ([filecoin-project/lotus#6857](https://github.com/filecoin-project/lotus/pull/6857)) +- add docker-compose file ([filecoin-project/lotus#6544](https://github.com/filecoin-project/lotus/pull/6544)) +- easy way to make install app ([filecoin-project/lotus#5183](https://github.com/filecoin-project/lotus/pull/5183)) +- api: Separate the Net interface from Common ([filecoin-project/lotus#6627](https://github.com/filecoin-project/lotus/pull/6627)) - add StateReadState to gateway api ([filecoin-project/lotus#6818](https://github.com/filecoin-project/lotus/pull/6818)) +- add SealProof in SectorBuilder ([filecoin-project/lotus#6815](https://github.com/filecoin-project/lotus/pull/6815)) +- sealing: Handle preCommitParams errors more correctly ([filecoin-project/lotus#6763](https://github.com/filecoin-project/lotus/pull/6763)) +- ClientFindData: always fetch peer id from chain ([filecoin-project/lotus#6807](https://github.com/filecoin-project/lotus/pull/6807)) +- test: handle null blocks in TestForkRefuseCall ([filecoin-project/lotus#6758](https://github.com/filecoin-project/lotus/pull/6758)) +- Add more deal details to lotus-miner info ([filecoin-project/lotus#6708](https://github.com/filecoin-project/lotus/pull/6708)) +- add election backtest ([filecoin-project/lotus#5950](https://github.com/filecoin-project/lotus/pull/5950)) +- add dollar sign ([filecoin-project/lotus#6690](https://github.com/filecoin-project/lotus/pull/6690)) +- get-actor cli spelling fix ([filecoin-project/lotus#6681](https://github.com/filecoin-project/lotus/pull/6681)) +- polish(statetree): accept a context in statetree diff for timeouts ([filecoin-project/lotus#6639](https://github.com/filecoin-project/lotus/pull/6639)) +- Add helptext to lotus chain export ([filecoin-project/lotus#6672](https://github.com/filecoin-project/lotus/pull/6672)) +- add an incremental nonce itest. ([filecoin-project/lotus#6663](https://github.com/filecoin-project/lotus/pull/6663)) +- commit batch: Initialize the FailedSectors map ([filecoin-project/lotus#6647](https://github.com/filecoin-project/lotus/pull/6647)) +- Fast-path retry submitting commit aggregate if commit is still valid ([filecoin-project/lotus#6638](https://github.com/filecoin-project/lotus/pull/6638)) +- Reuse timers in sealing batch logic ([filecoin-project/lotus#6636](https://github.com/filecoin-project/lotus/pull/6636)) + +## Dependency Updates +- Update to proof v8.0.3 ([filecoin-project/lotus#6890](https://github.com/filecoin-project/lotus/pull/6890)) +- update to go-fil-market v1.6.0 ([filecoin-project/lotus#6885](https://github.com/filecoin-project/lotus/pull/6885)) +- Bump go-multihash, adjust test for supported version ([filecoin-project/lotus#6674](https://github.com/filecoin-project/lotus/pull/6674)) +- github.com/filecoin-project/go-data-transfer (v1.6.0 -> v1.7.2): +- github.com/filecoin-project/go-fil-markets (v1.5.0 -> v1.6.2): +- github.com/filecoin-project/go-padreader (v0.0.0-20200903213702-ed5fae088b20 -> v0.0.0-20210723183308-812a16dc01b1) +- github.com/filecoin-project/go-state-types (v0.1.1-0.20210506134452-99b279731c48 -> v0.1.1-0.20210810190654-139e0e79e69e) +- github.com/filecoin-project/go-statemachine (v0.0.0-20200925024713-05bd7c71fbfe -> v1.0.1) +- update go-libp2p-pubsub to v0.5.0 ([filecoin-project/lotus#6764](https://github.com/filecoin-project/lotus/pull/6764)) + +## Others +- Master->v1.11.1 ([filecoin-project/lotus#7051](https://github.com/filecoin-project/lotus/pull/7051)) +- v1.11.1-rc2 ([filecoin-project/lotus#6966](https://github.com/filecoin-project/lotus/pull/6966)) +- Backport master -> v1.11.1 ([filecoin-project/lotus#6965](https://github.com/filecoin-project/lotus/pull/6965)) +- Fixes in master -> release ([filecoin-project/lotus#6933](https://github.com/filecoin-project/lotus/pull/6933)) +- Add changelog for v1.11.1-rc1 and bump the version ([filecoin-project/lotus#6900](https://github.com/filecoin-project/lotus/pull/6900)) +- Fix merge release -> v1.11.1 ([filecoin-project/lotus#6897](https://github.com/filecoin-project/lotus/pull/6897)) +- Update RELEASE_ISSUE_TEMPLATE.md ([filecoin-project/lotus#6880](https://github.com/filecoin-project/lotus/pull/6880)) +- Add github actions for staled pr ([filecoin-project/lotus#6879](https://github.com/filecoin-project/lotus/pull/6879)) +- Update issue templates and add templates for M1 ([filecoin-project/lotus#6856](https://github.com/filecoin-project/lotus/pull/6856)) +- Fix links in issue templates +- Update issue templates to forms ([filecoin-project/lotus#6798](https://github.com/filecoin-project/lotus/pull/6798) +- Nerpa v13 upgrade ([filecoin-project/lotus#6837](https://github.com/filecoin-project/lotus/pull/6837)) +- add docker-compose file ([filecoin-project/lotus#6544](https://github.com/filecoin-project/lotus/pull/6544)) +- release -> master ([filecoin-project/lotus#6828](https://github.com/filecoin-project/lotus/pull/6828)) +- Resurrect CODEOWNERS, but for maintainers group ([filecoin-project/lotus#6773](https://github.com/filecoin-project/lotus/pull/6773)) +- Master disclaimer ([filecoin-project/lotus#6757](https://github.com/filecoin-project/lotus/pull/6757)) +- Create stale.yml ([filecoin-project/lotus#6747](https://github.com/filecoin-project/lotus/pull/6747)) +- Release template: Update all testnet infra at once ([filecoin-project/lotus#6710](https://github.com/filecoin-project/lotus/pull/6710)) +- Release Template: remove binary validation step ([filecoin-project/lotus#6709](https://github.com/filecoin-project/lotus/pull/6709)) +- Reset of the interop network ([filecoin-project/lotus#6689](https://github.com/filecoin-project/lotus/pull/6689)) +- Update version.go to 1.11.1 ([filecoin-project/lotus#6621](https://github.com/filecoin-project/lotus/pull/6621)) + +## Contributors + +| Contributor | Commits | Lines ± | Files Changed | +|-------------|---------|---------|---------------| +| @vyzo | 313 | +8928/-6010 | 415 | +| @nonsense | 103 | +6041/-4041 | 304 | +| @magik6k | 37 | +3851/-1611 | 146 | +| @ZenGround0 | 24 | +1693/-1394 | 95 | +| @placer14 | 1 | +2310/-578 | 8 | +| @dirkmc | 7 | +1154/-726 | 29 | +| @raulk | 44 | +969/-616 | 141 | +| @jennijuju | 15 | +682/-354 | 47 | +| @ribasushi | 18 | +469/-273 | 64 | +| @coryschwartz | 5 | +576/-135 | 14 | +| @hunjixin | 7 | +404/-82 | 19 | +| @dirkmc | 17 | +348/-47 | 17 | +| @tchardin | 2 | +262/-34 | 5 | +| @aarshkshah1992 | 9 | +233/-63 | 44 | +| @Kubuxu | 4 | +254/-16 | 4 | +| @hannahhoward | 6 | +163/-75 | 8 | +| @whyrusleeping | 4 | +157/-16 | 9 | +| @Whyrusleeping | 2 | +87/-66 | 10 | +| @arajasek | 10 | +81/-53 | 13 | +| @zgfzgf | 2 | +104/-4 | 2 | +| @aarshkshah1992 | 6 | +85/-19 | 10 | +| @llifezou | 4 | +59/-20 | 4 | +| @Stebalien | 7 | +47/-17 | 9 | +| @johnli-helloworld | 3 | +46/-15 | 5 | +| @frrist | 1 | +28/-23 | 2 | +| @hannahhoward | 4 | +46/-5 | 11 | +| @Jennifer | 4 | +31/-2 | 4 | +| @wangchao | 1 | +1/-27 | 1 | +| @jennijuju | 2 | +7/-21 | 2 | +| @chadwick2143 | 1 | +15/-1 | 1 | +| @Jerry | 2 | +9/-4 | 2 | +| Steve Loeppky | 2 | +12/-0 | 2 | +| David Dias | 1 | +9/-0 | 1 | +| dependabot[bot] | 1 | +3/-3 | 1 | +| zhoutian527 | 1 | +2/-2 | 1 | +| xloem | 1 | +4/-0 | 1 | +| | 2 | +2/-2 | 3 | +| Liviu Damian | 2 | +2/-2 | 2 | +| @jimpick | 2 | +2/-2 | 2 | +| Frank | 1 | +3/-0 | 1 | +| turuslan | 1 | +1/-1 | 1 | +| Kirk Baird | 1 | +0/-0 | 1 | + +# 1.11.0 / 2021-07-22 + +This is a **highly recommended** release of Lotus that have many bug fixes, improvements and new features. + +## Highlights +- Miner SimultaneousTransfers config ([filecoin-project/lotus#6612](https://github.com/filecoin-project/lotus/pull/6612)) +- Miner SimultaneousTransfers config ([filecoin-project/lotus#6612](https://github.com/filecoin-project/lotus/pull/6612)) + - Set `SimultaneousTransfers` in lotus miner config to configure the maximum number of parallel online data transfers, including both storage and retrieval deals. +- Dynamic Retrieval pricing ([filecoin-project/lotus#6175](https://github.com/filecoin-project/lotus/pull/6175)) + - Customize your retrieval ask price, see a quick tutorial [here](https://github.com/filecoin-project/lotus/discussions/6780). +- Robust message management ([filecoin-project/lotus#5822](https://github.com/filecoin-project/lotus/pull/5822)) + - run `lotus mpool manage and follow the instructions! + - Demo available at https://www.youtube.com/watch?v=QDocpLQjZgQ. +- Add utils to use multisigs as miner owners ([filecoin-project/lotus#6490](https://github.com/filecoin-project/lotus/pull/6490)) + +## More New Features +- feat: implement lotus-sim ([filecoin-project/lotus#6406](https://github.com/filecoin-project/lotus/pull/6406)) +- implement a command to export a car ([filecoin-project/lotus#6405](https://github.com/filecoin-project/lotus/pull/6405)) +- Add a command to get the fees of a deal ([filecoin-project/lotus#5307](https://github.com/filecoin-project/lotus/pull/5307)) + - run `lotus-shed market get-deal-fees` +- Add a command to list retrievals ([filecoin-project/lotus#6337](https://github.com/filecoin-project/lotus/pull/6337)) + - run `lotus client list-retrievals` +- lotus-gateway: add check command ([filecoin-project/lotus#6373](https://github.com/filecoin-project/lotus/pull/6373)) +- lotus-wallet: JWT Support ([filecoin-project/lotus#6360](https://github.com/filecoin-project/lotus/pull/6360)) +- Allow starting networks from arbitrary actor versions ([filecoin-project/lotus#6305](https://github.com/filecoin-project/lotus/pull/6305)) +- oh, snap! ([filecoin-project/lotus#6202](https://github.com/filecoin-project/lotus/pull/6202)) +- Add a shed util to count 64 GiB miner stats ([filecoin-project/lotus#6290](https://github.com/filecoin-project/lotus/pull/6290)) +- Introduce stateless offline dealflow, bypassing the FSM/deallists ([filecoin-project/lotus#5961](https://github.com/filecoin-project/lotus/pull/5961)) +- Transplant some useful commands to lotus-shed actor ([filecoin-project/lotus#5913](https://github.com/filecoin-project/lotus/pull/5913)) + - run `lotus-shed actor` +- actor wrapper codegen ([filecoin-project/lotus#6108](https://github.com/filecoin-project/lotus/pull/6108)) +- Add a shed util to count miners by post type ([filecoin-project/lotus#6169](https://github.com/filecoin-project/lotus/pull/6169)) +- shed: command to list duplicate messages in tipsets (steb) ([filecoin-project/lotus#5847](https://github.com/filecoin-project/lotus/pull/5847)) +- feat: allow checkpointing to forks ([filecoin-project/lotus#6107](https://github.com/filecoin-project/lotus/pull/6107)) +- Add a CLI tool for miner proving deadline ([filecoin-project/lotus#6132](https://github.com/filecoin-project/lotus/pull/6132)) + - run `lotus state miner-proving-deadline` + + +## Bug Fixes +- 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)) +- 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)) +- Fix an error in msigLockCancel ([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)) +- Fix helptext for ask price([filecoin-project/lotus#6560](https://github.com/filecoin-project/lotus/pull/6560)) +- fix commit finalize failed ([filecoin-project/lotus#6521](https://github.com/filecoin-project/lotus/pull/6521)) +- 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)) +- sealing: Fix restartSectors race ([filecoin-project/lotus#6495](https://github.com/filecoin-project/lotus/pull/6495)) +- Fix: correct the change of message size limit ([filecoin-project/lotus#6430](https://github.com/filecoin-project/lotus/pull/6430)) +- Fix logging of stringified CIDs double-encoded in hex ([filecoin-project/lotus#6413](https://github.com/filecoin-project/lotus/pull/6413)) +- Fix success handling in Retreival ([filecoin-project/lotus#5921](https://github.com/filecoin-project/lotus/pull/5921)) +- storagefsm: Fix batch deal packing behavior ([filecoin-project/lotus#6041](https://github.com/filecoin-project/lotus/pull/6041)) +- events: Fix handling of multiple matched events per epoch ([filecoin-project/lotus#6355](https://github.com/filecoin-project/lotus/pull/6355)) +- 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)) +- Revert "chore: update go-libp2p" ([filecoin-project/lotus#6306](https://github.com/filecoin-project/lotus/pull/6306)) +- fix: wait-api should use GetAPI to acquire binary specific API ([filecoin-project/lotus#6246](https://github.com/filecoin-project/lotus/pull/6246)) +- fix(ci): Updates to lotus CI build process ([filecoin-project/lotus#6256](https://github.com/filecoin-project/lotus/pull/6256)) +- 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)) +- fix(splitstore): fix a panic on revert-only head changes ([filecoin-project/lotus#6133](https://github.com/filecoin-project/lotus/pull/6133)) +- drand: fix beacon cache ([filecoin-project/lotus#6164](https://github.com/filecoin-project/lotus/pull/6164)) + +## Improvements +- gateway: Add support for Version method ([filecoin-project/lotus#6618](https://github.com/filecoin-project/lotus/pull/6618)) +- revamped integration test kit (aka. Operation Sparks Joy) ([filecoin-project/lotus#6329](https://github.com/filecoin-project/lotus/pull/6329)) +- move with changed name ([filecoin-project/lotus#6587](https://github.com/filecoin-project/lotus/pull/6587)) +- dynamic circleci config for streamlining test execution ([filecoin-project/lotus#6561](https://github.com/filecoin-project/lotus/pull/6561)) +- extern/storage: add ability to ignore worker resources when scheduling. ([filecoin-project/lotus#6542](https://github.com/filecoin-project/lotus/pull/6542)) +- Adjust various CLI display ratios to arbitrary precision ([filecoin-project/lotus#6309](https://github.com/filecoin-project/lotus/pull/6309)) +- Test multicore SDR support ([filecoin-project/lotus#6479](https://github.com/filecoin-project/lotus/pull/6479)) +- Unit tests for sector batchers ([filecoin-project/lotus#6432](https://github.com/filecoin-project/lotus/pull/6432)) +- 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)) +- Fee config for sector batching ([filecoin-project/lotus#6420](https://github.com/filecoin-project/lotus/pull/6420)) +- 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)) +- 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)) +- 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)) +- Integration tests for offline deals ([filecoin-project/lotus#6081](https://github.com/filecoin-project/lotus/pull/6081)) +- 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)) +- Generate AppImage ([filecoin-project/lotus#6208](https://github.com/filecoin-project/lotus/pull/6208)) +- 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)) +- Remove few useless variable assignments ([filecoin-project/lotus#6359](https://github.com/filecoin-project/lotus/pull/6359)) +- Reduce noise from 'peer has different genesis' messages ([filecoin-project/lotus#6357](https://github.com/filecoin-project/lotus/pull/6357)) +- Get current seal proof when necessary ([filecoin-project/lotus#6339](https://github.com/filecoin-project/lotus/pull/6339)) +- Remove log line when tracing is not configured ([filecoin-project/lotus#6334](https://github.com/filecoin-project/lotus/pull/6334)) +- separate tracing environment variables ([filecoin-project/lotus#6323](https://github.com/filecoin-project/lotus/pull/6323)) +- feat: log dispute rate ([filecoin-project/lotus#6322](https://github.com/filecoin-project/lotus/pull/6322)) +- 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)) +- 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)) +- Return total power when GetPowerRaw doesn't find miner claim ([filecoin-project/lotus#4938](https://github.com/filecoin-project/lotus/pull/4938)) +- add flags to control gateway lookback parameters ([filecoin-project/lotus#6247](https://github.com/filecoin-project/lotus/pull/6247)) +- chore(ci): Enable build on RC tags ([filecoin-project/lotus#6238](https://github.com/filecoin-project/lotus/pull/6238)) +- cron-wc ([filecoin-project/lotus#6178](https://github.com/filecoin-project/lotus/pull/6178)) +- Allow creation of state tree v3s ([filecoin-project/lotus#6167](https://github.com/filecoin-project/lotus/pull/6167)) +- mpool: Cleanup pre-nv12 selection logic ([filecoin-project/lotus#6148](https://github.com/filecoin-project/lotus/pull/6148)) +- 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)) +- 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)) +- Use EmptyTSK where appropriate ([filecoin-project/lotus#6134](https://github.com/filecoin-project/lotus/pull/6134)) +- upgrade `lotus-soup` testplans and reduce deals concurrency to a single miner ([filecoin-project/lotus#6122](https://github.com/filecoin-project/lotus/pull/6122) + +## Dependency Updates +- downgrade libp2p/go-libp2p-yamux to v0.5.1. ([filecoin-project/lotus#6605](https://github.com/filecoin-project/lotus/pull/6605)) +- Update libp2p to 0.14.2 ([filecoin-project/lotus#6404](https://github.com/filecoin-project/lotus/pull/6404)) +- update to markets-v1.4.0 ([filecoin-project/lotus#6369](https://github.com/filecoin-project/lotus/pull/6369)) +- Use new actor tags ([filecoin-project/lotus#6291](https://github.com/filecoin-project/lotus/pull/6291)) +- chore: update go-libp2p ([filecoin-project/lotus#6231](https://github.com/filecoin-project/lotus/pull/6231)) +- Update ffi to proofs v7 ([filecoin-project/lotus#6150](https://github.com/filecoin-project/lotus/pull/6150)) + +## Others +- Initial draft: basic build instructions on Readme ([filecoin-project/lotus#6498](https://github.com/filecoin-project/lotus/pull/6498)) +- 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)) +- 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)) +- Add doc on gas balancing ([filecoin-project/lotus#6392](https://github.com/filecoin-project/lotus/pull/6392)) +- 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)) +- Add a warning to the release issue template ([filecoin-project/lotus#6374](https://github.com/filecoin-project/lotus/pull/6374)) +- Update RELEASE_ISSUE_TEMPLATE.md ([filecoin-project/lotus#6236](https://github.com/filecoin-project/lotus/pull/6236)) +- Delete CODEOWNERS ([filecoin-project/lotus#6289](https://github.com/filecoin-project/lotus/pull/6289)) +- Feat/nerpa v4 ([filecoin-project/lotus#6248](https://github.com/filecoin-project/lotus/pull/6248)) +- Introduce a release issue template ([filecoin-project/lotus#5826](https://github.com/filecoin-project/lotus/pull/5826)) +- 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)) +- Update cli gen ([filecoin-project/lotus#6155](https://github.com/filecoin-project/lotus/pull/6155)) +- Generate CLI docs ([filecoin-project/lotus#6145](https://github.com/filecoin-project/lotus/pull/6145)) + +## Contributors + +| Contributor | Commits | Lines ± | Files Changed | +|-------------|---------|---------|---------------| +| @raulk | 118 | +11972/-10860 | 472 | +| @magik6k | 65 | +10824/-4158 | 353 | +| @aarshkshah1992 | 59 | +8057/-3355 | 224 | +| @arajasek | 41 | +8786/-1691 | 331 | +| @Stebalien | 106 | +7653/-2718 | 273 | +| dirkmc | 11 | +2580/-1371 | 77 | +| @dirkmc | 39 | +1865/-1194 | 79 | +| | 19 | +1973/-485 | 81 | +| @vyzo | 4 | +1748/-330 | 50 | +| @aarshkshah1992 | 5 | +1462/-213 | 27 | +| @coryschwartz | 35 | +568/-206 | 59 | +| @chadwick2143 | 3 | +739/-1 | 4 | +| @ribasushi | 21 | +487/-164 | 36 | +| @hannahhoward | 5 | +544/-5 | 19 | +| @jennijuju | 9 | +241/-174 | 19 | +| @frrist | 1 | +137/-88 | 7 | +| @travisperson | 3 | +175/-6 | 7 | +| @wadeAlexC | 1 | +48/-129 | 1 | +| @whyrusleeping | 8 | +161/-13 | 11 | +| lotus | 1 | +114/-46 | 1 | +| @nonsense | 8 | +107/-53 | 20 | +| @rjan90 | 4 | +115/-33 | 4 | +| @ZenGround0 | 3 | +114/-1 | 4 | +| @Aloxaf | 1 | +43/-61 | 7 | +| @yaohcn | 4 | +89/-9 | 5 | +| @mitchellsoo | 1 | +51/-0 | 1 | +| @placer14 | 3 | +28/-18 | 4 | +| @jennijuju | 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.1 / 2021-07-05 + +This is an optional but **highly recommended** release of Lotus for lotus miners that has many bug fixes and improvements based on the feedback we got from the community since HyperDrive. + +## New Features +- commit batch: AggregateAboveBaseFee config #6650 + - `AggregateAboveBaseFee` is added to miner sealing configuration for setting the network base fee to start aggregating proofs. When the network base fee is lower than this value, the prove commits will be submitted individually via `ProveCommitSector`. According to the [Batch Incentive Alignment](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0013.md#batch-incentive-alignment) introduced in FIP-0013, we recommend miners to set this value to 0.15 nanoFIL(which is the default value) to avoid unexpected aggregation fee in burn and enjoy the most benefits of aggregation! + +## Bug Fixes +- storage: Fix FinalizeSector with sectors in storage paths #6652 +- Fix tiny error in check-client-datacap #6664 +- Fix: precommit_batch method used the wrong cfg.PreCommitBatchWait #6658 +- to optimize the batchwait #6636 +- fix getTicket: sector precommitted but expired case #6635 +- handleSubmitCommitAggregate() exception handling #6595 +- remove precommit check in handleCommitFailed #6634 +- ensure agg fee is adequate +- fix: miner balance is not enough, so that ProveCommitAggregate msg exec failed #6623 +- commit batch: Initialize the FailedSectors map #6647 + +Contributors + +| Contributor | Commits | Lines ± | Files Changed | +|-------------|---------|---------|---------------| +| @magik6k| 7 | +151/-56 | 21 | +| @llifezou | 4 | +59/-20 | 4 | +| @johnli-helloworld | 2 | +45/-14 | 4 | +| @wangchao | 1 | +1/-27 | 1 | +| Jerry | 2 | +9/-4 | 2 | +| @zhoutian527 | 1 | +2/-2 | 1 | +| @ribasushi| 1 | +1/-1 | 1 | + +# 1.10.1 / 2021-07-05 + +This is an optional but **highly recommended** release of Lotus for lotus miners that has many bug fixes and improvements based on the feedback we got from the community since HyperDrive. + +## New Features +- commit batch: AggregateAboveBaseFee config #6650 + - `AggregateAboveBaseFee` is added to miner sealing configuration for setting the network base fee to start aggregating proofs. When the network base fee is lower than this value, the prove commits will be submitted individually via `ProveCommitSector`. According to the [Batch Incentive Alignment](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0013.md#batch-incentive-alignment) introduced in FIP-0013, we recommend miners to set this value to 0.15 nanoFIL(which is the default value) to avoid unexpected aggregation fee in burn and enjoy the most benefits of aggregation! + +## Bug Fixes +- storage: Fix FinalizeSector with sectors in storage paths #6652 +- Fix tiny error in check-client-datacap #6664 +- Fix: precommit_batch method used the wrong cfg.PreCommitBatchWait #6658 +- to optimize the batchwait #6636 +- fix getTicket: sector precommitted but expired case #6635 +- handleSubmitCommitAggregate() exception handling #6595 +- remove precommit check in handleCommitFailed #6634 +- ensure agg fee is adequate +- fix: miner balance is not enough, so that ProveCommitAggregate msg exec failed #6623 +- commit batch: Initialize the FailedSectors map #6647 + +Contributors + +| Contributor | Commits | Lines ± | Files Changed | +|-------------|---------|---------|---------------| +| @magik6k| 7 | +151/-56 | 21 | +| @llifezou | 4 | +59/-20 | 4 | +| @johnli-helloworld | 2 | +45/-14 | 4 | +| @wangchao | 1 | +1/-27 | 1 | +| Jerry | 2 | +9/-4 | 2 | +| @zhoutian527 | 1 | +2/-2 | 1 | +| @ribasushi| 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://lotus.filecoin.io/storage-providers/advanced-configurations/sealing/#precommitsectorsbatch) for details on the new Lotus miner sealing config options, [here](https://lotus.filecoin.io/storage-providers/setup/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://lotus.filecoin.io/lotus/manage/chain-management/#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://lotus.filecoin.io/storage-providers/advanced-configurations/sealing/#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. + +## Highlights + +- OpenRPC Support (https://github.com/filecoin-project/lotus/pull/5843) +- Take latency into account when making interactive deals (https://github.com/filecoin-project/lotus/pull/5876) +- Update go-commp-utils for >10x faster client commp calculation (https://github.com/filecoin-project/lotus/pull/5892) +- add `lotus client cancel-retrieval` cmd to lotus CLI (https://github.com/filecoin-project/lotus/pull/5871) +- add `inspect-deal` command to `lotus client` (https://github.com/filecoin-project/lotus/pull/5833) +- Local retrieval support (https://github.com/filecoin-project/lotus/pull/5917) +- go-fil-markets v1.1.9 -> v1.2.5 + - For a detailed changelog see https://github.com/filecoin-project/go-fil-markets/blob/master/CHANGELOG.md +- rust-fil-proofs v5.4.1 -> v7.0.1 + - For a detailed changelog see https://github.com/filecoin-project/rust-fil-proofs/blob/master/CHANGELOG.md + +## Changes +- storagefsm: Apply global events even in broken states (https://github.com/filecoin-project/lotus/pull/5962) +- Default the AlwaysKeepUnsealedCopy flag to true (https://github.com/filecoin-project/lotus/pull/5743) +- splitstore: compact hotstore prior to garbage collection (https://github.com/filecoin-project/lotus/pull/5778) +- ipfs-force bootstrapper update (https://github.com/filecoin-project/lotus/pull/5799) +- better logging when unsealing fails (https://github.com/filecoin-project/lotus/pull/5851) +- perf: add cache for gas permium estimation (https://github.com/filecoin-project/lotus/pull/5709) +- backupds: Compact log on restart (https://github.com/filecoin-project/lotus/pull/5875) +- backupds: Improve truncated log handling (https://github.com/filecoin-project/lotus/pull/5891) +- State CLI improvements (State CLI improvements) +- API proxy struct codegen (https://github.com/filecoin-project/lotus/pull/5854) +- move DI stuff for paychmgr into modules (https://github.com/filecoin-project/lotus/pull/5791) +- Implement Event observer and Settings for 3rd party dep injection (https://github.com/filecoin-project/lotus/pull/5693) +- Export developer and network commands for consumption by derivatives of Lotus (https://github.com/filecoin-project/lotus/pull/5864) +- mock sealer: Simulate randomness sideeffects (https://github.com/filecoin-project/lotus/pull/5805) +- localstorage: Demote reservation stat error to debug (https://github.com/filecoin-project/lotus/pull/5976) +- shed command to unpack miner info dumps (https://github.com/filecoin-project/lotus/pull/5800) +- Add two utils to Lotus-shed (https://github.com/filecoin-project/lotus/pull/5867) +- add shed election estimate command (https://github.com/filecoin-project/lotus/pull/5092) +- Add --actor flag in lotus-shed sectors terminate (https://github.com/filecoin-project/lotus/pull/5819) +- Move lotus mpool clear to lotus-shed (https://github.com/filecoin-project/lotus/pull/5900) +- Centralize everything on ipfs/go-log/v2 (https://github.com/filecoin-project/lotus/pull/5974) +- expose NextID from nice market actor interface (https://github.com/filecoin-project/lotus/pull/5850) +- add available options for perm on error (https://github.com/filecoin-project/lotus/pull/5814) +- API docs clarification: Document StateSearchMsg replaced message behavior (https://github.com/filecoin-project/lotus/pull/5838) +- api: Document StateReplay replaced message behavior (https://github.com/filecoin-project/lotus/pull/5840) +- add godocs to miner objects (https://github.com/filecoin-project/lotus/pull/2184) +- Add description to the client deal CLI command (https://github.com/filecoin-project/lotus/pull/5999) +- lint: don't skip builtin (https://github.com/filecoin-project/lotus/pull/5881) +- use deal duration from actors (https://github.com/filecoin-project/lotus/pull/5270) +- remote calc winningpost proof (https://github.com/filecoin-project/lotus/pull/5884) +- packer: other network images (https://github.com/filecoin-project/lotus/pull/5930) +- Convert the chainstore lock to RW (https://github.com/filecoin-project/lotus/pull/5971) +- Remove CachedBlockstore (https://github.com/filecoin-project/lotus/pull/5972) +- remove messagepool CapGasFee duplicate code (https://github.com/filecoin-project/lotus/pull/5992) +- Add a mining-heartbeat INFO line at every epoch (https://github.com/filecoin-project/lotus/pull/6183) +- chore(ci): Enable build on RC tags (https://github.com/filecoin-project/lotus/pull/6245) +- Upgrade nerpa to actor v4 and bump the version to rc4 (https://github.com/filecoin-project/lotus/pull/6249) +## Fixes +- return buffers after canceling badger operation (https://github.com/filecoin-project/lotus/pull/5796) +- avoid holding a lock while calling the View callback (https://github.com/filecoin-project/lotus/pull/5792) +- storagefsm: Trigger input processing when below limits (https://github.com/filecoin-project/lotus/pull/5801) +- After importing a previously deleted key, be able to delete it again (https://github.com/filecoin-project/lotus/pull/4653) +- fix StateManager.Replay on reward actor (https://github.com/filecoin-project/lotus/pull/5804) +- make sure atomic 64bit fields are 64bit aligned (https://github.com/filecoin-project/lotus/pull/5794) +- Import secp sigs in paych tests (https://github.com/filecoin-project/lotus/pull/5879) +- fix ci build-macos (https://github.com/filecoin-project/lotus/pull/5934) +- Fix creation of remainder account when it's not a multisig (https://github.com/filecoin-project/lotus/pull/5807) +- Fix fallback chainstore (https://github.com/filecoin-project/lotus/pull/6003) +- fix 4857: show help for set-addrs (https://github.com/filecoin-project/lotus/pull/5943) +- fix health report (https://github.com/filecoin-project/lotus/pull/6011) +- fix(ci): Use recent ubuntu LTS release; Update release params ((https://github.com/filecoin-project/lotus/pull/6011)) + +# 1.8.0 / 2021-04-05 + +This is a mandatory release of Lotus that upgrades the network to version 12, which introduces various performance improvements to the cron processing of the power actor. The network will upgrade at height 712320, which is 2021-04-29T06:00:00Z. + +## Changes + +- v4 specs-actors integration, nv12 migration (https://github.com/filecoin-project/lotus/pull/6116) + +# 1.6.0 / 2021-04-05 + +This is a mandatory release of Lotus that upgrades the network to version 11, which implements [FIP-0014](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0014.md). The network will upgrade at height 665280, which is 2021-04-12T22:00:00Z. + +## v1 sector extension CLI + +This release also expands the `lotus-miner sectors extend` CLI, with a new option that automatically extends all extensible v1 sectors. The option can be run using `lotus-miner sectors extend --v1-sectors`. + +- The `tolerance` flag can be passed to indicate what durations aren't "worth" extending. It defaults to one week, which means that sectors whose current lifetime's are within one week of the maximum possible lifetime will not be extended. + +- The `expiration-cutoff` flag can be passed to skip sectors whose expiration is past a certain point from the current head. It defaults to infinity (no cutoff), but if, say, 28800 was specified, then only sectors expiring in the next 10 days would be extended (2880 epochs in 1 day). + +## Changes + +- Util for miners to extend all v1 sectors (https://github.com/filecoin-project/lotus/pull/5924) +- Upgrade the butterfly network (https://github.com/filecoin-project/lotus/pull/5929) +- Introduce the v11 network upgrade (https://github.com/filecoin-project/lotus/pull/5904) +- Debug mode: Make upgrade heights controllable by an envvar (https://github.com/filecoin-project/lotus/pull/5919) + +# 1.5.3 / 2021-03-24 + +This is a patch release of Lotus that introduces small fixes to the Storage FSM. + +## Changes + +- storagefsm: Fix double unlock with ready WaitDeals sectors (https://github.com/filecoin-project/lotus/pull/5783) +- backupds: Allow larger values in write log (https://github.com/filecoin-project/lotus/pull/5776) +- storagefsm: Don't log the SectorRestart event (https://github.com/filecoin-project/lotus/pull/5779) + +# 1.5.2 / 2021-03-11 + +This is an hotfix release of Lotus that fixes a critical bug introduced in v1.5.1 in the miner windowPoSt logic. This upgrade is only affecting miner nodes. + +## Changes +- fix window post rand check (https://github.com/filecoin-project/lotus/pull/5773) +- wdpost: Always use head tipset to get randomness (https://github.com/filecoin-project/lotus/pull/5774) + +# 1.5.1 / 2021-03-10 + +This is an optional release of Lotus that introduces an important fix to the WindowPoSt computation process. The change is to wait for some confidence before drawing beacon randomness for the proof. Without this, invalid proofs might be generated as the result of a null tipset. + +## Splitstore + +This release also introduces the splitstore, a new optional blockstore that segregates the monolithic blockstore into cold and hot regions. The hot region contains objects from the last 4-5 finalities plus all reachable objects from two finalities away. All other objects are moved to the cold region using a compaction process that executes every finality, once 5 finalities have elapsed. + +The splitstore allows us to separate the two regions quite effectively, using two separate badger blockstores. The separation +means that the live working set is much smaller, which results in potentially significant performance improvements. In addition, it means that the coldstore can be moved to a separate (bigger, slower, cheaper) disk without loss of performance. + +The design also allows us to use different implementations for the two blockstores; for example, an append-only blockstore could be used for coldstore and a faster memory mapped blockstore could be used for the hotstore (eg LMDB). We plan to experiment with these options in the future. + +Once the splitstore has been enabled, the existing monolithic blockstore becomes the coldstore. On the first head change notification, the splitstore will warm up the hotstore by copying all reachable objects from the current tipset into the hotstore. All new writes go into the hotstore, with the splitstore tracking the write epoch. Once 5 finalities have elapsed, and every finality thereafter, the splitstore compacts by moving cold objects into the coldstore. There is also experimental support for garbage collection, whereby nunreachable objects are simply discarded. + +To enable the splitstore, add the following to config.toml: + +``` +[Chainstore] + EnableSplitstore = true +``` + +## Highlights + +Other highlights include: + +- Improved deal data handling - now multiple deals can be adding to sectors in parallel +- Rewriten sector pledging - it now actually cares about max sealing sector limits +- Better handling for sectors stuck in the RecoverDealIDs state +- lotus-miner sectors extend command +- Optional configurable storage path size limit +- Config to disable owner/worker fallback from control addresses (useful when owner is a key on a hardware wallet) +- A write log for node metadata, which can be restored as a backup when the metadata leveldb becomes corrupted (e.g. when you run out of disk space / system crashes in some bad way) + +## Changes + +- avoid use mp.cfg directly to avoid race (https://github.com/filecoin-project/lotus/pull/5350) +- Show replacing message CID is state search-msg cli (https://github.com/filecoin-project/lotus/pull/5656) +- Fix riceing by importing the main package (https://github.com/filecoin-project/lotus/pull/5675) +- Remove sectors with all deals expired in RecoverDealIDs (https://github.com/filecoin-project/lotus/pull/5658) +- storagefsm: Rewrite input handling (https://github.com/filecoin-project/lotus/pull/5375) +- reintroduce Refactor send command for better testability (https://github.com/filecoin-project/lotus/pull/5668) +- Improve error message with importing a chain (https://github.com/filecoin-project/lotus/pull/5669) +- storagefsm: Cleanup CC sector creation (https://github.com/filecoin-project/lotus/pull/5612) +- chain list --gas-stats display capacity (https://github.com/filecoin-project/lotus/pull/5676) +- Correct some logs (https://github.com/filecoin-project/lotus/pull/5694) +- refactor blockstores (https://github.com/filecoin-project/lotus/pull/5484) +- Add idle to sync stage's String() (https://github.com/filecoin-project/lotus/pull/5702) +- packer provisioner (https://github.com/filecoin-project/lotus/pull/5604) +- add DeleteMany to Blockstore interface (https://github.com/filecoin-project/lotus/pull/5703) +- segregate chain and state blockstores (https://github.com/filecoin-project/lotus/pull/5695) +- fix(multisig): The format of the amount is not correct in msigLockApp (https://github.com/filecoin-project/lotus/pull/5718) +- Update butterfly network (https://github.com/filecoin-project/lotus/pull/5627) +- Collect worker task metrics (https://github.com/filecoin-project/lotus/pull/5648) +- Correctly format disputer log (https://github.com/filecoin-project/lotus/pull/5716) +- Log block CID in the large delay warning (https://github.com/filecoin-project/lotus/pull/5704) +- Move api client builders to a cliutil package (https://github.com/filecoin-project/lotus/pull/5728) +- Implement net peers --extended (https://github.com/filecoin-project/lotus/pull/5734) +- Command to extend sector expiration (https://github.com/filecoin-project/lotus/pull/5666) +- garbage collect hotstore after compaction (https://github.com/filecoin-project/lotus/pull/5744) +- tune badger gc to repeatedly gc the value log until there is no rewrite (https://github.com/filecoin-project/lotus/pull/5745) +- Add configuration option for pubsub IPColocationWhitelist subnets (https://github.com/filecoin-project/lotus/pull/5735) +- hot/cold blockstore segregation (aka. splitstore) (https://github.com/filecoin-project/lotus/pull/4992) +- Customize verifreg root key and remainder account when making genesis (https://github.com/filecoin-project/lotus/pull/5730) +- chore: update go-graphsync to 0.6.0 (https://github.com/filecoin-project/lotus/pull/5746) +- Add connmgr metadata to NetPeerInfo (https://github.com/filecoin-project/lotus/pull/5749) +- test: attempt to make the splitstore test deterministic (https://github.com/filecoin-project/lotus/pull/5750) +- Feat/api no dep build (https://github.com/filecoin-project/lotus/pull/5729) +- Fix bootstrapper profile setting (https://github.com/filecoin-project/lotus/pull/5756) +- Check liveness of sectors when processing termination batches (https://github.com/filecoin-project/lotus/pull/5759) +- Configurable storage path storage limit (https://github.com/filecoin-project/lotus/pull/5624) +- miner: Config to disable owner/worker address fallback (https://github.com/filecoin-project/lotus/pull/5620) +- Fix TestUnpadReader on Go 1.16 (https://github.com/filecoin-project/lotus/pull/5761) +- Metadata datastore log (https://github.com/filecoin-project/lotus/pull/5755) +- Remove the SR2 stats, leave just the network totals (https://github.com/filecoin-project/lotus/pull/5757) +- fix: wait a bit before starting to compute window post proofs (https://github.com/filecoin-project/lotus/pull/5764) +- fix: retry proof when randomness changes (https://github.com/filecoin-project/lotus/pull/5768) + + +# 1.5.0 / 2021-02-23 + +This is a mandatory release of Lotus that introduces the fifth upgrade to the Filecoin network. The network upgrade occurs at height 550321, before which time all nodes must have updated to this release (or later). At this height, [v3 specs-actors](https://github.com/filecoin-project/specs-actors/releases/tag/v3.0.0) will take effect, which in turn implements the following two FIPs: + +- [FIP-0007 h/amt-v3](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0007.md) which improves the performance of the Filecoin HAMT and AMT. +- [FIP-0010 off-chain Window PoSt Verification](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0010.md) which reduces the gas consumption of `SubmitWindowedPoSt` messages significantly by optimistically accepting Window PoSt proofs without verification, and allowing them to be disputed later by off-chain verifiers. + +Note that the integration of v3 actors was already completed in 1.4.2, this upgrade simply sets the epoch for the upgrade to occur. + +## Disputer + +FIP-0010 introduces the ability to dispute bad Window PoSts. Node operators are encouraged to run the new Lotus disputer alongside their Lotus daemons. For more information, see the announcement [here](https://github.com/filecoin-project/lotus/discussions/5617#discussioncomment-387333). + +## Changes + +- [#5341](https://github.com/filecoin-project/lotus/pull/5341) Add a `LOTUS_DISABLE_V3_ACTOR_MIGRATION` envvar + - Setting this envvar to 1 disables the v3 actor migration, should only be used in the event of a failed migration + +# 1.4.2 / 2021-02-17 + +This is a large, and highly recommended, optional release with new features and improvements for lotus miner and deal-making UX. The release also integrates [v3 specs-actors](https://github.com/filecoin-project/specs-actors/releases/tag/v3.0.0), which implements two FIPs: + +- [FIP-0007 h/amt-v3](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0007.md) which improves the performance of the Filecoin HAMT and AMT. +- [FIP-0010 off-chain Window PoSt Verification](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0010.md) which reduces the gas consumption of `SubmitWindowedPoSt` messages significantly by optimistically accepting Window PoSt proofs without verification, and allowing them to be disputed later by off-chain verifiers. + +Note that this release does NOT set an upgrade epoch for v3 actors to take effect. That will be done in the upcoming 1.5.0 release. + +## New Features + +- [#5341](https://github.com/filecoin-project/lotus/pull/5341) Added sector termination API and CLI + - Run `lotus-miner sectors terminate` +- [#5342](https://github.com/filecoin-project/lotus/pull/5342) Added CLI for using a multisig wallet as miner's owner address + - See how to set it up [here](https://github.com/filecoin-project/lotus/pull/5342#issue-554009129) +- [#5363](https://github.com/filecoin-project/lotus/pull/5363), [#5418](https://github.com/filecoin-project/lotus/pull/), [#5476](https://github.com/filecoin-project/lotus/pull/5476), [#5459](https://github.com/filecoin-project/lotus/pull/5459) Integrated [spec-actor v3](https://github.com/filecoin-pro5418ject/specs-actors/releases/tag/v3.0.0) + - [#5472](https://github.com/filecoin-project/lotus/pull/5472) Generate actor v3 methods for pond +- [#5379](https://github.com/filecoin-project/lotus/pull/5379) Added WindowPoSt disputer + - This is to support [FIP-0010 off-chian Window PoSt verification](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0010.md) + - See how to run a disputer [here](https://github.com/filecoin-project/lotus/pull/5379#issuecomment-776482445) +- [#5309](https://github.com/filecoin-project/lotus/pull/5309) Batch multiple deals in one `PublishStorageMessages` + - [#5411](https://github.com/filecoin-project/lotus/pull/5411) Handle batch `PublishStorageDeals` message in sealing recovery + - [#5505](https://github.com/filecoin-project/lotus/pull/5505) Exclude expired deals from batching in `PublishStorageDeals` messages + - Added `PublishMsgPeriod` and `MaxDealsPerPublishMsg` to miner `Dealmaking` [configuration](https://lotus.filecoin.io/storage-providers/advanced-configurations/market/#dealmaking-section). See how they work [here](https://lotus.filecoin.io/storage-providers/advanced-configurations/market/#publishing-several-deals-in-one-message). + - [#5538](https://github.com/filecoin-project/lotus/pull/5538), [#5549](https://github.com/filecoin-project/lotus/pull/5549) Added a command to list pending deals and force publish messages. + - Run `lotus-miner market pending-publish` + - [#5428](https://github.com/filecoin-project/lotus/pull/5428) Moved waiting for `PublishStorageDeals` messages' receipt from markets to lotus +- [#5510](https://github.com/filecoin-project/lotus/pull/5510) Added `nerpanet` build option + - To build `nerpanet`, run `make nerpanet` +- [#5433](https://github.com/filecoin-project/lotus/pull/5433) Added `AlwaysKeepUnsealedCopy` option to the miner configuration +- [#5520](https://github.com/filecoin-project/lotus/pull/5520) Added `MsigGetPending` to get pending transactions for multisig wallets +- [#5219](https://github.com/filecoin-project/lotus/pull/5219) Added interactive mode for lotus-wallet +- [5529](https://github.com/filecoin-project/lotus/pull/5529) Added support for minder nodes in `lotus-shed rpc` util + +## Bug Fixes + +- [#5210](https://github.com/filecoin-project/lotus/pull/5210) Miner should not dial client on restart +- [#5403](https://github.com/filecoin-project/lotus/pull/5403) When estimating GasLimit only apply prior messages up to the nonce +- [#5410](https://github.com/filecoin-project/lotus/pull/510) Fix the calibnet build option +- [#5492](https://github.com/filecoin-project/lotus/pull/5492) Fixed `has` for ipfsbstore for non-existing blocks +- [#5361](https://github.com/filecoin-project/lotus/pull/5361) Fixed retrieval hangs when using `IpfsOnlineMode=true` +- [#5493](https://github.com/filecoin-project/lotus/pull/5493) Fixed retrieval failure when price-per-byte is zero +- [#5506](https://github.com/filecoin-project/lotus/pull/5506) Fixed contexts in the storage adpater +- [#5515](https://github.com/filecoin-project/lotus/pull/5515) Properly wire up `StateReadState` on gateway API +- [#5582](https://github.com/filecoin-project/lotus/pull/5582) Fixed error logging format strings +- [#5614](https://github.com/filecoin-project/lotus/pull/5614) Fixed websocket reconnecting handling + + +## Improvements + +- [#5389](https://github.com/filecoin-project/lotus/pull/5389) Show verified indicator for `./lotus-miner storage-deals list` +- [#5229](https://github.com/filecoin-project/lotus/pull/5220) Show power for verified deals in `./lotus-miner setocr list` +- [#5407](https://github.com/filecoin-project/lotus/pull/5407) Added explicit check of the miner address protocol +- [#5399](https://github.com/filecoin-project/lotus/pull/5399) watchdog: increase heapprof capture threshold to 90% +- [#5398](https://github.com/filecoin-project/lotus/pull/5398) storageadapter: Look at precommits on-chain since deal publish msg +- [#5470](https://github.com/filecoin-project/lotus/pull/5470) Added `--no-timing` option for `./lotus state compute-state --html` +- [#5417](https://github.com/filecoin-project/lotus/pull/5417) Storage Manager: Always unseal full sectors +- [#5393](https://github.com/filecoin-project/lotus/pull/5393) Switched to [filecoin-ffi bls api ](https://github.com/filecoin-project/filecoin-ffi/pull/159)for bls signatures +- [#5380](https://github.com/filecoin-project/lotus/pull/5210) Refactor deals API tests +- [#5397](https://github.com/filecoin-project/lotus/pull/5397) Fixed a flake in the sync manager edge case test +- [#5406](https://github.com/filecoin-project/lotus/pull/5406) Added a test to ensure a correct window post cannot be disputed +- [#5294](https://github.com/filecoin-project/lotus/pull/5394) Added jobs to build Lotus docker image and push it to AWS ECR +- [#5387](https://github.com/filecoin-project/lotus/pull/5387) Added network info(mainnet|calibnet) in version +- [#5497](https://github.com/filecoin-project/lotus/pull/5497) Export metric for lotus-gateaway +- [#4950](https://github.com/filecoin-project/lotus/pull/4950) Removed bench policy +- [#5047](https://github.com/filecoin-project/lotus/pull/5047) Improved the UX for `./lotus-shed bitfield enc` +- [#5282](https://github.com/filecoin-project/lotus/pull/5282) Snake a context through the chian blockstore creation +- [#5350](https://github.com/filecoin-project/lotus/pull/5350) Avoid using `mp.cfg` directrly to prevent race condition +- [#5449](https://github.com/filecoin-project/lotus/pull/5449) Documented the block-header better +- [#5404](https://github.com/filecoin-project/lotus/pull/5404) Added retrying proofs if an incorrect one is generated +- [#4545](https://github.com/filecoin-project/lotus/pull/4545) Made state tipset usage consistent in the API +- [#5540](https://github.com/filecoin-project/lotus/pull/5540) Removed unnecessary database reads in validation check +- [#5554](https://github.com/filecoin-project/lotus/pull/5554) Fixed `build lotus-soup` CI job +- [#5552](https://github.com/filecoin-project/lotus/pull/5552) Updated CircleCI to halt gracefully +- [#5555](https://github.com/filecoin-project/lotus/pull/5555) Cleanup and add docstrings of node builder +- [#5564](https://github.com/filecoin-project/lotus/pull/5564) Stopped depending on gocheck with gomod +- [#5574](https://github.com/filecoin-project/lotus/pull/5574) Updated CLI UI +- [#5570](https://github.com/filecoin-project/lotus/pull/5570) Added code CID to `StateReadState` return object +- [#5565](https://github.com/filecoin-project/lotus/pull/5565) Added storageadapter.PublishMsgConfig to miner in testkit for lotus-soup testplan +- [#5571](https://github.com/filecoin-project/lotus/pull/5571) Added `lotus-seed gensis car` to generate lotus block for devnets +- [#5613](https://github.com/filecoin-project/lotus/pull/5613) Check format in client commP util +- [#5507](https://github.com/filecoin-project/lotus/pull/5507) Refactored coalescing logic into its own function and take both cancellation sets into account +- [#5592](https://github.com/filecoin-project/lotus/pull/5592) Verify FFI version before building + +## Dependency Updates +- [#5296](https://github.com/filecoin-project/lotus/pull/5396) Upgraded to [raulk/go-watchdog@v1.0.1](https://github.com/raulk/go-watchdog/releases/tag/v1.0.1) +- [#5450](https://github.com/filecoin-project/lotus/pull/5450) Dependency updates +- [#5425](https://github.com/filecoin-project/lotus/pull/5425) Fixed stale imports in testplans/lotus-soup +- [#5535](https://github.com/filecoin-project/lotus/pull/5535) Updated to [go-fil-markets@v1.1.7](https://github.com/filecoin-project/go-fil-markets/releases/tag/v1.1.7) +- [#5616](https://github.com/filecoin-project/lotus/pull/5600) Updated to [filecoin-ffi@b6e0b35fb49ed0fe](https://github.com/filecoin-project/filecoin-ffi/releases/tag/b6e0b35fb49ed0fe) +- [#5599](https://github.com/filecoin-project/lotus/pull/5599) Updated to [go-bitfield@v0.2.4](https://github.com/filecoin-project/go-bitfield/releases/tag/v0.2.4) +- [#5614](https://github.com/filecoin-project/lotus/pull/5614), , [#5621](https://github.com/filecoin-project/lotus/pull/5621) Updated to [go-jsonrpc@v0.1.3](https://github.com/filecoin-project/go-jsonrpc/releases/tag/v0.1.3) +- [#5459](https://github.com/filecoin-project/lotus/pull/5459) Updated to [spec-actors@v3.0.1](https://github.com/filecoin-project/specs-actors/releases/tag/v3.0.1) + + +## Network Version v10 Upgrade +- [#5473](https://github.com/filecoin-project/lotus/pull/5473) Merged staging branch for v1.5.0 +- [#5603](https://github.com/filecoin-project/lotus/pull/5603) Set nerpanet's upgrade epochs up to v3 actors +- [#5471](https://github.com/filecoin-project/lotus/pull/5471), [#5456](https://github.com/filecoin-project/lotus/pull/5456) Set calibration net actor v3 migration epochs for testing +- [#5434](https://github.com/filecoin-project/lotus/pull/5434) Implemented pre-migration framework +- [#5476](https://github.com/filecoin-project/lotus/pull/5477) Tune migration + +# 1.4.1 / 2021-01-20 + +This is an optional Lotus release that introduces various improvements to the sealing, mining, and deal-making processes. In particular, [#5341](https://github.com/filecoin-project/lotus/pull/5341) introduces the ability for Lotus miners to terminate sectors. + +## Changes + +#### Core Lotus + +- fix(sync): enforce ForkLengthThreshold for synced chain (https://github.com/filecoin-project/lotus/pull/5182) +- introduce memory watchdog; LOTUS_MAX_HEAP (https://github.com/filecoin-project/lotus/pull/5101) +- Skip bootstrapping if no peers specified (https://github.com/filecoin-project/lotus/pull/5301) +- Chainxchg write response timeout (https://github.com/filecoin-project/lotus/pull/5254) +- update NewestNetworkVersion (https://github.com/filecoin-project/lotus/pull/5277) +- fix(sync): remove checks bypass when we submit the block (https://github.com/filecoin-project/lotus/pull/4192) +- chore: export vm.ShouldBurn (https://github.com/filecoin-project/lotus/pull/5355) +- fix(sync): enforce fork len when changing head (https://github.com/filecoin-project/lotus/pull/5244) +- Use 55th percentile instead of median for gas-price (https://github.com/filecoin-project/lotus/pull/5369) +- update go-libp2p-pubsub to v0.4.1 (https://github.com/filecoin-project/lotus/pull/5329) + +#### Sealing + +- Sector termination support (https://github.com/filecoin-project/lotus/pull/5341) +- update weight canSeal and canStore when attach (https://github.com/filecoin-project/lotus/pull/5242/files) +- sector-storage/mock: improve mocked readpiece (https://github.com/filecoin-project/lotus/pull/5208) +- Fix deadlock in runWorker in sched_worker.go (https://github.com/filecoin-project/lotus/pull/5251) +- Skip checking terminated sectors provable (https://github.com/filecoin-project/lotus/pull/5217) +- storagefsm: Fix unsealedInfoMap.lk init race (https://github.com/filecoin-project/lotus/pull/5319) +- Multicore AddPiece CommP (https://github.com/filecoin-project/lotus/pull/5320) +- storagefsm: Send correct event on ErrExpiredTicket in CommitFailed (https://github.com/filecoin-project/lotus/pull/5366) +- expose StateSearchMessage on gateway (https://github.com/filecoin-project/lotus/pull/5382) +- fix FileSize to return correct disk usage recursively (https://github.com/filecoin-project/lotus/pull/5384) + +#### Dealmaking + +- Better error message when withdrawing funds (https://github.com/filecoin-project/lotus/pull/5293) +- add verbose for list transfers (https://github.com/filecoin-project/lotus/pull/5259) +- cli - rename `client info` to `client balances` (https://github.com/filecoin-project/lotus/pull/5304) +- Better CLI for wallet market withdraw and client info (https://github.com/filecoin-project/lotus/pull/5303) + +#### UX + +- correct flag usages for replace cmd (https://github.com/filecoin-project/lotus/pull/5255) +- lotus state call will panic (https://github.com/filecoin-project/lotus/pull/5275) +- fix get sector bug (https://github.com/filecoin-project/lotus/pull/4976) +- feat: lotus wallet market add (adds funds to storage market actor) (https://github.com/filecoin-project/lotus/pull/5300) +- Fix client flag parsing in client balances cli (https://github.com/filecoin-project/lotus/pull/5312) +- delete slash-consensus miner (https://github.com/filecoin-project/lotus/pull/4577) +- add fund sufficient check in send (https://github.com/filecoin-project/lotus/pull/5252) +- enable parse and shorten negative FIL values (https://github.com/filecoin-project/lotus/pull/5315) +- add limit and rate for chain noise (https://github.com/filecoin-project/lotus/pull/5223) +- add bench env print (https://github.com/filecoin-project/lotus/pull/5222) +- Implement full-node restore option (https://github.com/filecoin-project/lotus/pull/5362) +- add color for token amount (https://github.com/filecoin-project/lotus/pull/5352) +- correct log in maybeUseAddress (https://github.com/filecoin-project/lotus/pull/5359) +- add slash-consensus from flag (https://github.com/filecoin-project/lotus/pull/5378) + +#### Testing + +- tvx extract: more tipset extraction goodness (https://github.com/filecoin-project/lotus/pull/5258) +- Fix race in blockstore test suite (https://github.com/filecoin-project/lotus/pull/5297) + + +#### Build & Networks + +- Remove LOTUS_DISABLE_V2_ACTOR_MIGRATION envvar (https://github.com/filecoin-project/lotus/pull/5289) +- Create a calibnet build option (https://github.com/filecoin-project/lotus/pull/5288) +- Calibnet: Set Orange epoch (https://github.com/filecoin-project/lotus/pull/5325) + +#### Management + +- Update SECURITY.md (https://github.com/filecoin-project/lotus/pull/5246) +- README: Contribute section (https://github.com/filecoin-project/lotus/pull/5330) +- README: refine Contribute section (https://github.com/filecoin-project/lotus/pull/5331) +- Add misc tooling to codecov ignore list (https://github.com/filecoin-project/lotus/pull/5347) + +# 1.4.0 / 2020-12-19 + +This is a MANDATORY hotfix release of Lotus that resolves a chain halt at height 336,459 caused by nondeterminism in specs-actors. The fix is to update actors to 2.3.3 in order to incorporate this fix https://github.com/filecoin-project/specs-actors/pull/1334. + +# 1.3.0 / 2020-12-16 + +This is a mandatory release of Lotus that introduces the third post-liftoff upgrade to the Filecoin network. The network upgrade occurs at height 343200, before which time all nodes must have updated to this release (or later). The change that breaks consensus is an implementation of FIP-0009(https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0009.md). + +## Changes + +- Disable gas burning for window post messages (https://github.com/filecoin-project/lotus/pull/5200) +- fix lock propose (https://github.com/filecoin-project/lotus/pull/5197) + +# 1.2.3 / 2020-12-15 + +This is an optional Lotus release that introduces many performance improvements, bugfixes, and UX improvements. + +## Changes + +- When waiting for deal commit messages, ignore unsuccessful messages (https://github.com/filecoin-project/lotus/pull/5189) +- Bigger copy buffer size for stores (https://github.com/filecoin-project/lotus/pull/5177) +- Print MinPieceSize when querying ask (https://github.com/filecoin-project/lotus/pull/5178) +- Optimize miner info & sectors list loading (https://github.com/filecoin-project/lotus/pull/5176) +- Allow miners to filter (un)verified deals (https://github.com/filecoin-project/lotus/pull/5094) +- Fix curSealing out of MaxSealingSectors limit (https://github.com/filecoin-project/lotus/pull/5166) +- Add mpool pending from / to filter (https://github.com/filecoin-project/lotus/pull/5169) +- Add metrics for delayed blocks (https://github.com/filecoin-project/lotus/pull/5171) +- Fix PushUntrusted publishing -- the message is local (https://github.com/filecoin-project/lotus/pull/5173) +- Avoid potential hang in events API when starting event listener (https://github.com/filecoin-project/lotus/pull/5159) +- Show data transfer ID in list-deals (https://github.com/filecoin-project/lotus/pull/5150) +- Fix events API mutex locking (https://github.com/filecoin-project/lotus/pull/5160) +- Message pool refactors (https://github.com/filecoin-project/lotus/pull/5162) +- Fix lotus-shed cid output (https://github.com/filecoin-project/lotus/pull/5072) +- Use FundManager to withdraw funds, add MarketWithdraw API (https://github.com/filecoin-project/lotus/pull/5112) +- Add keygen outfile (https://github.com/filecoin-project/lotus/pull/5118) +- Update sr2 stat aggregation (https://github.com/filecoin-project/lotus/pull/5114) +- Fix miner control address lookup (https://github.com/filecoin-project/lotus/pull/5119) +- Fix send with declared nonce 0 (https://github.com/filecoin-project/lotus/pull/5111) +- Introduce memory watchdog; LOTUS_MAX_HEAP (https://github.com/filecoin-project/lotus/pull/5101) +- Miner control address config for (pre)commits (https://github.com/filecoin-project/lotus/pull/5103) +- Delete repeated call func (https://github.com/filecoin-project/lotus/pull/5099) +- lotus-shed ledger show command (https://github.com/filecoin-project/lotus/pull/5098) +- Log a message when there aren't enough peers for sync (https://github.com/filecoin-project/lotus/pull/5105) +- Miner code cleanup (https://github.com/filecoin-project/lotus/pull/5107) + +# 1.2.2 / 2020-12-03 + +This is an optional Lotus release that introduces various improvements to the mining logic and deal-making workflow, as well as several new UX features. + +## Changes + +- Set lower feecap on PoSt messages with low balance (https://github.com/filecoin-project/lotus/pull/4217) +- Add options to set BlockProfileRate and MutexProfileFraction (https://github.com/filecoin-project/lotus/pull/4140) +- Shed/post find (https://github.com/filecoin-project/lotus/pull/4355) +- tvx extract: make it work with secp messages.(https://github.com/filecoin-project/lotus/pull/4583) +- update go from 1.14 to 1.15 (https://github.com/filecoin-project/lotus/pull/4909) +- print multiple blocks from miner cid (https://github.com/filecoin-project/lotus/pull/4767) +- Connection Gater support (https://github.com/filecoin-project/lotus/pull/4849) +- just return storedask.NewStoredAsk to reduce unuseful code (https://github.com/filecoin-project/lotus/pull/4902) +- add go main version (https://github.com/filecoin-project/lotus/pull/4910) +- Use version0 when pre-sealing (https://github.com/filecoin-project/lotus/pull/4911) +- optimize code UpgradeTapeHeight and go fmt (https://github.com/filecoin-project/lotus/pull/4913) +- CLI to get network version (https://github.com/filecoin-project/lotus/pull/4914) +- Improve error for ActorsVersionPredicate (https://github.com/filecoin-project/lotus/pull/4915) +- upgrade to go-fil-markets 1.0.5 (https://github.com/filecoin-project/lotus/pull/4916) +- bug:replace with func recordFailure (https://github.com/filecoin-project/lotus/pull/4919) +- Remove unused key (https://github.com/filecoin-project/lotus/pull/4924) +- change typeV7 make len (https://github.com/filecoin-project/lotus/pull/4943) +- emit events for peer disconnections and act upon them in the blocksync tracker (https://github.com/filecoin-project/lotus/pull/4754) +- Fix lotus bench error (https://github.com/filecoin-project/lotus/pull/4305) +- Reduce badger ValueTreshold to 128 (https://github.com/filecoin-project/lotus/pull/4629) +- Downgrade duplicate nonce logs to debug (https://github.com/filecoin-project/lotus/pull/4933) +- readme update golang version from 1.14.7 to 1.15.5 (https://github.com/filecoin-project/lotus/pull/4974) +- add data transfer logging (https://github.com/filecoin-project/lotus/pull/4975) +- Remove all temp file generation for deals (https://github.com/filecoin-project/lotus/pull/4929) +- fix get sector bug (https://github.com/filecoin-project/lotus/pull/4976) +- fix nil pointer in StateSectorPreCommitInfo (https://github.com/filecoin-project/lotus/pull/4082) +- Add logging on data-transfer to miner (https://github.com/filecoin-project/lotus/pull/4980) +- bugfix: fixup devnet script (https://github.com/filecoin-project/lotus/pull/4956) +- modify for unsafe (https://github.com/filecoin-project/lotus/pull/4024) +- move testground/lotus-soup testplan from oni to lotus (https://github.com/filecoin-project/lotus/pull/4727) +- Setup remainder msig signers when parsing genesis template (https://github.com/filecoin-project/lotus/pull/4904) +- Update JSON RPC server to enforce a maximum request size (https://github.com/filecoin-project/lotus/pull/4923) +- New SR-specific lotus-shed cmd (https://github.com/filecoin-project/lotus/pull/4971) +- update index to sectorNumber (https://github.com/filecoin-project/lotus/pull/4987) +- storagefsm: Fix expired ticket retry loop (https://github.com/filecoin-project/lotus/pull/4876) +- add .sec scale to measurements; humanize for metric tags (https://github.com/filecoin-project/lotus/pull/4989) +- Support seal proof type switching (https://github.com/filecoin-project/lotus/pull/4873) +- fix log format (https://github.com/filecoin-project/lotus/pull/4984) +- Format workerID as string (https://github.com/filecoin-project/lotus/pull/4973) +- miner: Winning PoSt Warmup (https://github.com/filecoin-project/lotus/pull/4824) +- Default StartDealParams's fast retrieval field to true over JSON (https://github.com/filecoin-project/lotus/pull/4998) +- Fix actor not found in chain inspect-usage (https://github.com/filecoin-project/lotus/pull/5010) +- storagefsm: Improve new deal sector logic (https://github.com/filecoin-project/lotus/pull/5007) +- Configure simultaneous requests (https://github.com/filecoin-project/lotus/pull/4996) +- miner: log winningPoSt duration separately (https://github.com/filecoin-project/lotus/pull/5005) +- fix wallet dead lock (https://github.com/filecoin-project/lotus/pull/5002) +- Update go-jsonrpc to v0.1.2 (https://github.com/filecoin-project/lotus/pull/5015) +- markets - separate watching for pre-commit from prove-commit (https://github.com/filecoin-project/lotus/pull/4945) +- storagefsm: Add missing planners (https://github.com/filecoin-project/lotus/pull/5016) +- fix wallet delete address where address is default (https://github.com/filecoin-project/lotus/pull/5019) +- worker: More robust remote checks (https://github.com/filecoin-project/lotus/pull/5008) +- Add new booststrappers (https://github.com/filecoin-project/lotus/pull/4007) +- add a tooling to make filecoin accounting a little easier (https://github.com/filecoin-project/lotus/pull/5025) +- fix: start a new line in print miner-info to avoid ambiguous display (https://github.com/filecoin-project/lotus/pull/5029) +- Print gas limit sum in mpool stat (https://github.com/filecoin-project/lotus/pull/5035) +- Fix chainstore tipset leak (https://github.com/filecoin-project/lotus/pull/5037) +- shed rpc: Allow calling with args (https://github.com/filecoin-project/lotus/pull/5036) +- Make --gas-limit optional in mpool replace cli (https://github.com/filecoin-project/lotus/pull/5059) +- client list-asks --by-ping (https://github.com/filecoin-project/lotus/pull/5060) +- Ledger signature verification (https://github.com/filecoin-project/lotus/pull/5068) +- Fix helptext for verified-deal default in client deal (https://github.com/filecoin-project/lotus/pull/5074) +- worker: Support setting task types at runtime (https://github.com/filecoin-project/lotus/pull/5023) +- Enable Callers tracing when GasTracing is enabled (https://github.com/filecoin-project/lotus/pull/5080) +- Cancel transfer cancels storage deal (https://github.com/filecoin-project/lotus/pull/5032) +- Sector check command (https://github.com/filecoin-project/lotus/pull/5041) +- add commp-to-cid base64 decode (https://github.com/filecoin-project/lotus/pull/5079) +- miner info cli improvements (https://github.com/filecoin-project/lotus/pull/5083) +- miner: Add slow mode to proving check (https://github.com/filecoin-project/lotus/pull/5086) +- Error out deals that are not activated by proposed deal start epoch (https://github.com/filecoin-project/lotus/pull/5061) + +# 1.2.1 / 2020-11-20 + +This is a very small release of Lotus that fixes an issue users are experiencing when importing snapshots. There is no need to upgrade unless you experience an issue with creating a new datastore directory in the Lotus repo. + +## Changes + +- fix blockstore directory not created automatically (https://github.com/filecoin-project/lotus/pull/4922) +- WindowPoStScheduler.checkSectors() delete useless judgment (https://github.com/filecoin-project/lotus/pull/4918) + + +# 1.2.0 / 2020-11-18 + +This is a mandatory release of Lotus that introduces the second post-liftoff upgrade to the Filecoin network. The network upgrade occurs at height 265200, before which time all nodes must have updated to this release (or later). This release also bumps the required version of Go to 1.15. + +The changes that break consensus are: + +- Upgrading to sepcs-actors 2.3.2 (https://github.com/filecoin-project/specs-actors/releases/tag/v2.3.2) +- Introducing proofs v5.4.0 (https://github.com/filecoin-project/rust-fil-proofs/releases/tag/storage-proofs-v5.4.0), and switching between the proof types (https://github.com/filecoin-project/lotus/pull/4873) +- Don't use terminated sectors for winning PoSt (https://github.com/filecoin-project/lotus/pull/4770) +- Various small VM-level edge-case handling (https://github.com/filecoin-project/lotus/pull/4783) +- Correction of the VM circulating supply calculation (https://github.com/filecoin-project/lotus/pull/4862) +- Retuning gas costs (https://github.com/filecoin-project/lotus/pull/4830) +- Avoid sending messages to the zero BLS address (https://github.com/filecoin-project/lotus/pull/4888) + +## Other Changes + +- delayed pubsub subscribe for messages topic (https://github.com/filecoin-project/lotus/pull/3646) +- add chain base64 decode params (https://github.com/filecoin-project/lotus/pull/4748) +- chore(dep): update bitswap to fix an initialization race that could panic (https://github.com/filecoin-project/lotus/pull/4855) +- Chore/blockstore nits (https://github.com/filecoin-project/lotus/pull/4813) +- Print Consensus Faults in miner info (https://github.com/filecoin-project/lotus/pull/4853) +- Truncate genesis file before generating (https://github.com/filecoin-project/lotus/pull/4851) +- miner: Winning PoSt Warmup (https://github.com/filecoin-project/lotus/pull/4824) +- Fix init actor address map diffing (https://github.com/filecoin-project/lotus/pull/4875) +- Bump API versions to 1.0.0 (https://github.com/filecoin-project/lotus/pull/4884) +- Fix cid recording issue (https://github.com/filecoin-project/lotus/pull/4874) +- Speed up worker key retrieval (https://github.com/filecoin-project/lotus/pull/4885) +- Add error codes to worker return (https://github.com/filecoin-project/lotus/pull/4890) +- Update go to 1.15.5 (https://github.com/filecoin-project/lotus/pull/4896) +- Fix MaxSealingSectrosForDeals getting reset to 0 (https://github.com/filecoin-project/lotus/pull/4879) +- add sanity check for maximum block size (https://github.com/filecoin-project/lotus/pull/3171) +- Check (pre)commit receipt before other checks in failed states (https://github.com/filecoin-project/lotus/pull/4712) +- fix badger double open on daemon --import-snapshot; chainstore lifecycle (https://github.com/filecoin-project/lotus/pull/4872) +- Update to ipfs-blockstore 1.0.3 (https://github.com/filecoin-project/lotus/pull/4897) +- break loop when found warm up sector (https://github.com/filecoin-project/lotus/pull/4869) +- Tweak handling of bad beneficaries in DeleteActor (https://github.com/filecoin-project/lotus/pull/4903) +- cap maximum number of messages per block in selection (https://github.com/filecoin-project/lotus/pull/4905) +- Set Calico epoch (https://github.com/filecoin-project/lotus/pull/4889) + +# 1.1.3 / 2020-11-13 + +This is an optional release of Lotus that upgrades Lotus dependencies, and includes many performance enhancements, bugfixes, and UX improvements. + +## Highlights + +- Refactored much of the miner code (https://github.com/filecoin-project/lotus/pull/3618), improving its recovery from restarts and overall sector success rate +- Updated [proofs](https://github.com/filecoin-project/rust-fil-proofs) to v5.3.0, which brings significant performance improvements +- Updated [markets](https://github.com/filecoin-project/go-fil-markets/releases/tag/v1.0.4) to v1.0.4, which reduces failures due to reorgs (https://github.com/filecoin-project/lotus/pull/4730) and uses the newly refactored fund manager (https://github.com/filecoin-project/lotus/pull/4736) + +## Changes + +#### Core Lotus + +- polish: add Equals method to MinerInfo shim (https://github.com/filecoin-project/lotus/pull/4604) +- Fix messagepool accounting (https://github.com/filecoin-project/lotus/pull/4668) +- Prep for gas balancing (https://github.com/filecoin-project/lotus/pull/4651) +- Reduce badger ValueThreshold to 128 (https://github.com/filecoin-project/lotus/pull/4629) +- Config for default max gas fee (https://github.com/filecoin-project/lotus/pull/4652) +- bootstrap: don't return early when one drand resolution fails (https://github.com/filecoin-project/lotus/pull/4626) +- polish: add ClaimsChanged and DiffClaims method to power shim (https://github.com/filecoin-project/lotus/pull/4628) +- Simplify chain event Called API (https://github.com/filecoin-project/lotus/pull/4664) +- Cache deal states for most recent old/new tipset (https://github.com/filecoin-project/lotus/pull/4623) +- Add miner available balance and power info to state miner info (https://github.com/filecoin-project/lotus/pull/4618) +- Call GetHeaviestTipSet() only once when syncing (https://github.com/filecoin-project/lotus/pull/4696) +- modify runtime gasUsed printf (https://github.com/filecoin-project/lotus/pull/4704) +- Rename builtin actor generators (https://github.com/filecoin-project/lotus/pull/4697) +- Move gas multiplier as property of pricelist (https://github.com/filecoin-project/lotus/pull/4728) +- polish: add msig pendingtxn diffing and comp (https://github.com/filecoin-project/lotus/pull/4719) +- Optional chain Bitswap (https://github.com/filecoin-project/lotus/pull/4717) +- rewrite sync manager (https://github.com/filecoin-project/lotus/pull/4599) +- async connect to bootstrappers (https://github.com/filecoin-project/lotus/pull/4785) +- head change coalescer (https://github.com/filecoin-project/lotus/pull/4688) +- move to native badger blockstore; leverage zero-copy View() to deserialize in-place (https://github.com/filecoin-project/lotus/pull/4681) +- badger blockstore: minor improvements (https://github.com/filecoin-project/lotus/pull/4811) +- Do not fail wallet delete because of pre-existing trashed key (https://github.com/filecoin-project/lotus/pull/4589) +- Correctly delete the default wallet address (https://github.com/filecoin-project/lotus/pull/4705) +- Reduce badger ValueTreshold to 128 (https://github.com/filecoin-project/lotus/pull/4629) +- predicates: Fast StateGetActor wrapper (https://github.com/filecoin-project/lotus/pull/4835) + +#### Mining + +- worker key should change when set sender found key not equal with the value on chain (https://github.com/filecoin-project/lotus/pull/4595) +- extern/sector-storage: fix GPU usage overwrite bug (https://github.com/filecoin-project/lotus/pull/4627) +- sectorstorage: Fix manager restart edge-case (https://github.com/filecoin-project/lotus/pull/4645) +- storagefsm: Fix GetTicket loop when the sector is already precommitted (https://github.com/filecoin-project/lotus/pull/4643) +- Debug flag to force running sealing scheduler (https://github.com/filecoin-project/lotus/pull/4662) +- Fix worker reenabling, handle multiple restarts in worker (https://github.com/filecoin-project/lotus/pull/4666) +- keep retrying the proof until we run out of sectors to skip (https://github.com/filecoin-project/lotus/pull/4633) +- worker: Commands to pause/resume task processing (https://github.com/filecoin-project/lotus/pull/4615) +- struct name incorrect (https://github.com/filecoin-project/lotus/pull/4699) +- optimize code replace strings with constants (https://github.com/filecoin-project/lotus/pull/4769) +- optimize pledge sector (https://github.com/filecoin-project/lotus/pull/4765) +- Track sealing processes across lotus-miner restarts (https://github.com/filecoin-project/lotus/pull/3618) +- Fix scheduler lockups after storage is freed (https://github.com/filecoin-project/lotus/pull/4778) +- storage: Track worker hostnames with work (https://github.com/filecoin-project/lotus/pull/4779) +- Expand sched-diag; Command to abort sealing calls (https://github.com/filecoin-project/lotus/pull/4804) +- miner: Winning PoSt Warmup (https://github.com/filecoin-project/lotus/pull/4824) +- docsgen: Support miner/worker (https://github.com/filecoin-project/lotus/pull/4817) +- miner: Basic storage cleanup command (https://github.com/filecoin-project/lotus/pull/4834) + +#### Markets and Data Transfer + +- Flesh out data transfer features (https://github.com/filecoin-project/lotus/pull/4572) +- Fix memory leaks in data transfer (https://github.com/filecoin-project/lotus/pull/4619) +- Handle deal id changes in OnDealSectorCommitted (https://github.com/filecoin-project/lotus/pull/4730) +- Refactor FundManager (https://github.com/filecoin-project/lotus/pull/4736) +- refactor: integrate new FundManager (https://github.com/filecoin-project/lotus/pull/4787) +- Fix race in paych manager when req context is cancelled (https://github.com/filecoin-project/lotus/pull/4803) +- fix race in paych manager add funds (https://github.com/filecoin-project/lotus/pull/4597) +- Fix panic in FundManager (https://github.com/filecoin-project/lotus/pull/4808) +- Fix: dont crash on startup if funds migration fails (https://github.com/filecoin-project/lotus/pull/4827) + +#### UX + +- Make EarlyExpiration in sectors list less scary (https://github.com/filecoin-project/lotus/pull/4600) +- Add commands to change the worker key (https://github.com/filecoin-project/lotus/pull/4513) +- Expose ClientDealSize via CLI (https://github.com/filecoin-project/lotus/pull/4569) +- client deal: Cache CommD when creating multiple deals (https://github.com/filecoin-project/lotus/pull/4535) +- miner sectors list: flags for events/seal time (https://github.com/filecoin-project/lotus/pull/4649) +- make IPFS online mode configurable (https://github.com/filecoin-project/lotus/pull/4650) +- Add sync status to miner info command (https://github.com/filecoin-project/lotus/pull/4669) +- Add a StateDecodeParams method (https://github.com/filecoin-project/lotus/pull/4105) +- sched: Interactive RPC Shell (https://github.com/filecoin-project/lotus/pull/4692) +- Add api for getting status given a code (https://github.com/filecoin-project/lotus/pull/4210) +- Update lotus-stats with a richer cli (https://github.com/filecoin-project/lotus/pull/4718) +- Use TSK passed to GasEstimateGasLimit (https://github.com/filecoin-project/lotus/pull/4739) +- match data type for reward state api (https://github.com/filecoin-project/lotus/pull/4745) +- Add `termination-estimate` to get an estimation for how much a termination penalty will be (https://github.com/filecoin-project/lotus/pull/4617) +- Restrict `ParseFIL` input length (https://github.com/filecoin-project/lotus/pull/4780) +- cmd sectors commitIDs len debug (https://github.com/filecoin-project/lotus/pull/4786) +- Add client deal-stats CLI (https://github.com/filecoin-project/lotus/pull/4788) +- Modify printf format (https://github.com/filecoin-project/lotus/pull/4795) +- Updated msig inspect (https://github.com/filecoin-project/lotus/pull/4533) +- Delete the duplicate output (https://github.com/filecoin-project/lotus/pull/4819) +- miner: Storage list sectors command (https://github.com/filecoin-project/lotus/pull/4831) +- drop a few logs down to debug (https://github.com/filecoin-project/lotus/pull/4832) + +#### Testing and Tooling + +- refactor: share code between CLI tests (https://github.com/filecoin-project/lotus/pull/4598) +- Fix flaky TestCLIDealFlow (https://github.com/filecoin-project/lotus/pull/4608) +- Fix flaky testMiningReal (https://github.com/filecoin-project/lotus/pull/4609) +- Add election run-dummy command (https://github.com/filecoin-project/lotus/pull/4498) +- Fix .gitmodules (https://github.com/filecoin-project/lotus/pull/4713) +- fix metrics wiring.(https://github.com/filecoin-project/lotus/pull/4691) +- shed: Util for creating ID CIDs (https://github.com/filecoin-project/lotus/pull/4726) +- Run kumquat upgrade on devnets (https://github.com/filecoin-project/lotus/pull/4734) +- Make pond work again (https://github.com/filecoin-project/lotus/pull/4775) +- lotus-stats: fix influx flags (https://github.com/filecoin-project/lotus/pull/4810) +- 2k sync BootstrapPeerThreshold (https://github.com/filecoin-project/lotus/pull/4797) +- test for FundManager panic to ensure it is fixed (https://github.com/filecoin-project/lotus/pull/4825) +- Stop mining at the end of tests (https://github.com/filecoin-project/lotus/pull/4826) +- Make some logs quieter (https://github.com/filecoin-project/lotus/pull/4709) + +#### Dependencies + +- update filecoin-ffi in go mod (https://github.com/filecoin-project/lotus/pull/4584) +- Update FFI (https://github.com/filecoin-project/lotus/pull/4613) +- feat: integrate new optional blst backend and verification optimizations from proofs (https://github.com/filecoin-project/lotus/pull/4630) +- Use https for blst submodule (https://github.com/filecoin-project/lotus/pull/4710) +- Update go-bitfield (https://github.com/filecoin-project/lotus/pull/4756) +- Update Yamux (https://github.com/filecoin-project/lotus/pull/4758) +- Update to latest go-bitfield (https://github.com/filecoin-project/lotus/pull/4793) +- Update to latest go-address (https://github.com/filecoin-project/lotus/pull/4798) +- update libp2p for stream interface changes (https://github.com/filecoin-project/lotus/pull/4814) + +# 1.1.2 / 2020-10-24 + +This is a patch release of Lotus that builds on the fixes involving worker keys that was introduced in v1.1.1. Miners and node operators should update to this release as soon as possible in order to ensure their blocks are propagated and validated. + +## Changes + +- Handle worker key changes correctly in runtime (https://github.com/filecoin-project/lotus/pull/4579) + +# 1.1.1 / 2020-10-24 + +This is a patch release of Lotus that addresses some issues caused by when miners change their worker keys. Miners and node operators should update to this release as soon as possible, especially any miner who has changed their worker key recently. + +## Changes + +- Miner finder for interactive client deal CLI (https://github.com/filecoin-project/lotus/pull/4504) +- Disable blockstore bloom filter (https://github.com/filecoin-project/lotus/pull/4512) +- Add api for getting status given a code (https://github.com/filecoin-project/lotus/pull/4210) +- add batch api for push messages (https://github.com/filecoin-project/lotus/pull/4236) +- add measure datastore wrapper around bench chain datastore (https://github.com/filecoin-project/lotus/pull/4302) +- Look at block base fee for PCR (https://github.com/filecoin-project/lotus/pull/4313) +- Add a shed util to determine % of power that has won a block (https://github.com/filecoin-project/lotus/pull/4318) +- Shed/borked cmd (https://github.com/filecoin-project/lotus/pull/4339) +- optimize mining code (https://github.com/filecoin-project/lotus/pull/4379) +- heaviestTipSet reurning nil is a ok (https://github.com/filecoin-project/lotus/pull/4523) +- Remove most v0 actor imports (https://github.com/filecoin-project/lotus/pull/4383) +- Small chain export optimization (https://github.com/filecoin-project/lotus/pull/4536) +- Add block list to pcr (https://github.com/filecoin-project/lotus/pull/4314) +- Fix circ supply default in conformance (https://github.com/filecoin-project/lotus/pull/4449) +- miner: fix init --create-worker-key (https://github.com/filecoin-project/lotus/pull/4475) +- make push and addLocal atomic (https://github.com/filecoin-project/lotus/pull/4500) +- add some methods that oni needs (https://github.com/filecoin-project/lotus/pull/4501) +- MinerGetBaseInfo: if miner is not found in lookback, check current (https://github.com/filecoin-project/lotus/pull/4508) +- Delete wallet from local wallet cache (https://github.com/filecoin-project/lotus/pull/4526) +- Fix lotus-shed ledger list (https://github.com/filecoin-project/lotus/pull/4521) +- Manage sectors by size instead of proof type (https://github.com/filecoin-project/lotus/pull/4511) +- Feat/api request metrics wrapper (https://github.com/filecoin-project/lotus/pull/4516) +- Fix chain sync stopping to sync (https://github.com/filecoin-project/lotus/pull/4541) +- Use the correct lookback for the worker key when creating blocks (https://github.com/filecoin-project/lotus/pull/4539) +- Cleanup test initialization and always validate VRFs in tests (https://github.com/filecoin-project/lotus/pull/4538) +- Add a market WithdrawBalance CLI (https://github.com/filecoin-project/lotus/pull/4524) +- wallet list: Add market balance and ID address flags (https://github.com/filecoin-project/lotus/pull/4555) +- tvx simulate command; tvx extract --ignore-sanity-checks (https://github.com/filecoin-project/lotus/pull/4554) +- lotus-lite: CLI tests for `lotus client` commands (https://github.com/filecoin-project/lotus/pull/4497) +- lite-mode - market storage and retrieval clients (https://github.com/filecoin-project/lotus/pull/4263) +- Chore: update drand to v1.2.0 (https://github.com/filecoin-project/lotus/pull/4420) +- Fix random test failures (https://github.com/filecoin-project/lotus/pull/4559) +- Fix flaky TestTimedBSSimple (https://github.com/filecoin-project/lotus/pull/4561) +- Make wallet market withdraw usable with miner addresses (https://github.com/filecoin-project/lotus/pull/4556) +- Fix flaky TestChainExportImportFull (https://github.com/filecoin-project/lotus/pull/4564) +- Use older randomness for the PoSt commit on specs-actors version 2 (https://github.com/filecoin-project/lotus/pull/4563) +- shed: Commad to decode messages (https://github.com/filecoin-project/lotus/pull/4565) +- Fetch worker key from correct block on sync (https://github.com/filecoin-project/lotus/pull/4573) + +# 1.1.0 / 2020-10-20 + +This is a mandatory release that introduces the first post-liftoff upgrade to the Filecoin network. The changes that break consensus are an upgrade to specs-actors v2.2.0 at epoch 170000. + +## Changes + +- Introduce Network version 6 (https://github.com/filecoin-project/lotus/pull/4506) +- Update markets v1.0.0 (https://github.com/filecoin-project/lotus/pull/4505) +- Add some extra logging to try and debug sync issues (https://github.com/filecoin-project/lotus/pull/4486) +- Circle: Run tests for some subsystems separately (https://github.com/filecoin-project/lotus/pull/4496) +- Add a terminate sectors command to lotus-shed (https://github.com/filecoin-project/lotus/pull/4507) +- Add a comment to BlockMessages to address #4446 (https://github.com/filecoin-project/lotus/pull/4491) + +# 1.0.0 / 2020-10-19 + +It's 1.0.0! This is an optional release of Lotus that introduces some UX improvements to the 0.10 series. + +This very small release is largely cosmetic, and intended to flag the code that the Filecoin mainnet was launched with. + +## API changes + +- `StateMsgGasCost` has been removed. The equivalent information can be gained by calling `StateReplay`. +- A `GasCost` field has been added to the `InvocResult` type, meaning detailed gas costs will be returned when calling `StateReplay`, `StateCompute`, and `StateCall`. +- The behaviour of `StateReplay` in response to an empty tipset key has been changed. Instead of simply using the heaviest tipset (which is almost guaranteed to be an unsuccessful replay), we search now search the chain for the tipset that included the message, and replay the message in that tipset (we fail if no such tipset is found). + +## Changes + +- Increase code coverage! (https://github.com/filecoin-project/lotus/pull/4410) +- Mpool: Don't block node startup loading messages (https://github.com/filecoin-project/lotus/pull/4411) +- Improve the UX of multisig approves (https://github.com/filecoin-project/lotus/pull/4398) +- Use build.BlockDelaySecs for deal start buffer (https://github.com/filecoin-project/lotus/pull/4415) +- Conformance: support multiple protocol versions (https://github.com/filecoin-project/lotus/pull/4393) +- Ensure msig inspect cli works with lotus-lite (https://github.com/filecoin-project/lotus/pull/4421) +- Add command to (slowly) prune lotus chain datastore (https://github.com/filecoin-project/lotus/pull/3876) +- Add WalletVerify to lotus-gateway (https://github.com/filecoin-project/lotus/pull/4373) +- Improve StateMsg APIs (https://github.com/filecoin-project/lotus/pull/4429) +- Add endpoints needed by spacegap (https://github.com/filecoin-project/lotus/pull/4426) +- Make audit balances capable of printing robust addresses (https://github.com/filecoin-project/lotus/pull/4423) +- Custom filters for retrieval deals (https://github.com/filecoin-project/lotus/pull/4424) +- Fix message list api (https://github.com/filecoin-project/lotus/pull/4422) +- Replace bootstrap peers (https://github.com/filecoin-project/lotus/pull/4447) +- Don't overwrite previously-configured maxPieceSize for a persisted ask (https://github.com/filecoin-project/lotus/pull/4480) +- State: optimize state snapshot address cache (https://github.com/filecoin-project/lotus/pull/4481) + +# 0.10.2 / 2020-10-14 + +This is an optional release of Lotus that updates markets to 0.9.1, which fixes an issue affecting deals that were mid-transfer when the node was upgraded to 0.9.0. This release also includes some tweaks to default gas values and minor performance improvements. + +## Changes + +- Use updated stored ask API (https://github.com/filecoin-project/lotus/pull/4384) +- tvx: trace puts to blockstore for inclusion in CAR. (https://github.com/filecoin-project/lotus/pull/4278) +- Add propose remove (https://github.com/filecoin-project/lotus/pull/4311) +- Update to 0.9.1 bugfix release (https://github.com/filecoin-project/lotus/pull/4402) +- Update drand endpoints (https://github.com/filecoin-project/lotus/pull/4125) +- fix: return true when deadlines changed (https://github.com/filecoin-project/lotus/pull/4403) +- sync wait --watch (https://github.com/filecoin-project/lotus/pull/4396) +- reduce garbage in blockstore (https://github.com/filecoin-project/lotus/pull/4406) +- give the TimeCacheBS tests a bit more time (https://github.com/filecoin-project/lotus/pull/4407) +- Improve gas defaults (https://github.com/filecoin-project/lotus/pull/4408) +- Change default gas premium to for 10 block inclusion (https://github.com/filecoin-project/lotus/pull/4222) + +# 0.10.1 / 2020-10-14 + +This is an optional release of Lotus that updates markets to 0.9.0, which adds the ability to restart data transfers. This release also introduces Ledger support, and various UX improvements. + +## Changes + +- Test the tape upgrade (https://github.com/filecoin-project/lotus/pull/4328) +- Adding in Ledger support (https://github.com/filecoin-project/lotus/pull/4290) +- Improve the UX for lotus-miner sealing workers (https://github.com/filecoin-project/lotus/pull/4329) +- Add a CLI tool for miner's to repay debt (https://github.com/filecoin-project/lotus/pull/4319) +- Rename params_testnet to params_mainnet (https://github.com/filecoin-project/lotus/pull/4336) +- Use seal-duration in calculating the earliest StartEpoch (https://github.com/filecoin-project/lotus/pull/4337) +- Reject deals that are > 7 days in the future in the BasicDealFilter (https://github.com/filecoin-project/lotus/pull/4173) +- Add an API endpoint to calculate the exact circulating supply (https://github.com/filecoin-project/lotus/pull/4148) +- lotus-pcr: ignore all other market messages (https://github.com/filecoin-project/lotus/pull/4341) +- Add message CID to InvocResult (https://github.com/filecoin-project/lotus/pull/4382) +- types: Add CID fields to messages in json marshalers (https://github.com/filecoin-project/lotus/pull/4338) +- fix(sync state): set state height to actual tipset height (https://github.com/filecoin-project/lotus/pull/4347) +- Fix off by one tipset in searchBackForMsg (https://github.com/filecoin-project/lotus/pull/4367) +- fix a panic on startup when we fail to load the tipset (https://github.com/filecoin-project/lotus/pull/4376) +- Avoid having the same message CID show up in execution traces (https://github.com/filecoin-project/lotus/pull/4350) +- feat(markets): update markets 0.9.0 and add data transfer restart (https://github.com/filecoin-project/lotus/pull/4363) + +# 0.10.0 / 2020-10-12 + +This is a consensus-breaking hotfix that addresses an issue in specs-actors v2.0.3 that made it impossible to pledge new 32GiB sectors. The change in Lotus is to update to actors v2.1.0, behind the new network version 5. + +## Changes + +- make pledge test pass with the race detector (https://github.com/filecoin-project/lotus/pull/4291) +- fix a race in tipset cache usage (https://github.com/filecoin-project/lotus/pull/4282) +- add an api for removing multisig signers (https://github.com/filecoin-project/lotus/pull/4274) +- cli: Don't output errors to stdout (https://github.com/filecoin-project/lotus/pull/4298) +- Fix panic in wallet export when key is not found (https://github.com/filecoin-project/lotus/pull/4299) +- Dump the block validation cache whenever we perform an import (https://github.com/filecoin-project/lotus/pull/4287) +- Fix two races (https://github.com/filecoin-project/lotus/pull/4301) +- sync unmark-bad --all (https://github.com/filecoin-project/lotus/pull/4296) +- decode parameters for multisig transactions in inspect (https://github.com/filecoin-project/lotus/pull/4312) +- Chain is love (https://github.com/filecoin-project/lotus/pull/4321) +- lotus-stats: optmize getting miner power (https://github.com/filecoin-project/lotus/pull/4315) +- implement tape upgrade (https://github.com/filecoin-project/lotus/pull/4322) + +# 0.9.1 / 2020-10-10 + +This release fixes an issue which may cause the actors v2 migration to compute the state incorrectly when more than one migration is running in parallel. + +## Changes + +- Make concurrent actor migrations safe (https://github.com/filecoin-project/lotus/pull/4293) +- Remote wallet backends (https://github.com/filecoin-project/lotus/pull/3583) +- Track funds in FundMgr correctly in case of AddFunds failing (https://github.com/filecoin-project/lotus/pull/4273) +- Partial lite-node mode (https://github.com/filecoin-project/lotus/pull/4095) +- Fix potential infinite loop in GetBestMiningCandidate (https://github.com/filecoin-project/lotus/pull/3444) +- sync wait: Handle processed message offset (https://github.com/filecoin-project/lotus/pull/4253) +- Add some new endpoints for querying Msig info (https://github.com/filecoin-project/lotus/pull/4250) +- Update markets v0.7.1 (https://github.com/filecoin-project/lotus/pull/4254) +- Optimize SearchForMessage and GetReceipt (https://github.com/filecoin-project/lotus/pull/4246) +- Use FIL instead of attoFIL in CLI more consistently (https://github.com/filecoin-project/lotus/pull/4249) +- fix: clash between daemon --api flag and cli tests (https://github.com/filecoin-project/lotus/pull/4241) +- add more info to chain sync lookback failure (https://github.com/filecoin-project/lotus/pull/4245) +- Add message counts to inspect chain output (https://github.com/filecoin-project/lotus/pull/4230) + +# 0.9.0 / 2020-10-07 + +This consensus-breaking release of Lotus upgrades the actors version to v2.0.0. This requires migrating actor state from v0 to v2. The changes that break consensus are: + +- Introducing v2 actors and its migration (https://github.com/filecoin-project/lotus/pull/3936) +- Runtime's Receiver() should only return ID addresses (https://github.com/filecoin-project/lotus/pull/3589) +- Update miner eligibility checks for v2 actors (https://github.com/filecoin-project/lotus/pull/4188) +- Add funds that have left FilReserve to circ supply (https://github.com/filecoin-project/lotus/pull/4160) +- Set WinningPoStSectorSetLookback to finality post-v2 actors (https://github.com/filecoin-project/lotus/pull/4190) +- fix: error when actor panics directly (https://github.com/filecoin-project/lotus/pull/3697) + +## Changes + +#### Dependencies + +- Update go-bitfield (https://github.com/filecoin-project/lotus/pull/4171) +- update the AMT implementation (https://github.com/filecoin-project/lotus/pull/4194) +- Update to actors v0.2.1 (https://github.com/filecoin-project/lotus/pull/4199) + +#### Core Lotus + +- Paych: fix voucher amount verification (https://github.com/filecoin-project/lotus/pull/3821) +- Cap market provider messages (https://github.com/filecoin-project/lotus/pull/4141) +- Run fork function after cron for null block safety (https://github.com/filecoin-project/lotus/pull/4114) +- use bitswap sessions when fetching messages, and cancel them (https://github.com/filecoin-project/lotus/pull/4142) +- relax pubsub IPColocationFactorThreshold to 5 (https://github.com/filecoin-project/lotus/pull/4183) +- Support addresses with mainnet prefixes (https://github.com/filecoin-project/lotus/pull/4186) +- fix: make message signer nonce generation transactional (https://github.com/filecoin-project/lotus/pull/4165) +- build: Env var to keep test address output (https://github.com/filecoin-project/lotus/pull/4213) +- make vm.EnableGasTracing public (https://github.com/filecoin-project/lotus/pull/4214) +- introduce separate state-tree versions (https://github.com/filecoin-project/lotus/pull/4197) +- reject explicit "calls" at the upgrade height (https://github.com/filecoin-project/lotus/pull/4231) +- return an illegal actor error when we see an unsupported actor version (https://github.com/filecoin-project/lotus/pull/4232) +- Set head should unmark blocks as valid (https://gist.github.com/travisperson/3c7cddd77a33979a519ccef4e6515f20) + +#### Mining + +- Increased ExpectedSealDuration and and WaitDealsDelay (https://github.com/filecoin-project/lotus/pull/3743) +- Miner backup/restore commands (https://github.com/filecoin-project/lotus/pull/4133) +- lotus-miner: add more help text to storage / attach (https://github.com/filecoin-project/lotus/pull/3961) +- Reject deals that are > 7 days in the future in the BasicDealFilter (https://github.com/filecoin-project/lotus/pull/4173) +- feat(miner): add miner deadline diffing logic (https://github.com/filecoin-project/lotus/pull/4178) + +#### UX + +- Improve the UX for replacing messages (https://github.com/filecoin-project/lotus/pull/4134) +- Add verified flag to interactive deal creation (https://github.com/filecoin-project/lotus/pull/4145) +- Add command to (slowly) prune lotus chain datastore (https://github.com/filecoin-project/lotus/pull/3876) +- Some helpers for verifreg work (https://github.com/filecoin-project/lotus/pull/4124) +- Always use default 720h for setask duration and hide the duration param option (https://github.com/filecoin-project/lotus/pull/4077) +- Convert ID addresses to key addresses before checking wallet (https://github.com/filecoin-project/lotus/pull/4122) +- add a command to view block space utilization (https://github.com/filecoin-project/lotus/pull/4176) +- allow usage inspection on a chain segment (https://github.com/filecoin-project/lotus/pull/4177) +- Add mpool stats for base fee (https://github.com/filecoin-project/lotus/pull/4170) +- Add verified status to api.DealInfo (https://github.com/filecoin-project/lotus/pull/4153) +- Add a CLI command to set a miner's owner address (https://github.com/filecoin-project/lotus/pull/4189) + +#### Tooling and validation + +- Lotus-pcr: add recover-miners command (https://github.com/filecoin-project/lotus/pull/3714) +- MpoolPushUntrusted API for gateway (https://github.com/filecoin-project/lotus/pull/3915) +- Test lotus-miner info all (https://github.com/filecoin-project/lotus/pull/4166) +- chain export: Error with unfinished exports (https://github.com/filecoin-project/lotus/pull/4179) +- add printf in TestWindowPost (https://github.com/filecoin-project/lotus/pull/4043) +- add trace wdpost (https://github.com/filecoin-project/lotus/pull/4020) +- Fix noncefix (https://github.com/filecoin-project/lotus/pull/4202) +- Lotus-pcr: Limit the fee cap of messages we will process, refund gas fees for windowed post and storage deals (https://github.com/filecoin-project/lotus/pull/4198) +- Fix pond (https://github.com/filecoin-project/lotus/pull/4203) +- allow manual setting of noncefix fee cap (https://github.com/filecoin-project/lotus/pull/4205) +- implement command to get execution traces of any message (https://github.com/filecoin-project/lotus/pull/4200) +- conformance: minor driver refactors (https://github.com/filecoin-project/lotus/pull/4211) +- lotus-pcr: ignore all other messages (https://github.com/filecoin-project/lotus/pull/4218) +- lotus-pcr: zero refund (https://github.com/filecoin-project/lotus/pull/4229) + +## Contributors + +The following contributors had 5 or more commits go into this release. +We are grateful for every contribution! + +| Contributor | Commits | Lines ± | +|--------------------|---------|---------------| +| Stebalien | 84 | +3425/-2287 | +| magik6k | 41 | +2121/-506 | +| arajasek | 39 | +2467/-424 | +| Kubuxu | 25 | +2344/-775 | +| raulk | 21 | +287/-196 | +| whyrusleeping | 13 | +727/-71 | +| hsanjuan | 13 | +5886/-7956 | +| dirkmc | 11 | +2634/-576 | +| travisperson | 8 | +923/-202 | +| ribasushi | 6 | +188/-128 | +| zgfzgf | 5 | +21/-17 | + +# 0.8.1 / 2020-09-30 + +This optional release of Lotus introduces a new version of markets which switches to CBOR-map encodings, and allows datastore migrations. The release also introduces several improvements to the mining process, a few performance optimizations, and a battery of UX additions and enhancements. + +## Changes + +#### Dependencies + +- Markets 0.7.0 with updated data stores (https://github.com/filecoin-project/lotus/pull/4089) +- Update ffi to code with blst fixes (https://github.com/filecoin-project/lotus/pull/3998) + +#### Core Lotus + +- Fix GetPower with no miner address (https://github.com/filecoin-project/lotus/pull/4049) +- Refactor: Move nonce generation out of mpool (https://github.com/filecoin-project/lotus/pull/3970) + +#### Performance + +- Implement caching syscalls for import-bench (https://github.com/filecoin-project/lotus/pull/3888) +- Fetch tipset blocks in parallel (https://github.com/filecoin-project/lotus/pull/4074) +- Optimize Tipset equals() (https://github.com/filecoin-project/lotus/pull/4056) +- Make state transition in validation async (https://github.com/filecoin-project/lotus/pull/3868) + +#### Mining + +- Add trace window post (https://github.com/filecoin-project/lotus/pull/4020) +- Use abstract types for Dont recompute post on revert (https://github.com/filecoin-project/lotus/pull/4022) +- Fix injectNulls logic in test miner (https://github.com/filecoin-project/lotus/pull/4058) +- Fix potential panic in FinalizeSector (https://github.com/filecoin-project/lotus/pull/4092) +- Don't recompute post on revert (https://github.com/filecoin-project/lotus/pull/3924) +- Fix some failed precommit handling (https://github.com/filecoin-project/lotus/pull/3445) +- Add --no-swap flag for worker (https://github.com/filecoin-project/lotus/pull/4107) +- Allow some single-thread tasks to run in parallel with PC2/C2 (https://github.com/filecoin-project/lotus/pull/4116) + +#### UX + +- Add an envvar to set address network version (https://github.com/filecoin-project/lotus/pull/4028) +- Add logging to chain export (https://github.com/filecoin-project/lotus/pull/4030) +- Add JSON output to state compute (https://github.com/filecoin-project/lotus/pull/4038) +- Wallet list CLI: Print balances/nonces (https://github.com/filecoin-project/lotus/pull/4088) +- Added an option to show or not show sector info for `lotus-miner info` (https://github.com/filecoin-project/lotus/pull/4003) +- Add a command to import an ipld object into the chainstore (https://github.com/filecoin-project/lotus/pull/3434) +- Improve the lotus-shed dealtracker (https://github.com/filecoin-project/lotus/pull/4051) +- Docs review and re-organization (https://github.com/filecoin-project/lotus/pull/3431) +- Fix wallet list (https://github.com/filecoin-project/lotus/pull/4104) +- Add an endpoint to validate whether a string is a well-formed address (https://github.com/filecoin-project/lotus/pull/4106) +- Add an option to set config path (https://github.com/filecoin-project/lotus/pull/4103) +- Add printf in TestWindowPost (https://github.com/filecoin-project/lotus/pull/4043) +- Improve miner sectors list UX (https://github.com/filecoin-project/lotus/pull/4108) + +#### Tooling + +- Move policy change to seal bench (https://github.com/filecoin-project/lotus/pull/4032) +- Add back network power to stats (https://github.com/filecoin-project/lotus/pull/4050) +- Conformance: Record and feed circulating supply (https://github.com/filecoin-project/lotus/pull/4078) +- Snapshot import progress bar, add HTTP support (https://github.com/filecoin-project/lotus/pull/4070) +- Add lotus shed util to validate a tipset (https://github.com/filecoin-project/lotus/pull/4065) +- tvx: a test vector extraction and execution tool (https://github.com/filecoin-project/lotus/pull/4064) + +#### Bootstrap + +- Add new bootstrappers (https://github.com/filecoin-project/lotus/pull/4007) +- Add Glif node to bootstrap peers (https://github.com/filecoin-project/lotus/pull/4004) +- Add one more node located in China (https://github.com/filecoin-project/lotus/pull/4041) +- Add ipfsmain bootstrapper (https://github.com/filecoin-project/lotus/pull/4067) + +# 0.8.0 / 2020-09-26 + +This consensus-breaking release of Lotus introduces an upgrade to the network. The changes that break consensus are: + +- Upgrading to specs-actors v0.9.11, which reduces WindowPoSt faults per [FIP 0002](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0002.md) to reduce cost for honest miners with occasional faults (see https://github.com/filecoin-project/specs-actors/pull/1181) +- Revisions to some cryptoeconomics and network params + +This release also updates go-fil-markets to fix an incompatibility issue between v0.7.2 and earlier versions. + +## Changes + +#### Dependencies + +- Update spec actors to 0.9.11 (https://github.com/filecoin-project/lotus/pull/4039) +- Update markets to 0.6.3 (https://github.com/filecoin-project/lotus/pull/4013) + +#### Core Lotus + +- Network upgrade (https://github.com/filecoin-project/lotus/pull/4039) +- Fix AddSupportedProofTypes (https://github.com/filecoin-project/lotus/pull/4033) +- Return an error when we fail to find a sector when checking sector expiration (https://github.com/filecoin-project/lotus/pull/4026) +- Batch blockstore copies after block validation (https://github.com/filecoin-project/lotus/pull/3980) +- Remove a misleading miner actor abstraction (https://github.com/filecoin-project/lotus/pull/3977) +- Fix out-of-bounds when loading all sector infos (https://github.com/filecoin-project/lotus/pull/3976) +- Fix break condition in the miner (https://github.com/filecoin-project/lotus/pull/3953) + +#### UX + +- Correct helptext around miners setting ask (https://github.com/filecoin-project/lotus/pull/4009) +- Make sync wait nicer (https://github.com/filecoin-project/lotus/pull/3991) + +#### Tooling and validation + +- Small adjustments following network upgradability changes (https://github.com/filecoin-project/lotus/pull/3996) +- Add some more big pictures stats to stateroot stat (https://github.com/filecoin-project/lotus/pull/3995) +- Add some actors policy setters for testing (https://github.com/filecoin-project/lotus/pull/3975) + +## Contributors + +The following contributors had 5 or more commits go into this release. +We are grateful for every contribution! + +| Contributor | Commits | Lines ± | +|--------------------|---------|---------------| +| arajasek | 66 | +3140/-1261 | +| Stebalien | 64 | +3797/-3434 | +| magik6k | 48 | +1892/-976 | +| raulk | 40 | +2412/-1549 | +| vyzo | 22 | +287/-196 | +| alanshaw | 15 | +761/-146 | +| whyrusleeping | 15 | +736/-52 | +| hannahhoward | 14 | +1237/-837 | +| anton | 6 | +32/-8 | +| travisperson | 5 | +502/-6 | +| Frank | 5 | +78/-39 | +| Jennifer | 5 | +148/-41 | + +# 0.7.2 / 2020-09-23 + +This optional release of Lotus introduces a major refactor around how a Lotus node interacts with code from the specs-actors repo. We now use interfaces to read the state of actors, which is required to be able to reason about different versions of actors code at the same time. + +Additionally, this release introduces various improvements to the sync process, as well as changes to better the overall UX experience. + +## Changes + +#### Core Lotus + +- Network upgrade support (https://github.com/filecoin-project/lotus/pull/3781) +- Upgrade markets to `v0.6.2` (https://github.com/filecoin-project/lotus/pull/3974) +- Validate chain sync response indices when fetching messages (https://github.com/filecoin-project/lotus/pull/3939) +- Add height diff to sync wait (https://github.com/filecoin-project/lotus/pull/3926) +- Replace Requires with Wants (https://github.com/filecoin-project/lotus/pull/3898) +- Update state diffing for market actor (https://github.com/filecoin-project/lotus/pull/3889) +- Parallel fetch for sync (https://github.com/filecoin-project/lotus/pull/3887) +- Fix SectorState (https://github.com/filecoin-project/lotus/pull/3881) + +#### User Experience + +- Add basic deal stats api server for spacerace slingshot (https://github.com/filecoin-project/lotus/pull/3963) +- When doing `sectors update-state`, show a list of existing states if user inputs an invalid one (https://github.com/filecoin-project/lotus/pull/3944) +- Fix `lotus-miner storage find` error (https://github.com/filecoin-project/lotus/pull/3927) +- Log shutdown method for lotus daemon and miner (https://github.com/filecoin-project/lotus/pull/3925) +- Update build and setup instruction link (https://github.com/filecoin-project/lotus/pull/3919) +- Add an option to hide removed sectors from `sectors list` output (https://github.com/filecoin-project/lotus/pull/3903) + +#### Testing and validation + +- Add init.State#Remove() for testing (https://github.com/filecoin-project/lotus/pull/3971) +- lotus-shed: add consensus check command (https://github.com/filecoin-project/lotus/pull/3933) +- Add keyinfo verify and jwt token command to lotus-shed (https://github.com/filecoin-project/lotus/pull/3914) +- Fix conformance gen (https://github.com/filecoin-project/lotus/pull/3892) + +# 0.7.1 / 2020-09-17 + +This optional release of Lotus introduces some critical fixes to the window PoSt process. It also upgrades some core dependencies, and introduces many improvements to the mining process, deal-making cycle, and overall User Experience. + +## Changes + +#### Some notable improvements: + +- Correctly construct params for `SubmitWindowedPoSt` messages (https://github.com/filecoin-project/lotus/pull/3909) +- Skip sectors correctly for Window PoSt (https://github.com/filecoin-project/lotus/pull/3839) +- Split window PoST submission into multiple messages (https://github.com/filecoin-project/lotus/pull/3689) +- Improve journal coverage (https://github.com/filecoin-project/lotus/pull/2455) +- Allow retrievals while sealing (https://github.com/filecoin-project/lotus/pull/3778) +- Don't prune locally published messages (https://github.com/filecoin-project/lotus/pull/3772) +- Add get-ask, set-ask retrieval commands (https://github.com/filecoin-project/lotus/pull/3886) +- Consistently name winning and window post in logs (https://github.com/filecoin-project/lotus/pull/3873)) +- Add auto flag to mpool replace (https://github.com/filecoin-project/lotus/pull/3752)) + +#### Dependencies + +- Upgrade markets to `v0.6.1` (https://github.com/filecoin-project/lotus/pull/3906) +- Upgrade specs-actors to `v0.9.10` (https://github.com/filecoin-project/lotus/pull/3846) +- Upgrade badger (https://github.com/filecoin-project/lotus/pull/3739) + +# 0.7.0 / 2020-09-10 + +This consensus-breaking release of Lotus is designed to test a network upgrade on the space race testnet. The changes that break consensus are: + +- Upgrading the Drand network used from the test Drand network to the League of Entropy main drand network. This is the same Drand network that will be used in the Filecoin mainnet. +- Upgrading to specs-actors v0.9.8, which adds a new method to the Multisig actor. + +## Changes + +#### Core Lotus + +- Fix IsAncestorOf (https://github.com/filecoin-project/lotus/pull/3717) +- Update to specs-actors v0.9.8 (https://github.com/filecoin-project/lotus/pull/3725) +- Increase chain throughput by 20% (https://github.com/filecoin-project/lotus/pull/3732) +- Updare to go-libp2p-pubsub `master` (https://github.com/filecoin-project/lotus/pull/3735) +- Drand upgrade (https://github.com/filecoin-project/lotus/pull/3670) +- Multisig API additions (https://github.com/filecoin-project/lotus/pull/3590) + +#### Storage Miner + +- Increase the number of times precommit2 is attempted before moving back to precommit1 (https://github.com/filecoin-project/lotus/pull/3720) + +#### Message pool + +- Relax mpool add strictness checks for local pushes (https://github.com/filecoin-project/lotus/pull/3724) + + +#### Maintenance + +- Fix devnets (https://github.com/filecoin-project/lotus/pull/3712) +- Fix(chainwatch): compare prev miner with cur miner (https://github.com/filecoin-project/lotus/pull/3715) +- CI: fix statediff build; make optional (https://github.com/filecoin-project/lotus/pull/3729) +- Feat: Chaos abort (https://github.com/filecoin-project/lotus/pull/3733) + +## Contributors + +The following contributors had commits go into this release. +We are grateful for every contribution! + +| Contributor | Commits | Lines ± | +|--------------------|---------|---------------| +| arajasek | 28 | +1144/-239 | +| Kubuxu | 19 | +452/-261 | +| whyrusleeping | 13 | +456/-87 | +| vyzo | 11 | +318/-20 | +| raulk | 10 | +1289/-350 | +| magik6k | 6 | +188/-55 | +| dirkmc | 3 | +31/-8 | +| alanshaw | 3 | +176/-37 | +| Stebalien | 2 | +9/-12 | +| lanzafame | 1 | +1/-1 | +| frrist | 1 | +1/-1 | +| mishmosh | 1 | +1/-1 | +| nonsense | 1 | +1/-0 | + +# 0.6.2 / 2020-09-09 + +This release introduces some critical fixes to message selection and gas estimation logic. It also adds the ability for nodes to mark a certain tipset as checkpointed, as well as various minor improvements and bugfixes. + +## Changes + +#### Messagepool + +- Warn when optimal selection fails to pack a block and we fall back to random selection (https://github.com/filecoin-project/lotus/pull/3708) +- Add basic command for printing gas performance of messages in the mpool (https://github.com/filecoin-project/lotus/pull/3701) +- Adjust optimal selection to always try to fill blocks (https://github.com/filecoin-project/lotus/pull/3685) +- Fix very minor bug in repub baseFeeLowerBound (https://github.com/filecoin-project/lotus/pull/3663) +- Add an auto flag to mpool replace (https://github.com/filecoin-project/lotus/pull/3676) +- Fix mpool optimal selection packing failure (https://github.com/filecoin-project/lotus/pull/3698) + +#### Core Lotus + +- Don't use latency as initital estimate for blocksync (https://github.com/filecoin-project/lotus/pull/3648) +- Add niceSleep 1 second when drand errors (https://github.com/filecoin-project/lotus/pull/3664) +- Fix isChainNearSync check in block validator (https://github.com/filecoin-project/lotus/pull/3650) +- Add peer to peer manager before fetching the tipset (https://github.com/filecoin-project/lotus/pull/3667) +- Add StageFetchingMessages to sync status (https://github.com/filecoin-project/lotus/pull/3668) +- Pass tipset through upgrade logic (https://github.com/filecoin-project/lotus/pull/3673) +- Allow nodes to mark tipsets as checkpointed (https://github.com/filecoin-project/lotus/pull/3680) +- Remove hard-coded late-fee in window PoSt (https://github.com/filecoin-project/lotus/pull/3702) +- Gas: Fix median calc (https://github.com/filecoin-project/lotus/pull/3686) + +#### Storage + +- Storage manager: bail out with an error if unsealed cid is undefined (https://github.com/filecoin-project/lotus/pull/3655) +- Storage: return true from Sealer.ReadPiece() on success (https://github.com/filecoin-project/lotus/pull/3657) + +#### Maintenance + +- Resolve lotus, test-vectors, statediff dependency cycle (https://github.com/filecoin-project/lotus/pull/3688) +- Paych: add docs on how to use paych status (https://github.com/filecoin-project/lotus/pull/3690) +- Initial CODEOWNERS (https://github.com/filecoin-project/lotus/pull/3691) + +# 0.6.1 / 2020-09-08 + +This optional release introduces a minor improvement to the sync process, ensuring nodes don't fall behind and then resync. + +## Changes + +- Update `test-vectors` (https://github.com/filecoin-project/lotus/pull/3645) +- Revert "only subscribe to pubsub topics once we are synced" (https://github.com/filecoin-project/lotus/pull/3643) + +# 0.6.0 / 2020-09-07 + +This consensus-breaking release of Lotus is designed to test a network upgrade on the space race testnet. The changes that break consensus are: + +- Tweaking of some cryptoecon parameters in specs-actors 0.9.7 (https://github.com/filecoin-project/specs-actors/releases/tag/v0.9.7) +- Rebalancing FIL distribution to make testnet FIL scarce, which prevents base fee spikes and sets better expectations for mainnet + +This release also introduces many improvements to Lotus! Among them are a new version of go-fil-markets that supports non-blocking retrieval, various spam reduction measures in the messagepool and p2p logic, and UX improvements to payment channels, dealmaking, and state inspection. + +## Changes + +#### Core Lotus and dependencies + +- Implement faucet funds reallocation logic (https://github.com/filecoin-project/lotus/pull/3632) +- Network upgrade: Upgrade to correct fork threshold (https://github.com/filecoin-project/lotus/pull/3628) +- Update to specs 0.9.7 and markets 0.6.0 (https://github.com/filecoin-project/lotus/pull/3627) +- Network upgrade: Perform base fee tamping (https://github.com/filecoin-project/lotus/pull/3623) +- Chain events: if cache best() is nil, return chain head (https://github.com/filecoin-project/lotus/pull/3611) +- Update to specs actors v0.9.6 (https://github.com/filecoin-project/lotus/pull/3603) + +#### Messagepool + +- Temporarily allow negative chains (https://github.com/filecoin-project/lotus/pull/3625) +- Improve publish/republish logic (https://github.com/filecoin-project/lotus/pull/3592) +- Fix selection bug; priority messages were not included if other chains were negative (https://github.com/filecoin-project/lotus/pull/3580) +- Add defensive check for minimum GasFeeCap for inclusion within the next 20 blocks (https://github.com/filecoin-project/lotus/pull/3579) +- Add additional info about gas premium (https://github.com/filecoin-project/lotus/pull/3578) +- Fix GasPremium capping logic (https://github.com/filecoin-project/lotus/pull/3552) + +#### Payment channels + +- Get available funds by address or by from/to (https://github.com/filecoin-project/lotus/pull/3547) +- Create `lotus paych status` command (https://github.com/filecoin-project/lotus/pull/3523) +- Rename CLI command from "paych get" to "paych add-funds" (https://github.com/filecoin-project/lotus/pull/3520) + +#### Peer-to-peer + +- Only subscribe to pubsub topics once we are synced (https://github.com/filecoin-project/lotus/pull/3602) +- Reduce mpool add failure log spam (https://github.com/filecoin-project/lotus/pull/3562) +- Republish messages even if the chains have negative performance(https://github.com/filecoin-project/lotus/pull/3557) +- Adjust gossipsub gossip factor (https://github.com/filecoin-project/lotus/pull/3556) +- Integrate pubsub Random Early Drop (https://github.com/filecoin-project/lotus/pull/3518) + +#### Miscellaneous + +- Fix panic in OnDealExpiredSlashed (https://github.com/filecoin-project/lotus/pull/3553) +- Robustify state manager against holes in actor method numbers (https://github.com/filecoin-project/lotus/pull/3538) + +#### UX + +- VM: Fix an error message (https://github.com/filecoin-project/lotus/pull/3608) +- Documentation: Batch replacement,update lotus-storage-miner to lotus-miner (https://github.com/filecoin-project/lotus/pull/3571) +- CLI: Robust actor lookup (https://github.com/filecoin-project/lotus/pull/3535) +- Add agent flag to net peers (https://github.com/filecoin-project/lotus/pull/3534) +- Add watch option to storage-deals list (https://github.com/filecoin-project/lotus/pull/3527) + +#### Testing & tooling + +- Decommission chain-validation (https://github.com/filecoin-project/lotus/pull/3606) +- Metrics: add expected height metric (https://github.com/filecoin-project/lotus/pull/3586) +- PCR: Use current tipset during refund (https://github.com/filecoin-project/lotus/pull/3570) +- Lotus-shed: Add math command (https://github.com/filecoin-project/lotus/pull/3568) +- PCR: Add tipset aggergation (https://github.com/filecoin-project/lotus/pull/3565)- Fix broken paych tests (https://github.com/filecoin-project/lotus/pull/3551) +- Make chain export ~1000x times faster (https://github.com/filecoin-project/lotus/pull/3533) +- Chainwatch: Stop SyncIncomingBlocks from leaking into chainwatch processing; No panics during processing (https://github.com/filecoin-project/lotus/pull/3526) +- Conformance: various changes (https://github.com/filecoin-project/lotus/pull/3521) + +# 0.5.10 / 2020-09-03 + +This patch includes a crucial fix to the message pool selection logic, strongly disfavouring messages that might cause a miner penalty. + +## Changes + +- Fix calculation of GasReward in messagepool (https://github.com/filecoin-project/lotus/pull/3528) + +# 0.5.9 / 2020-09-03 + +This patch includes a hotfix to the `GasEstimateFeeCap` method, capping the estimated fee to a reasonable level by default. + +## Changes + +- Added target height to sync wait (https://github.com/filecoin-project/lotus/pull/3502) +- Disable codecov annotations (https://github.com/filecoin-project/lotus/pull/3514) +- Cap fees to reasonable level by default (https://github.com/filecoin-project/lotus/pull/3516) +- Add APIs and command to inspect bandwidth usage (https://github.com/filecoin-project/lotus/pull/3497) +- Track expected nonce in mpool, ignore messages with large nonce gaps (https://github.com/filecoin-project/lotus/pull/3450) + +# 0.5.8 / 2020-09-02 + +This patch includes some bugfixes to the sector sealing process, and updates go-fil-markets. It also improves the performance of blocksync, adds a method to export chain state trees, and improves chainwatch. + +## Changes + +- Upgrade markets to v0.5.9 (https://github.com/filecoin-project/lotus/pull/3496) +- Improve blocksync to load fewer messages: (https://github.com/filecoin-project/lotus/pull/3494) +- Fix a panic in the ffi-wrapper's `ReadPiece` (https://github.com/filecoin-project/lotus/pull/3492/files) +- Fix a deadlock in the sealing scheduler (https://github.com/filecoin-project/lotus/pull/3489) +- Add test vectors for tipset tests (https://github.com/filecoin-project/lotus/pull/3485/files) +- Improve the advance-block debug command (https://github.com/filecoin-project/lotus/pull/3476) +- Add toggle for message processing to Lotus PCR (https://github.com/filecoin-project/lotus/pull/3470) +- Allow exporting recent chain state trees (https://github.com/filecoin-project/lotus/pull/3463) +- Remove height from chain rand (https://github.com/filecoin-project/lotus/pull/3458) +- Disable GC on chain badger datastore (https://github.com/filecoin-project/lotus/pull/3457) +- Account for `GasPremium` in `GasEstimateFeeCap` (https://github.com/filecoin-project/lotus/pull/3456) +- Update go-libp2p-pubsub to `master` (https://github.com/filecoin-project/lotus/pull/3455) +- Chainwatch improvements (https://github.com/filecoin-project/lotus/pull/3442) + +# 0.5.7 / 2020-08-31 + +This patch release includes some bugfixes and enhancements to the sector lifecycle and message pool logic. + +## Changes + +- Rebuild unsealed infos on miner restart (https://github.com/filecoin-project/lotus/pull/3401) +- CLI to attach storage paths to workers (https://github.com/filecoin-project/lotus/pull/3405) +- Do not select negative performing message chains for inclusion (https://github.com/filecoin-project/lotus/pull/3392) +- Remove a redundant error-check (https://github.com/filecoin-project/lotus/pull/3421) +- Correctly move unsealed sectors in `FinalizeSectors` (https://github.com/filecoin-project/lotus/pull/3424) +- Improve worker selection logic (https://github.com/filecoin-project/lotus/pull/3425) +- Don't use context to close bitswap (https://github.com/filecoin-project/lotus/pull/3430) +- Correctly estimate gas premium when there is only one message on chain (https://github.com/filecoin-project/lotus/pull/3428) + +# 0.5.6 / 2020-08-29 + +Hotfix release that fixes a panic in the sealing scheduler (https://github.com/filecoin-project/lotus/pull/3389). + +# 0.5.5 + +This patch release introduces a large number of improvements to the sealing process. +It also updates go-fil-markets to +[version 0.5.8](https://github.com/filecoin-project/go-fil-markets/releases/tag/v0.5.8), +and go-libp2p-pubsub to [v0.3.5](https://github.com/libp2p/go-libp2p-pubsub/releases/tag/v0.3.5). + +#### Downstream upgrades + +- Upgrades markets to v0.5.8 (https://github.com/filecoin-project/lotus/pull/3384) +- Upgrades go-libp2p-pubsub to v0.3.5 (https://github.com/filecoin-project/lotus/pull/3305) + +#### Sector sealing + +- The following improvements were introduced in https://github.com/filecoin-project/lotus/pull/3350. + + - Allow `lotus-miner sectors remove` to remove a sector in any state. + - Create a separate state in the storage FSM dedicated to submitting the Commit message. + - Recovery for when the Deal IDs of deals in a sector get changed in a reorg. + - Auto-retry sending Precommit and Commit messages if they run out of gas + - Auto-retry sector remove tasks when they fail + - Compact worker windows, and allow their tasks to be executed in any order + +- Don't simply skip PoSt for bad sectors (https://github.com/filecoin-project/lotus/pull/3323) + +#### Message Pool + +- Spam Protection: Track required funds for pending messages (https://github.com/filecoin-project/lotus/pull/3313) + +#### Chainwatch + +- Add more power and reward metrics (https://github.com/filecoin-project/lotus/pull/3367) +- Fix raciness in sector deal table (https://github.com/filecoin-project/lotus/pull/3275) +- Parallelize miner processing (https://github.com/filecoin-project/lotus/pull/3380) +- Accept Lotus API and token (https://github.com/filecoin-project/lotus/pull/3337) + +# 0.5.4 + +A patch release, containing a few nice bugfixes and improvements: + +- Fix parsing of peer ID in `lotus-miner actor set-peer-id` (@whyrusleeping) +- Update dependencies, fixing several bugs (@Stebalien) +- Fix remaining linter warnings (@Stebalien) +- Use safe string truncation (@Ingar) +- Allow tweaking of blocksync message window size (@whyrusleeping) +- Add some additional gas stats to metrics (@Kubuxu) +- Fix an edge case bug in message selection, add many tests (@vyzo) + +# 0.5.3 + +Yet another hotfix release. +A lesson for readers, having people who have been awake for 12+ hours review +your hotfix PR is not a good idea. Find someone who has enough slept recently +enough to give you good code review, otherwise you'll end up quickly bumping +versions again. + +- Fixed a bug in the mempool that was introduced in v0.5.2 + +# 0.5.2 / 2020-08-24 + +This is a hotfix release. + +- Fix message selection to not include messages that are invalid for block + inclusion. +- Improve SelectMessage handling of the case where the message pools tipset + differs from our mining base. + +# 0.5.1 / 2020-08-24 + +The Space Race release! +This release contains the genesis car file and bootstrap peers for the space +race network. + +Additionally, we included two small fixes to genesis creation: +- Randomize ticket value in genesis generation +- Correctly set t099 (burnt funds actor) to have valid account actor state + +# 0.5.0 / 2020-08-20 + +This version of Lotus will be used for the incentivized testnet Space Race competition, +and can be considered mainnet-ready code. It includes some protocol +changes, upgrades of core dependencies, and various bugfixes and UX/performance improvements. + +## Highlights + +Among the highlights included in this release are: + +- Gas changes: We implemented EIP-1559 and introduced real gas values. +- Deal-making: We now support "Committed Capacity" sectors, "fast-retrieval" deals, + and the packing of multiple deals into a single sector. +- Renamed features: We renamed some of the binaries, environment variables, and default + paths associated with a Lotus node. + +### Gas changes + +We made some significant changes to the mechanics of gas in this release. + +#### Network fee + +We implemented something similar to +[Ethereum's EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md). +The `Message` structure had three changes: +- The `GasPrice` field has been removed +- A new `GasFeeCap` field has been added, which controls the maximum cost + the sender incurs for the message +- A new `GasPremium` field has been added, which controls the reward a miner + earns for including the message + +A sender will never be charged more than `GasFeeCap * GasLimit`. +A miner will typically earn `GasPremium * GasLimit` as a reward. + +The `Blockheader` structure has one new field, called `ParentBaseFee`. +Informally speaking,the `ParentBaseFee` +is increased when blocks are densely packed with messages, and decreased otherwise. + +The `ParentBaseFee` is used when calculating how much a sender burns when executing a message. _Burning_ simply refers to sending attoFIL to a dedicated, unreachable account. +A message causes `ParentBaseFee * GasUsed` attoFIL to be burnt. + +#### Real gas values + +This release also includes our first "real" gas costs for primitive operations. +The costs were designed to account for both the _time_ that message execution takes, +as well as the _space_ a message adds to the state tree. + +## Deal-making changes + +There are three key changes to the deal-making process. + +#### Committed Capacity sectors + +Miners can now pledge "Committed Capacity" (CC) sectors, which are explicitly +stated as containing junk data, and must not include any deals. Miners can do this +to increase their storage power, and win block rewards from this pledged storage. + +They can mark these sectors as "upgradable" with `lotus-miner sectors mark-for-upgrade`. +If the miner receives and accepts one or more storage deals, the sector that includes +those deals will _replace_ the CC sector. This is intended to maximize the amount of useful +storage on the Filecoin network. + +#### Fast-retrieval deals + +Clients can now include a `fast-retrieval` flag when proposing deals with storage miners. +If set to true, the miner will include an extra copy of the deal data. This +data can be quickly served in a retrieval deal, since it will not need to be unsealed. + +#### Multiple deals per sector + +Miners can now pack multiple deals into a single sector, so long as all the deals +fit into the sector capacity. This should increase the packing efficiency of miners. + +### Renamed features + +To improve the user experience, we updated several names to mainatin +standard prefixing, and to better reflect the meaning of the features being referenced. + +In particular, the Lotus miner binary is now called `lotus-miner`, the default +path for miner data is now `~/.lotusminer`, and the environment variable +that sets the path for miner data is now `$LOTUS_MINER_PATH`. A full list of renamed +features can be found [here](https://github.com/filecoin-project/lotus/issues/2304). + +## Changelog + +#### Downstream upgrades +- Upgrades markets to v0.5.6 (https://github.com/filecoin-project/lotus/pull/3058) +- Upgrades specs-actors to v0.9.3 (https://github.com/filecoin-project/lotus/pull/3151) + +#### Core protocol +- Introduces gas values, replacing placeholders (https://github.com/filecoin-project/lotus/pull/2343) +- Implements EIP-1559, introducing a network base fee, message gas fee cap, and message gas fee premium (https://github.com/filecoin-project/lotus/pull/2874) +- Implements Poisson Sortition for elections (https://github.com/filecoin-project/lotus/pull/2084) + +#### Deal-making lifecycle +- Introduces "Committed Capacity" sectors (https://github.com/filecoin-project/lotus/pull/2220) +- Introduces "fast-retrieval" flag for deals (https://github.com/filecoin-project/lotus/pull/2323 +- Supports packing multiple deals into one sector (https://github.com/filecoin-project/storage-fsm/pull/38) + +#### Enhancements + +- Optimized message pool selection logic (https://github.com/filecoin-project/lotus/pull/2838) +- Window-based scheduling of sealing tasks (https://github.com/filecoin-project/sector-storage/pull/67) +- Faster window PoSt (https://github.com/filecoin-project/lotus/pull/2209/files) +- Refactors the payment channel manager (https://github.com/filecoin-project/lotus/pull/2640) +- Refactors blocksync (https://github.com/filecoin-project/lotus/pull/2715/files) + +#### UX + +- Provide status updates for data-transfer (https://github.com/filecoin-project/lotus/pull/3162, https://github.com/filecoin-project/lotus/pull/3191) +- Miners can customise asks (https://github.com/filecoin-project/lotus/pull/2046) +- Miners can toggle auto-acceptance of deals (https://github.com/filecoin-project/lotus/pull/1994) +- Miners can maintain a blocklist of piece CIDs (https://github.com/filecoin-project/lotus/pull/2069) + +## Contributors + +The following contributors had 10 or more commits go into this release. +We are grateful for every contribution! + +| Contributor | Commits | Lines ± | +|--------------------|---------|---------------| +| magik6k | 361 | +13197/-6136 | +| Kubuxu | 227 | +5670/-2587 | +| arajasek | 120 | +2916/-1264 | +| whyrusleeping | 112 | +3979/-1089 | +| vyzo | 99 | +3343/-1305 | +| dirkmc | 68 | +8732/-3621 | +| laser | 45 | +1489/-501 | +| hannahhoward | 43 | +2654/-990 | +| frrist | 37 | +6630/-4338 | +| schomatis | 28 | +3016/-1368 | +| placer14 | 27 | +824/-350 | +| raulk | 25 | +28718/-29849 | +| mrsmkl | 22 | +560/-368 | +| travisperson | 18 | +1354/-314 | +| nonsense | 16 | +2956/-2842 | +| ingar | 13 | +331/-123 | +| daviddias | 11 | +311/-11 | +| Stebalien | 11 | +1204/-980 | +| RobQuistNL | 10 | +69/-74 | + +# 0.1.0 / 2019-12-11 + +We are very excited to release **lotus** 0.1.0. This is our testnet release. To install lotus and join the testnet, please visit [lotu.sh](lotu.sh). Please file bug reports as [issues](https://github.com/filecoin-project/lotus/issues). + +A huge thank you to all contributors for this testnet release! diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..dfdfedce3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,136 @@ +##################################### +FROM golang:1.19.7-buster AS lotus-builder +MAINTAINER Lotus Development Team + +RUN apt-get update && apt-get install -y ca-certificates build-essential clang ocl-icd-opencl-dev ocl-icd-libopencl1 jq libhwloc-dev + +ENV XDG_CACHE_HOME="/tmp" + +### taken from https://github.com/rust-lang/docker-rust/blob/master/1.63.0/buster/Dockerfile +ENV RUSTUP_HOME=/usr/local/rustup \ + CARGO_HOME=/usr/local/cargo \ + PATH=/usr/local/cargo/bin:$PATH \ + RUST_VERSION=1.63.0 + +RUN set -eux; \ + dpkgArch="$(dpkg --print-architecture)"; \ + case "${dpkgArch##*-}" in \ + amd64) rustArch='x86_64-unknown-linux-gnu'; rustupSha256='5cc9ffd1026e82e7fb2eec2121ad71f4b0f044e88bca39207b3f6b769aaa799c' ;; \ + arm64) rustArch='aarch64-unknown-linux-gnu'; rustupSha256='e189948e396d47254103a49c987e7fb0e5dd8e34b200aa4481ecc4b8e41fb929' ;; \ + *) echo >&2 "unsupported architecture: ${dpkgArch}"; exit 1 ;; \ + esac; \ + url="https://static.rust-lang.org/rustup/archive/1.25.1/${rustArch}/rustup-init"; \ + wget "$url"; \ + echo "${rustupSha256} *rustup-init" | sha256sum -c -; \ + chmod +x rustup-init; \ + ./rustup-init -y --no-modify-path --profile minimal --default-toolchain $RUST_VERSION --default-host ${rustArch}; \ + rm rustup-init; \ + chmod -R a+w $RUSTUP_HOME $CARGO_HOME; \ + rustup --version; \ + cargo --version; \ + rustc --version; + +COPY ./ /opt/filecoin +WORKDIR /opt/filecoin + +RUN scripts/docker-git-state-check.sh + +### make configurable filecoin-ffi build +ARG FFI_BUILD_FROM_SOURCE=0 +ENV FFI_BUILD_FROM_SOURCE=${FFI_BUILD_FROM_SOURCE} + +RUN make clean deps + +ARG RUSTFLAGS="" +ARG GOFLAGS="" + +RUN make buildall + +##################################### +FROM ubuntu:20.04 AS lotus-base +MAINTAINER Lotus Development Team + +# Base resources +COPY --from=lotus-builder /etc/ssl/certs /etc/ssl/certs +COPY --from=lotus-builder /lib/*/libdl.so.2 /lib/ +COPY --from=lotus-builder /lib/*/librt.so.1 /lib/ +COPY --from=lotus-builder /lib/*/libgcc_s.so.1 /lib/ +COPY --from=lotus-builder /lib/*/libutil.so.1 /lib/ +COPY --from=lotus-builder /usr/lib/*/libltdl.so.7 /lib/ +COPY --from=lotus-builder /usr/lib/*/libnuma.so.1 /lib/ +COPY --from=lotus-builder /usr/lib/*/libhwloc.so.5 /lib/ +COPY --from=lotus-builder /usr/lib/*/libOpenCL.so.1 /lib/ + +RUN useradd -r -u 532 -U fc \ + && mkdir -p /etc/OpenCL/vendors \ + && echo "libnvidia-opencl.so.1" > /etc/OpenCL/vendors/nvidia.icd + +##################################### +FROM lotus-base AS lotus +MAINTAINER Lotus Development Team + +COPY --from=lotus-builder /opt/filecoin/lotus /usr/local/bin/ +COPY --from=lotus-builder /opt/filecoin/lotus-shed /usr/local/bin/ +COPY scripts/docker-lotus-entrypoint.sh / + +ARG DOCKER_LOTUS_IMPORT_SNAPSHOT https://snapshots.mainnet.filops.net/minimal/latest +ENV DOCKER_LOTUS_IMPORT_SNAPSHOT ${DOCKER_LOTUS_IMPORT_SNAPSHOT} +ENV FILECOIN_PARAMETER_CACHE /var/tmp/filecoin-proof-parameters +ENV LOTUS_PATH /var/lib/lotus +ENV DOCKER_LOTUS_IMPORT_WALLET "" + +RUN mkdir /var/lib/lotus /var/tmp/filecoin-proof-parameters +RUN chown fc: /var/lib/lotus /var/tmp/filecoin-proof-parameters + +VOLUME /var/lib/lotus +VOLUME /var/tmp/filecoin-proof-parameters + +USER fc + +EXPOSE 1234 + +ENTRYPOINT ["/docker-lotus-entrypoint.sh"] + +CMD ["-help"] + +##################################### +FROM lotus-base AS lotus-all-in-one + +ENV FILECOIN_PARAMETER_CACHE /var/tmp/filecoin-proof-parameters +ENV LOTUS_MINER_PATH /var/lib/lotus-miner +ENV LOTUS_PATH /var/lib/lotus +ENV LOTUS_WORKER_PATH /var/lib/lotus-worker +ENV WALLET_PATH /var/lib/lotus-wallet + +COPY --from=lotus-builder /opt/filecoin/lotus /usr/local/bin/ +COPY --from=lotus-builder /opt/filecoin/lotus-seed /usr/local/bin/ +COPY --from=lotus-builder /opt/filecoin/lotus-shed /usr/local/bin/ +COPY --from=lotus-builder /opt/filecoin/lotus-wallet /usr/local/bin/ +COPY --from=lotus-builder /opt/filecoin/lotus-gateway /usr/local/bin/ +COPY --from=lotus-builder /opt/filecoin/lotus-miner /usr/local/bin/ +COPY --from=lotus-builder /opt/filecoin/lotus-worker /usr/local/bin/ +COPY --from=lotus-builder /opt/filecoin/lotus-stats /usr/local/bin/ +COPY --from=lotus-builder /opt/filecoin/lotus-fountain /usr/local/bin/ + +RUN mkdir /var/tmp/filecoin-proof-parameters +RUN mkdir /var/lib/lotus +RUN mkdir /var/lib/lotus-miner +RUN mkdir /var/lib/lotus-worker +RUN mkdir /var/lib/lotus-wallet +RUN chown fc: /var/tmp/filecoin-proof-parameters +RUN chown fc: /var/lib/lotus +RUN chown fc: /var/lib/lotus-miner +RUN chown fc: /var/lib/lotus-worker +RUN chown fc: /var/lib/lotus-wallet + + +VOLUME /var/tmp/filecoin-proof-parameters +VOLUME /var/lib/lotus +VOLUME /var/lib/lotus-miner +VOLUME /var/lib/lotus-worker +VOLUME /var/lib/lotus-wallet + +EXPOSE 1234 +EXPOSE 2345 +EXPOSE 3456 +EXPOSE 1777 diff --git a/GO_VERSION_MIN b/GO_VERSION_MIN new file mode 100644 index 000000000..98adfe8e1 --- /dev/null +++ b/GO_VERSION_MIN @@ -0,0 +1 @@ +1.19.7 diff --git a/LOTUS_RELEASE_FLOW.md b/LOTUS_RELEASE_FLOW.md new file mode 100644 index 000000000..4a327125a --- /dev/null +++ b/LOTUS_RELEASE_FLOW.md @@ -0,0 +1,142 @@ + + + +- [`lotus` Release Flow](#lotus-release-flow) + - [Purpose](#purpose) + - [High-level Summary](#high-level-summary) + - [Motivation and Requirements](#motivation-and-requirements) + - [Adopted Conventions](#adopted-conventions) + - [Major Releases](#major-releases) + - [Mandatory Releases](#mandatory-releases) + - [Feature Releases](#feature-releases) + - [Examples Scenarios](#examples-scenarios) + - [Release Cycle](#release-cycle) + - [Patch Releases](#patch-releases) + - [Performing a Release](#performing-a-release) + - [Security Fix Policy](#security-fix-policy) + - [FAQ](#faq) + - [Why aren't Go major versions used more?](#why-arent-go-major-versions-used-more) + - [Related Items](#related-items) + + + +# `lotus` Release Flow + +## Purpose + +This document aims to describe how the Lotus team plans to ship releases of the Lotus implementation of Filecoin network. Interested parties can expect new releases to come out as described in this document. + +## High-level Summary + +- **Major releases** (1.0.0, 2.0.0, etc.) are reserved for significant changes to the Filecoin protocol that transform the network and its usecases. Such changes could include the addition of new Virtual Machines to the protocol, major upgrades to the Proofs of Replication used by Filecoin, etc. +- Even minor releases (1.2.0, 1.4.0, etc.) of the Lotus software correspond to **mandatory releases** as they ship Filecoin network upgrades. Users **must** upgrade to these releases before a certain time in order to keep in sync with the Filecoin network. We aim to ensure there is at least one week before the upgrade deadline. +- Patch versions of even minor releases (1.2.1, 1.4.2, etc.) correspond to **hotfix releases**. Such releases will only be shipped when a critical fix is needed to be applied on top of a mandatory release. +- Odd minor releases (1.3.0, 1.5.0, etc.), as well as patch releases in these series (1.3.1, 1.5.2, etc.) correspond to **feature releases** with new development and bugfixes. These releases are not mandatory, but still highly recommended, **as they may contain critical security fixes**. +- We aim to ship a new feature release of the Lotus software every 3 weeks, so users can expect a regular cadence of Lotus feature releases. Note that mandatory releases for network upgrades may disrupt this schedule. + +## Motivation and Requirements + +Our primary motivation is for users of the Lotus software (storage providers, storage clients, developers, etc.) to have a clear idea about when they can expect Lotus releases, and what they can expect in a release. + +In order to achieve this, we need the following from our release process and conventions: + +- Lotus version conventions make it immediately obvious whether a new Lotus release is mandatory or not. A release is mandatory if it ships a network upgrade to the Filecoin protocol. +- The ability to ship critical fixes on top of mandatory releases, so as to avoid forcing users to consume larger unrelated changes. +- A regular cadence of feature releases, so that users can know when a new Lotus release will be available for consumption. +- The ability to ship any number of feature releases between two mandatory releases. +- A clear description of the various stages of testing that a Lotus Release Candidate (RC) goes through. +- Lotus Release issues will present a single source of truth for what may be contained in Lotus releases, including security fixes, and how they will be disclosed. + +## Adopted Conventions + +This section describes the conventions we have adopted. Users of Lotus are encouraged to review this in detail so as to be informed when new Lotus releases are shipped. + +### Major Releases + +Bumps to the Lotus major version number (1.0.0, 2.0.0, etc.) are reserved for significant changes to the Filecoin protocol that dramatically transform the network itself. Such changes could include the addition of new Virtual Machines to the protocol, major upgrades to the Proofs of Replication used by Filecoin, etc. These releases are expected to take lots of time to develop and will be rare. See also "Why aren't Go major versions used more?" below. + +### Mandatory Releases + +Even bumps to the Lotus minor version number (1.2.0, 1.4.0, etc.) are reserved for **mandatory releases** that ship Filecoin network upgrades. Users **must** upgrade to these releases before a certain time in order to keep in sync with the Filecoin network. + +Depending on the scope of the upgrade, these releases may take up to several weeks to fully develop and test. We aim to ensure there are at least 2 weeks between the publication of the final Lotus release and the Filecoin network upgrade deadline. + +These releases do not follow a regular cadence, as they are developed in lockstep with the other implementations of the Filecoin protocol. As of August 2021, the developers aim to ship 3-4 Filecoin network upgrades a year, though smaller security-critical upgrades may occur unpredictably. + +Mandatory releases are somewhat sensitive since all Lotus users are forced to upgrade to them at the same time. As a result, they will be shipped on top of the most recent stable release of Lotus, which will generally be the latest Lotus release that has been in production for more than 2 weeks. (Note: given this rule, the basis of a mandatory release could be a mandatory release or a feature release depending on timing). Mandatory releases will not include any new feature development or bugfixes that haven't already baked in production for 2+ weeks, except for the changes needed for the network upgrade itself. Further, any critical fixes that are needed after the network upgrade will be shipped as patch version bumps to the mandatory release (1.2.1, 1.2.2, etc.) This prevents users from being forced to quickly digest unnecessary changes. + +Users should generally aim to always upgrade to a new even minor version release since they either introduce a mandatory network upgrade or a critical fix. + +### Feature Releases + +All releases under an odd minor version number indicate **feature releases**. These could include releases such as 1.3.0, 1.3.1, 1.5.2, etc. + +Feature releases include new development and bug fixes. They are not mandatory, but still highly recommended, **as they may contain critical security fixes**. Note that some of these releases may be very small patch releases that include critical hotfixes. There is no way to distinguish between a bug fix release and a feature release on the "feature" version. Both cases will use the "patch" version number. + +We aim to ship a new feature release of the Lotus software from our development (master) branch every 3 weeks, so users can expect a regular cadence of Lotus feature releases. Note that mandatory releases for network upgrades may disrupt this schedule. For more, see the Release Cycle section (TODO: Link). + +### Examples Scenarios + +- **Scenario 1**: **Lotus 1.12.0 shipped a network upgrade, and no network upgrades are needed for a long while.** + + In this case, the next feature release will be Lotus 1.13.0. In three-week intervals, we will ship Lotus 1.13.1, 1.13.2, and 1.13.3, all containing new features and bug fixes. + + Let us assume that after the release of 1.13.3, a critical issue is discovered and a hotfix quickly developed. This hotfix will then be shipped in **both** 1.12.1 and 1.13.4. Users who have already upgrade to the 1.13 series can simply upgrade to 1.13.4. Users who have chosen to still be on 1.12.0, however, can use 1.12.1 to patch the critical issue without being forced to consume all the changes in the 1.13 series. + +- **Scenario 2**: **Lotus 1.12.0 shipped a network upgrade, but the need for an unexpected network upgrade soon arises** + + In this case, the Lotus 1.13 series will be dropped entirely, including any RCs that may have been undergoing testing. Instead, the network upgrade will be shipped as Lotus 1.14.0, built on top of Lotus 1.12.0. It will thus include no unnecessary changes, only the work needed to support the new network upgrade. + + Any changes that were being worked on in the 1.13.0 series will then get applied on top of Lotus 1.14.0 and get shipped as Lotus 1.15.0. + +## Release Cycle + +A mandatory release process should take about 3-6 weeks, depending on the amount and the overall complexity of new features being introduced to the network protocol. It may also be shorter if there is a network incident that requires an emergency upgrade. A feature release process should take about 2-3 weeks. + +The start time of the mandatory release process is subject to the network upgrade timeline. We will start a new feature release process every 3 weeks on Tuesdays, regardless of when the previous release landed unless it's still ongoing. + +### Patch Releases + +**Mandatory Release** + +If we encounter a serious bug in a mandatory release post a network upgrade, we will create a patch release based on this release. Strictly only the fix to the bug will be included in the patch, and the bug fix will be backported to the master (dev) branch, and any ongoing feature release branch if applicable. + +Patch release process for the mandatory releases will follow a compressed release cycle from hours to days depending on the severity and the impact to the network of the bug. In a patch release: + +1. Automated and internal testing (stage 0 and 1) will be compressed into a few hours. +2. Stage 2-3 will be skipped or shortened case by case. + +Some patch releases, especially ones fixing one or more complex bugs that doesn't require a follow-up mandatory upgrade, may undergo the full release process. + +**Feature Release** + +Patch releases in odd minor releases (1.3.0, 1.5.0, etc.) like 1.3.1, 1.5.2 and etc are corresponding to another **feature releases** with new development and bugfixes. These releases are not mandatory, but still **highly** recommended, **as they may contain critical security fixes**. + +--- + +### Performing a Release + +At the beginning of each release cycle, we will generate our "Release tracking issue", which is populated with the content at [https://github.com/filecoin-project/lotus/blob/master/documentation/misc/RELEASE_ISSUE_TEMPLATE.md](https://github.com/filecoin-project/lotus/blob/master/documentation/misc/RELEASE_ISSUE_TEMPLATE.md) + +This template will be used to track major goals we have, a planned shipping date, and a complete release checklist tied to a specific release. + +### Security Fix Policy + +Any release may contain security fixes. Unless the fix addresses a bug being exploited in the wild, the fix will *not* be called out in the release notes. Please make sure to update ASAP. + +By policy, the team will usually wait until about 3 weeks after the final release to announce any fixed security issues. However, depending on the impact and ease of discovery of the issue, the team may wait more or less time. + + It is important to always update to the latest version ASAP and file issues if you're unable to update for some reason. + +Finally, unless a security issue is actively being exploited or a significant number of users are unable to update to the latest version (e.g., due to a difficult migration, breaking changes, etc.), security fixes will *not* be backported to previous releases. + +## FAQ + +### Why aren't Go major versions used more? + +Golang tightly couples source code with versioning (major versions beyond v1 leak into import paths). This poses logistical difficulties to using major versions here. Concretely, if we were to pick a policy that bumped the major version on every network upgrade, we would disrupt every single downstream library/application that consumed the native Lotus API (e.g., libraries depending on the JSON-RPC client, testground tests). They would need to update their code every single time that we released a network breaking change, even if it brought on zero expectation of breakage for the Golang APIs that they depend on. In this scenario, we are signaling breakage on the wrong API surface! We're signaling breakage on the Go level, when what breaks is the network protocol. + +## Related Items + +1. [Release Issue template](https://github.com/filecoin-project/lotus/blob/master/documentation/misc/RELEASE_ISSUE_TEMPLATE.md) +2. [Lotus Release Flow Discussion](https://github.com/filecoin-project/lotus/discussions/7053): Leave a comment if you have any questions or feedbacks with regard to the lotus release flow. diff --git a/Makefile b/Makefile index 92772ab19..d1e7d159a 100644 --- a/Makefile +++ b/Makefile @@ -5,10 +5,14 @@ all: build unexport GOFLAGS -GOVERSION:=$(shell go version | cut -d' ' -f 3 | cut -d. -f 2) -ifeq ($(shell expr $(GOVERSION) \< 13), 1) -$(warning Your Golang version is go 1.$(GOVERSION)) -$(error Update Golang to version $(shell grep '^go' go.mod)) +GOCC?=go + +GOVERSION:=$(shell $(GOCC) version | tr ' ' '\n' | grep go1 | sed 's/^go//' | awk -F. '{printf "%d%03d%03d", $$1, $$2, $$3}') +GOVERSIONMIN:=$(shell cat GO_VERSION_MIN | awk -F. '{printf "%d%03d%03d", $$1, $$2, $$3}') + +ifeq ($(shell expr $(GOVERSION) \< $(GOVERSIONMIN)), 1) +$(warning Your Golang version is go$(shell expr $(GOVERSION) / 1000000).$(shell expr $(GOVERSION) % 1000000 / 1000).$(shell expr $(GOVERSION) % 1000)) +$(error Update Golang to version to at least $(shell cat GO_VERSION_MIN)) endif # git modules that need to be loaded @@ -17,7 +21,7 @@ MODULES:= CLEAN:= BINS:= -ldflags=-X=github.com/filecoin-project/lotus/build.CurrentCommit='+git$(subst -,.,$(shell git describe --always --match=NeVeRmAtCh --dirty 2>/dev/null || git rev-parse --short HEAD 2>/dev/null))' +ldflags=-X=github.com/filecoin-project/lotus/build.CurrentCommit=+git.$(subst -,.,$(shell git describe --always --match=NeVeRmAtCh --dirty 2>/dev/null || git rev-parse --short HEAD 2>/dev/null)) ifneq ($(strip $(LDFLAGS)),) ldflags+=-extldflags=$(LDFLAGS) endif @@ -41,8 +45,13 @@ MODULES+=$(FFI_PATH) BUILD_DEPS+=build/.filecoin-install CLEAN+=build/.filecoin-install -$(MODULES): build/.update-modules ; +ffi-version-check: + @[[ "$$(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 + +$(MODULES): build/.update-modules ; # dummy file that marks the last time modules were updated build/.update-modules: git submodule update --init --recursive @@ -57,146 +66,213 @@ CLEAN+=build/.update-modules deps: $(BUILD_DEPS) .PHONY: deps +build-devnets: build lotus-seed lotus-shed +.PHONY: build-devnets + debug: GOFLAGS+=-tags=debug -debug: lotus lotus-storage-miner lotus-seal-worker lotus-seed +debug: build-devnets 2k: GOFLAGS+=-tags=2k -2k: lotus lotus-storage-miner lotus-seal-worker lotus-seed +2k: build-devnets + +calibnet: GOFLAGS+=-tags=calibnet +calibnet: build-devnets + +butterflynet: GOFLAGS+=-tags=butterflynet +butterflynet: build-devnets + +interopnet: GOFLAGS+=-tags=interopnet +interopnet: build-devnets lotus: $(BUILD_DEPS) rm -f lotus - go build $(GOFLAGS) -o lotus ./cmd/lotus - go run github.com/GeertJohan/go.rice/rice append --exec lotus -i ./build + $(GOCC) build $(GOFLAGS) -o lotus ./cmd/lotus .PHONY: lotus BINS+=lotus -lotus-storage-miner: $(BUILD_DEPS) - rm -f lotus-storage-miner - go build $(GOFLAGS) -o lotus-storage-miner ./cmd/lotus-storage-miner - go run github.com/GeertJohan/go.rice/rice append --exec lotus-storage-miner -i ./build -.PHONY: lotus-storage-miner -BINS+=lotus-storage-miner +lotus-miner: $(BUILD_DEPS) + rm -f lotus-miner + $(GOCC) build $(GOFLAGS) -o lotus-miner ./cmd/lotus-miner +.PHONY: lotus-miner +BINS+=lotus-miner -lotus-seal-worker: $(BUILD_DEPS) - rm -f lotus-seal-worker - go build $(GOFLAGS) -o lotus-seal-worker ./cmd/lotus-seal-worker - go run github.com/GeertJohan/go.rice/rice append --exec lotus-seal-worker -i ./build -.PHONY: lotus-seal-worker -BINS+=lotus-seal-worker +lotus-worker: $(BUILD_DEPS) + rm -f lotus-worker + $(GOCC) build $(GOFLAGS) -o lotus-worker ./cmd/lotus-worker +.PHONY: lotus-worker +BINS+=lotus-worker lotus-shed: $(BUILD_DEPS) rm -f lotus-shed - go build $(GOFLAGS) -o lotus-shed ./cmd/lotus-shed - go run github.com/GeertJohan/go.rice/rice append --exec lotus-shed -i ./build + $(GOCC) build $(GOFLAGS) -o lotus-shed ./cmd/lotus-shed .PHONY: lotus-shed BINS+=lotus-shed -build: lotus lotus-storage-miner lotus-seal-worker +lotus-gateway: $(BUILD_DEPS) + rm -f lotus-gateway + $(GOCC) build $(GOFLAGS) -o lotus-gateway ./cmd/lotus-gateway +.PHONY: lotus-gateway +BINS+=lotus-gateway + +build: lotus lotus-miner lotus-worker @[[ $$(type -P "lotus") ]] && echo "Caution: you have \ an existing lotus binary in your PATH. This may cause problems if you don't run 'sudo make install'" || true .PHONY: build -install: +install: install-daemon install-miner install-worker + +install-daemon: install -C ./lotus /usr/local/bin/lotus - install -C ./lotus-storage-miner /usr/local/bin/lotus-storage-miner - install -C ./lotus-seal-worker /usr/local/bin/lotus-seal-worker -install-services: install - mkdir -p /usr/local/lib/systemd/system - install -C -m 0644 ./scripts/lotus-daemon.service /usr/local/lib/systemd/system/lotus-daemon.service - install -C -m 0644 ./scripts/lotus-miner.service /usr/local/lib/systemd/system/lotus-miner.service - systemctl daemon-reload - @echo - @echo "lotus and lotus-miner services installed. Don't forget to 'systemctl enable lotus|lotus-miner' for it to be enabled on startup." +install-miner: + install -C ./lotus-miner /usr/local/bin/lotus-miner -clean-services: - rm -f /usr/local/lib/systemd/system/lotus-daemon.service - rm -f /usr/local/lib/systemd/system/lotus-miner.service - systemctl daemon-reload +install-worker: + install -C ./lotus-worker /usr/local/bin/lotus-worker + +install-app: + install -C ./$(APP) /usr/local/bin/$(APP) + +uninstall: uninstall-daemon uninstall-miner uninstall-worker +.PHONY: uninstall + +uninstall-daemon: + rm -f /usr/local/bin/lotus + +uninstall-miner: + rm -f /usr/local/bin/lotus-miner + +uninstall-worker: + rm -f /usr/local/bin/lotus-worker # TOOLS lotus-seed: $(BUILD_DEPS) rm -f lotus-seed - go build $(GOFLAGS) -o lotus-seed ./cmd/lotus-seed - go run github.com/GeertJohan/go.rice/rice append --exec lotus-seed -i ./build + $(GOCC) build $(GOFLAGS) -o lotus-seed ./cmd/lotus-seed .PHONY: lotus-seed BINS+=lotus-seed benchmarks: - go run github.com/whyrusleeping/bencher ./... > bench.json + $(GOCC) run github.com/whyrusleeping/bencher ./... > bench.json @echo Submitting results @curl -X POST 'http://benchmark.kittyhawk.wtf/benchmark' -d '@bench.json' -u "${benchmark_http_cred}" .PHONY: benchmarks -pond: build - go build -o pond ./lotuspond - (cd lotuspond/front && npm i && CI=false npm run build) -.PHONY: pond -BINS+=pond +lotus-fountain: + rm -f lotus-fountain + $(GOCC) build $(GOFLAGS) -o lotus-fountain ./cmd/lotus-fountain + $(GOCC) run github.com/GeertJohan/go.rice/rice append --exec lotus-fountain -i ./cmd/lotus-fountain -i ./build +.PHONY: lotus-fountain +BINS+=lotus-fountain -townhall: - rm -f townhall - go build -o townhall ./cmd/lotus-townhall - (cd ./cmd/lotus-townhall/townhall && npm i && npm run build) - go run github.com/GeertJohan/go.rice/rice append --exec townhall -i ./cmd/lotus-townhall -i ./build -.PHONY: townhall -BINS+=townhall +lotus-bench: + rm -f lotus-bench + $(GOCC) build $(GOFLAGS) -o lotus-bench ./cmd/lotus-bench +.PHONY: lotus-bench +BINS+=lotus-bench -fountain: - rm -f fountain - go build -o fountain ./cmd/lotus-fountain - go run github.com/GeertJohan/go.rice/rice append --exec fountain -i ./cmd/lotus-fountain -i ./build -.PHONY: fountain -BINS+=fountain +lotus-stats: + rm -f lotus-stats + $(GOCC) build $(GOFLAGS) -o lotus-stats ./cmd/lotus-stats +.PHONY: lotus-stats +BINS+=lotus-stats -chainwatch: - rm -f chainwatch - go build -o chainwatch ./cmd/lotus-chainwatch - go run github.com/GeertJohan/go.rice/rice append --exec chainwatch -i ./cmd/lotus-chainwatch -i ./build -.PHONY: chainwatch -BINS+=chainwatch +lotus-pcr: + rm -f lotus-pcr + $(GOCC) build $(GOFLAGS) -o lotus-pcr ./cmd/lotus-pcr +.PHONY: lotus-pcr +BINS+=lotus-pcr -bench: - rm -f bench - go build -o bench ./cmd/lotus-bench - go run github.com/GeertJohan/go.rice/rice append --exec bench -i ./build -.PHONY: bench -BINS+=bench - -stats: - rm -f stats - go build -o stats ./tools/stats - go run github.com/GeertJohan/go.rice/rice append --exec stats -i ./build -.PHONY: stats -BINS+=stats - -health: +lotus-health: rm -f lotus-health - go build -o lotus-health ./cmd/lotus-health - go run github.com/GeertJohan/go.rice/rice append --exec lotus-health -i ./build + $(GOCC) build -o lotus-health ./cmd/lotus-health +.PHONY: lotus-health +BINS+=lotus-health -.PHONY: health -BINS+=health +lotus-wallet: + rm -f lotus-wallet + $(GOCC) build $(GOFLAGS) -o lotus-wallet ./cmd/lotus-wallet +.PHONY: lotus-wallet +BINS+=lotus-wallet + +lotus-keygen: + rm -f lotus-keygen + $(GOCC) build -o lotus-keygen ./cmd/lotus-keygen +.PHONY: lotus-keygen +BINS+=lotus-keygen + +testground: + $(GOCC) build -tags testground -o /dev/null ./cmd/lotus +.PHONY: testground +BINS+=testground + + +tvx: + rm -f tvx + $(GOCC) build -o tvx ./cmd/tvx +.PHONY: tvx +BINS+=tvx + +lotus-sim: $(BUILD_DEPS) + rm -f lotus-sim + $(GOCC) build $(GOFLAGS) -o lotus-sim ./cmd/lotus-sim +.PHONY: lotus-sim +BINS+=lotus-sim + +# SYSTEMD + +install-daemon-service: install-daemon + mkdir -p /etc/systemd/system + mkdir -p /var/log/lotus + install -C -m 0644 ./scripts/lotus-daemon.service /etc/systemd/system/lotus-daemon.service + systemctl daemon-reload + @echo + @echo "lotus-daemon service installed. Don't forget to run 'sudo systemctl start lotus-daemon' to start it and 'sudo systemctl enable lotus-daemon' for it to be enabled on startup." + +install-miner-service: install-miner install-daemon-service + mkdir -p /etc/systemd/system + mkdir -p /var/log/lotus + install -C -m 0644 ./scripts/lotus-miner.service /etc/systemd/system/lotus-miner.service + systemctl daemon-reload + @echo + @echo "lotus-miner service installed. Don't forget to run 'sudo systemctl start lotus-miner' to start it and 'sudo systemctl enable lotus-miner' for it to be enabled on startup." + +install-main-services: install-miner-service + +install-all-services: install-main-services + +install-services: install-main-services + +clean-daemon-service: clean-miner-service + -systemctl stop lotus-daemon + -systemctl disable lotus-daemon + rm -f /etc/systemd/system/lotus-daemon.service + systemctl daemon-reload + +clean-miner-service: + -systemctl stop lotus-miner + -systemctl disable lotus-miner + rm -f /etc/systemd/system/lotus-miner.service + systemctl daemon-reload + +clean-main-services: clean-daemon-service + +clean-all-services: clean-main-services + +clean-services: clean-all-services # MISC buildall: $(BINS) -completions: - ./scripts/make-completions.sh lotus - ./scripts/make-completions.sh lotus-storage-miner -.PHONY: completions - install-completions: mkdir -p /usr/share/bash-completion/completions /usr/local/share/zsh/site-functions/ install -C ./scripts/bash-completion/lotus /usr/share/bash-completion/completions/lotus - install -C ./scripts/bash-completion/lotus-storage-miner /usr/share/bash-completion/completions/lotus-storage-miner install -C ./scripts/zsh-completion/lotus /usr/local/share/zsh/site-functions/_lotus - install -C ./scripts/zsh-completion/lotus-storage-miner /usr/local/share/zsh/site-functions/_lotus-storage-miner clean: rm -rf $(CLEAN) $(BINS) @@ -208,13 +284,95 @@ dist-clean: git submodule deinit --all -f .PHONY: dist-clean -type-gen: - go run ./gen/main.go +type-gen: api-gen + $(GOCC) run ./gen/main.go + $(GOCC) generate -x ./... + goimports -w api/ -method-gen: - (cd ./lotuspond/front/src/chain && go run ./methodgen.go) +actors-code-gen: + $(GOCC) run ./gen/inline-gen . gen/inlinegen-data.json + $(GOCC) run ./chain/actors/agen + $(GOCC) fmt ./... -gen: type-gen method-gen +actors-gen: actors-code-gen fiximports +.PHONY: actors-gen + +bundle-gen: + $(GOCC) run ./gen/bundle $(VERSION) $(RELEASE) $(RELEASE_OVERRIDES) + $(GOCC) fmt ./build/... +.PHONY: bundle-gen + + +api-gen: + $(GOCC) run ./gen/api + goimports -w api + goimports -w api +.PHONY: api-gen + +cfgdoc-gen: + $(GOCC) run ./node/config/cfgdocgen > ./node/config/doc_gen.go + +appimage: lotus + rm -rf appimage-builder-cache || true + rm AppDir/io.filecoin.lotus.desktop || true + rm AppDir/icon.svg || true + rm Appdir/AppRun || true + mkdir -p AppDir/usr/bin + cp ./lotus AppDir/usr/bin/ + appimage-builder + +docsgen: docsgen-md docsgen-openrpc fiximports + +docsgen-md-bin: api-gen actors-gen + $(GOCC) build $(GOFLAGS) -o docgen-md ./api/docgen/cmd +docsgen-openrpc-bin: api-gen actors-gen + $(GOCC) build $(GOFLAGS) -o docgen-openrpc ./api/docgen-openrpc/cmd + +docsgen-md: docsgen-md-full docsgen-md-storage docsgen-md-worker + +docsgen-md-full: docsgen-md-bin + ./docgen-md "api/api_full.go" "FullNode" "api" "./api" > documentation/en/api-v1-unstable-methods.md + ./docgen-md "api/v0api/full.go" "FullNode" "v0api" "./api/v0api" > documentation/en/api-v0-methods.md +docsgen-md-storage: docsgen-md-bin + ./docgen-md "api/api_storage.go" "StorageMiner" "api" "./api" > documentation/en/api-v0-methods-miner.md +docsgen-md-worker: docsgen-md-bin + ./docgen-md "api/api_worker.go" "Worker" "api" "./api" > documentation/en/api-v0-methods-worker.md + +docsgen-openrpc: docsgen-openrpc-full docsgen-openrpc-storage docsgen-openrpc-worker docsgen-openrpc-gateway + +docsgen-openrpc-full: docsgen-openrpc-bin + ./docgen-openrpc "api/api_full.go" "FullNode" "api" "./api" -gzip > build/openrpc/full.json.gz +docsgen-openrpc-storage: docsgen-openrpc-bin + ./docgen-openrpc "api/api_storage.go" "StorageMiner" "api" "./api" -gzip > build/openrpc/miner.json.gz +docsgen-openrpc-worker: docsgen-openrpc-bin + ./docgen-openrpc "api/api_worker.go" "Worker" "api" "./api" -gzip > build/openrpc/worker.json.gz +docsgen-openrpc-gateway: docsgen-openrpc-bin + ./docgen-openrpc "api/api_gateway.go" "Gateway" "api" "./api" -gzip > build/openrpc/gateway.json.gz + +.PHONY: docsgen docsgen-md-bin docsgen-openrpc-bin + +fiximports: + ./scripts/fiximports + +gen: actors-code-gen type-gen cfgdoc-gen docsgen api-gen circleci fiximports + @echo ">>> IF YOU'VE MODIFIED THE CLI OR CONFIG, REMEMBER TO ALSO MAKE docsgen-cli" +.PHONY: gen + +jen: gen + +snap: lotus lotus-miner lotus-worker + snapcraft + # snapcraft upload ./lotus_*.snap + +# separate from gen because it needs binaries +docsgen-cli: lotus lotus-miner lotus-worker + python3 ./scripts/generate-lotus-cli.py + ./lotus config default > documentation/en/default-lotus-config.toml + ./lotus-miner config default > documentation/en/default-lotus-miner-config.toml +.PHONY: docsgen-cli print-%: @echo $*=$($*) + +circleci: + go generate -x ./.circleci diff --git a/README.md b/README.md index b0d9867d9..b67cb952f 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,137 @@ -![Lotus](documentation/images/lotus_logo_h.png) +

+ + Project Lotus Logo + +

-# Project Lotus - 莲 +

Project Lotus - 莲

-Lotus is an implementation of the Filecoin Distributed Storage Network. For more details about Filecoin, check out the [Filecoin Spec](https://github.com/filecoin-project/specs). +

+ + + + +
+

-## Development - -All work is tracked via issues. An attempt at keeping an up-to-date view on remaining work is in the [lotus testnet github project board](https://github.com/filecoin-project/lotus/projects/1). +Lotus is an implementation of the Filecoin Distributed Storage Network. For more details about Filecoin, check out the [Filecoin Spec](https://spec.filecoin.io). ## Building & Documentation -For instructions on how to build lotus from source, please visit [https://docs.lotu.sh](https://docs.lotu.sh) or read the source [here](https://github.com/filecoin-project/lotus/tree/master/documentation). +> Note: The default `master` branch is the dev branch, please use with caution. For the latest stable version, checkout the most recent [`Latest release`](https://github.com/filecoin-project/lotus/releases). + +For complete instructions on how to build, install and setup lotus, please visit [https://lotus.filecoin.io](https://lotus.filecoin.io/lotus/install/prerequisites/#supported-platforms). Basic build instructions can be found further down in this readme. + +## Reporting a Vulnerability + +Please send an email to security@filecoin.org. See our [security policy](SECURITY.md) for more details. + +## Related packages + +These repos are independent and reusable modules, but are tightly integrated into Lotus to make up a fully featured Filecoin implementation: + +- [go-fil-markets](https://github.com/filecoin-project/go-fil-markets) which has its own [kanban work tracker available here](https://app.zenhub.com/workspaces/markets-shared-components-5daa144a7046a60001c6e253/board) +- [builtin-actors](https://github.com/filecoin-project/builtin-actors) + +## Contribute + +Lotus is a universally open project and welcomes contributions of all kinds: code, docs, and more. However, before making a contribution, we ask you to heed these recommendations: + +1. If the proposal entails a protocol change, please first submit a [Filecoin Improvement Proposal](https://github.com/filecoin-project/FIPs). +2. If the change is complex and requires prior discussion, [open an issue](github.com/filecoin-project/lotus/issues) or a [discussion](https://github.com/filecoin-project/lotus/discussions) to request feedback before you start working on a pull request. This is to avoid disappointment and sunk costs, in case the change is not actually needed or accepted. +3. Please refrain from submitting PRs to adapt existing code to subjective preferences. The changeset should contain functional or technical improvements/enhancements, bug fixes, new features, or some other clear material contribution. Simple stylistic changes are likely to be rejected in order to reduce code churn. + +When implementing a change: + +1. Adhere to the standard Go formatting guidelines, e.g. [Effective Go](https://golang.org/doc/effective_go.html). Run `go fmt`. +2. Stick to the idioms and patterns used in the codebase. Familiar-looking code has a higher chance of being accepted than eerie code. Pay attention to commonly used variable and parameter names, avoidance of naked returns, error handling patterns, etc. +3. Comments: follow the advice on the [Commentary](https://golang.org/doc/effective_go.html#commentary) section of Effective Go. +4. Minimize code churn. Modify only what is strictly necessary. Well-encapsulated changesets will get a quicker response from maintainers. +5. Lint your code with [`golangci-lint`](https://golangci-lint.run) (CI will reject your PR if unlinted). +6. Add tests. +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 hwloc-devel +``` + +For other distributions you can find the required dependencies [here.](https://lotus.filecoin.io/lotus/install/prerequisites/#supported-platforms) For instructions specific to macOS, you can find them [here.](https://lotus.filecoin.io/lotus/install/macos/) + +#### Go + +To build Lotus, you need a working installation of [Go 1.19.7 or higher](https://golang.org/dl/): + +```bash +wget -c https://golang.org/dl/go1.19.7.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://lotus.filecoin.io/lotus/manage/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://lotus.filecoin.io/lotus/configure/nodes-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://lotus.filecoin.io/lotus/install/prerequisites/). Note, if you are building the proof binaries from source, [installing rustup](https://lotus.filecoin.io/lotus/install/linux/#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 + + 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://lotus.filecoin.io/lotus/configure/defaults/#environment-variables) 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://lotus.filecoin.io/lotus/install/linux/#start-the-lotus-daemon-and-sync-the-chain). ## License diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..d53c2b920 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,23 @@ +# Security Policy + +## Reporting a Vulnerability + +For reporting security vulnerabilities/bugs, please consult our Security Policy and Responsible Disclosure Program information at https://github.com/filecoin-project/community/blob/master/SECURITY.md. Security vulnerabilities should be reported via our [Vulnerability Reporting channels](https://github.com/filecoin-project/community/blob/master/SECURITY.md#vulnerability-reporting) and will be eligible for a [Bug Bounty](https://security.filecoin.io/bug-bounty/). + +Please try to provide a clear description of any bugs reported, along with how to reproduce the bug if possible. More detailed bug reports (especially those with a PoC included) will help us move forward much faster. Additionally, please avoid reporting bugs that already have open issues. Take a moment to search the issue list of the related GitHub repositories before writing up a new report. + +Here are some examples of bugs we would consider to be security vulnerabilities: + +* If you can spend from a `multisig` wallet you do not control the keys for. +* If you can cause a miner to be slashed without them actually misbehaving. +* If you can maintain power without submitting windowed posts regularly. +* If you can craft a message that causes lotus nodes to panic. +* If you can cause your miner to win significantly more blocks than it should. +* If you can craft a message that causes a persistent fork in the network. +* If you can cause the total amount of Filecoin in the network to no longer be 2 billion. + +This is not an exhaustive list, but should provide some idea of what we consider as a security vulnerability, . + +## Reporting a non security bug + +For non-security bugs, please simply file a GitHub [issue](https://github.com/filecoin-project/lotus/issues/new?template=bug_report.md). diff --git a/api/README.md b/api/README.md new file mode 100644 index 000000000..07089d7ae --- /dev/null +++ b/api/README.md @@ -0,0 +1,14 @@ +## Lotus API + +This package contains all lotus API definitions. Interfaces defined here are +exposed as JsonRPC 2.0 endpoints by lotus programs. + +### Versions + +| File | Alias File | Interface | Exposed by | Version | HTTP Endpoint | Status | Docs +|------------------|-------------------|----------------|--------------------|---------|---------------|------------------------------|------ +| `api_common.go` | `v0api/latest.go` | `Common` | lotus; lotus-miner | v0 | `/rpc/v0` | Latest, Stable | [Methods](../documentation/en/api-v0-methods.md) +| `api_full.go` | `v1api/latest.go` | `FullNode` | lotus | v1 | `/rpc/v1` | Latest, **Work in progress** | [Methods](../documentation/en/api-v1-unstable-methods.md) +| `api_storage.go` | `v0api/latest.go` | `StorageMiner` | lotus-miner | v0 | `/rpc/v0` | Latest, Stable | [Methods](../documentation/en/api-v0-methods-miner.md) +| `api_worker.go` | `v0api/latest.go` | `Worker` | lotus-worker | v0 | `/rpc/v0` | Latest, Stable | [Methods](../documentation/en/api-v0-methods-worker.md) +| `v0api/full.go` | | `FullNode` | lotus | v0 | `/rpc/v0` | Stable | [Methods](../documentation/en/api-v0-methods.md) diff --git a/api/api_common.go b/api/api_common.go index aac2a61a7..2a887a26a 100644 --- a/api/api_common.go +++ b/api/api_common.go @@ -3,51 +3,71 @@ package api import ( "context" "fmt" + "time" - "github.com/libp2p/go-libp2p-core/network" - "github.com/libp2p/go-libp2p-core/peer" + "github.com/google/uuid" "github.com/filecoin-project/go-jsonrpc/auth" - "github.com/filecoin-project/lotus/build" + apitypes "github.com/filecoin-project/lotus/api/types" + "github.com/filecoin-project/lotus/journal/alerting" ) +// MODIFYING THE API INTERFACE +// +// When adding / changing methods in this file: +// * Do the change here +// * Adjust implementation in `node/impl/` +// * Run `make gen` - this will: +// * Generate proxy structs +// * Generate mocks +// * Generate markdown docs +// * Generate openrpc blobs + type Common interface { - // Auth - AuthVerify(ctx context.Context, token string) ([]auth.Permission, error) - AuthNew(ctx context.Context, perms []auth.Permission) ([]byte, error) + // MethodGroup: Auth - // network + AuthVerify(ctx context.Context, token string) ([]auth.Permission, error) //perm:read + AuthNew(ctx context.Context, perms []auth.Permission) ([]byte, error) //perm:admin - NetConnectedness(context.Context, peer.ID) (network.Connectedness, error) - NetPeers(context.Context) ([]peer.AddrInfo, error) - NetConnect(context.Context, peer.AddrInfo) error - NetAddrsListen(context.Context) (peer.AddrInfo, error) - NetDisconnect(context.Context, peer.ID) error - NetFindPeer(context.Context, peer.ID) (peer.AddrInfo, error) + // MethodGroup: Log - // ID returns peerID of libp2p node backing this API - ID(context.Context) (peer.ID, error) + LogList(context.Context) ([]string, error) //perm:write + LogSetLevel(context.Context, string, string) error //perm:write + + // LogAlerts returns list of all, active and inactive alerts tracked by the + // node + LogAlerts(ctx context.Context) ([]alerting.Alert, error) //perm:admin + + // MethodGroup: Common // Version provides information about API provider - Version(context.Context) (Version, error) + Version(context.Context) (APIVersion, error) //perm:read - LogList(context.Context) ([]string, error) - LogSetLevel(context.Context, string, string) error + // Discover returns an OpenRPC document describing an RPC API. + Discover(ctx context.Context) (apitypes.OpenRPCDocument, error) //perm:read // trigger graceful shutdown - Shutdown(context.Context) error + Shutdown(context.Context) error //perm:admin + + // StartTime returns node start time + StartTime(context.Context) (time.Time, error) //perm:read + + // Session returns a random UUID of api provider session + Session(context.Context) (uuid.UUID, error) //perm:read + + Closing(context.Context) (<-chan struct{}, error) //perm:read } -// Version provides various build-time information -type Version struct { +// APIVersion provides various build-time information +type APIVersion struct { Version string // APIVersion is a binary encoded semver version of the remote implementing // this api // // See APIVersion in build/version.go - APIVersion build.Version + APIVersion Version // TODO: git commit / os / genesis cid? @@ -55,6 +75,6 @@ type Version struct { BlockDelay uint64 } -func (v Version) String() string { +func (v APIVersion) String() string { return fmt.Sprintf("%s+api%s", v.Version, v.APIVersion.String()) } diff --git a/api/api_errors.go b/api/api_errors.go new file mode 100644 index 000000000..fd157be5f --- /dev/null +++ b/api/api_errors.go @@ -0,0 +1,42 @@ +package api + +import ( + "errors" + "reflect" + + "github.com/filecoin-project/go-jsonrpc" +) + +const ( + EOutOfGas = iota + jsonrpc.FirstUserCode + EActorNotFound +) + +type ErrOutOfGas struct{} + +func (e *ErrOutOfGas) Error() string { + return "call ran out of gas" +} + +type ErrActorNotFound struct{} + +func (e *ErrActorNotFound) Error() string { + return "actor not found" +} + +var RPCErrors = jsonrpc.NewErrors() + +func ErrorIsIn(err error, errorTypes []error) bool { + for _, etype := range errorTypes { + tmp := reflect.New(reflect.PointerTo(reflect.ValueOf(etype).Elem().Type())).Interface() + if errors.As(err, tmp) { + return true + } + } + return false +} + +func init() { + RPCErrors.Register(EOutOfGas, new(*ErrOutOfGas)) + RPCErrors.Register(EActorNotFound, new(*ErrActorNotFound)) +} diff --git a/api/api_full.go b/api/api_full.go index cf7f5b79b..af62c3b0c 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -2,105 +2,349 @@ package api import ( "context" + "encoding/json" + "fmt" "time" + "github.com/google/uuid" + blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" - "github.com/ipfs/go-filestore" - "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p/core/peer" "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-bitfield" + datatransfer "github.com/filecoin-project/go-data-transfer/v2" + "github.com/filecoin-project/go-fil-markets/retrievalmarket" "github.com/filecoin-project/go-fil-markets/storagemarket" - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/abi/big" - "github.com/filecoin-project/specs-actors/actors/builtin/market" - "github.com/filecoin-project/specs-actors/actors/builtin/miner" - "github.com/filecoin-project/specs-actors/actors/builtin/paych" - "github.com/filecoin-project/specs-actors/actors/builtin/power" - "github.com/filecoin-project/specs-actors/actors/crypto" + "github.com/filecoin-project/go-jsonrpc" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/builtin/v8/paych" + "github.com/filecoin-project/go-state-types/builtin/v9/market" + "github.com/filecoin-project/go-state-types/builtin/v9/miner" + verifregtypes "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/go-state-types/dline" + abinetwork "github.com/filecoin-project/go-state-types/network" + apitypes "github.com/filecoin-project/lotus/api/types" + "github.com/filecoin-project/lotus/chain/actors/builtin" + lminer "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/chain/types/ethtypes" "github.com/filecoin-project/lotus/node/modules/dtypes" + "github.com/filecoin-project/lotus/node/repo/imports" ) +//go:generate go run github.com/golang/mock/mockgen -destination=mocks/mock_full.go -package=mocks . FullNode + +// ChainIO abstracts operations for accessing raw IPLD objects. +type ChainIO interface { + ChainReadObj(context.Context, cid.Cid) ([]byte, error) + ChainHasObj(context.Context, cid.Cid) (bool, error) + ChainPutObj(context.Context, blocks.Block) error +} + +const LookbackNoLimit = abi.ChainEpoch(-1) + +// MODIFYING THE API INTERFACE +// +// NOTE: This is the V1 (Unstable) API - to add methods to the V0 (Stable) API +// you'll have to add those methods to interfaces in `api/v0api` +// +// When adding / changing methods in this file: +// * Do the change here +// * Adjust implementation in `node/impl/` +// * Run `make gen` - this will: +// * Generate proxy structs +// * Generate mocks +// * Generate markdown docs +// * Generate openrpc blobs + // FullNode API is a low-level interface to the Filecoin network full node type FullNode interface { Common - - // TODO: TipSetKeys + Net // MethodGroup: Chain // The Chain method group contains methods for interacting with the - // blockchain, but that do not require any form of state computation + // blockchain, but that do not require any form of state computation. - // ChainNotify returns channel with chain head updates - // First message is guaranteed to be of len == 1, and type == 'current' - ChainNotify(context.Context) (<-chan []*HeadChange, error) - // ChainHead returns the current head of the chain - ChainHead(context.Context) (*types.TipSet, error) - // ChainGetRandomness is used to sample the chain for randomness - ChainGetRandomness(ctx context.Context, tsk types.TipSetKey, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) (abi.Randomness, error) - // ChainGetBlock returns the block specified by the given CID - ChainGetBlock(context.Context, cid.Cid) (*types.BlockHeader, error) - ChainGetTipSet(context.Context, types.TipSetKey) (*types.TipSet, error) - ChainGetBlockMessages(context.Context, cid.Cid) (*BlockMessages, error) - ChainGetParentReceipts(context.Context, cid.Cid) ([]*types.MessageReceipt, error) - ChainGetParentMessages(context.Context, cid.Cid) ([]Message, error) - ChainGetTipSetByHeight(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error) - ChainReadObj(context.Context, cid.Cid) ([]byte, error) - ChainHasObj(context.Context, cid.Cid) (bool, error) - ChainStatObj(context.Context, cid.Cid, cid.Cid) (ObjStat, error) - ChainSetHead(context.Context, types.TipSetKey) error - ChainGetGenesis(context.Context) (*types.TipSet, error) - ChainTipSetWeight(context.Context, types.TipSetKey) (types.BigInt, error) - ChainGetNode(ctx context.Context, p string) (*IpldObject, error) - ChainGetMessage(context.Context, cid.Cid) (*types.Message, error) - ChainGetPath(ctx context.Context, from types.TipSetKey, to types.TipSetKey) ([]*HeadChange, error) - ChainExport(context.Context, types.TipSetKey) (<-chan []byte, error) + // ChainNotify returns channel with chain head updates. + // First message is guaranteed to be of len == 1, and type == 'current'. + ChainNotify(context.Context) (<-chan []*HeadChange, error) //perm:read + + // ChainHead returns the current head of the chain. + ChainHead(context.Context) (*types.TipSet, error) //perm:read + + // ChainGetBlock returns the block specified by the given CID. + ChainGetBlock(context.Context, cid.Cid) (*types.BlockHeader, error) //perm:read + // ChainGetTipSet returns the tipset specified by the given TipSetKey. + ChainGetTipSet(context.Context, types.TipSetKey) (*types.TipSet, error) //perm:read + + // ChainGetBlockMessages returns messages stored in the specified block. + // + // Note: If there are multiple blocks in a tipset, it's likely that some + // messages will be duplicated. It's also possible for blocks in a tipset to have + // different messages from the same sender at the same nonce. When that happens, + // only the first message (in a block with lowest ticket) will be considered + // for execution + // + // NOTE: THIS METHOD SHOULD ONLY BE USED FOR GETTING MESSAGES IN A SPECIFIC BLOCK + // + // DO NOT USE THIS METHOD TO GET MESSAGES INCLUDED IN A TIPSET + // Use ChainGetParentMessages, which will perform correct message deduplication + ChainGetBlockMessages(ctx context.Context, blockCid cid.Cid) (*BlockMessages, error) //perm:read + + // ChainGetParentReceipts returns receipts for messages in parent tipset of + // the specified block. The receipts in the list returned is one-to-one with the + // messages returned by a call to ChainGetParentMessages with the same blockCid. + ChainGetParentReceipts(ctx context.Context, blockCid cid.Cid) ([]*types.MessageReceipt, error) //perm:read + + // ChainGetParentMessages returns messages stored in parent tipset of the + // specified block. + ChainGetParentMessages(ctx context.Context, blockCid cid.Cid) ([]Message, error) //perm:read + + // ChainGetMessagesInTipset returns message stores in current tipset + ChainGetMessagesInTipset(ctx context.Context, tsk types.TipSetKey) ([]Message, error) //perm:read + + // ChainGetTipSetByHeight looks back for a tipset at the specified epoch. + // If there are no blocks at the specified epoch, a tipset at an earlier epoch + // will be returned. + ChainGetTipSetByHeight(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error) //perm:read + + // ChainGetTipSetAfterHeight looks back for a tipset at the specified epoch. + // If there are no blocks at the specified epoch, the first non-nil tipset at a later epoch + // will be returned. + ChainGetTipSetAfterHeight(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error) //perm:read + + // ChainReadObj reads ipld nodes referenced by the specified CID from chain + // blockstore and returns raw bytes. + ChainReadObj(context.Context, cid.Cid) ([]byte, error) //perm:read + + // ChainDeleteObj deletes node referenced by the given CID + ChainDeleteObj(context.Context, cid.Cid) error //perm:admin + + // ChainHasObj checks if a given CID exists in the chain blockstore. + ChainHasObj(context.Context, cid.Cid) (bool, error) //perm:read + + // ChainPutObj puts a given object into the block store + ChainPutObj(context.Context, blocks.Block) error //perm:admin + + // ChainStatObj returns statistics about the graph referenced by 'obj'. + // If 'base' is also specified, then the returned stat will be a diff + // between the two objects. + ChainStatObj(ctx context.Context, obj cid.Cid, base cid.Cid) (ObjStat, error) //perm:read + + // ChainSetHead forcefully sets current chain head. Use with caution. + ChainSetHead(context.Context, types.TipSetKey) error //perm:admin + + // ChainGetGenesis returns the genesis tipset. + ChainGetGenesis(context.Context) (*types.TipSet, error) //perm:read + + // ChainTipSetWeight computes weight for the specified tipset. + ChainTipSetWeight(context.Context, types.TipSetKey) (types.BigInt, error) //perm:read + ChainGetNode(ctx context.Context, p string) (*IpldObject, error) //perm:read + + // ChainGetMessage reads a message referenced by the specified CID from the + // chain blockstore. + ChainGetMessage(context.Context, cid.Cid) (*types.Message, error) //perm:read + + // ChainGetPath returns a set of revert/apply operations needed to get from + // one tipset to another, for example: + // ``` + // to + // ^ + // from tAA + // ^ ^ + // tBA tAB + // ^---*--^ + // ^ + // tRR + // ``` + // Would return `[revert(tBA), apply(tAB), apply(tAA)]` + ChainGetPath(ctx context.Context, from types.TipSetKey, to types.TipSetKey) ([]*HeadChange, error) //perm:read + + // ChainExport returns a stream of bytes with CAR dump of chain data. + // The exported chain data includes the header chain from the given tipset + // back to genesis, the entire genesis state, and the most recent 'nroots' + // state trees. + // If oldmsgskip is set, messages from before the requested roots are also not included. + ChainExport(ctx context.Context, nroots abi.ChainEpoch, oldmsgskip bool, tsk types.TipSetKey) (<-chan []byte, error) //perm:read + + // ChainExportRangeInternal triggers the export of a chain + // CAR-snapshot directly to disk. It is similar to ChainExport, + // except, depending on options, the snapshot can include receipts, + // messages and stateroots for the length between the specified head + // and tail, thus producing "archival-grade" snapshots that include + // all the on-chain data. The header chain is included back to + // genesis and these snapshots can be used to initialize Filecoin + // nodes. + ChainExportRangeInternal(ctx context.Context, head, tail types.TipSetKey, cfg ChainExportConfig) error //perm:admin + + // ChainPrune forces compaction on cold store and garbage collects; only supported if you + // are using the splitstore + ChainPrune(ctx context.Context, opts PruneOpts) error //perm:admin + + // ChainHotGC does online (badger) GC on the hot store; only supported if you are using + // the splitstore + ChainHotGC(ctx context.Context, opts HotGCOpts) error //perm:admin + + // ChainCheckBlockstore performs an (asynchronous) health check on the chain/state blockstore + // if supported by the underlying implementation. + ChainCheckBlockstore(context.Context) error //perm:admin + + // ChainBlockstoreInfo returns some basic information about the blockstore + ChainBlockstoreInfo(context.Context) (map[string]interface{}, error) //perm:read + + // ChainGetEvents returns the events under an event AMT root CID. + ChainGetEvents(context.Context, cid.Cid) ([]types.Event, error) //perm:read + + // GasEstimateFeeCap estimates gas fee cap + GasEstimateFeeCap(context.Context, *types.Message, int64, types.TipSetKey) (types.BigInt, error) //perm:read + + // GasEstimateGasLimit estimates gas used by the message and returns it. + // It fails if message fails to execute. + GasEstimateGasLimit(context.Context, *types.Message, types.TipSetKey) (int64, error) //perm:read + + // GasEstimateGasPremium estimates what gas price should be used for a + // message to have high likelihood of inclusion in `nblocksincl` epochs. + + GasEstimateGasPremium(_ context.Context, nblocksincl uint64, + sender address.Address, gaslimit int64, tsk types.TipSetKey) (types.BigInt, error) //perm:read + + // GasEstimateMessageGas estimates gas values for unset message gas fields + GasEstimateMessageGas(context.Context, *types.Message, *MessageSendSpec, types.TipSetKey) (*types.Message, error) //perm:read // MethodGroup: Sync // The Sync method group contains methods for interacting with and - // observing the lotus sync service + // observing the lotus sync service. - // SyncState returns the current status of the lotus sync system - SyncState(context.Context) (*SyncState, error) - // SyncSubmitBlock can be used to submit a newly created block to the + // SyncState returns the current status of the lotus sync system. + SyncState(context.Context) (*SyncState, error) //perm:read + + // SyncSubmitBlock can be used to submit a newly created block to the. // network through this node - SyncSubmitBlock(ctx context.Context, blk *types.BlockMsg) error - SyncIncomingBlocks(ctx context.Context) (<-chan *types.BlockHeader, error) - SyncMarkBad(ctx context.Context, bcid cid.Cid) error - SyncCheckBad(ctx context.Context, bcid cid.Cid) (string, error) + SyncSubmitBlock(ctx context.Context, blk *types.BlockMsg) error //perm:write + + // SyncIncomingBlocks returns a channel streaming incoming, potentially not + // yet synced block headers. + SyncIncomingBlocks(ctx context.Context) (<-chan *types.BlockHeader, error) //perm:read + + // SyncCheckpoint marks a blocks as checkpointed, meaning that it won't ever fork away from it. + SyncCheckpoint(ctx context.Context, tsk types.TipSetKey) error //perm:admin + + // SyncMarkBad marks a blocks as bad, meaning that it won't ever by synced. + // Use with extreme caution. + SyncMarkBad(ctx context.Context, bcid cid.Cid) error //perm:admin + + // SyncUnmarkBad unmarks a blocks as bad, making it possible to be validated and synced again. + SyncUnmarkBad(ctx context.Context, bcid cid.Cid) error //perm:admin + + // SyncUnmarkAllBad purges bad block cache, making it possible to sync to chains previously marked as bad + SyncUnmarkAllBad(ctx context.Context) error //perm:admin + + // SyncCheckBad checks if a block was marked as bad, and if it was, returns + // the reason. + SyncCheckBad(ctx context.Context, bcid cid.Cid) (string, error) //perm:read + + // SyncValidateTipset indicates whether the provided tipset is valid or not + SyncValidateTipset(ctx context.Context, tsk types.TipSetKey) (bool, error) //perm:read // MethodGroup: Mpool // The Mpool methods are for interacting with the message pool. The message pool // manages all incoming and outgoing 'messages' going over the network. - MpoolPending(context.Context, types.TipSetKey) ([]*types.SignedMessage, error) - MpoolPush(context.Context, *types.SignedMessage) (cid.Cid, error) - MpoolPushMessage(context.Context, *types.Message) (*types.SignedMessage, error) // get nonce, sign, push - MpoolGetNonce(context.Context, address.Address) (uint64, error) - MpoolSub(context.Context) (<-chan MpoolUpdate, error) - MpoolEstimateGasPrice(context.Context, uint64, address.Address, int64, types.TipSetKey) (types.BigInt, error) + // MpoolPending returns pending mempool messages. + MpoolPending(context.Context, types.TipSetKey) ([]*types.SignedMessage, error) //perm:read + + // MpoolSelect returns a list of pending messages for inclusion in the next block + MpoolSelect(context.Context, types.TipSetKey, float64) ([]*types.SignedMessage, error) //perm:read + + // MpoolPush pushes a signed message to mempool. + MpoolPush(context.Context, *types.SignedMessage) (cid.Cid, error) //perm:write + + // MpoolPushUntrusted pushes a signed message to mempool from untrusted sources. + MpoolPushUntrusted(context.Context, *types.SignedMessage) (cid.Cid, error) //perm:write + + // MpoolPushMessage atomically assigns a nonce, signs, and pushes a message + // to mempool. + // maxFee is only used when GasFeeCap/GasPremium fields aren't specified + // + // When maxFee is set to 0, MpoolPushMessage will guess appropriate fee + // based on current chain conditions + MpoolPushMessage(ctx context.Context, msg *types.Message, spec *MessageSendSpec) (*types.SignedMessage, error) //perm:sign + + // MpoolBatchPush batch pushes a signed message to mempool. + MpoolBatchPush(context.Context, []*types.SignedMessage) ([]cid.Cid, error) //perm:write + + // MpoolBatchPushUntrusted batch pushes a signed message to mempool from untrusted sources. + MpoolBatchPushUntrusted(context.Context, []*types.SignedMessage) ([]cid.Cid, error) //perm:write + + // MpoolBatchPushMessage batch pushes a unsigned message to mempool. + MpoolBatchPushMessage(context.Context, []*types.Message, *MessageSendSpec) ([]*types.SignedMessage, error) //perm:sign + + // MpoolCheckMessages performs logical checks on a batch of messages + MpoolCheckMessages(context.Context, []*MessagePrototype) ([][]MessageCheckStatus, error) //perm:read + // MpoolCheckPendingMessages performs logical checks for all pending messages from a given address + MpoolCheckPendingMessages(context.Context, address.Address) ([][]MessageCheckStatus, error) //perm:read + // MpoolCheckReplaceMessages performs logical checks on pending messages with replacement + MpoolCheckReplaceMessages(context.Context, []*types.Message) ([][]MessageCheckStatus, error) //perm:read + + // MpoolGetNonce gets next nonce for the specified sender. + // Note that this method may not be atomic. Use MpoolPushMessage instead. + MpoolGetNonce(context.Context, address.Address) (uint64, error) //perm:read + MpoolSub(context.Context) (<-chan MpoolUpdate, error) //perm:read + + // MpoolClear clears pending messages from the mpool. + // If clearLocal is true, ALL messages will be cleared. + // If clearLocal is false, local messages will be protected, all others will be cleared. + MpoolClear(ctx context.Context, clearLocal bool) error //perm:write + + // MpoolGetConfig returns (a copy of) the current mpool config + MpoolGetConfig(context.Context) (*types.MpoolConfig, error) //perm:read + // MpoolSetConfig sets the mpool config to (a copy of) the supplied config + MpoolSetConfig(context.Context, *types.MpoolConfig) error //perm:admin // MethodGroup: Miner - MinerGetBaseInfo(context.Context, address.Address, abi.ChainEpoch, types.TipSetKey) (*MiningBaseInfo, error) - MinerCreateBlock(context.Context, *BlockTemplate) (*types.BlockMsg, error) + MinerGetBaseInfo(context.Context, address.Address, abi.ChainEpoch, types.TipSetKey) (*MiningBaseInfo, error) //perm:read + MinerCreateBlock(context.Context, *BlockTemplate) (*types.BlockMsg, error) //perm:write // // UX ? - // MethodGroup: Wallet + // MethodGroup: WalletF - WalletNew(context.Context, crypto.SigType) (address.Address, error) - WalletHas(context.Context, address.Address) (bool, error) - WalletList(context.Context) ([]address.Address, error) - WalletBalance(context.Context, address.Address) (types.BigInt, error) - WalletSign(context.Context, address.Address, []byte) (*crypto.Signature, error) - WalletSignMessage(context.Context, address.Address, *types.Message) (*types.SignedMessage, error) - WalletVerify(context.Context, address.Address, []byte, *crypto.Signature) bool - WalletDefaultAddress(context.Context) (address.Address, error) - WalletSetDefault(context.Context, address.Address) error - WalletExport(context.Context, address.Address) (*types.KeyInfo, error) - WalletImport(context.Context, *types.KeyInfo) (address.Address, error) + // WalletNew creates a new address in the wallet with the given sigType. + // Available key types: bls, secp256k1, secp256k1-ledger + // Support for numerical types: 1 - secp256k1, 2 - BLS is deprecated + WalletNew(context.Context, types.KeyType) (address.Address, error) //perm:write + // WalletHas indicates whether the given address is in the wallet. + WalletHas(context.Context, address.Address) (bool, error) //perm:write + // WalletList lists all the addresses in the wallet. + WalletList(context.Context) ([]address.Address, error) //perm:write + // WalletBalance returns the balance of the given address at the current head of the chain. + WalletBalance(context.Context, address.Address) (types.BigInt, error) //perm:read + // WalletSign signs the given bytes using the given address. + WalletSign(context.Context, address.Address, []byte) (*crypto.Signature, error) //perm:sign + // WalletSignMessage signs the given message using the given address. + WalletSignMessage(context.Context, address.Address, *types.Message) (*types.SignedMessage, error) //perm:sign + // WalletVerify takes an address, a signature, and some bytes, and indicates whether the signature is valid. + // The address does not have to be in the wallet. + WalletVerify(context.Context, address.Address, []byte, *crypto.Signature) (bool, error) //perm:read + // WalletDefaultAddress returns the address marked as default in the wallet. + WalletDefaultAddress(context.Context) (address.Address, error) //perm:write + // WalletSetDefault marks the given address as as the default one. + WalletSetDefault(context.Context, address.Address) error //perm:write + // WalletExport returns the private key of an address in the wallet. + WalletExport(context.Context, address.Address) (*types.KeyInfo, error) //perm:admin + // WalletImport receives a KeyInfo, which includes a private key, and imports it into the wallet. + WalletImport(context.Context, *types.KeyInfo) (address.Address, error) //perm:admin + // WalletDelete deletes an address from the wallet. + WalletDelete(context.Context, address.Address) error //perm:admin + // WalletValidateAddress validates whether a given string can be decoded as a well-formed address + WalletValidateAddress(context.Context, string) (address.Address, error) //perm:read // Other @@ -108,96 +352,537 @@ type FullNode interface { // The Client methods all have to do with interacting with the storage and // retrieval markets as a client - // ClientImport imports file under the specified path into filestore - ClientImport(ctx context.Context, ref FileRef) (cid.Cid, error) - // ClientStartDeal proposes a deal with a miner - ClientStartDeal(ctx context.Context, params *StartDealParams) (*cid.Cid, error) - // ClientGetDeal info returns the latest information about a given deal - ClientGetDealInfo(context.Context, cid.Cid) (*DealInfo, error) - ClientListDeals(ctx context.Context) ([]DealInfo, error) - ClientHasLocal(ctx context.Context, root cid.Cid) (bool, error) - ClientFindData(ctx context.Context, root cid.Cid) ([]QueryOffer, error) - ClientRetrieve(ctx context.Context, order RetrievalOrder, ref *FileRef) error - ClientQueryAsk(ctx context.Context, p peer.ID, miner address.Address) (*storagemarket.SignedStorageAsk, error) - ClientCalcCommP(ctx context.Context, inpath string, miner address.Address) (*CommPRet, error) - ClientGenCar(ctx context.Context, ref FileRef, outpath string) error + // ClientImport imports file under the specified path into filestore. + ClientImport(ctx context.Context, ref FileRef) (*ImportRes, error) //perm:admin + // ClientRemoveImport removes file import + ClientRemoveImport(ctx context.Context, importID imports.ID) error //perm:admin + // ClientStartDeal proposes a deal with a miner. + ClientStartDeal(ctx context.Context, params *StartDealParams) (*cid.Cid, error) //perm:admin + // ClientStatelessDeal fire-and-forget-proposes an offline deal to a miner without subsequent tracking. + ClientStatelessDeal(ctx context.Context, params *StartDealParams) (*cid.Cid, error) //perm:write + // ClientGetDealInfo returns the latest information about a given deal. + ClientGetDealInfo(context.Context, cid.Cid) (*DealInfo, error) //perm:read + // ClientListDeals returns information about the deals made by the local client. + ClientListDeals(ctx context.Context) ([]DealInfo, error) //perm:write + // ClientGetDealUpdates returns the status of updated deals + ClientGetDealUpdates(ctx context.Context) (<-chan DealInfo, error) //perm:write + // ClientGetDealStatus returns status given a code + ClientGetDealStatus(ctx context.Context, statusCode uint64) (string, error) //perm:read + // ClientHasLocal indicates whether a certain CID is locally stored. + ClientHasLocal(ctx context.Context, root cid.Cid) (bool, error) //perm:write + // ClientFindData identifies peers that have a certain file, and returns QueryOffers (one per peer). + ClientFindData(ctx context.Context, root cid.Cid, piece *cid.Cid) ([]QueryOffer, error) //perm:read + // ClientMinerQueryOffer returns a QueryOffer for the specific miner and file. + ClientMinerQueryOffer(ctx context.Context, miner address.Address, root cid.Cid, piece *cid.Cid) (QueryOffer, error) //perm:read + // ClientRetrieve initiates the retrieval of a file, as specified in the order. + ClientRetrieve(ctx context.Context, params RetrievalOrder) (*RestrievalRes, error) //perm:admin + // ClientRetrieveWait waits for retrieval to be complete + ClientRetrieveWait(ctx context.Context, deal retrievalmarket.DealID) error //perm:admin + // ClientExport exports a file stored in the local filestore to a system file + ClientExport(ctx context.Context, exportRef ExportRef, fileRef FileRef) error //perm:admin + // ClientListRetrievals returns information about retrievals made by the local client + ClientListRetrievals(ctx context.Context) ([]RetrievalInfo, error) //perm:write + // ClientGetRetrievalUpdates returns status of updated retrieval deals + ClientGetRetrievalUpdates(ctx context.Context) (<-chan RetrievalInfo, error) //perm:write + // ClientQueryAsk returns a signed StorageAsk from the specified miner. + ClientQueryAsk(ctx context.Context, p peer.ID, miner address.Address) (*StorageAsk, error) //perm:read + // ClientCalcCommP calculates the CommP and data size of the specified CID + ClientDealPieceCID(ctx context.Context, root cid.Cid) (DataCIDSize, error) //perm:read + // ClientCalcCommP calculates the CommP for a specified file + ClientCalcCommP(ctx context.Context, inpath string) (*CommPRet, error) //perm:write + // ClientGenCar generates a CAR file for the specified file. + ClientGenCar(ctx context.Context, ref FileRef, outpath string) error //perm:write + // ClientDealSize calculates real deal data size + ClientDealSize(ctx context.Context, root cid.Cid) (DataSize, error) //perm:read + // ClientListTransfers returns the status of all ongoing transfers of data + ClientListDataTransfers(ctx context.Context) ([]DataTransferChannel, error) //perm:write + ClientDataTransferUpdates(ctx context.Context) (<-chan DataTransferChannel, error) //perm:write + // ClientRestartDataTransfer attempts to restart a data transfer with the given transfer ID and other peer + ClientRestartDataTransfer(ctx context.Context, transferID datatransfer.TransferID, otherPeer peer.ID, isInitiator bool) error //perm:write + // ClientCancelDataTransfer cancels a data transfer with the given transfer ID and other peer + ClientCancelDataTransfer(ctx context.Context, transferID datatransfer.TransferID, otherPeer peer.ID, isInitiator bool) error //perm:write + // ClientRetrieveTryRestartInsufficientFunds attempts to restart stalled retrievals on a given payment channel + // which are stuck due to insufficient funds + ClientRetrieveTryRestartInsufficientFunds(ctx context.Context, paymentChannel address.Address) error //perm:write + + // ClientCancelRetrievalDeal cancels an ongoing retrieval deal based on DealID + ClientCancelRetrievalDeal(ctx context.Context, dealid retrievalmarket.DealID) error //perm:write // ClientUnimport removes references to the specified file from filestore - //ClientUnimport(path string) + // ClientUnimport(path string) // ClientListImports lists imported files and their root CIDs - ClientListImports(ctx context.Context) ([]Import, error) + ClientListImports(ctx context.Context) ([]Import, error) //perm:write - //ClientListAsks() []Ask + // ClientListAsks() []Ask // MethodGroup: State - // The State methods are used to query, inspect, and interact with chain state + // The State methods are used to query, inspect, and interact with chain state. + // Most methods take a TipSetKey as a parameter. The state looked up is the parent state of the tipset. + // A nil TipSetKey can be provided as a param, this will cause the heaviest tipset in the chain to be used. - // if tipset is nil, we'll use heaviest - StateCall(context.Context, *types.Message, types.TipSetKey) (*InvocResult, error) - StateReplay(context.Context, types.TipSetKey, cid.Cid) (*InvocResult, error) - StateGetActor(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*types.Actor, error) - StateReadState(ctx context.Context, act *types.Actor, tsk types.TipSetKey) (*ActorState, error) - StateListMessages(ctx context.Context, match *types.Message, tsk types.TipSetKey, toht abi.ChainEpoch) ([]cid.Cid, error) + // StateCall runs the given message and returns its result without any persisted changes. + // + // StateCall applies the message to the tipset's parent state. The + // message is not applied on-top-of the messages in the passed-in + // tipset. + StateCall(context.Context, *types.Message, types.TipSetKey) (*InvocResult, error) //perm:read + // StateReplay replays a given message, assuming it was included in a block in the specified tipset. + // + // If a tipset key is provided, and a replacing message is not found on chain, + // the method will return an error saying that the message wasn't found + // + // If no tipset key is provided, the appropriate tipset is looked up, and if + // the message was gas-repriced, the on-chain message will be replayed - in + // that case the returned InvocResult.MsgCid will not match the Cid param + // + // If the caller wants to ensure that exactly the requested message was executed, + // they MUST check that InvocResult.MsgCid is equal to the provided Cid. + // Without this check both the requested and original message may appear as + // successfully executed on-chain, which may look like a double-spend. + // + // A replacing message is a message with a different CID, any of Gas values, and + // different signature, but with all other parameters matching (source/destination, + // nonce, params, etc.) + StateReplay(context.Context, types.TipSetKey, cid.Cid) (*InvocResult, error) //perm:read + // StateGetActor returns the indicated actor's nonce and balance. + StateGetActor(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*types.Actor, error) //perm:read + // StateReadState returns the indicated actor's state. + StateReadState(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*ActorState, error) //perm:read + // StateListMessages looks back and returns all messages with a matching to or from address, stopping at the given height. + StateListMessages(ctx context.Context, match *MessageMatch, tsk types.TipSetKey, toht abi.ChainEpoch) ([]cid.Cid, error) //perm:read + // StateDecodeParams attempts to decode the provided params, based on the recipient actor address and method number. + StateDecodeParams(ctx context.Context, toAddr address.Address, method abi.MethodNum, params []byte, tsk types.TipSetKey) (interface{}, error) //perm:read + // StateEncodeParams attempts to encode the provided json params to the binary from + StateEncodeParams(ctx context.Context, toActCode cid.Cid, method abi.MethodNum, params json.RawMessage) ([]byte, error) //perm:read - StateNetworkName(context.Context) (dtypes.NetworkName, error) - StateMinerSectors(context.Context, address.Address, *abi.BitField, bool, types.TipSetKey) ([]*ChainSectorInfo, error) - StateMinerProvingSet(context.Context, address.Address, types.TipSetKey) ([]*ChainSectorInfo, error) - StateMinerProvingDeadline(context.Context, address.Address, types.TipSetKey) (*miner.DeadlineInfo, error) - StateMinerPower(context.Context, address.Address, types.TipSetKey) (*MinerPower, error) - StateMinerInfo(context.Context, address.Address, types.TipSetKey) (miner.MinerInfo, error) - StateMinerDeadlines(context.Context, address.Address, types.TipSetKey) (*miner.Deadlines, error) - StateMinerFaults(context.Context, address.Address, types.TipSetKey) (*abi.BitField, error) - // Returns all non-expired Faults that occur within lookback epochs of the given tipset - StateAllMinerFaults(ctx context.Context, lookback abi.ChainEpoch, ts types.TipSetKey) ([]*Fault, error) - StateMinerRecoveries(context.Context, address.Address, types.TipSetKey) (*abi.BitField, error) - StateMinerInitialPledgeCollateral(context.Context, address.Address, abi.SectorNumber, types.TipSetKey) (types.BigInt, error) - StateMinerAvailableBalance(context.Context, address.Address, types.TipSetKey) (types.BigInt, error) - StateSectorPreCommitInfo(context.Context, address.Address, abi.SectorNumber, types.TipSetKey) (miner.SectorPreCommitOnChainInfo, error) - StatePledgeCollateral(context.Context, types.TipSetKey) (types.BigInt, error) - StateWaitMsg(context.Context, cid.Cid) (*MsgLookup, error) - StateSearchMsg(context.Context, cid.Cid) (*MsgLookup, error) - StateListMiners(context.Context, types.TipSetKey) ([]address.Address, error) - StateListActors(context.Context, types.TipSetKey) ([]address.Address, error) - StateMarketBalance(context.Context, address.Address, types.TipSetKey) (MarketBalance, error) - StateMarketParticipants(context.Context, types.TipSetKey) (map[string]MarketBalance, error) - StateMarketDeals(context.Context, types.TipSetKey) (map[string]MarketDeal, error) - StateMarketStorageDeal(context.Context, abi.DealID, types.TipSetKey) (*MarketDeal, error) - StateLookupID(context.Context, address.Address, types.TipSetKey) (address.Address, error) - StateAccountKey(context.Context, address.Address, types.TipSetKey) (address.Address, error) - StateChangedActors(context.Context, cid.Cid, cid.Cid) (map[string]types.Actor, error) - StateGetReceipt(context.Context, cid.Cid, types.TipSetKey) (*types.MessageReceipt, error) - StateMinerSectorCount(context.Context, address.Address, types.TipSetKey) (MinerSectors, error) - StateCompute(context.Context, abi.ChainEpoch, []*types.Message, types.TipSetKey) (*ComputeStateOutput, error) + // StateNetworkName returns the name of the network the node is synced to + StateNetworkName(context.Context) (dtypes.NetworkName, error) //perm:read + // StateMinerSectors returns info about the given miner's sectors. If the filter bitfield is nil, all sectors are included. + StateMinerSectors(context.Context, address.Address, *bitfield.BitField, types.TipSetKey) ([]*miner.SectorOnChainInfo, error) //perm:read + // StateMinerActiveSectors returns info about sectors that a given miner is actively proving. + StateMinerActiveSectors(context.Context, address.Address, types.TipSetKey) ([]*miner.SectorOnChainInfo, error) //perm:read + // StateMinerProvingDeadline calculates the deadline at some epoch for a proving period + // and returns the deadline-related calculations. + StateMinerProvingDeadline(context.Context, address.Address, types.TipSetKey) (*dline.Info, error) //perm:read + // StateMinerPower returns the power of the indicated miner + StateMinerPower(context.Context, address.Address, types.TipSetKey) (*MinerPower, error) //perm:read + // StateMinerInfo returns info about the indicated miner + StateMinerInfo(context.Context, address.Address, types.TipSetKey) (MinerInfo, error) //perm:read + // StateMinerDeadlines returns all the proving deadlines for the given miner + StateMinerDeadlines(context.Context, address.Address, types.TipSetKey) ([]Deadline, error) //perm:read + // StateMinerPartitions returns all partitions in the specified deadline + StateMinerPartitions(ctx context.Context, m address.Address, dlIdx uint64, tsk types.TipSetKey) ([]Partition, error) //perm:read + // StateMinerFaults returns a bitfield indicating the faulty sectors of the given miner + StateMinerFaults(context.Context, address.Address, types.TipSetKey) (bitfield.BitField, error) //perm:read + // StateAllMinerFaults returns all non-expired Faults that occur within lookback epochs of the given tipset + StateAllMinerFaults(ctx context.Context, lookback abi.ChainEpoch, ts types.TipSetKey) ([]*Fault, error) //perm:read + // StateMinerRecoveries returns a bitfield indicating the recovering sectors of the given miner + StateMinerRecoveries(context.Context, address.Address, types.TipSetKey) (bitfield.BitField, error) //perm:read + // StateMinerInitialPledgeCollateral returns the precommit deposit for the specified miner's sector + StateMinerPreCommitDepositForPower(context.Context, address.Address, miner.SectorPreCommitInfo, types.TipSetKey) (types.BigInt, error) //perm:read + // StateMinerInitialPledgeCollateral returns the initial pledge collateral for the specified miner's sector + StateMinerInitialPledgeCollateral(context.Context, address.Address, miner.SectorPreCommitInfo, types.TipSetKey) (types.BigInt, error) //perm:read + // StateMinerAvailableBalance returns the portion of a miner's balance that can be withdrawn or spent + StateMinerAvailableBalance(context.Context, address.Address, types.TipSetKey) (types.BigInt, error) //perm:read + // StateMinerSectorAllocated checks if a sector number is marked as allocated. + StateMinerSectorAllocated(context.Context, address.Address, abi.SectorNumber, types.TipSetKey) (bool, error) //perm:read + // StateSectorPreCommitInfo returns the PreCommit info for the specified miner's sector. + // Returns nil and no error if the sector isn't precommitted. + // + // Note that the sector number may be allocated while PreCommitInfo is nil. This means that either allocated sector + // numbers were compacted, and the sector number was marked as allocated in order to reduce size of the allocated + // sectors bitfield, or that the sector was precommitted, but the precommit has expired. + StateSectorPreCommitInfo(context.Context, address.Address, abi.SectorNumber, types.TipSetKey) (*miner.SectorPreCommitOnChainInfo, error) //perm:read + // StateSectorGetInfo returns the on-chain info for the specified miner's sector. Returns null in case the sector info isn't found + // NOTE: returned info.Expiration may not be accurate in some cases, use StateSectorExpiration to get accurate + // expiration epoch + StateSectorGetInfo(context.Context, address.Address, abi.SectorNumber, types.TipSetKey) (*miner.SectorOnChainInfo, error) //perm:read + // StateSectorExpiration returns epoch at which given sector will expire + StateSectorExpiration(context.Context, address.Address, abi.SectorNumber, types.TipSetKey) (*lminer.SectorExpiration, error) //perm:read + // StateSectorPartition finds deadline/partition with the specified sector + StateSectorPartition(ctx context.Context, maddr address.Address, sectorNumber abi.SectorNumber, tok types.TipSetKey) (*lminer.SectorLocation, error) //perm:read + // StateSearchMsg looks back up to limit epochs in the chain for a message, and returns its receipt and the tipset where it was executed + // + // NOTE: If a replacing message is found on chain, this method will return + // a MsgLookup for the replacing message - the MsgLookup.Message will be a different + // CID than the one provided in the 'cid' param, MsgLookup.Receipt will contain the + // result of the execution of the replacing message. + // + // If the caller wants to ensure that exactly the requested message was executed, + // they must check that MsgLookup.Message is equal to the provided 'cid', or set the + // `allowReplaced` parameter to false. Without this check, and with `allowReplaced` + // set to true, both the requested and original message may appear as + // successfully executed on-chain, which may look like a double-spend. + // + // A replacing message is a message with a different CID, any of Gas values, and + // different signature, but with all other parameters matching (source/destination, + // nonce, params, etc.) + StateSearchMsg(ctx context.Context, from types.TipSetKey, msg cid.Cid, limit abi.ChainEpoch, allowReplaced bool) (*MsgLookup, error) //perm:read + // StateWaitMsg looks back up to limit epochs in the chain for a message. + // If not found, it blocks until the message arrives on chain, and gets to the + // indicated confidence depth. + // + // NOTE: If a replacing message is found on chain, this method will return + // a MsgLookup for the replacing message - the MsgLookup.Message will be a different + // CID than the one provided in the 'cid' param, MsgLookup.Receipt will contain the + // result of the execution of the replacing message. + // + // If the caller wants to ensure that exactly the requested message was executed, + // they must check that MsgLookup.Message is equal to the provided 'cid', or set the + // `allowReplaced` parameter to false. Without this check, and with `allowReplaced` + // set to true, both the requested and original message may appear as + // successfully executed on-chain, which may look like a double-spend. + // + // A replacing message is a message with a different CID, any of Gas values, and + // different signature, but with all other parameters matching (source/destination, + // nonce, params, etc.) + StateWaitMsg(ctx context.Context, cid cid.Cid, confidence uint64, limit abi.ChainEpoch, allowReplaced bool) (*MsgLookup, error) //perm:read + // StateListMiners returns the addresses of every miner that has claimed power in the Power Actor + StateListMiners(context.Context, types.TipSetKey) ([]address.Address, error) //perm:read + // StateListActors returns the addresses of every actor in the state + StateListActors(context.Context, types.TipSetKey) ([]address.Address, error) //perm:read + // StateMarketBalance looks up the Escrow and Locked balances of the given address in the Storage Market + StateMarketBalance(context.Context, address.Address, types.TipSetKey) (MarketBalance, error) //perm:read + // StateMarketParticipants returns the Escrow and Locked balances of every participant in the Storage Market + StateMarketParticipants(context.Context, types.TipSetKey) (map[string]MarketBalance, error) //perm:read + // StateMarketDeals returns information about every deal in the Storage Market + StateMarketDeals(context.Context, types.TipSetKey) (map[string]*MarketDeal, error) //perm:read + // StateMarketStorageDeal returns information about the indicated deal + StateMarketStorageDeal(context.Context, abi.DealID, types.TipSetKey) (*MarketDeal, error) //perm:read + // StateGetAllocationForPendingDeal returns the allocation for a given deal ID of a pending deal. Returns nil if + // pending allocation is not found. + StateGetAllocationForPendingDeal(ctx context.Context, dealId abi.DealID, tsk types.TipSetKey) (*verifregtypes.Allocation, error) //perm:read + // StateGetAllocation returns the allocation for a given address and allocation ID. + StateGetAllocation(ctx context.Context, clientAddr address.Address, allocationId verifregtypes.AllocationId, tsk types.TipSetKey) (*verifregtypes.Allocation, error) //perm:read + // StateGetAllocations returns the all the allocations for a given client. + StateGetAllocations(ctx context.Context, clientAddr address.Address, tsk types.TipSetKey) (map[verifregtypes.AllocationId]verifregtypes.Allocation, error) //perm:read + // StateGetClaim returns the claim for a given address and claim ID. + StateGetClaim(ctx context.Context, providerAddr address.Address, claimId verifregtypes.ClaimId, tsk types.TipSetKey) (*verifregtypes.Claim, error) //perm:read + // StateGetClaims returns the all the claims for a given provider. + StateGetClaims(ctx context.Context, providerAddr address.Address, tsk types.TipSetKey) (map[verifregtypes.ClaimId]verifregtypes.Claim, error) //perm:read + // StateComputeDataCID computes DataCID from a set of on-chain deals + StateComputeDataCID(ctx context.Context, maddr address.Address, sectorType abi.RegisteredSealProof, deals []abi.DealID, tsk types.TipSetKey) (cid.Cid, error) //perm:read + // StateLookupID retrieves the ID address of the given address + StateLookupID(context.Context, address.Address, types.TipSetKey) (address.Address, error) //perm:read + // StateAccountKey returns the public key address of the given ID address for secp and bls accounts + StateAccountKey(context.Context, address.Address, types.TipSetKey) (address.Address, error) //perm:read + // StateLookupRobustAddress returns the public key address of the given ID address for non-account addresses (multisig, miners etc) + StateLookupRobustAddress(context.Context, address.Address, types.TipSetKey) (address.Address, error) //perm:read + // StateChangedActors returns all the actors whose states change between the two given state CIDs + // TODO: Should this take tipset keys instead? + StateChangedActors(context.Context, cid.Cid, cid.Cid) (map[string]types.Actor, error) //perm:read + // StateMinerSectorCount returns the number of sectors in a miner's sector set and proving set + StateMinerSectorCount(context.Context, address.Address, types.TipSetKey) (MinerSectors, error) //perm:read + // StateMinerAllocated returns a bitfield containing all sector numbers marked as allocated in miner state + StateMinerAllocated(context.Context, address.Address, types.TipSetKey) (*bitfield.BitField, error) //perm:read + // StateCompute is a flexible command that applies the given messages on the given tipset. + // The messages are run as though the VM were at the provided height. + // + // When called, StateCompute will: + // - Load the provided tipset, or use the current chain head if not provided + // - Compute the tipset state of the provided tipset on top of the parent state + // - (note that this step runs before vmheight is applied to the execution) + // - Execute state upgrade if any were scheduled at the epoch, or in null + // blocks preceding the tipset + // - Call the cron actor on null blocks preceding the tipset + // - For each block in the tipset + // - Apply messages in blocks in the specified + // - Award block reward by calling the reward actor + // - Call the cron actor for the current epoch + // - If the specified vmheight is higher than the current epoch, apply any + // needed state upgrades to the state + // - Apply the specified messages to the state + // + // The vmheight parameter sets VM execution epoch, and can be used to simulate + // message execution in different network versions. If the specified vmheight + // epoch is higher than the epoch of the specified tipset, any state upgrades + // until the vmheight will be executed on the state before applying messages + // specified by the user. + // + // Note that the initial tipset state computation is not affected by the + // vmheight parameter - only the messages in the `apply` set are + // + // If the caller wants to simply compute the state, vmheight should be set to + // the epoch of the specified tipset. + // + // Messages in the `apply` parameter must have the correct nonces, and gas + // values set. + StateCompute(context.Context, abi.ChainEpoch, []*types.Message, types.TipSetKey) (*ComputeStateOutput, error) //perm:read + // StateVerifierStatus returns the data cap for the given address. + // Returns nil if there is no entry in the data cap table for the + // address. + StateVerifierStatus(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*abi.StoragePower, error) //perm:read + // StateVerifiedClientStatus returns the data cap for the given address. + // Returns nil if there is no entry in the data cap table for the + // address. + StateVerifiedClientStatus(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*abi.StoragePower, error) //perm:read + // StateVerifiedRegistryRootKey returns the address of the Verified Registry's root key + StateVerifiedRegistryRootKey(ctx context.Context, tsk types.TipSetKey) (address.Address, error) //perm:read + // StateDealProviderCollateralBounds returns the min and max collateral a storage provider + // can issue. It takes the deal size and verified status as parameters. + StateDealProviderCollateralBounds(context.Context, abi.PaddedPieceSize, bool, types.TipSetKey) (DealCollateralBounds, error) //perm:read + + // StateCirculatingSupply returns the exact circulating supply of Filecoin at the given tipset. + // This is not used anywhere in the protocol itself, and is only for external consumption. + StateCirculatingSupply(context.Context, types.TipSetKey) (abi.TokenAmount, error) //perm:read + // StateVMCirculatingSupplyInternal returns an approximation of the circulating supply of Filecoin at the given tipset. + // This is the value reported by the runtime interface to actors code. + StateVMCirculatingSupplyInternal(context.Context, types.TipSetKey) (CirculatingSupply, error) //perm:read + // StateNetworkVersion returns the network version at the given tipset + StateNetworkVersion(context.Context, types.TipSetKey) (apitypes.NetworkVersion, error) //perm:read + // StateActorCodeCIDs returns the CIDs of all the builtin actors for the given network version + StateActorCodeCIDs(context.Context, abinetwork.Version) (map[string]cid.Cid, error) //perm:read + // StateActorManifestCID returns the CID of the builtin actors manifest for the given network version + StateActorManifestCID(context.Context, abinetwork.Version) (cid.Cid, error) //perm:read + + // StateGetRandomnessFromTickets is used to sample the chain for randomness. + StateGetRandomnessFromTickets(ctx context.Context, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte, tsk types.TipSetKey) (abi.Randomness, error) //perm:read + // StateGetRandomnessFromBeacon is used to sample the beacon for randomness. + StateGetRandomnessFromBeacon(ctx context.Context, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte, tsk types.TipSetKey) (abi.Randomness, error) //perm:read + + // StateGetBeaconEntry returns the beacon entry for the given filecoin epoch. If + // the entry has not yet been produced, the call will block until the entry + // becomes available + StateGetBeaconEntry(ctx context.Context, epoch abi.ChainEpoch) (*types.BeaconEntry, error) //perm:read + + // StateGetNetworkParams return current network params + StateGetNetworkParams(ctx context.Context) (*NetworkParams, error) //perm:read // MethodGroup: Msig // The Msig methods are used to interact with multisig wallets on the // filecoin network - MsigGetAvailableBalance(context.Context, address.Address, types.TipSetKey) (types.BigInt, error) - MsigCreate(context.Context, int64, []address.Address, types.BigInt, address.Address, types.BigInt) (cid.Cid, error) - MsigPropose(context.Context, address.Address, address.Address, types.BigInt, address.Address, uint64, []byte) (cid.Cid, error) - MsigApprove(context.Context, address.Address, uint64, address.Address, address.Address, types.BigInt, address.Address, uint64, []byte) (cid.Cid, error) - MsigCancel(context.Context, address.Address, uint64, address.Address, address.Address, types.BigInt, address.Address, uint64, []byte) (cid.Cid, error) + // MsigGetAvailableBalance returns the portion of a multisig's balance that can be withdrawn or spent + MsigGetAvailableBalance(context.Context, address.Address, types.TipSetKey) (types.BigInt, error) //perm:read + // MsigGetVestingSchedule returns the vesting details of a given multisig. + MsigGetVestingSchedule(context.Context, address.Address, types.TipSetKey) (MsigVesting, error) //perm:read + // MsigGetVested returns the amount of FIL that vested in a multisig in a certain period. + // It takes the following params: , , + MsigGetVested(context.Context, address.Address, types.TipSetKey, types.TipSetKey) (types.BigInt, error) //perm:read - MarketEnsureAvailable(context.Context, address.Address, address.Address, types.BigInt) (cid.Cid, error) - // MarketFreeBalance + // MsigGetPending returns pending transactions for the given multisig + // wallet. Once pending transactions are fully approved, they will no longer + // appear here. + MsigGetPending(context.Context, address.Address, types.TipSetKey) ([]*MsigTransaction, error) //perm:read + + // MsigCreate creates a multisig wallet + // It takes the following params: , , + // , , + MsigCreate(context.Context, uint64, []address.Address, abi.ChainEpoch, types.BigInt, address.Address, types.BigInt) (*MessagePrototype, error) //perm:sign + + // MsigPropose proposes a multisig message + // It takes the following params: , , , + // , , + MsigPropose(context.Context, address.Address, address.Address, types.BigInt, address.Address, uint64, []byte) (*MessagePrototype, error) //perm:sign + + // MsigApprove approves a previously-proposed multisig message by transaction ID + // It takes the following params: , + MsigApprove(context.Context, address.Address, uint64, address.Address) (*MessagePrototype, error) //perm:sign + + // MsigApproveTxnHash approves a previously-proposed multisig message, specified + // using both transaction ID and a hash of the parameters used in the + // proposal. This method of approval can be used to ensure you only approve + // exactly the transaction you think you are. + // It takes the following params: , , , , , + // , , + MsigApproveTxnHash(context.Context, address.Address, uint64, address.Address, address.Address, types.BigInt, address.Address, uint64, []byte) (*MessagePrototype, error) //perm:sign + + // MsigCancel cancels a previously-proposed multisig message + // It takes the following params: , + MsigCancel(context.Context, address.Address, uint64, address.Address) (*MessagePrototype, error) //perm:sign + + // MsigCancel cancels a previously-proposed multisig message + // It takes the following params: , , , , + // , , + MsigCancelTxnHash(context.Context, address.Address, uint64, address.Address, types.BigInt, address.Address, uint64, []byte) (*MessagePrototype, error) //perm:sign + + // MsigAddPropose proposes adding a signer in the multisig + // It takes the following params: , , + // , + MsigAddPropose(context.Context, address.Address, address.Address, address.Address, bool) (*MessagePrototype, error) //perm:sign + + // MsigAddApprove approves a previously proposed AddSigner message + // It takes the following params: , , , + // , , + MsigAddApprove(context.Context, address.Address, address.Address, uint64, address.Address, address.Address, bool) (*MessagePrototype, error) //perm:sign + + // MsigAddCancel cancels a previously proposed AddSigner message + // It takes the following params: , , , + // , + MsigAddCancel(context.Context, address.Address, address.Address, uint64, address.Address, bool) (*MessagePrototype, error) //perm:sign + + // MsigSwapPropose proposes swapping 2 signers in the multisig + // It takes the following params: , , + // , + MsigSwapPropose(context.Context, address.Address, address.Address, address.Address, address.Address) (*MessagePrototype, error) //perm:sign + + // MsigSwapApprove approves a previously proposed SwapSigner + // It takes the following params: , , , + // , , + MsigSwapApprove(context.Context, address.Address, address.Address, uint64, address.Address, address.Address, address.Address) (*MessagePrototype, error) //perm:sign + + // MsigSwapCancel cancels a previously proposed SwapSigner message + // It takes the following params: , , , + // , + MsigSwapCancel(context.Context, address.Address, address.Address, uint64, address.Address, address.Address) (*MessagePrototype, error) //perm:sign + + // MsigRemoveSigner proposes the removal of a signer from the multisig. + // It accepts the multisig to make the change on, the proposer address to + // send the message from, the address to be removed, and a boolean + // indicating whether or not the signing threshold should be lowered by one + // along with the address removal. + MsigRemoveSigner(ctx context.Context, msig address.Address, proposer address.Address, toRemove address.Address, decrease bool) (*MessagePrototype, error) //perm:sign + + // MarketAddBalance adds funds to the market actor + MarketAddBalance(ctx context.Context, wallet, addr address.Address, amt types.BigInt) (cid.Cid, error) //perm:sign + // MarketGetReserved gets the amount of funds that are currently reserved for the address + MarketGetReserved(ctx context.Context, addr address.Address) (types.BigInt, error) //perm:sign + // MarketReserveFunds reserves funds for a deal + MarketReserveFunds(ctx context.Context, wallet address.Address, addr address.Address, amt types.BigInt) (cid.Cid, error) //perm:sign + // MarketReleaseFunds releases funds reserved by MarketReserveFunds + MarketReleaseFunds(ctx context.Context, addr address.Address, amt types.BigInt) error //perm:sign + // MarketWithdraw withdraws unlocked funds from the market actor + MarketWithdraw(ctx context.Context, wallet, addr address.Address, amt types.BigInt) (cid.Cid, error) //perm:sign // MethodGroup: Paych // The Paych methods are for interacting with and managing payment channels - PaychGet(ctx context.Context, from, to address.Address, ensureFunds types.BigInt) (*ChannelInfo, error) - PaychList(context.Context) ([]address.Address, error) - PaychStatus(context.Context, address.Address) (*PaychStatus, error) - PaychClose(context.Context, address.Address) (cid.Cid, error) - PaychAllocateLane(ctx context.Context, ch address.Address) (uint64, error) - PaychNewPayment(ctx context.Context, from, to address.Address, vouchers []VoucherSpec) (*PaymentInfo, error) - PaychVoucherCheckValid(context.Context, address.Address, *paych.SignedVoucher) error - PaychVoucherCheckSpendable(context.Context, address.Address, *paych.SignedVoucher, []byte, []byte) (bool, error) - PaychVoucherCreate(context.Context, address.Address, types.BigInt, uint64) (*paych.SignedVoucher, error) - PaychVoucherAdd(context.Context, address.Address, *paych.SignedVoucher, []byte, types.BigInt) (types.BigInt, error) - PaychVoucherList(context.Context, address.Address) ([]*paych.SignedVoucher, error) - PaychVoucherSubmit(context.Context, address.Address, *paych.SignedVoucher) (cid.Cid, error) + // PaychGet gets or creates a payment channel between address pair + // The specified amount will be reserved for use. If there aren't enough non-reserved funds + // available, funds will be added through an on-chain message. + // - When opts.OffChain is true, this call will not cause any messages to be sent to the chain (no automatic + // channel creation/funds adding). If the operation can't be performed without sending a message an error will be + // returned. Note that even when this option is specified, this call can be blocked by previous operations on the + // channel waiting for on-chain operations. + PaychGet(ctx context.Context, from, to address.Address, amt types.BigInt, opts PaychGetOpts) (*ChannelInfo, error) //perm:sign + // PaychFund gets or creates a payment channel between address pair. + // The specified amount will be added to the channel through on-chain send for future use + PaychFund(ctx context.Context, from, to address.Address, amt types.BigInt) (*ChannelInfo, error) //perm:sign + PaychGetWaitReady(context.Context, cid.Cid) (address.Address, error) //perm:sign + PaychAvailableFunds(ctx context.Context, ch address.Address) (*ChannelAvailableFunds, error) //perm:sign + PaychAvailableFundsByFromTo(ctx context.Context, from, to address.Address) (*ChannelAvailableFunds, error) //perm:sign + PaychList(context.Context) ([]address.Address, error) //perm:read + PaychStatus(context.Context, address.Address) (*PaychStatus, error) //perm:read + PaychSettle(context.Context, address.Address) (cid.Cid, error) //perm:sign + PaychCollect(context.Context, address.Address) (cid.Cid, error) //perm:sign + PaychAllocateLane(ctx context.Context, ch address.Address) (uint64, error) //perm:sign + PaychNewPayment(ctx context.Context, from, to address.Address, vouchers []VoucherSpec) (*PaymentInfo, error) //perm:sign + PaychVoucherCheckValid(context.Context, address.Address, *paych.SignedVoucher) error //perm:read + PaychVoucherCheckSpendable(context.Context, address.Address, *paych.SignedVoucher, []byte, []byte) (bool, error) //perm:read + PaychVoucherCreate(context.Context, address.Address, types.BigInt, uint64) (*VoucherCreateResult, error) //perm:sign + PaychVoucherAdd(context.Context, address.Address, *paych.SignedVoucher, []byte, types.BigInt) (types.BigInt, error) //perm:write + PaychVoucherList(context.Context, address.Address) ([]*paych.SignedVoucher, error) //perm:write + PaychVoucherSubmit(context.Context, address.Address, *paych.SignedVoucher, []byte, []byte) (cid.Cid, error) //perm:sign + + // MethodGroup: Node + // These methods are general node management and status commands + + NodeStatus(ctx context.Context, inclChainStatus bool) (NodeStatus, error) //perm:read + + // MethodGroup: Eth + // These methods are used for Ethereum-compatible JSON-RPC calls + // + // EthAccounts will always return [] since we don't expect Lotus to manage private keys + EthAccounts(ctx context.Context) ([]ethtypes.EthAddress, error) //perm:read + // EthAddressToFilecoinAddress converts an EthAddress into an f410 Filecoin Address + EthAddressToFilecoinAddress(ctx context.Context, ethAddress ethtypes.EthAddress) (address.Address, error) //perm:read + // FilecoinAddressToEthAddress converts an f410 or f0 Filecoin Address to an EthAddress + FilecoinAddressToEthAddress(ctx context.Context, filecoinAddress address.Address) (ethtypes.EthAddress, error) //perm:read + // EthBlockNumber returns the height of the latest (heaviest) TipSet + EthBlockNumber(ctx context.Context) (ethtypes.EthUint64, error) //perm:read + // EthGetBlockTransactionCountByNumber returns the number of messages in the TipSet + EthGetBlockTransactionCountByNumber(ctx context.Context, blkNum ethtypes.EthUint64) (ethtypes.EthUint64, error) //perm:read + // EthGetBlockTransactionCountByHash returns the number of messages in the TipSet + EthGetBlockTransactionCountByHash(ctx context.Context, blkHash ethtypes.EthHash) (ethtypes.EthUint64, error) //perm:read + + EthGetBlockByHash(ctx context.Context, blkHash ethtypes.EthHash, fullTxInfo bool) (ethtypes.EthBlock, error) //perm:read + EthGetBlockByNumber(ctx context.Context, blkNum string, fullTxInfo bool) (ethtypes.EthBlock, error) //perm:read + EthGetTransactionByHash(ctx context.Context, txHash *ethtypes.EthHash) (*ethtypes.EthTx, error) //perm:read + EthGetTransactionByHashLimited(ctx context.Context, txHash *ethtypes.EthHash, limit abi.ChainEpoch) (*ethtypes.EthTx, error) //perm:read + EthGetTransactionHashByCid(ctx context.Context, cid cid.Cid) (*ethtypes.EthHash, error) //perm:read + EthGetMessageCidByTransactionHash(ctx context.Context, txHash *ethtypes.EthHash) (*cid.Cid, error) //perm:read + EthGetTransactionCount(ctx context.Context, sender ethtypes.EthAddress, blkOpt string) (ethtypes.EthUint64, error) //perm:read + EthGetTransactionReceipt(ctx context.Context, txHash ethtypes.EthHash) (*EthTxReceipt, error) //perm:read + EthGetTransactionReceiptLimited(ctx context.Context, txHash ethtypes.EthHash, limit abi.ChainEpoch) (*EthTxReceipt, error) //perm:read + EthGetTransactionByBlockHashAndIndex(ctx context.Context, blkHash ethtypes.EthHash, txIndex ethtypes.EthUint64) (ethtypes.EthTx, error) //perm:read + EthGetTransactionByBlockNumberAndIndex(ctx context.Context, blkNum ethtypes.EthUint64, txIndex ethtypes.EthUint64) (ethtypes.EthTx, error) //perm:read + + EthGetCode(ctx context.Context, address ethtypes.EthAddress, blkOpt string) (ethtypes.EthBytes, error) //perm:read + EthGetStorageAt(ctx context.Context, address ethtypes.EthAddress, position ethtypes.EthBytes, blkParam string) (ethtypes.EthBytes, error) //perm:read + EthGetBalance(ctx context.Context, address ethtypes.EthAddress, blkParam string) (ethtypes.EthBigInt, error) //perm:read + EthChainId(ctx context.Context) (ethtypes.EthUint64, error) //perm:read + EthSyncing(ctx context.Context) (ethtypes.EthSyncingResult, error) //perm:read + NetVersion(ctx context.Context) (string, error) //perm:read + NetListening(ctx context.Context) (bool, error) //perm:read + EthProtocolVersion(ctx context.Context) (ethtypes.EthUint64, error) //perm:read + EthGasPrice(ctx context.Context) (ethtypes.EthBigInt, error) //perm:read + EthFeeHistory(ctx context.Context, p jsonrpc.RawParams) (ethtypes.EthFeeHistory, error) //perm:read + + EthMaxPriorityFeePerGas(ctx context.Context) (ethtypes.EthBigInt, error) //perm:read + EthEstimateGas(ctx context.Context, tx ethtypes.EthCall) (ethtypes.EthUint64, error) //perm:read + EthCall(ctx context.Context, tx ethtypes.EthCall, blkParam string) (ethtypes.EthBytes, error) //perm:read + + EthSendRawTransaction(ctx context.Context, rawTx ethtypes.EthBytes) (ethtypes.EthHash, error) //perm:read + + // Returns event logs matching given filter spec. + EthGetLogs(ctx context.Context, filter *ethtypes.EthFilterSpec) (*ethtypes.EthFilterResult, error) //perm:read + + // Polling method for a filter, returns event logs which occurred since last poll. + // (requires write perm since timestamp of last filter execution will be written) + EthGetFilterChanges(ctx context.Context, id ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) //perm:read + + // Returns event logs matching filter with given id. + // (requires write perm since timestamp of last filter execution will be written) + EthGetFilterLogs(ctx context.Context, id ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) //perm:read + + // Installs a persistent filter based on given filter spec. + EthNewFilter(ctx context.Context, filter *ethtypes.EthFilterSpec) (ethtypes.EthFilterID, error) //perm:read + + // Installs a persistent filter to notify when a new block arrives. + EthNewBlockFilter(ctx context.Context) (ethtypes.EthFilterID, error) //perm:read + + // Installs a persistent filter to notify when new messages arrive in the message pool. + EthNewPendingTransactionFilter(ctx context.Context) (ethtypes.EthFilterID, error) //perm:read + + // Uninstalls a filter with given id. + EthUninstallFilter(ctx context.Context, id ethtypes.EthFilterID) (bool, error) //perm:read + + // Subscribe to different event types using websockets + // eventTypes is one or more of: + // - newHeads: notify when new blocks arrive. + // - pendingTransactions: notify when new messages arrive in the message pool. + // - logs: notify new event logs that match a criteria + // params contains additional parameters used with the log event type + // The client will receive a stream of EthSubscriptionResponse values until EthUnsubscribe is called. + EthSubscribe(ctx context.Context, params jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error) //perm:read + + // Unsubscribe from a websocket subscription + EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionID) (bool, error) //perm:read + + // Returns the client version + Web3ClientVersion(ctx context.Context) (string, error) //perm:read + + // CreateBackup creates node backup onder the specified file name. The + // method requires that the lotus daemon is running with the + // LOTUS_BACKUP_BASE_PATH environment variable set to some path, and that + // the path specified when calling CreateBackup is within the base path + CreateBackup(ctx context.Context, fpath string) error //perm:admin + + RaftState(ctx context.Context) (*RaftStateData, error) //perm:read + RaftLeader(ctx context.Context) (peer.ID, error) //perm:read +} + +// reverse interface to the client, called after EthSubscribe +type EthSubscriber interface { + // note: the parameter is ethtypes.EthSubscriptionResponse serialized as json object + EthSubscription(ctx context.Context, r jsonrpc.RawParams) error // rpc_method:eth_subscription notify:true +} + +type StorageAsk struct { + Response *storagemarket.StorageAsk + + DealProtocols []string } type FileRef struct { @@ -206,23 +891,48 @@ type FileRef struct { } type MinerSectors struct { - Sset uint64 - Pset uint64 + // Live sectors that should be proven. + Live uint64 + // Sectors actively contributing to power. + Active uint64 + // Sectors with failed proofs. + Faulty uint64 +} + +type ImportRes struct { + Root cid.Cid + ImportID imports.ID } type Import struct { - Status filestore.Status - Key cid.Cid + Key imports.ID + Err string + + Root *cid.Cid + + // Source is the provenance of the import, e.g. "import", "unknown", else. + // Currently useless but may be used in the future. + Source string + + // FilePath is the path of the original file. It is important that the file + // is retained at this path, because it will be referenced during + // the transfer (when we do the UnixFS chunking, we don't duplicate the + // leaves, but rather point to chunks of the original data through + // positional references). FilePath string - Size uint64 + + // CARPath is the path of the CAR file containing the DAG for this import. + CARPath string } type DealInfo struct { ProposalCid cid.Cid State storagemarket.StorageDealStatus Message string // more information about deal state, particularly errors + DealStages *storagemarket.DealStages Provider address.Address + DataRef *storagemarket.DataRef PieceCID cid.Cid Size uint64 @@ -230,13 +940,35 @@ type DealInfo struct { Duration uint64 DealID abi.DealID + + CreationTime time.Time + Verified bool + + TransferChannelID *datatransfer.ChannelID + DataTransfer *DataTransferChannel } type MsgLookup struct { - Receipt types.MessageReceipt - TipSet *types.TipSet + Message cid.Cid // Can be different than requested, in case it was replaced, but only gas values changed + Receipt types.MessageReceipt + ReturnDec interface{} + TipSet types.TipSetKey + Height abi.ChainEpoch } +type MsgGasCost struct { + Message cid.Cid // Can be different than requested, in case it was replaced, but only gas values changed + GasUsed abi.TokenAmount + BaseFeeBurn abi.TokenAmount + OverEstimationBurn abi.TokenAmount + MinerPenalty abi.TokenAmount + MinerTip abi.TokenAmount + Refund abi.TokenAmount + TotalCost abi.TokenAmount +} + +// BlsMessages[x].cid = Cids[x] +// SecpkMessages[y].cid = Cids[BlsMessages.length + y] type BlockMessages struct { BlsMessages []*types.Message SecpkMessages []*types.SignedMessage @@ -249,13 +981,9 @@ type Message struct { Message *types.Message } -type ChainSectorInfo struct { - Info miner.SectorOnChainInfo - ID abi.SectorNumber -} - type ActorState struct { Balance types.BigInt + Code cid.Cid State interface{} } @@ -267,20 +995,53 @@ const ( PCHOutbound ) +type PaychGetOpts struct { + OffChain bool +} + type PaychStatus struct { ControlAddr address.Address Direction PCHDir } type ChannelInfo struct { - Channel address.Address - ChannelMessage cid.Cid + Channel address.Address + WaitSentinel cid.Cid +} + +type ChannelAvailableFunds struct { + // Channel is the address of the channel + Channel *address.Address + // From is the from address of the channel (channel creator) + From address.Address + // To is the to address of the channel + To address.Address + + // ConfirmedAmt is the total amount of funds that have been confirmed on-chain for the channel + ConfirmedAmt types.BigInt + // PendingAmt is the amount of funds that are pending confirmation on-chain + PendingAmt types.BigInt + + // NonReservedAmt is part of ConfirmedAmt that is available for use (e.g. when the payment channel was pre-funded) + NonReservedAmt types.BigInt + // PendingAvailableAmt is the amount of funds that are pending confirmation on-chain that will become available once confirmed + PendingAvailableAmt types.BigInt + + // PendingWaitSentinel can be used with PaychGetWaitReady to wait for + // confirmation of pending funds + PendingWaitSentinel *cid.Cid + // QueuedAmt is the amount that is queued up behind a pending request + QueuedAmt types.BigInt + + // VoucherRedeemedAmt is the amount that is redeemed by vouchers on-chain + // and in the local datastore + VoucherReedeemedAmt types.BigInt } type PaymentInfo struct { - Channel address.Address - ChannelMessage *cid.Cid - Vouchers []*paych.SignedVoucher + Channel address.Address + WaitSentinel cid.Cid + Vouchers []*paych.SignedVoucher } type VoucherSpec struct { @@ -292,35 +1053,51 @@ type VoucherSpec struct { Extra *paych.ModVerifyParams } +// VoucherCreateResult is the response to calling PaychVoucherCreate +type VoucherCreateResult struct { + // Voucher that was created, or nil if there was an error or if there + // were insufficient funds in the channel + Voucher *paych.SignedVoucher + // Shortfall is the additional amount that would be needed in the channel + // in order to be able to create the voucher + Shortfall types.BigInt +} + type MinerPower struct { - MinerPower power.Claim - TotalPower power.Claim + MinerPower power.Claim + TotalPower power.Claim + HasMinPower bool } type QueryOffer struct { Err string - Root cid.Cid + Root cid.Cid + Piece *cid.Cid Size uint64 MinPrice types.BigInt + UnsealPrice types.BigInt + PricePerByte abi.TokenAmount PaymentInterval uint64 PaymentIntervalIncrease uint64 Miner address.Address - MinerPeerID peer.ID + MinerPeer retrievalmarket.RetrievalPeer } func (o *QueryOffer) Order(client address.Address) RetrievalOrder { return RetrievalOrder{ Root: o.Root, + Piece: o.Piece, Size: o.Size, Total: o.MinPrice, + UnsealPrice: o.UnsealPrice, PaymentInterval: o.PaymentInterval, PaymentIntervalIncrease: o.PaymentIntervalIncrease, Client: client, - Miner: o.Miner, - MinerPeerID: o.MinerPeerID, + Miner: o.Miner, + MinerPeer: &o.MinerPeer, } } @@ -335,24 +1112,34 @@ type MarketDeal struct { } type RetrievalOrder struct { - // TODO: make this less unixfs specific - Root cid.Cid - Size uint64 - // TODO: support offset - Total types.BigInt + Root cid.Cid + Piece *cid.Cid + DataSelector *Selector + + // todo: Size/Total are only used for calculating price per byte; we should let users just pass that + Size uint64 + Total types.BigInt + + UnsealPrice types.BigInt PaymentInterval uint64 PaymentIntervalIncrease uint64 Client address.Address Miner address.Address - MinerPeerID peer.ID + MinerPeer *retrievalmarket.RetrievalPeer + + RemoteStore *RemoteStoreID `json:"RemoteStore,omitempty"` } +type RemoteStoreID = uuid.UUID + type InvocResult struct { - Msg *types.Message - MsgRct *types.MessageReceipt - InternalExecutions []*types.ExecutionResult - Error string - Duration time.Duration + MsgCid cid.Cid + Msg *types.Message + MsgRct *types.MessageReceipt + GasCost MsgGasCost + ExecutionTrace types.ExecutionTrace + Error string + Duration time.Duration } type MethodCall struct { @@ -361,12 +1148,31 @@ type MethodCall struct { } type StartDealParams struct { - Data *storagemarket.DataRef - Wallet address.Address - Miner address.Address - EpochPrice types.BigInt - MinBlocksDuration uint64 - DealStartEpoch abi.ChainEpoch + Data *storagemarket.DataRef + Wallet address.Address + Miner address.Address + EpochPrice types.BigInt + MinBlocksDuration uint64 + ProviderCollateral big.Int + DealStartEpoch abi.ChainEpoch + FastRetrieval bool + VerifiedDeal bool +} + +func (s *StartDealParams) UnmarshalJSON(raw []byte) (err error) { + type sdpAlias StartDealParams + + sdp := sdpAlias{ + FastRetrieval: true, + } + + if err := json.Unmarshal(raw, &sdp); err != nil { + return err + } + + *s = StartDealParams(sdp) + + return nil } type IpldObject struct { @@ -375,8 +1181,9 @@ type IpldObject struct { } type ActiveSync struct { - Base *types.TipSet - Target *types.TipSet + WorkerID uint64 + Base *types.TipSet + Target *types.TipSet Stage SyncStateStage Height abi.ChainEpoch @@ -388,6 +1195,8 @@ type ActiveSync struct { type SyncState struct { ActiveSyncs []ActiveSync + + VMApplied uint64 } type SyncStateStage int @@ -399,8 +1208,30 @@ const ( StageMessages StageSyncComplete StageSyncErrored + StageFetchingMessages ) +func (v SyncStateStage) String() string { + switch v { + case StageIdle: + return "idle" + case StageHeaders: + return "header sync" + case StagePersistHeaders: + return "persisting headers" + case StageMessages: + return "message sync" + case StageSyncComplete: + return "complete" + case StageSyncErrored: + return "error" + case StageFetchingMessages: + return "fetching messages" + default: + return fmt.Sprintf("", v) + } +} + type MpoolChange int const ( @@ -418,14 +1249,29 @@ type ComputeStateOutput struct { Trace []*InvocResult } +type DealCollateralBounds struct { + Min abi.TokenAmount + Max abi.TokenAmount +} + +type CirculatingSupply struct { + FilVested abi.TokenAmount + FilMined abi.TokenAmount + FilBurnt abi.TokenAmount + FilLocked abi.TokenAmount + FilCirculating abi.TokenAmount + FilReserveDisbursed abi.TokenAmount +} + type MiningBaseInfo struct { - MinerPower types.BigInt - NetworkPower types.BigInt - Sectors []abi.SectorInfo - WorkerKey address.Address - SectorSize abi.SectorSize - PrevBeaconEntry types.BeaconEntry - BeaconEntries []types.BeaconEntry + MinerPower types.BigInt + NetworkPower types.BigInt + Sectors []builtin.ExtendedSectorInfo + WorkerKey address.Address + SectorSize abi.SectorSize + PrevBeaconEntry types.BeaconEntry + BeaconEntries []types.BeaconEntry + EligibleForMining bool } type BlockTemplate struct { @@ -437,7 +1283,18 @@ type BlockTemplate struct { Messages []*types.SignedMessage Epoch abi.ChainEpoch Timestamp uint64 - WinningPoStProof []abi.PoStProof + WinningPoStProof []builtin.PoStProof +} + +type DataSize struct { + PayloadSize int64 + PieceSize abi.PaddedPieceSize +} + +type DataCIDSize struct { + PayloadSize int64 + PieceSize abi.PaddedPieceSize + PieceCID cid.Cid } type CommPRet struct { @@ -456,7 +1313,76 @@ const ( MsigCancel ) +type Deadline struct { + PostSubmissions bitfield.BitField + DisputableProofCount uint64 +} + +type Partition struct { + AllSectors bitfield.BitField + FaultySectors bitfield.BitField + RecoveringSectors bitfield.BitField + LiveSectors bitfield.BitField + ActiveSectors bitfield.BitField +} + type Fault struct { Miner address.Address Epoch abi.ChainEpoch } + +var EmptyVesting = MsigVesting{ + InitialBalance: types.EmptyInt, + StartEpoch: -1, + UnlockDuration: -1, +} + +type MsigVesting struct { + InitialBalance abi.TokenAmount + StartEpoch abi.ChainEpoch + UnlockDuration abi.ChainEpoch +} + +type MessageMatch struct { + To address.Address + From address.Address +} + +type MsigTransaction struct { + ID int64 + To address.Address + Value abi.TokenAmount + Method abi.MethodNum + Params []byte + + Approved []address.Address +} + +type PruneOpts struct { + MovingGC bool + RetainState int64 +} + +type HotGCOpts struct { + Threshold float64 + Periodic bool + Moving bool +} + +type EthTxReceipt struct { + TransactionHash ethtypes.EthHash `json:"transactionHash"` + TransactionIndex ethtypes.EthUint64 `json:"transactionIndex"` + BlockHash ethtypes.EthHash `json:"blockHash"` + BlockNumber ethtypes.EthUint64 `json:"blockNumber"` + From ethtypes.EthAddress `json:"from"` + To *ethtypes.EthAddress `json:"to"` + StateRoot ethtypes.EthHash `json:"root"` + Status ethtypes.EthUint64 `json:"status"` + ContractAddress *ethtypes.EthAddress `json:"contractAddress"` + CumulativeGasUsed ethtypes.EthUint64 `json:"cumulativeGasUsed"` + GasUsed ethtypes.EthUint64 `json:"gasUsed"` + EffectiveGasPrice ethtypes.EthBigInt `json:"effectiveGasPrice"` + LogsBloom ethtypes.EthBytes `json:"logsBloom"` + Logs []ethtypes.EthLog `json:"logs"` + Type ethtypes.EthUint64 `json:"type"` +} diff --git a/api/api_gateway.go b/api/api_gateway.go new file mode 100644 index 000000000..0fa329724 --- /dev/null +++ b/api/api_gateway.go @@ -0,0 +1,121 @@ +package api + +import ( + "context" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-jsonrpc" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/builtin/v9/miner" + "github.com/filecoin-project/go-state-types/dline" + + apitypes "github.com/filecoin-project/lotus/api/types" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/ethtypes" + "github.com/filecoin-project/lotus/node/modules/dtypes" +) + +// MODIFYING THE API INTERFACE +// +// NOTE: This is the V1 (Unstable) API - to add methods to the V0 (Stable) API +// you'll have to add those methods to interfaces in `api/v0api` +// +// When adding / changing methods in this file: +// * Do the change here +// * Adjust implementation in `node/impl/` +// * Run `make clean && make deps && make gen` - this will: +// * Generate proxy structs +// * Generate mocks +// * Generate markdown docs +// * Generate openrpc blobs + +type Gateway interface { + StateMinerSectorCount(context.Context, address.Address, types.TipSetKey) (MinerSectors, error) + GasEstimateGasPremium(context.Context, uint64, address.Address, int64, types.TipSetKey) (types.BigInt, error) + StateReplay(context.Context, types.TipSetKey, cid.Cid) (*InvocResult, error) + ChainHasObj(context.Context, cid.Cid) (bool, error) + ChainPutObj(context.Context, blocks.Block) error + ChainHead(ctx context.Context) (*types.TipSet, error) + ChainGetParentMessages(context.Context, cid.Cid) ([]Message, error) + ChainGetParentReceipts(context.Context, cid.Cid) ([]*types.MessageReceipt, error) + ChainGetBlockMessages(context.Context, cid.Cid) (*BlockMessages, error) + ChainGetMessage(ctx context.Context, mc cid.Cid) (*types.Message, error) + ChainGetPath(ctx context.Context, from, to types.TipSetKey) ([]*HeadChange, error) + ChainGetTipSet(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error) + ChainGetTipSetByHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error) + ChainGetTipSetAfterHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error) + ChainNotify(context.Context) (<-chan []*HeadChange, error) + ChainReadObj(context.Context, cid.Cid) ([]byte, error) + ChainGetGenesis(context.Context) (*types.TipSet, error) + GasEstimateMessageGas(ctx context.Context, msg *types.Message, spec *MessageSendSpec, tsk types.TipSetKey) (*types.Message, error) + MpoolGetNonce(ctx context.Context, addr address.Address) (uint64, error) + MpoolPush(ctx context.Context, sm *types.SignedMessage) (cid.Cid, error) + MsigGetAvailableBalance(ctx context.Context, addr address.Address, tsk types.TipSetKey) (types.BigInt, error) + MsigGetPending(context.Context, address.Address, types.TipSetKey) ([]*MsigTransaction, error) + MsigGetVested(ctx context.Context, addr address.Address, start types.TipSetKey, end types.TipSetKey) (types.BigInt, error) + MsigGetVestingSchedule(ctx context.Context, addr address.Address, tsk types.TipSetKey) (MsigVesting, error) + StateAccountKey(ctx context.Context, addr address.Address, tsk types.TipSetKey) (address.Address, error) + StateCall(ctx context.Context, msg *types.Message, tsk types.TipSetKey) (*InvocResult, error) + StateDealProviderCollateralBounds(ctx context.Context, size abi.PaddedPieceSize, verified bool, tsk types.TipSetKey) (DealCollateralBounds, error) + StateDecodeParams(ctx context.Context, toAddr address.Address, method abi.MethodNum, params []byte, tsk types.TipSetKey) (interface{}, error) + StateGetActor(ctx context.Context, actor address.Address, ts types.TipSetKey) (*types.Actor, error) + StateReadState(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*ActorState, error) + StateListMiners(ctx context.Context, tsk types.TipSetKey) ([]address.Address, error) + StateLookupID(ctx context.Context, addr address.Address, tsk types.TipSetKey) (address.Address, error) + StateMarketBalance(ctx context.Context, addr address.Address, tsk types.TipSetKey) (MarketBalance, error) + StateMarketStorageDeal(ctx context.Context, dealId abi.DealID, tsk types.TipSetKey) (*MarketDeal, error) + StateMinerInfo(ctx context.Context, actor address.Address, tsk types.TipSetKey) (MinerInfo, error) + StateMinerProvingDeadline(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*dline.Info, error) + StateMinerPower(context.Context, address.Address, types.TipSetKey) (*MinerPower, error) + StateNetworkName(context.Context) (dtypes.NetworkName, error) + StateNetworkVersion(context.Context, types.TipSetKey) (apitypes.NetworkVersion, error) + StateSectorGetInfo(ctx context.Context, maddr address.Address, n abi.SectorNumber, tsk types.TipSetKey) (*miner.SectorOnChainInfo, error) + StateVerifierStatus(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*abi.StoragePower, error) + StateVerifiedClientStatus(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*abi.StoragePower, error) + 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) + Discover(context.Context) (apitypes.OpenRPCDocument, error) + + EthAccounts(ctx context.Context) ([]ethtypes.EthAddress, error) + EthBlockNumber(ctx context.Context) (ethtypes.EthUint64, error) + EthGetBlockTransactionCountByNumber(ctx context.Context, blkNum ethtypes.EthUint64) (ethtypes.EthUint64, error) + EthGetBlockTransactionCountByHash(ctx context.Context, blkHash ethtypes.EthHash) (ethtypes.EthUint64, error) + EthGetBlockByHash(ctx context.Context, blkHash ethtypes.EthHash, fullTxInfo bool) (ethtypes.EthBlock, error) + EthGetBlockByNumber(ctx context.Context, blkNum string, fullTxInfo bool) (ethtypes.EthBlock, error) + EthGetTransactionByHash(ctx context.Context, txHash *ethtypes.EthHash) (*ethtypes.EthTx, error) + EthGetTransactionByHashLimited(ctx context.Context, txHash *ethtypes.EthHash, limit abi.ChainEpoch) (*ethtypes.EthTx, error) + EthGetTransactionHashByCid(ctx context.Context, cid cid.Cid) (*ethtypes.EthHash, error) + EthGetMessageCidByTransactionHash(ctx context.Context, txHash *ethtypes.EthHash) (*cid.Cid, error) + EthGetTransactionCount(ctx context.Context, sender ethtypes.EthAddress, blkOpt string) (ethtypes.EthUint64, error) + EthGetTransactionReceipt(ctx context.Context, txHash ethtypes.EthHash) (*EthTxReceipt, error) + EthGetTransactionReceiptLimited(ctx context.Context, txHash ethtypes.EthHash, limit abi.ChainEpoch) (*EthTxReceipt, error) + EthGetCode(ctx context.Context, address ethtypes.EthAddress, blkOpt string) (ethtypes.EthBytes, error) + EthGetStorageAt(ctx context.Context, address ethtypes.EthAddress, position ethtypes.EthBytes, blkParam string) (ethtypes.EthBytes, error) + EthGetBalance(ctx context.Context, address ethtypes.EthAddress, blkParam string) (ethtypes.EthBigInt, error) + EthChainId(ctx context.Context) (ethtypes.EthUint64, error) + EthSyncing(ctx context.Context) (ethtypes.EthSyncingResult, error) + NetVersion(ctx context.Context) (string, error) + NetListening(ctx context.Context) (bool, error) + EthProtocolVersion(ctx context.Context) (ethtypes.EthUint64, error) + EthGasPrice(ctx context.Context) (ethtypes.EthBigInt, error) + EthFeeHistory(ctx context.Context, p jsonrpc.RawParams) (ethtypes.EthFeeHistory, error) + EthMaxPriorityFeePerGas(ctx context.Context) (ethtypes.EthBigInt, error) + EthEstimateGas(ctx context.Context, tx ethtypes.EthCall) (ethtypes.EthUint64, error) + EthCall(ctx context.Context, tx ethtypes.EthCall, blkParam string) (ethtypes.EthBytes, error) + EthSendRawTransaction(ctx context.Context, rawTx ethtypes.EthBytes) (ethtypes.EthHash, error) + EthGetLogs(ctx context.Context, filter *ethtypes.EthFilterSpec) (*ethtypes.EthFilterResult, error) + EthGetFilterChanges(ctx context.Context, id ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) + EthGetFilterLogs(ctx context.Context, id ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) + EthNewFilter(ctx context.Context, filter *ethtypes.EthFilterSpec) (ethtypes.EthFilterID, error) + EthNewBlockFilter(ctx context.Context) (ethtypes.EthFilterID, error) + EthNewPendingTransactionFilter(ctx context.Context) (ethtypes.EthFilterID, error) + EthUninstallFilter(ctx context.Context, id ethtypes.EthFilterID) (bool, error) + EthSubscribe(ctx context.Context, params jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error) + EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionID) (bool, error) + Web3ClientVersion(ctx context.Context) (string, error) +} diff --git a/api/api_net.go b/api/api_net.go new file mode 100644 index 000000000..cfcd8d87e --- /dev/null +++ b/api/api_net.go @@ -0,0 +1,77 @@ +package api + +import ( + "context" + "time" + + "github.com/libp2p/go-libp2p/core/metrics" + "github.com/libp2p/go-libp2p/core/network" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/protocol" +) + +// MODIFYING THE API INTERFACE +// +// When adding / changing methods in this file: +// * Do the change here +// * Adjust implementation in `node/impl/` +// * Run `make gen` - this will: +// * Generate proxy structs +// * Generate mocks +// * Generate markdown docs +// * Generate openrpc blobs + +type Net interface { + // MethodGroup: Net + + NetConnectedness(context.Context, peer.ID) (network.Connectedness, error) //perm:read + NetPeers(context.Context) ([]peer.AddrInfo, error) //perm:read + NetPing(context.Context, peer.ID) (time.Duration, error) //perm:read + NetConnect(context.Context, peer.AddrInfo) error //perm:write + NetAddrsListen(context.Context) (peer.AddrInfo, error) //perm:read + NetDisconnect(context.Context, peer.ID) error //perm:write + NetFindPeer(context.Context, peer.ID) (peer.AddrInfo, error) //perm:read + NetPubsubScores(context.Context) ([]PubsubScore, error) //perm:read + NetAutoNatStatus(context.Context) (NatInfo, error) //perm:read + NetAgentVersion(ctx context.Context, p peer.ID) (string, error) //perm:read + NetPeerInfo(context.Context, peer.ID) (*ExtendedPeerInfo, error) //perm:read + + // NetBandwidthStats returns statistics about the nodes total bandwidth + // usage and current rate across all peers and protocols. + NetBandwidthStats(ctx context.Context) (metrics.Stats, error) //perm:read + + // NetBandwidthStatsByPeer returns statistics about the nodes bandwidth + // usage and current rate per peer + NetBandwidthStatsByPeer(ctx context.Context) (map[string]metrics.Stats, error) //perm:read + + // NetBandwidthStatsByProtocol returns statistics about the nodes bandwidth + // usage and current rate per protocol + NetBandwidthStatsByProtocol(ctx context.Context) (map[protocol.ID]metrics.Stats, error) //perm:read + + // ConnectionGater API + NetBlockAdd(ctx context.Context, acl NetBlockList) error //perm:admin + NetBlockRemove(ctx context.Context, acl NetBlockList) error //perm:admin + NetBlockList(ctx context.Context) (NetBlockList, error) //perm:read + + NetProtectAdd(ctx context.Context, acl []peer.ID) error //perm:admin + NetProtectRemove(ctx context.Context, acl []peer.ID) error //perm:admin + NetProtectList(ctx context.Context) ([]peer.ID, error) //perm:read + + // ResourceManager API + NetStat(ctx context.Context, scope string) (NetStat, error) //perm:read + NetLimit(ctx context.Context, scope string) (NetLimit, error) //perm:read + NetSetLimit(ctx context.Context, scope string, limit NetLimit) error //perm:admin + + // ID returns peerID of libp2p node backing this API + ID(context.Context) (peer.ID, error) //perm:read +} + +type CommonNet interface { + Common + Net +} + +type NatInfo struct { + Reachability network.Reachability + PublicAddrs []string +} diff --git a/api/api_storage.go b/api/api_storage.go index ab75e8058..a9e632998 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -3,62 +3,339 @@ package api import ( "bytes" "context" + "time" + "github.com/google/uuid" "github.com/ipfs/go-cid" + "github.com/libp2p/go-libp2p/core/peer" "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-bitfield" + datatransfer "github.com/filecoin-project/go-data-transfer/v2" + "github.com/filecoin-project/go-fil-markets/piecestore" + "github.com/filecoin-project/go-fil-markets/retrievalmarket" "github.com/filecoin-project/go-fil-markets/storagemarket" - "github.com/filecoin-project/sector-storage/stores" - "github.com/filecoin-project/sector-storage/storiface" - "github.com/filecoin-project/specs-actors/actors/abi" + "github.com/filecoin-project/go-jsonrpc/auth" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/builtin/v9/market" + "github.com/filecoin-project/go-state-types/builtin/v9/miner" + abinetwork "github.com/filecoin-project/go-state-types/network" + builtinactors "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/storage/pipeline/sealiface" + "github.com/filecoin-project/lotus/storage/sealer/fsutil" + "github.com/filecoin-project/lotus/storage/sealer/storiface" ) +// MODIFYING THE API INTERFACE +// +// When adding / changing methods in this file: +// * Do the change here +// * Adjust implementation in `node/impl/` +// * Run `make gen` - this will: +// * Generate proxy structs +// * Generate mocks +// * Generate markdown docs +// * Generate openrpc blobs + // StorageMiner is a low-level interface to the Filecoin network storage miner node type StorageMiner interface { Common + Net - ActorAddress(context.Context) (address.Address, error) + ActorAddress(context.Context) (address.Address, error) //perm:read - ActorSectorSize(context.Context, address.Address) (abi.SectorSize, error) + ActorSectorSize(context.Context, address.Address) (abi.SectorSize, error) //perm:read + ActorAddressConfig(ctx context.Context) (AddressConfig, error) //perm:read - MiningBase(context.Context) (*types.TipSet, error) + // WithdrawBalance allows to withdraw balance from miner actor to owner address + // Specify amount as "0" to withdraw full balance. This method returns a message CID + // and does not wait for message execution + ActorWithdrawBalance(ctx context.Context, amount abi.TokenAmount) (cid.Cid, error) //perm:admin + + // BeneficiaryWithdrawBalance allows the beneficiary of a miner to withdraw balance from miner actor + // Specify amount as "0" to withdraw full balance. This method returns a message CID + // and does not wait for message execution + BeneficiaryWithdrawBalance(context.Context, abi.TokenAmount) (cid.Cid, error) //perm:admin + + MiningBase(context.Context) (*types.TipSet, error) //perm:read + + ComputeWindowPoSt(ctx context.Context, dlIdx uint64, tsk types.TipSetKey) ([]miner.SubmitWindowedPoStParams, error) //perm:admin + + ComputeDataCid(ctx context.Context, pieceSize abi.UnpaddedPieceSize, pieceData storiface.Data) (abi.PieceInfo, error) //perm:admin // Temp api for testing - PledgeSector(context.Context) error + PledgeSector(context.Context) (abi.SectorID, error) //perm:write // Get the status of a given sector by ID - SectorsStatus(context.Context, abi.SectorNumber) (SectorInfo, error) + SectorsStatus(ctx context.Context, sid abi.SectorNumber, showOnChainInfo bool) (SectorInfo, error) //perm:read + + // Add piece to an open sector. If no sectors with enough space are open, + // either a new sector will be created, or this call will block until more + // sectors can be created. + SectorAddPieceToAny(ctx context.Context, size abi.UnpaddedPieceSize, r storiface.Data, d PieceDealInfo) (SectorOffset, error) //perm:admin + + SectorsUnsealPiece(ctx context.Context, sector storiface.SectorRef, offset storiface.UnpaddedByteIndex, size abi.UnpaddedPieceSize, randomness abi.SealRandomness, commd *cid.Cid) error //perm:admin // List all staged sectors - SectorsList(context.Context) ([]abi.SectorNumber, error) + SectorsList(context.Context) ([]abi.SectorNumber, error) //perm:read - SectorsRefs(context.Context) (map[string][]SealedRef, error) + // Get summary info of sectors + SectorsSummary(ctx context.Context) (map[SectorState]int, error) //perm:read - SectorsUpdate(context.Context, abi.SectorNumber, SectorState) error + // List sectors in particular states + SectorsListInStates(context.Context, []SectorState) ([]abi.SectorNumber, error) //perm:read - StorageList(ctx context.Context) (map[stores.ID][]stores.Decl, error) - StorageLocal(ctx context.Context) (map[stores.ID]string, error) - StorageStat(ctx context.Context, id stores.ID) (stores.FsStat, error) + SectorsRefs(context.Context) (map[string][]SealedRef, error) //perm:read + + // SectorStartSealing can be called on sectors in Empty or WaitDeals states + // to trigger sealing early + SectorStartSealing(context.Context, abi.SectorNumber) error //perm:write + // SectorSetSealDelay sets the time that a newly-created sector + // waits for more deals before it starts sealing + SectorSetSealDelay(context.Context, time.Duration) error //perm:write + // SectorGetSealDelay gets the time that a newly-created sector + // waits for more deals before it starts sealing + SectorGetSealDelay(context.Context) (time.Duration, error) //perm:read + // SectorSetExpectedSealDuration sets the expected time for a sector to seal + SectorSetExpectedSealDuration(context.Context, time.Duration) error //perm:write + // SectorGetExpectedSealDuration gets the expected time for a sector to seal + SectorGetExpectedSealDuration(context.Context) (time.Duration, error) //perm:read + SectorsUpdate(context.Context, abi.SectorNumber, SectorState) error //perm:admin + // SectorRemove removes the sector from storage. It doesn't terminate it on-chain, which can + // be done with SectorTerminate. Removing and not terminating live sectors will cause additional penalties. + SectorRemove(context.Context, abi.SectorNumber) error //perm:admin + SectorMarkForUpgrade(ctx context.Context, id abi.SectorNumber, snap bool) error //perm:admin + // SectorTerminate terminates the sector on-chain (adding it to a termination batch first), then + // automatically removes it from storage + SectorTerminate(context.Context, abi.SectorNumber) error //perm:admin + // SectorTerminateFlush immediately sends a terminate message with sectors batched for termination. + // Returns null if message wasn't sent + SectorTerminateFlush(ctx context.Context) (*cid.Cid, error) //perm:admin + // SectorTerminatePending returns a list of pending sector terminations to be sent in the next batch message + SectorTerminatePending(ctx context.Context) ([]abi.SectorID, error) //perm:admin + // SectorPreCommitFlush immediately sends a PreCommit message with sectors batched for PreCommit. + // Returns null if message wasn't sent + SectorPreCommitFlush(ctx context.Context) ([]sealiface.PreCommitBatchRes, error) //perm:admin + // SectorPreCommitPending returns a list of pending PreCommit sectors to be sent in the next batch message + SectorPreCommitPending(ctx context.Context) ([]abi.SectorID, error) //perm:admin + // SectorCommitFlush immediately sends a Commit message with sectors aggregated for Commit. + // Returns null if message wasn't sent + SectorCommitFlush(ctx context.Context) ([]sealiface.CommitBatchRes, error) //perm:admin + // SectorCommitPending returns a list of pending Commit sectors to be sent in the next aggregate message + SectorCommitPending(ctx context.Context) ([]abi.SectorID, error) //perm:admin + SectorMatchPendingPiecesToOpenSectors(ctx context.Context) error //perm:admin + // SectorAbortUpgrade can be called on sectors that are in the process of being upgraded to abort it + SectorAbortUpgrade(context.Context, abi.SectorNumber) error //perm:admin + // SectorUnseal unseals the provided sector + SectorUnseal(ctx context.Context, number abi.SectorNumber) error //perm:admin + + // SectorNumAssignerMeta returns sector number assigner metadata - reserved/allocated + SectorNumAssignerMeta(ctx context.Context) (NumAssignerMeta, error) //perm:read + // SectorNumReservations returns a list of sector number reservations + SectorNumReservations(ctx context.Context) (map[string]bitfield.BitField, error) //perm:read + // SectorNumReserve creates a new sector number reservation. Will fail if any other reservation has colliding + // numbers or name. Set force to true to override safety checks. + // Valid characters for name: a-z, A-Z, 0-9, _, - + SectorNumReserve(ctx context.Context, name string, sectors bitfield.BitField, force bool) error //perm:admin + // SectorNumReserveCount creates a new sector number reservation for `count` sector numbers. + // by default lotus will allocate lowest-available sector numbers to the reservation. + // For restrictions on `name` see SectorNumReserve + SectorNumReserveCount(ctx context.Context, name string, count uint64) (bitfield.BitField, error) //perm:admin + // SectorNumFree drops a sector reservation + SectorNumFree(ctx context.Context, name string) error //perm:admin + + SectorReceive(ctx context.Context, meta RemoteSectorMeta) error //perm:admin // WorkerConnect tells the node to connect to workers RPC - WorkerConnect(context.Context, string) error - WorkerStats(context.Context) (map[uint64]storiface.WorkerStats, error) + WorkerConnect(context.Context, string) error //perm:admin retry:true + WorkerStats(context.Context) (map[uuid.UUID]storiface.WorkerStats, error) //perm:admin + WorkerJobs(context.Context) (map[uuid.UUID][]storiface.WorkerJob, error) //perm:admin - stores.SectorIndex + // storiface.WorkerReturn + ReturnDataCid(ctx context.Context, callID storiface.CallID, pi abi.PieceInfo, err *storiface.CallError) error //perm:admin retry:true + ReturnAddPiece(ctx context.Context, callID storiface.CallID, pi abi.PieceInfo, err *storiface.CallError) error //perm:admin retry:true + ReturnSealPreCommit1(ctx context.Context, callID storiface.CallID, p1o storiface.PreCommit1Out, err *storiface.CallError) error //perm:admin retry:true + ReturnSealPreCommit2(ctx context.Context, callID storiface.CallID, sealed storiface.SectorCids, err *storiface.CallError) error //perm:admin retry:true + ReturnSealCommit1(ctx context.Context, callID storiface.CallID, out storiface.Commit1Out, err *storiface.CallError) error //perm:admin retry:true + ReturnSealCommit2(ctx context.Context, callID storiface.CallID, proof storiface.Proof, err *storiface.CallError) error //perm:admin retry:true + ReturnFinalizeSector(ctx context.Context, callID storiface.CallID, err *storiface.CallError) error //perm:admin retry:true + ReturnReplicaUpdate(ctx context.Context, callID storiface.CallID, out storiface.ReplicaUpdateOut, err *storiface.CallError) error //perm:admin retry:true + ReturnProveReplicaUpdate1(ctx context.Context, callID storiface.CallID, vanillaProofs storiface.ReplicaVanillaProofs, err *storiface.CallError) error //perm:admin retry:true + ReturnProveReplicaUpdate2(ctx context.Context, callID storiface.CallID, proof storiface.ReplicaUpdateProof, err *storiface.CallError) error //perm:admin retry:true + ReturnGenerateSectorKeyFromData(ctx context.Context, callID storiface.CallID, err *storiface.CallError) error //perm:admin retry:true + ReturnFinalizeReplicaUpdate(ctx context.Context, callID storiface.CallID, err *storiface.CallError) error //perm:admin retry:true + ReturnReleaseUnsealed(ctx context.Context, callID storiface.CallID, err *storiface.CallError) error //perm:admin retry:true + ReturnMoveStorage(ctx context.Context, callID storiface.CallID, err *storiface.CallError) error //perm:admin retry:true + ReturnUnsealPiece(ctx context.Context, callID storiface.CallID, err *storiface.CallError) error //perm:admin retry:true + ReturnReadPiece(ctx context.Context, callID storiface.CallID, ok bool, err *storiface.CallError) error //perm:admin retry:true + ReturnDownloadSector(ctx context.Context, callID storiface.CallID, err *storiface.CallError) error //perm:admin retry:true + ReturnFetch(ctx context.Context, callID storiface.CallID, err *storiface.CallError) error //perm:admin retry:true - MarketImportDealData(ctx context.Context, propcid cid.Cid, path string) error - MarketListDeals(ctx context.Context) ([]storagemarket.StorageDeal, error) - MarketListIncompleteDeals(ctx context.Context) ([]storagemarket.MinerDeal, error) - MarketSetPrice(context.Context, types.BigInt) error + // SealingSchedDiag dumps internal sealing scheduler state + SealingSchedDiag(ctx context.Context, doSched bool) (interface{}, error) //perm:admin + SealingAbort(ctx context.Context, call storiface.CallID) error //perm:admin + // SealingSchedRemove removes a request from sealing pipeline + SealingRemoveRequest(ctx context.Context, schedId uuid.UUID) error //perm:admin - DealsImportData(ctx context.Context, dealPropCid cid.Cid, file string) error - DealsList(ctx context.Context) ([]storagemarket.StorageDeal, error) + // paths.SectorIndex + StorageAttach(context.Context, storiface.StorageInfo, fsutil.FsStat) error //perm:admin + StorageDetach(ctx context.Context, id storiface.ID, url string) error //perm:admin + StorageInfo(context.Context, storiface.ID) (storiface.StorageInfo, error) //perm:admin + StorageReportHealth(context.Context, storiface.ID, storiface.HealthReport) error //perm:admin + StorageDeclareSector(ctx context.Context, storageID storiface.ID, s abi.SectorID, ft storiface.SectorFileType, primary bool) error //perm:admin + StorageDropSector(ctx context.Context, storageID storiface.ID, s abi.SectorID, ft storiface.SectorFileType) error //perm:admin + // StorageFindSector returns list of paths where the specified sector files exist. + // + // If allowFetch is set, list of paths to which the sector can be fetched will also be returned. + // - Paths which have sector files locally (don't require fetching) will be listed first. + // - Paths which have sector files locally will not be filtered based on based on AllowTypes/DenyTypes. + // - Paths which require fetching will be filtered based on AllowTypes/DenyTypes. If multiple + // file types are specified, each type will be considered individually, and a union of all paths + // which can accommodate each file type will be returned. + StorageFindSector(ctx context.Context, sector abi.SectorID, ft storiface.SectorFileType, ssize abi.SectorSize, allowFetch bool) ([]storiface.SectorStorageInfo, error) //perm:admin + // StorageBestAlloc returns list of paths where sector files of the specified type can be allocated, ordered by preference. + // Paths with more weight and more % of free space are preferred. + // Note: This method doesn't filter paths based on AllowTypes/DenyTypes. + StorageBestAlloc(ctx context.Context, allocate storiface.SectorFileType, ssize abi.SectorSize, pathType storiface.PathType) ([]storiface.StorageInfo, error) //perm:admin + StorageLock(ctx context.Context, sector abi.SectorID, read storiface.SectorFileType, write storiface.SectorFileType) error //perm:admin + StorageTryLock(ctx context.Context, sector abi.SectorID, read storiface.SectorFileType, write storiface.SectorFileType) (bool, error) //perm:admin + StorageList(ctx context.Context) (map[storiface.ID][]storiface.Decl, error) //perm:admin + StorageGetLocks(ctx context.Context) (storiface.SectorLocks, error) //perm:admin - StorageAddLocal(ctx context.Context, path string) error + StorageLocal(ctx context.Context) (map[storiface.ID]string, error) //perm:admin + StorageStat(ctx context.Context, id storiface.ID) (fsutil.FsStat, error) //perm:admin + + StorageAuthVerify(ctx context.Context, token string) ([]auth.Permission, error) //perm:read + + StorageAddLocal(ctx context.Context, path string) error //perm:admin + StorageDetachLocal(ctx context.Context, path string) error //perm:admin + StorageRedeclareLocal(ctx context.Context, id *storiface.ID, dropMissing bool) error //perm:admin + + MarketImportDealData(ctx context.Context, propcid cid.Cid, path string) error //perm:write + MarketListDeals(ctx context.Context) ([]*MarketDeal, error) //perm:read + + // MarketListRetrievalDeals is deprecated, returns empty list + MarketListRetrievalDeals(ctx context.Context) ([]struct{}, error) //perm:read + MarketGetDealUpdates(ctx context.Context) (<-chan storagemarket.MinerDeal, error) //perm:read + MarketListIncompleteDeals(ctx context.Context) ([]storagemarket.MinerDeal, error) //perm:read + MarketSetAsk(ctx context.Context, price types.BigInt, verifiedPrice types.BigInt, duration abi.ChainEpoch, minPieceSize abi.PaddedPieceSize, maxPieceSize abi.PaddedPieceSize) error //perm:admin + MarketGetAsk(ctx context.Context) (*storagemarket.SignedStorageAsk, error) //perm:read + MarketSetRetrievalAsk(ctx context.Context, rask *retrievalmarket.Ask) error //perm:admin + MarketGetRetrievalAsk(ctx context.Context) (*retrievalmarket.Ask, error) //perm:read + MarketListDataTransfers(ctx context.Context) ([]DataTransferChannel, error) //perm:write + MarketDataTransferUpdates(ctx context.Context) (<-chan DataTransferChannel, error) //perm:write + // MarketDataTransferDiagnostics generates debugging information about current data transfers over graphsync + MarketDataTransferDiagnostics(ctx context.Context, p peer.ID) (*TransferDiagnostics, error) //perm:write + // MarketRestartDataTransfer attempts to restart a data transfer with the given transfer ID and other peer + MarketRestartDataTransfer(ctx context.Context, transferID datatransfer.TransferID, otherPeer peer.ID, isInitiator bool) error //perm:write + // MarketCancelDataTransfer cancels a data transfer with the given transfer ID and other peer + MarketCancelDataTransfer(ctx context.Context, transferID datatransfer.TransferID, otherPeer peer.ID, isInitiator bool) error //perm:write + MarketPendingDeals(ctx context.Context) (PendingDealInfo, error) //perm:write + MarketPublishPendingDeals(ctx context.Context) error //perm:admin + MarketRetryPublishDeal(ctx context.Context, propcid cid.Cid) error //perm:admin + + // DagstoreListShards returns information about all shards known to the + // DAG store. Only available on nodes running the markets subsystem. + DagstoreListShards(ctx context.Context) ([]DagstoreShardInfo, error) //perm:read + + // DagstoreInitializeShard initializes an uninitialized shard. + // + // Initialization consists of fetching the shard's data (deal payload) from + // the storage subsystem, generating an index, and persisting the index + // to facilitate later retrievals, and/or to publish to external sources. + // + // This operation is intended to complement the initial migration. The + // migration registers a shard for every unique piece CID, with lazy + // initialization. Thus, shards are not initialized immediately to avoid + // IO activity competing with proving. Instead, shard are initialized + // when first accessed. This method forces the initialization of a shard by + // accessing it and immediately releasing it. This is useful to warm up the + // cache to facilitate subsequent retrievals, and to generate the indexes + // to publish them externally. + // + // This operation fails if the shard is not in ShardStateNew state. + // It blocks until initialization finishes. + DagstoreInitializeShard(ctx context.Context, key string) error //perm:write + + // DagstoreRecoverShard attempts to recover a failed shard. + // + // This operation fails if the shard is not in ShardStateErrored state. + // It blocks until recovery finishes. If recovery failed, it returns the + // error. + DagstoreRecoverShard(ctx context.Context, key string) error //perm:write + + // DagstoreInitializeAll initializes all uninitialized shards in bulk, + // according to the policy passed in the parameters. + // + // It is recommended to set a maximum concurrency to avoid extreme + // IO pressure if the storage subsystem has a large amount of deals. + // + // It returns a stream of events to report progress. + DagstoreInitializeAll(ctx context.Context, params DagstoreInitializeAllParams) (<-chan DagstoreInitializeAllEvent, error) //perm:write + + // DagstoreGC runs garbage collection on the DAG store. + DagstoreGC(ctx context.Context) ([]DagstoreShardResult, error) //perm:admin + + // DagstoreRegisterShard registers a shard manually with dagstore with given pieceCID + DagstoreRegisterShard(ctx context.Context, key string) error //perm:admin + + // IndexerAnnounceDeal informs indexer nodes that a new deal was received, + // so they can download its index + IndexerAnnounceDeal(ctx context.Context, proposalCid cid.Cid) error //perm:admin + + // IndexerAnnounceAllDeals informs the indexer nodes aboutall active deals. + IndexerAnnounceAllDeals(ctx context.Context) error //perm:admin + + // DagstoreLookupPieces returns information about shards that contain the given CID. + DagstoreLookupPieces(ctx context.Context, cid cid.Cid) ([]DagstoreShardInfo, error) //perm:admin + + // RuntimeSubsystems returns the subsystems that are enabled + // in this instance. + RuntimeSubsystems(ctx context.Context) (MinerSubsystems, error) //perm:read + + DealsImportData(ctx context.Context, dealPropCid cid.Cid, file string) error //perm:admin + DealsList(ctx context.Context) ([]*MarketDeal, error) //perm:admin + DealsConsiderOnlineStorageDeals(context.Context) (bool, error) //perm:admin + DealsSetConsiderOnlineStorageDeals(context.Context, bool) error //perm:admin + DealsConsiderOnlineRetrievalDeals(context.Context) (bool, error) //perm:admin + DealsSetConsiderOnlineRetrievalDeals(context.Context, bool) error //perm:admin + DealsPieceCidBlocklist(context.Context) ([]cid.Cid, error) //perm:admin + DealsSetPieceCidBlocklist(context.Context, []cid.Cid) error //perm:admin + DealsConsiderOfflineStorageDeals(context.Context) (bool, error) //perm:admin + DealsSetConsiderOfflineStorageDeals(context.Context, bool) error //perm:admin + DealsConsiderOfflineRetrievalDeals(context.Context) (bool, error) //perm:admin + DealsSetConsiderOfflineRetrievalDeals(context.Context, bool) error //perm:admin + DealsConsiderVerifiedStorageDeals(context.Context) (bool, error) //perm:admin + DealsSetConsiderVerifiedStorageDeals(context.Context, bool) error //perm:admin + DealsConsiderUnverifiedStorageDeals(context.Context) (bool, error) //perm:admin + DealsSetConsiderUnverifiedStorageDeals(context.Context, bool) error //perm:admin + + PiecesListPieces(ctx context.Context) ([]cid.Cid, error) //perm:read + PiecesListCidInfos(ctx context.Context) ([]cid.Cid, error) //perm:read + PiecesGetPieceInfo(ctx context.Context, pieceCid cid.Cid) (*piecestore.PieceInfo, error) //perm:read + PiecesGetCIDInfo(ctx context.Context, payloadCid cid.Cid) (*piecestore.CIDInfo, error) //perm:read + + // CreateBackup creates node backup onder the specified file name. The + // method requires that the lotus-miner is running with the + // LOTUS_BACKUP_BASE_PATH environment variable set to some path, and that + // the path specified when calling CreateBackup is within the base path + CreateBackup(ctx context.Context, fpath string) error //perm:admin + + CheckProvable(ctx context.Context, pp abi.RegisteredPoStProof, sectors []storiface.SectorRef) (map[abi.SectorNumber]string, error) //perm:admin + + ComputeProof(ctx context.Context, ssi []builtinactors.ExtendedSectorInfo, rand abi.PoStRandomness, poStEpoch abi.ChainEpoch, nv abinetwork.Version) ([]builtinactors.PoStProof, error) //perm:read + + // RecoverFault can be used to declare recoveries manually. It sends messages + // to the miner actor with details of recovered sectors and returns the CID of messages. It honors the + // maxPartitionsPerRecoveryMessage from the config + RecoverFault(ctx context.Context, sectors []abi.SectorNumber) ([]cid.Cid, error) //perm:admin } +var _ storiface.WorkerReturn = *new(StorageMiner) + type SealRes struct { Err string GoErr error `json:"-"` @@ -75,25 +352,48 @@ type SectorLog struct { Message string } +type SectorPiece struct { + Piece abi.PieceInfo + DealInfo *PieceDealInfo // nil for pieces which do not appear in deals (e.g. filler pieces) +} + type SectorInfo struct { - SectorID abi.SectorNumber - State SectorState - CommD *cid.Cid - CommR *cid.Cid - Proof []byte - Deals []abi.DealID - Ticket SealTicket - Seed SealSeed - Retries uint64 + SectorID abi.SectorNumber + State SectorState + CommD *cid.Cid + CommR *cid.Cid + Proof []byte + Deals []abi.DealID + Pieces []SectorPiece + Ticket SealTicket + Seed SealSeed + PreCommitMsg *cid.Cid + CommitMsg *cid.Cid + Retries uint64 + ToUpgrade bool + ReplicaUpdateMessage *cid.Cid LastErr string Log []SectorLog + + // On Chain Info + SealProof abi.RegisteredSealProof // The seal proof type implies the PoSt proof/s + Activation abi.ChainEpoch // Epoch during which the sector proof was accepted + Expiration abi.ChainEpoch // Epoch during which the sector expires + DealWeight abi.DealWeight // Integral of active deals over sector lifetime + VerifiedDealWeight abi.DealWeight // Integral of active verified deals over sector lifetime + InitialPledge abi.TokenAmount // Pledge collected to commit this sector + // Expiration Info + OnTime abi.ChainEpoch + // non-zero if sector is faulty, epoch at which it will be permanently + // removed if it doesn't recover + Early abi.ChainEpoch } type SealedRef struct { SectorID abi.SectorNumber - Offset uint64 + Offset abi.PaddedPieceSize Size abi.UnpaddedPieceSize } @@ -120,3 +420,205 @@ func (st *SealSeed) Equals(ost *SealSeed) bool { } type SectorState string + +func (s *SectorState) String() string { + return string(*s) +} + +type AddrUse int + +const ( + PreCommitAddr AddrUse = iota + CommitAddr + DealPublishAddr + PoStAddr + + TerminateSectorsAddr +) + +type AddressConfig struct { + PreCommitControl []address.Address + CommitControl []address.Address + TerminateControl []address.Address + DealPublishControl []address.Address + + DisableOwnerFallback bool + DisableWorkerFallback bool +} + +// PendingDealInfo has info about pending deals and when they are due to be +// published +type PendingDealInfo struct { + Deals []market.ClientDealProposal + PublishPeriodStart time.Time + PublishPeriod time.Duration +} + +type SectorOffset struct { + Sector abi.SectorNumber + Offset abi.PaddedPieceSize +} + +// DealInfo is a tuple of deal identity and its schedule +type PieceDealInfo struct { + PublishCid *cid.Cid + DealID abi.DealID + DealProposal *market.DealProposal + DealSchedule DealSchedule + KeepUnsealed bool +} + +// DealSchedule communicates the time interval of a storage deal. The deal must +// appear in a sealed (proven) sector no later than StartEpoch, otherwise it +// is invalid. +type DealSchedule struct { + StartEpoch abi.ChainEpoch + EndEpoch abi.ChainEpoch +} + +// DagstoreShardInfo is the serialized form of dagstore.DagstoreShardInfo that +// we expose through JSON-RPC to avoid clients having to depend on the +// dagstore lib. +type DagstoreShardInfo struct { + Key string + State string + Error string +} + +// DagstoreShardResult enumerates results per shard. +type DagstoreShardResult struct { + Key string + Success bool + Error string +} + +type DagstoreInitializeAllParams struct { + MaxConcurrency int + IncludeSealed bool +} + +// DagstoreInitializeAllEvent represents an initialization event. +type DagstoreInitializeAllEvent struct { + Key string + Event string // "start", "end" + Success bool + Error string + Total int + Current int +} + +type NumAssignerMeta struct { + Reserved bitfield.BitField + Allocated bitfield.BitField + + // ChainAllocated+Reserved+Allocated + InUse bitfield.BitField + + Next abi.SectorNumber +} + +type RemoteSectorMeta struct { + //////// + // BASIC SECTOR INFORMATION + + // State specifies the first state the sector will enter after being imported + // Must be one of the following states: + // * Packing + // * GetTicket + // * PreCommitting + // * SubmitCommit + // * Proving/Available + State SectorState + + Sector abi.SectorID + Type abi.RegisteredSealProof + + //////// + // SEALING METADATA + // (allows lotus to continue the sealing process) + + // Required in Packing and later + Pieces []SectorPiece // todo better type? + + // Required in PreCommitting and later + TicketValue abi.SealRandomness + TicketEpoch abi.ChainEpoch + PreCommit1Out storiface.PreCommit1Out // todo specify better + + CommD *cid.Cid + CommR *cid.Cid // SectorKey + + // Required in SubmitCommit and later + PreCommitInfo *miner.SectorPreCommitInfo + PreCommitDeposit *big.Int + PreCommitMessage *cid.Cid + PreCommitTipSet types.TipSetKey + + SeedValue abi.InteractiveSealRandomness + SeedEpoch abi.ChainEpoch + + CommitProof []byte + + // Required in Proving/Available + CommitMessage *cid.Cid + + // Optional sector metadata to import + Log []SectorLog + + //////// + // SECTOR DATA SOURCE + + // Sector urls - lotus will use those for fetching files into local storage + + // Required in all states + DataUnsealed *storiface.SectorLocation + + // Required in PreCommitting and later + DataSealed *storiface.SectorLocation + DataCache *storiface.SectorLocation + + //////// + // SEALING SERVICE HOOKS + + // URL + // RemoteCommit1Endpoint is an URL of POST endpoint which lotus will call requesting Commit1 (seal_commit_phase1) + // request body will be json-serialized RemoteCommit1Params struct + RemoteCommit1Endpoint string + + // RemoteCommit2Endpoint is an URL of POST endpoint which lotus will call requesting Commit2 (seal_commit_phase2) + // request body will be json-serialized RemoteCommit2Params struct + RemoteCommit2Endpoint string + + // RemoteSealingDoneEndpoint is called after the sector exists the sealing pipeline + // request body will be json-serialized RemoteSealingDoneParams struct + RemoteSealingDoneEndpoint string +} + +type RemoteCommit1Params struct { + Ticket, Seed []byte + + Unsealed cid.Cid + Sealed cid.Cid + + ProofType abi.RegisteredSealProof +} + +type RemoteCommit2Params struct { + Sector abi.SectorID + ProofType abi.RegisteredSealProof + + // todo spec better + Commit1Out storiface.Commit1Out +} + +type RemoteSealingDoneParams struct { + // Successful is true if the sector has entered state considered as "successfully sealed" + Successful bool + + // State is the state the sector has entered + // For example "Proving" / "Removing" + State string + + // Optional commit message CID + CommitMessage *cid.Cid +} diff --git a/api/api_test.go b/api/api_test.go index 1b438258a..1316d9fa4 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -1,12 +1,20 @@ +// stm: #unit package api import ( + "encoding/json" "os" "os/exec" "path/filepath" + "reflect" "runtime" "strings" "testing" + + "github.com/stretchr/testify/require" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-jsonrpc" ) func goCmd() string { @@ -22,6 +30,7 @@ func goCmd() string { } func TestDoesntDependOnFFI(t *testing.T) { + //stm: @OTHER_IMPLEMENTATION_FFI_DEPENDENCE_001 deps, err := exec.Command(goCmd(), "list", "-deps", "github.com/filecoin-project/lotus/api").Output() if err != nil { t.Fatal(err) @@ -32,3 +41,104 @@ func TestDoesntDependOnFFI(t *testing.T) { } } } + +func TestDoesntDependOnBuild(t *testing.T) { + //stm: @OTHER_IMPLEMENTATION_FFI_DEPENDENCE_002 + deps, err := exec.Command(goCmd(), "list", "-deps", "github.com/filecoin-project/lotus/api").Output() + if err != nil { + t.Fatal(err) + } + for _, pkg := range strings.Fields(string(deps)) { + if pkg == "github.com/filecoin-project/build" { + t.Fatal("api depends on filecoin-ffi") + } + } +} + +func TestReturnTypes(t *testing.T) { + //stm: @OTHER_IMPLEMENTATION_001 + errType := reflect.TypeOf(new(error)).Elem() + bareIface := reflect.TypeOf(new(interface{})).Elem() + jmarsh := reflect.TypeOf(new(json.Marshaler)).Elem() + + tst := func(api interface{}) func(t *testing.T) { + return func(t *testing.T) { + ra := reflect.TypeOf(api).Elem() + for i := 0; i < ra.NumMethod(); i++ { + m := ra.Method(i) + switch m.Type.NumOut() { + case 1: // if 1 return value, it must be an error + require.Equal(t, errType, m.Type.Out(0), m.Name) + + case 2: // if 2 return values, first cant be an interface/function, second must be an error + seen := map[reflect.Type]struct{}{} + todo := []reflect.Type{m.Type.Out(0)} + for len(todo) > 0 { + typ := todo[len(todo)-1] + todo = todo[:len(todo)-1] + + if _, ok := seen[typ]; ok { + continue + } + seen[typ] = struct{}{} + + if typ.Kind() == reflect.Interface && typ != bareIface && !typ.Implements(jmarsh) { + t.Error("methods can't return interfaces or struct types not implementing json.Marshaller", m.Name) + } + + switch typ.Kind() { + case reflect.Ptr: + fallthrough + case reflect.Array: + fallthrough + case reflect.Slice: + fallthrough + case reflect.Chan: + todo = append(todo, typ.Elem()) + case reflect.Map: + todo = append(todo, typ.Elem()) + todo = append(todo, typ.Key()) + case reflect.Struct: + for i := 0; i < typ.NumField(); i++ { + todo = append(todo, typ.Field(i).Type) + } + } + } + + require.NotEqual(t, reflect.Func.String(), m.Type.Out(0).Kind().String(), m.Name) + require.Equal(t, errType, m.Type.Out(1), m.Name) + + default: + t.Error("methods can only have 1 or 2 return values", m.Name) + } + } + } + } + + t.Run("common", tst(new(Common))) + t.Run("full", tst(new(FullNode))) + t.Run("miner", tst(new(StorageMiner))) + t.Run("worker", tst(new(Worker))) +} + +func TestPermTags(t *testing.T) { + //stm: @OTHER_IMPLEMENTATION_PERM_TAGS_001 + _ = PermissionedFullAPI(&FullNodeStruct{}) + _ = PermissionedStorMinerAPI(&StorageMinerStruct{}) + _ = PermissionedWorkerAPI(&WorkerStruct{}) +} + +func TestRetryErrorIsInTrue(t *testing.T) { + errorsToRetry := []error{&jsonrpc.RPCConnectionError{}} + require.True(t, ErrorIsIn(&jsonrpc.RPCConnectionError{}, errorsToRetry)) +} + +func TestRetryErrorIsInFalse(t *testing.T) { + errorsToRetry := []error{&jsonrpc.RPCConnectionError{}} + require.False(t, ErrorIsIn(xerrors.Errorf("random error"), errorsToRetry)) +} + +func TestRetryWrappedErrorIsInTrue(t *testing.T) { + errorsToRetry := []error{&jsonrpc.RPCConnectionError{}} + require.True(t, ErrorIsIn(xerrors.Errorf("wrapped: %w", &jsonrpc.RPCConnectionError{}), errorsToRetry)) +} diff --git a/api/api_wallet.go b/api/api_wallet.go new file mode 100644 index 000000000..973aaaf6d --- /dev/null +++ b/api/api_wallet.go @@ -0,0 +1,47 @@ +package api + +import ( + "context" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/crypto" + + "github.com/filecoin-project/lotus/chain/types" +) + +type MsgType string + +const ( + MTUnknown = "unknown" + + // Signing message CID. MsgMeta.Extra contains raw cbor message bytes + MTChainMsg = "message" + + // Signing a blockheader. signing raw cbor block bytes (MsgMeta.Extra is empty) + MTBlock = "block" + + // Signing a deal proposal. signing raw cbor proposal bytes (MsgMeta.Extra is empty) + MTDealProposal = "dealproposal" + + // TODO: Deals, Vouchers, VRF +) + +type MsgMeta struct { + Type MsgType + + // Additional data related to what is signed. Should be verifiable with the + // signed bytes (e.g. CID(Extra).Bytes() == toSign) + Extra []byte +} + +type Wallet interface { + WalletNew(context.Context, types.KeyType) (address.Address, error) //perm:admin + WalletHas(context.Context, address.Address) (bool, error) //perm:admin + WalletList(context.Context) ([]address.Address, error) //perm:admin + + WalletSign(ctx context.Context, signer address.Address, toSign []byte, meta MsgMeta) (*crypto.Signature, error) //perm:admin + + WalletExport(context.Context, address.Address) (*types.KeyInfo, error) //perm:admin + WalletImport(context.Context, *types.KeyInfo) (address.Address, error) //perm:admin + WalletDelete(context.Context, address.Address) error //perm:admin +} diff --git a/api/api_worker.go b/api/api_worker.go index dbad20651..197ca898d 100644 --- a/api/api_worker.go +++ b/api/api_worker.go @@ -3,25 +3,88 @@ package api import ( "context" - "github.com/filecoin-project/sector-storage/sealtasks" - "github.com/filecoin-project/sector-storage/stores" - "github.com/filecoin-project/sector-storage/storiface" - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-storage/storage" + "github.com/google/uuid" + "github.com/ipfs/go-cid" - "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/proof" + + "github.com/filecoin-project/lotus/storage/sealer/sealtasks" + "github.com/filecoin-project/lotus/storage/sealer/storiface" ) -type WorkerApi interface { - Version(context.Context) (build.Version, error) - // TODO: Info() (name, ...) ? +// MODIFYING THE API INTERFACE +// +// When adding / changing methods in this file: +// * Do the change here +// * Adjust implementation in `node/impl/` +// * Run `make gen` - this will: +// * Generate proxy structs +// * Generate mocks +// * Generate markdown docs +// * Generate openrpc blobs - TaskTypes(context.Context) (map[sealtasks.TaskType]struct{}, error) // TaskType -> Weight - Paths(context.Context) ([]stores.StoragePath, error) - Info(context.Context) (storiface.WorkerInfo, error) +type Worker interface { + Version(context.Context) (Version, error) //perm:admin - storage.Sealer - Fetch(context.Context, abi.SectorID, stores.SectorFileType, bool) error + // TaskType -> Weight + TaskTypes(context.Context) (map[sealtasks.TaskType]struct{}, error) //perm:admin + Paths(context.Context) ([]storiface.StoragePath, error) //perm:admin + Info(context.Context) (storiface.WorkerInfo, error) //perm:admin + + // storiface.WorkerCalls + DataCid(ctx context.Context, pieceSize abi.UnpaddedPieceSize, pieceData storiface.Data) (storiface.CallID, error) //perm:admin + AddPiece(ctx context.Context, sector storiface.SectorRef, pieceSizes []abi.UnpaddedPieceSize, newPieceSize abi.UnpaddedPieceSize, pieceData storiface.Data) (storiface.CallID, error) //perm:admin + SealPreCommit1(ctx context.Context, sector storiface.SectorRef, ticket abi.SealRandomness, pieces []abi.PieceInfo) (storiface.CallID, error) //perm:admin + SealPreCommit2(ctx context.Context, sector storiface.SectorRef, pc1o storiface.PreCommit1Out) (storiface.CallID, error) //perm:admin + SealCommit1(ctx context.Context, sector storiface.SectorRef, ticket abi.SealRandomness, seed abi.InteractiveSealRandomness, pieces []abi.PieceInfo, cids storiface.SectorCids) (storiface.CallID, error) //perm:admin + SealCommit2(ctx context.Context, sector storiface.SectorRef, c1o storiface.Commit1Out) (storiface.CallID, error) //perm:admin + FinalizeSector(ctx context.Context, sector storiface.SectorRef) (storiface.CallID, error) //perm:admin + FinalizeReplicaUpdate(ctx context.Context, sector storiface.SectorRef) (storiface.CallID, error) //perm:admin + ReplicaUpdate(ctx context.Context, sector storiface.SectorRef, pieces []abi.PieceInfo) (storiface.CallID, error) //perm:admin + ProveReplicaUpdate1(ctx context.Context, sector storiface.SectorRef, sectorKey, newSealed, newUnsealed cid.Cid) (storiface.CallID, error) //perm:admin + ProveReplicaUpdate2(ctx context.Context, sector storiface.SectorRef, sectorKey, newSealed, newUnsealed cid.Cid, vanillaProofs storiface.ReplicaVanillaProofs) (storiface.CallID, error) //perm:admin + GenerateSectorKeyFromData(ctx context.Context, sector storiface.SectorRef, commD cid.Cid) (storiface.CallID, error) //perm:admin + ReleaseUnsealed(ctx context.Context, sector storiface.SectorRef, keepUnsealed []storiface.Range) (storiface.CallID, error) //perm:admin + MoveStorage(ctx context.Context, sector storiface.SectorRef, types storiface.SectorFileType) (storiface.CallID, error) //perm:admin + UnsealPiece(context.Context, storiface.SectorRef, storiface.UnpaddedByteIndex, abi.UnpaddedPieceSize, abi.SealRandomness, cid.Cid) (storiface.CallID, error) //perm:admin + Fetch(context.Context, storiface.SectorRef, storiface.SectorFileType, storiface.PathType, storiface.AcquireMode) (storiface.CallID, error) //perm:admin + DownloadSectorData(ctx context.Context, sector storiface.SectorRef, finalized bool, src map[storiface.SectorFileType]storiface.SectorLocation) (storiface.CallID, error) //perm:admin + + GenerateWinningPoSt(ctx context.Context, ppt abi.RegisteredPoStProof, mid abi.ActorID, sectors []storiface.PostSectorChallenge, randomness abi.PoStRandomness) ([]proof.PoStProof, error) //perm:admin + GenerateWindowPoSt(ctx context.Context, ppt abi.RegisteredPoStProof, mid abi.ActorID, sectors []storiface.PostSectorChallenge, partitionIdx int, randomness abi.PoStRandomness) (storiface.WindowPoStResult, error) //perm:admin + + TaskDisable(ctx context.Context, tt sealtasks.TaskType) error //perm:admin + TaskEnable(ctx context.Context, tt sealtasks.TaskType) error //perm:admin + + // Storage / Other + Remove(ctx context.Context, sector abi.SectorID) error //perm:admin + + StorageLocal(ctx context.Context) (map[storiface.ID]string, error) //perm:admin + StorageAddLocal(ctx context.Context, path string) error //perm:admin + StorageDetachLocal(ctx context.Context, path string) error //perm:admin + StorageDetachAll(ctx context.Context) error //perm:admin + StorageRedeclareLocal(ctx context.Context, id *storiface.ID, dropMissing bool) error //perm:admin + + // SetEnabled marks the worker as enabled/disabled. Not that this setting + // may take a few seconds to propagate to task scheduler + SetEnabled(ctx context.Context, enabled bool) error //perm:admin + + Enabled(ctx context.Context) (bool, error) //perm:admin + + // WaitQuiet blocks until there are no tasks running + WaitQuiet(ctx context.Context) error //perm:admin + + // returns a random UUID of worker session, generated randomly when worker + // process starts + ProcessSession(context.Context) (uuid.UUID, error) //perm:admin + + // Like ProcessSession, but returns an error when worker is disabled + Session(context.Context) (uuid.UUID, error) //perm:admin + + // Trigger shutdown + Shutdown(context.Context) error //perm:admin - Closing(context.Context) (<-chan struct{}, error) } + +var _ storiface.WorkerCalls = *new(Worker) diff --git a/api/apibstore/apibstore.go b/api/apibstore/apibstore.go deleted file mode 100644 index 5bd0f0ad7..000000000 --- a/api/apibstore/apibstore.go +++ /dev/null @@ -1,67 +0,0 @@ -package apibstore - -import ( - "context" - - blocks "github.com/ipfs/go-block-format" - "github.com/ipfs/go-cid" - blockstore "github.com/ipfs/go-ipfs-blockstore" - "golang.org/x/xerrors" -) - -type ChainIO interface { - ChainReadObj(context.Context, cid.Cid) ([]byte, error) - ChainHasObj(context.Context, cid.Cid) (bool, error) -} - -type apiBStore struct { - api ChainIO -} - -func NewAPIBlockstore(cio ChainIO) blockstore.Blockstore { - return &apiBStore{ - api: cio, - } -} - -func (a *apiBStore) DeleteBlock(cid.Cid) error { - return xerrors.New("not supported") -} - -func (a *apiBStore) Has(c cid.Cid) (bool, error) { - return a.api.ChainHasObj(context.TODO(), c) -} - -func (a *apiBStore) Get(c cid.Cid) (blocks.Block, error) { - bb, err := a.api.ChainReadObj(context.TODO(), c) - if err != nil { - return nil, err - } - return blocks.NewBlockWithCid(bb, c) -} - -func (a *apiBStore) GetSize(c cid.Cid) (int, error) { - bb, err := a.api.ChainReadObj(context.TODO(), c) - if err != nil { - return 0, err - } - return len(bb), nil -} - -func (a *apiBStore) Put(blocks.Block) error { - return xerrors.New("not supported") -} - -func (a *apiBStore) PutMany([]blocks.Block) error { - return xerrors.New("not supported") -} - -func (a *apiBStore) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { - return nil, xerrors.New("not supported") -} - -func (a *apiBStore) HashOnRead(enabled bool) { - return -} - -var _ blockstore.Blockstore = &apiBStore{} diff --git a/api/apistruct/permissioned.go b/api/apistruct/permissioned.go deleted file mode 100644 index 8ffc49bf9..000000000 --- a/api/apistruct/permissioned.go +++ /dev/null @@ -1,38 +0,0 @@ -package apistruct - -import ( - "github.com/filecoin-project/go-jsonrpc/auth" - "github.com/filecoin-project/lotus/api" -) - -const ( - // When changing these, update docs/API.md too - - PermRead auth.Permission = "read" // default - PermWrite auth.Permission = "write" - PermSign auth.Permission = "sign" // Use wallet keys for signing - PermAdmin auth.Permission = "admin" // Manage permissions -) - -var AllPermissions = []auth.Permission{PermRead, PermWrite, PermSign, PermAdmin} -var DefaultPerms = []auth.Permission{PermRead} - -func PermissionedStorMinerAPI(a api.StorageMiner) api.StorageMiner { - var out StorageMinerStruct - auth.PermissionedProxy(AllPermissions, DefaultPerms, a, &out.Internal) - auth.PermissionedProxy(AllPermissions, DefaultPerms, a, &out.CommonStruct.Internal) - return &out -} - -func PermissionedFullAPI(a api.FullNode) api.FullNode { - var out FullNodeStruct - auth.PermissionedProxy(AllPermissions, DefaultPerms, a, &out.Internal) - auth.PermissionedProxy(AllPermissions, DefaultPerms, a, &out.CommonStruct.Internal) - return &out -} - -func PermissionedWorkerAPI(a api.WorkerApi) api.WorkerApi { - var out WorkerStruct - auth.PermissionedProxy(AllPermissions, DefaultPerms, a, &out.Internal) - return &out -} diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go deleted file mode 100644 index 38fd89279..000000000 --- a/api/apistruct/struct.go +++ /dev/null @@ -1,879 +0,0 @@ -package apistruct - -import ( - "context" - - "github.com/ipfs/go-cid" - "github.com/libp2p/go-libp2p-core/network" - "github.com/libp2p/go-libp2p-core/peer" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-fil-markets/storagemarket" - "github.com/filecoin-project/go-jsonrpc/auth" - - "github.com/filecoin-project/sector-storage/sealtasks" - "github.com/filecoin-project/sector-storage/stores" - "github.com/filecoin-project/sector-storage/storiface" - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/abi/big" - "github.com/filecoin-project/specs-actors/actors/builtin/miner" - "github.com/filecoin-project/specs-actors/actors/builtin/paych" - "github.com/filecoin-project/specs-actors/actors/crypto" - "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/node/modules/dtypes" -) - -// All permissions are listed in permissioned.go -var _ = AllPermissions - -type CommonStruct struct { - Internal struct { - AuthVerify func(ctx context.Context, token string) ([]auth.Permission, error) `perm:"read"` - AuthNew func(ctx context.Context, perms []auth.Permission) ([]byte, error) `perm:"admin"` - - NetConnectedness func(context.Context, peer.ID) (network.Connectedness, error) `perm:"read"` - NetPeers func(context.Context) ([]peer.AddrInfo, error) `perm:"read"` - NetConnect func(context.Context, peer.AddrInfo) error `perm:"write"` - NetAddrsListen func(context.Context) (peer.AddrInfo, error) `perm:"read"` - NetDisconnect func(context.Context, peer.ID) error `perm:"write"` - NetFindPeer func(context.Context, peer.ID) (peer.AddrInfo, error) `perm:"read"` - - ID func(context.Context) (peer.ID, error) `perm:"read"` - Version func(context.Context) (api.Version, error) `perm:"read"` - - LogList func(context.Context) ([]string, error) `perm:"write"` - LogSetLevel func(context.Context, string, string) error `perm:"write"` - - Shutdown func(context.Context) error `perm:"admin"` - } -} - -// FullNodeStruct implements API passing calls to user-provided function values. -type FullNodeStruct struct { - CommonStruct - - Internal struct { - ChainNotify func(context.Context) (<-chan []*api.HeadChange, error) `perm:"read"` - ChainHead func(context.Context) (*types.TipSet, error) `perm:"read"` - ChainGetRandomness func(context.Context, types.TipSetKey, crypto.DomainSeparationTag, abi.ChainEpoch, []byte) (abi.Randomness, error) `perm:"read"` - ChainGetBlock func(context.Context, cid.Cid) (*types.BlockHeader, error) `perm:"read"` - ChainGetTipSet func(context.Context, types.TipSetKey) (*types.TipSet, error) `perm:"read"` - ChainGetBlockMessages func(context.Context, cid.Cid) (*api.BlockMessages, error) `perm:"read"` - ChainGetParentReceipts func(context.Context, cid.Cid) ([]*types.MessageReceipt, error) `perm:"read"` - ChainGetParentMessages func(context.Context, cid.Cid) ([]api.Message, error) `perm:"read"` - ChainGetTipSetByHeight func(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error) `perm:"read"` - ChainReadObj func(context.Context, cid.Cid) ([]byte, error) `perm:"read"` - ChainHasObj func(context.Context, cid.Cid) (bool, error) `perm:"read"` - ChainStatObj func(context.Context, cid.Cid, cid.Cid) (api.ObjStat, error) `perm:"read"` - ChainSetHead func(context.Context, types.TipSetKey) error `perm:"admin"` - ChainGetGenesis func(context.Context) (*types.TipSet, error) `perm:"read"` - ChainTipSetWeight func(context.Context, types.TipSetKey) (types.BigInt, error) `perm:"read"` - ChainGetNode func(ctx context.Context, p string) (*api.IpldObject, error) `perm:"read"` - ChainGetMessage func(context.Context, cid.Cid) (*types.Message, error) `perm:"read"` - ChainGetPath func(context.Context, types.TipSetKey, types.TipSetKey) ([]*api.HeadChange, error) `perm:"read"` - ChainExport func(context.Context, types.TipSetKey) (<-chan []byte, error) `perm:"read"` - - SyncState func(context.Context) (*api.SyncState, error) `perm:"read"` - SyncSubmitBlock func(ctx context.Context, blk *types.BlockMsg) error `perm:"write"` - SyncIncomingBlocks func(ctx context.Context) (<-chan *types.BlockHeader, error) `perm:"read"` - SyncMarkBad func(ctx context.Context, bcid cid.Cid) error `perm:"admin"` - SyncCheckBad func(ctx context.Context, bcid cid.Cid) (string, error) `perm:"read"` - - MpoolPending func(context.Context, types.TipSetKey) ([]*types.SignedMessage, error) `perm:"read"` - MpoolPush func(context.Context, *types.SignedMessage) (cid.Cid, error) `perm:"write"` - MpoolPushMessage func(context.Context, *types.Message) (*types.SignedMessage, error) `perm:"sign"` - MpoolGetNonce func(context.Context, address.Address) (uint64, error) `perm:"read"` - MpoolSub func(context.Context) (<-chan api.MpoolUpdate, error) `perm:"read"` - MpoolEstimateGasPrice func(context.Context, uint64, address.Address, int64, types.TipSetKey) (types.BigInt, error) `perm:"read"` - - MinerGetBaseInfo func(context.Context, address.Address, abi.ChainEpoch, types.TipSetKey) (*api.MiningBaseInfo, error) `perm:"read"` - MinerCreateBlock func(context.Context, *api.BlockTemplate) (*types.BlockMsg, error) `perm:"write"` - - WalletNew func(context.Context, crypto.SigType) (address.Address, error) `perm:"write"` - WalletHas func(context.Context, address.Address) (bool, error) `perm:"write"` - WalletList func(context.Context) ([]address.Address, error) `perm:"write"` - WalletBalance func(context.Context, address.Address) (types.BigInt, error) `perm:"read"` - WalletSign func(context.Context, address.Address, []byte) (*crypto.Signature, error) `perm:"sign"` - WalletSignMessage func(context.Context, address.Address, *types.Message) (*types.SignedMessage, error) `perm:"sign"` - WalletVerify func(context.Context, address.Address, []byte, *crypto.Signature) bool `perm:"read"` - WalletDefaultAddress func(context.Context) (address.Address, error) `perm:"write"` - WalletSetDefault func(context.Context, address.Address) error `perm:"admin"` - WalletExport func(context.Context, address.Address) (*types.KeyInfo, error) `perm:"admin"` - WalletImport func(context.Context, *types.KeyInfo) (address.Address, error) `perm:"admin"` - - ClientImport func(ctx context.Context, ref api.FileRef) (cid.Cid, error) `perm:"admin"` - ClientListImports func(ctx context.Context) ([]api.Import, error) `perm:"write"` - ClientHasLocal func(ctx context.Context, root cid.Cid) (bool, error) `perm:"write"` - ClientFindData func(ctx context.Context, root cid.Cid) ([]api.QueryOffer, error) `perm:"read"` - ClientStartDeal func(ctx context.Context, params *api.StartDealParams) (*cid.Cid, error) `perm:"admin"` - ClientGetDealInfo func(context.Context, cid.Cid) (*api.DealInfo, error) `perm:"read"` - ClientListDeals func(ctx context.Context) ([]api.DealInfo, error) `perm:"write"` - ClientRetrieve func(ctx context.Context, order api.RetrievalOrder, ref *api.FileRef) error `perm:"admin"` - ClientQueryAsk func(ctx context.Context, p peer.ID, miner address.Address) (*storagemarket.SignedStorageAsk, error) `perm:"read"` - ClientCalcCommP func(ctx context.Context, inpath string, miner address.Address) (*api.CommPRet, error) `perm:"read"` - ClientGenCar func(ctx context.Context, ref api.FileRef, outpath string) error `perm:"write"` - - StateNetworkName func(context.Context) (dtypes.NetworkName, error) `perm:"read"` - StateMinerSectors func(context.Context, address.Address, *abi.BitField, bool, types.TipSetKey) ([]*api.ChainSectorInfo, error) `perm:"read"` - StateMinerProvingSet func(context.Context, address.Address, types.TipSetKey) ([]*api.ChainSectorInfo, error) `perm:"read"` - StateMinerProvingDeadline func(context.Context, address.Address, types.TipSetKey) (*miner.DeadlineInfo, error) `perm:"read"` - StateMinerPower func(context.Context, address.Address, types.TipSetKey) (*api.MinerPower, error) `perm:"read"` - StateMinerInfo func(context.Context, address.Address, types.TipSetKey) (miner.MinerInfo, error) `perm:"read"` - StateMinerDeadlines func(context.Context, address.Address, types.TipSetKey) (*miner.Deadlines, error) `perm:"read"` - StateMinerFaults func(context.Context, address.Address, types.TipSetKey) (*abi.BitField, error) `perm:"read"` - StateAllMinerFaults func(context.Context, abi.ChainEpoch, types.TipSetKey) ([]*api.Fault, error) `perm:"read"` - StateMinerRecoveries func(context.Context, address.Address, types.TipSetKey) (*abi.BitField, error) `perm:"read"` - StateMinerInitialPledgeCollateral func(context.Context, address.Address, abi.SectorNumber, types.TipSetKey) (types.BigInt, error) `perm:"read"` - StateMinerAvailableBalance func(context.Context, address.Address, types.TipSetKey) (types.BigInt, error) `perm:"read"` - StateSectorPreCommitInfo func(context.Context, address.Address, abi.SectorNumber, types.TipSetKey) (miner.SectorPreCommitOnChainInfo, error) `perm:"read"` - StateCall func(context.Context, *types.Message, types.TipSetKey) (*api.InvocResult, error) `perm:"read"` - StateReplay func(context.Context, types.TipSetKey, cid.Cid) (*api.InvocResult, error) `perm:"read"` - StateGetActor func(context.Context, address.Address, types.TipSetKey) (*types.Actor, error) `perm:"read"` - StateReadState func(context.Context, *types.Actor, types.TipSetKey) (*api.ActorState, error) `perm:"read"` - StatePledgeCollateral func(context.Context, types.TipSetKey) (types.BigInt, error) `perm:"read"` - StateWaitMsg func(context.Context, cid.Cid) (*api.MsgLookup, error) `perm:"read"` - StateSearchMsg func(context.Context, cid.Cid) (*api.MsgLookup, error) `perm:"read"` - StateListMiners func(context.Context, types.TipSetKey) ([]address.Address, error) `perm:"read"` - StateListActors func(context.Context, types.TipSetKey) ([]address.Address, error) `perm:"read"` - StateMarketBalance func(context.Context, address.Address, types.TipSetKey) (api.MarketBalance, error) `perm:"read"` - StateMarketParticipants func(context.Context, types.TipSetKey) (map[string]api.MarketBalance, error) `perm:"read"` - StateMarketDeals func(context.Context, types.TipSetKey) (map[string]api.MarketDeal, error) `perm:"read"` - StateMarketStorageDeal func(context.Context, abi.DealID, types.TipSetKey) (*api.MarketDeal, error) `perm:"read"` - StateLookupID func(ctx context.Context, addr address.Address, tsk types.TipSetKey) (address.Address, error) `perm:"read"` - StateAccountKey func(context.Context, address.Address, types.TipSetKey) (address.Address, error) `perm:"read"` - StateChangedActors func(context.Context, cid.Cid, cid.Cid) (map[string]types.Actor, error) `perm:"read"` - StateGetReceipt func(context.Context, cid.Cid, types.TipSetKey) (*types.MessageReceipt, error) `perm:"read"` - StateMinerSectorCount func(context.Context, address.Address, types.TipSetKey) (api.MinerSectors, error) `perm:"read"` - StateListMessages func(ctx context.Context, match *types.Message, tsk types.TipSetKey, toht abi.ChainEpoch) ([]cid.Cid, error) `perm:"read"` - StateCompute func(context.Context, abi.ChainEpoch, []*types.Message, types.TipSetKey) (*api.ComputeStateOutput, error) `perm:"read"` - - MsigGetAvailableBalance func(context.Context, address.Address, types.TipSetKey) (types.BigInt, error) `perm:"read"` - MsigCreate func(context.Context, int64, []address.Address, types.BigInt, address.Address, types.BigInt) (cid.Cid, error) `perm:"sign"` - MsigPropose func(context.Context, address.Address, address.Address, types.BigInt, address.Address, uint64, []byte) (cid.Cid, error) `perm:"sign"` - MsigApprove func(context.Context, address.Address, uint64, address.Address, address.Address, types.BigInt, address.Address, uint64, []byte) (cid.Cid, error) `perm:"sign"` - MsigCancel func(context.Context, address.Address, uint64, address.Address, address.Address, types.BigInt, address.Address, uint64, []byte) (cid.Cid, error) `perm:"sign"` - - MarketEnsureAvailable func(context.Context, address.Address, address.Address, types.BigInt) (cid.Cid, error) `perm:"sign"` - - PaychGet func(ctx context.Context, from, to address.Address, ensureFunds types.BigInt) (*api.ChannelInfo, error) `perm:"sign"` - PaychList func(context.Context) ([]address.Address, error) `perm:"read"` - PaychStatus func(context.Context, address.Address) (*api.PaychStatus, error) `perm:"read"` - PaychClose func(context.Context, address.Address) (cid.Cid, error) `perm:"sign"` - PaychAllocateLane func(context.Context, address.Address) (uint64, error) `perm:"sign"` - PaychNewPayment func(ctx context.Context, from, to address.Address, vouchers []api.VoucherSpec) (*api.PaymentInfo, error) `perm:"sign"` - PaychVoucherCheck func(context.Context, *paych.SignedVoucher) error `perm:"read"` - PaychVoucherCheckValid func(context.Context, address.Address, *paych.SignedVoucher) error `perm:"read"` - PaychVoucherCheckSpendable func(context.Context, address.Address, *paych.SignedVoucher, []byte, []byte) (bool, error) `perm:"read"` - PaychVoucherAdd func(context.Context, address.Address, *paych.SignedVoucher, []byte, types.BigInt) (types.BigInt, error) `perm:"write"` - PaychVoucherCreate func(context.Context, address.Address, big.Int, uint64) (*paych.SignedVoucher, error) `perm:"sign"` - PaychVoucherList func(context.Context, address.Address) ([]*paych.SignedVoucher, error) `perm:"write"` - PaychVoucherSubmit func(context.Context, address.Address, *paych.SignedVoucher) (cid.Cid, error) `perm:"sign"` - } -} - -func (c *FullNodeStruct) StateMinerSectorCount(ctx context.Context, addr address.Address, tsk types.TipSetKey) (api.MinerSectors, error) { - return c.Internal.StateMinerSectorCount(ctx, addr, tsk) -} - -type StorageMinerStruct struct { - CommonStruct - - Internal struct { - ActorAddress func(context.Context) (address.Address, error) `perm:"read"` - ActorSectorSize func(context.Context, address.Address) (abi.SectorSize, error) `perm:"read"` - - MiningBase func(context.Context) (*types.TipSet, error) `perm:"read"` - - MarketImportDealData func(context.Context, cid.Cid, string) error `perm:"write"` - MarketListDeals func(ctx context.Context) ([]storagemarket.StorageDeal, error) `perm:"read"` - MarketListIncompleteDeals func(ctx context.Context) ([]storagemarket.MinerDeal, error) `perm:"read"` - MarketSetPrice func(context.Context, types.BigInt) error `perm:"admin"` - - PledgeSector func(context.Context) error `perm:"write"` - - SectorsStatus func(context.Context, abi.SectorNumber) (api.SectorInfo, error) `perm:"read"` - SectorsList func(context.Context) ([]abi.SectorNumber, error) `perm:"read"` - SectorsRefs func(context.Context) (map[string][]api.SealedRef, error) `perm:"read"` - SectorsUpdate func(context.Context, abi.SectorNumber, api.SectorState) error `perm:"write"` - - WorkerConnect func(context.Context, string) error `perm:"admin"` // TODO: worker perm - WorkerStats func(context.Context) (map[uint64]storiface.WorkerStats, error) `perm:"admin"` - - StorageList func(context.Context) (map[stores.ID][]stores.Decl, error) `perm:"admin"` - StorageLocal func(context.Context) (map[stores.ID]string, error) `perm:"admin"` - StorageStat func(context.Context, stores.ID) (stores.FsStat, error) `perm:"admin"` - StorageAttach func(context.Context, stores.StorageInfo, stores.FsStat) error `perm:"admin"` - StorageDeclareSector func(context.Context, stores.ID, abi.SectorID, stores.SectorFileType) error `perm:"admin"` - StorageDropSector func(context.Context, stores.ID, abi.SectorID, stores.SectorFileType) error `perm:"admin"` - StorageFindSector func(context.Context, abi.SectorID, stores.SectorFileType, bool) ([]stores.StorageInfo, error) `perm:"admin"` - StorageInfo func(context.Context, stores.ID) (stores.StorageInfo, error) `perm:"admin"` - StorageBestAlloc func(ctx context.Context, allocate stores.SectorFileType, spt abi.RegisteredProof, sealing bool) ([]stores.StorageInfo, error) `perm:"admin"` - StorageReportHealth func(ctx context.Context, id stores.ID, report stores.HealthReport) error `perm:"admin"` - - DealsImportData func(ctx context.Context, dealPropCid cid.Cid, file string) error `perm:"write"` - DealsList func(ctx context.Context) ([]storagemarket.StorageDeal, error) `perm:"read"` - - StorageAddLocal func(ctx context.Context, path string) error `perm:"admin"` - } -} - -type WorkerStruct struct { - Internal struct { - // TODO: lower perms - - Version func(context.Context) (build.Version, error) `perm:"admin"` - - TaskTypes func(context.Context) (map[sealtasks.TaskType]struct{}, error) `perm:"admin"` - Paths func(context.Context) ([]stores.StoragePath, error) `perm:"admin"` - Info func(context.Context) (storiface.WorkerInfo, error) `perm:"admin"` - - SealPreCommit1 func(ctx context.Context, sector abi.SectorID, ticket abi.SealRandomness, pieces []abi.PieceInfo) (storage.PreCommit1Out, error) `perm:"admin"` - SealPreCommit2 func(context.Context, abi.SectorID, storage.PreCommit1Out) (cids storage.SectorCids, err error) `perm:"admin"` - SealCommit1 func(ctx context.Context, sector abi.SectorID, ticket abi.SealRandomness, seed abi.InteractiveSealRandomness, pieces []abi.PieceInfo, cids storage.SectorCids) (storage.Commit1Out, error) `perm:"admin"` - SealCommit2 func(context.Context, abi.SectorID, storage.Commit1Out) (storage.Proof, error) `perm:"admin"` - FinalizeSector func(context.Context, abi.SectorID) error `perm:"admin"` - - Fetch func(context.Context, abi.SectorID, stores.SectorFileType, bool) error `perm:"admin"` - - Closing func(context.Context) (<-chan struct{}, error) `perm:"admin"` - } -} - -// CommonStruct - -func (c *CommonStruct) AuthVerify(ctx context.Context, token string) ([]auth.Permission, error) { - return c.Internal.AuthVerify(ctx, token) -} - -func (c *CommonStruct) AuthNew(ctx context.Context, perms []auth.Permission) ([]byte, error) { - return c.Internal.AuthNew(ctx, perms) -} - -func (c *CommonStruct) NetConnectedness(ctx context.Context, pid peer.ID) (network.Connectedness, error) { - return c.Internal.NetConnectedness(ctx, pid) -} - -func (c *CommonStruct) NetPeers(ctx context.Context) ([]peer.AddrInfo, error) { - return c.Internal.NetPeers(ctx) -} - -func (c *CommonStruct) NetConnect(ctx context.Context, p peer.AddrInfo) error { - return c.Internal.NetConnect(ctx, p) -} - -func (c *CommonStruct) NetAddrsListen(ctx context.Context) (peer.AddrInfo, error) { - return c.Internal.NetAddrsListen(ctx) -} - -func (c *CommonStruct) NetDisconnect(ctx context.Context, p peer.ID) error { - return c.Internal.NetDisconnect(ctx, p) -} - -func (c *CommonStruct) NetFindPeer(ctx context.Context, p peer.ID) (peer.AddrInfo, error) { - return c.Internal.NetFindPeer(ctx, p) -} - -// ID implements API.ID -func (c *CommonStruct) ID(ctx context.Context) (peer.ID, error) { - return c.Internal.ID(ctx) -} - -// Version implements API.Version -func (c *CommonStruct) Version(ctx context.Context) (api.Version, error) { - return c.Internal.Version(ctx) -} - -func (c *CommonStruct) LogList(ctx context.Context) ([]string, error) { - return c.Internal.LogList(ctx) -} - -func (c *CommonStruct) LogSetLevel(ctx context.Context, group, level string) error { - return c.Internal.LogSetLevel(ctx, group, level) -} - -func (c *CommonStruct) Shutdown(ctx context.Context) error { - return c.Internal.Shutdown(ctx) -} - -// FullNodeStruct - -func (c *FullNodeStruct) ClientListImports(ctx context.Context) ([]api.Import, error) { - return c.Internal.ClientListImports(ctx) -} - -func (c *FullNodeStruct) ClientImport(ctx context.Context, ref api.FileRef) (cid.Cid, error) { - return c.Internal.ClientImport(ctx, ref) -} - -func (c *FullNodeStruct) ClientHasLocal(ctx context.Context, root cid.Cid) (bool, error) { - return c.Internal.ClientHasLocal(ctx, root) -} - -func (c *FullNodeStruct) ClientFindData(ctx context.Context, root cid.Cid) ([]api.QueryOffer, error) { - return c.Internal.ClientFindData(ctx, root) -} - -func (c *FullNodeStruct) ClientStartDeal(ctx context.Context, params *api.StartDealParams) (*cid.Cid, error) { - return c.Internal.ClientStartDeal(ctx, params) -} -func (c *FullNodeStruct) ClientGetDealInfo(ctx context.Context, deal cid.Cid) (*api.DealInfo, error) { - return c.Internal.ClientGetDealInfo(ctx, deal) -} - -func (c *FullNodeStruct) ClientListDeals(ctx context.Context) ([]api.DealInfo, error) { - return c.Internal.ClientListDeals(ctx) -} - -func (c *FullNodeStruct) ClientRetrieve(ctx context.Context, order api.RetrievalOrder, ref *api.FileRef) error { - return c.Internal.ClientRetrieve(ctx, order, ref) -} - -func (c *FullNodeStruct) ClientQueryAsk(ctx context.Context, p peer.ID, miner address.Address) (*storagemarket.SignedStorageAsk, error) { - return c.Internal.ClientQueryAsk(ctx, p, miner) -} -func (c *FullNodeStruct) ClientCalcCommP(ctx context.Context, inpath string, miner address.Address) (*api.CommPRet, error) { - return c.Internal.ClientCalcCommP(ctx, inpath, miner) -} - -func (c *FullNodeStruct) ClientGenCar(ctx context.Context, ref api.FileRef, outpath string) error { - return c.Internal.ClientGenCar(ctx, ref, outpath) -} - -func (c *FullNodeStruct) MpoolPending(ctx context.Context, tsk types.TipSetKey) ([]*types.SignedMessage, error) { - return c.Internal.MpoolPending(ctx, tsk) -} - -func (c *FullNodeStruct) MpoolPush(ctx context.Context, smsg *types.SignedMessage) (cid.Cid, error) { - return c.Internal.MpoolPush(ctx, smsg) -} - -func (c *FullNodeStruct) MpoolPushMessage(ctx context.Context, msg *types.Message) (*types.SignedMessage, error) { - return c.Internal.MpoolPushMessage(ctx, msg) -} - -func (c *FullNodeStruct) MpoolSub(ctx context.Context) (<-chan api.MpoolUpdate, error) { - return c.Internal.MpoolSub(ctx) -} - -func (c *FullNodeStruct) MpoolEstimateGasPrice(ctx context.Context, nblocksincl uint64, sender address.Address, limit int64, tsk types.TipSetKey) (types.BigInt, error) { - return c.Internal.MpoolEstimateGasPrice(ctx, nblocksincl, sender, limit, tsk) -} - -func (c *FullNodeStruct) MinerGetBaseInfo(ctx context.Context, maddr address.Address, epoch abi.ChainEpoch, tsk types.TipSetKey) (*api.MiningBaseInfo, error) { - return c.Internal.MinerGetBaseInfo(ctx, maddr, epoch, tsk) -} - -func (c *FullNodeStruct) MinerCreateBlock(ctx context.Context, bt *api.BlockTemplate) (*types.BlockMsg, error) { - return c.Internal.MinerCreateBlock(ctx, bt) -} - -func (c *FullNodeStruct) ChainHead(ctx context.Context) (*types.TipSet, error) { - return c.Internal.ChainHead(ctx) -} - -func (c *FullNodeStruct) ChainGetRandomness(ctx context.Context, tsk types.TipSetKey, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) (abi.Randomness, error) { - return c.Internal.ChainGetRandomness(ctx, tsk, personalization, randEpoch, entropy) -} - -func (c *FullNodeStruct) ChainGetTipSetByHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error) { - return c.Internal.ChainGetTipSetByHeight(ctx, h, tsk) -} - -func (c *FullNodeStruct) WalletNew(ctx context.Context, typ crypto.SigType) (address.Address, error) { - return c.Internal.WalletNew(ctx, typ) -} - -func (c *FullNodeStruct) WalletHas(ctx context.Context, addr address.Address) (bool, error) { - return c.Internal.WalletHas(ctx, addr) -} - -func (c *FullNodeStruct) WalletList(ctx context.Context) ([]address.Address, error) { - return c.Internal.WalletList(ctx) -} - -func (c *FullNodeStruct) WalletBalance(ctx context.Context, a address.Address) (types.BigInt, error) { - return c.Internal.WalletBalance(ctx, a) -} - -func (c *FullNodeStruct) WalletSign(ctx context.Context, k address.Address, msg []byte) (*crypto.Signature, error) { - return c.Internal.WalletSign(ctx, k, msg) -} - -func (c *FullNodeStruct) WalletSignMessage(ctx context.Context, k address.Address, msg *types.Message) (*types.SignedMessage, error) { - return c.Internal.WalletSignMessage(ctx, k, msg) -} - -func (c *FullNodeStruct) WalletVerify(ctx context.Context, k address.Address, msg []byte, sig *crypto.Signature) bool { - return c.Internal.WalletVerify(ctx, k, msg, sig) -} - -func (c *FullNodeStruct) WalletDefaultAddress(ctx context.Context) (address.Address, error) { - return c.Internal.WalletDefaultAddress(ctx) -} - -func (c *FullNodeStruct) WalletSetDefault(ctx context.Context, a address.Address) error { - return c.Internal.WalletSetDefault(ctx, a) -} - -func (c *FullNodeStruct) WalletExport(ctx context.Context, a address.Address) (*types.KeyInfo, error) { - return c.Internal.WalletExport(ctx, a) -} - -func (c *FullNodeStruct) WalletImport(ctx context.Context, ki *types.KeyInfo) (address.Address, error) { - return c.Internal.WalletImport(ctx, ki) -} - -func (c *FullNodeStruct) MpoolGetNonce(ctx context.Context, addr address.Address) (uint64, error) { - return c.Internal.MpoolGetNonce(ctx, addr) -} - -func (c *FullNodeStruct) ChainGetBlock(ctx context.Context, b cid.Cid) (*types.BlockHeader, error) { - return c.Internal.ChainGetBlock(ctx, b) -} - -func (c *FullNodeStruct) ChainGetTipSet(ctx context.Context, key types.TipSetKey) (*types.TipSet, error) { - return c.Internal.ChainGetTipSet(ctx, key) -} - -func (c *FullNodeStruct) ChainGetBlockMessages(ctx context.Context, b cid.Cid) (*api.BlockMessages, error) { - return c.Internal.ChainGetBlockMessages(ctx, b) -} - -func (c *FullNodeStruct) ChainGetParentReceipts(ctx context.Context, b cid.Cid) ([]*types.MessageReceipt, error) { - return c.Internal.ChainGetParentReceipts(ctx, b) -} - -func (c *FullNodeStruct) ChainGetParentMessages(ctx context.Context, b cid.Cid) ([]api.Message, error) { - return c.Internal.ChainGetParentMessages(ctx, b) -} - -func (c *FullNodeStruct) ChainNotify(ctx context.Context) (<-chan []*api.HeadChange, error) { - return c.Internal.ChainNotify(ctx) -} - -func (c *FullNodeStruct) ChainReadObj(ctx context.Context, obj cid.Cid) ([]byte, error) { - return c.Internal.ChainReadObj(ctx, obj) -} - -func (c *FullNodeStruct) ChainHasObj(ctx context.Context, o cid.Cid) (bool, error) { - return c.Internal.ChainHasObj(ctx, o) -} - -func (c *FullNodeStruct) ChainStatObj(ctx context.Context, obj, base cid.Cid) (api.ObjStat, error) { - return c.Internal.ChainStatObj(ctx, obj, base) -} - -func (c *FullNodeStruct) ChainSetHead(ctx context.Context, tsk types.TipSetKey) error { - return c.Internal.ChainSetHead(ctx, tsk) -} - -func (c *FullNodeStruct) ChainGetGenesis(ctx context.Context) (*types.TipSet, error) { - return c.Internal.ChainGetGenesis(ctx) -} - -func (c *FullNodeStruct) ChainTipSetWeight(ctx context.Context, tsk types.TipSetKey) (types.BigInt, error) { - return c.Internal.ChainTipSetWeight(ctx, tsk) -} - -func (c *FullNodeStruct) ChainGetNode(ctx context.Context, p string) (*api.IpldObject, error) { - return c.Internal.ChainGetNode(ctx, p) -} - -func (c *FullNodeStruct) ChainGetMessage(ctx context.Context, mc cid.Cid) (*types.Message, error) { - return c.Internal.ChainGetMessage(ctx, mc) -} - -func (c *FullNodeStruct) ChainGetPath(ctx context.Context, from types.TipSetKey, to types.TipSetKey) ([]*api.HeadChange, error) { - return c.Internal.ChainGetPath(ctx, from, to) -} - -func (c *FullNodeStruct) ChainExport(ctx context.Context, tsk types.TipSetKey) (<-chan []byte, error) { - return c.Internal.ChainExport(ctx, tsk) -} - -func (c *FullNodeStruct) SyncState(ctx context.Context) (*api.SyncState, error) { - return c.Internal.SyncState(ctx) -} - -func (c *FullNodeStruct) SyncSubmitBlock(ctx context.Context, blk *types.BlockMsg) error { - return c.Internal.SyncSubmitBlock(ctx, blk) -} - -func (c *FullNodeStruct) SyncIncomingBlocks(ctx context.Context) (<-chan *types.BlockHeader, error) { - return c.Internal.SyncIncomingBlocks(ctx) -} - -func (c *FullNodeStruct) SyncMarkBad(ctx context.Context, bcid cid.Cid) error { - return c.Internal.SyncMarkBad(ctx, bcid) -} - -func (c *FullNodeStruct) SyncCheckBad(ctx context.Context, bcid cid.Cid) (string, error) { - return c.Internal.SyncCheckBad(ctx, bcid) -} - -func (c *FullNodeStruct) StateNetworkName(ctx context.Context) (dtypes.NetworkName, error) { - return c.Internal.StateNetworkName(ctx) -} - -func (c *FullNodeStruct) StateMinerSectors(ctx context.Context, addr address.Address, filter *abi.BitField, filterOut bool, tsk types.TipSetKey) ([]*api.ChainSectorInfo, error) { - return c.Internal.StateMinerSectors(ctx, addr, filter, filterOut, tsk) -} - -func (c *FullNodeStruct) StateMinerProvingSet(ctx context.Context, addr address.Address, tsk types.TipSetKey) ([]*api.ChainSectorInfo, error) { - return c.Internal.StateMinerProvingSet(ctx, addr, tsk) -} - -func (c *FullNodeStruct) StateMinerProvingDeadline(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*miner.DeadlineInfo, error) { - return c.Internal.StateMinerProvingDeadline(ctx, addr, tsk) -} - -func (c *FullNodeStruct) StateMinerPower(ctx context.Context, a address.Address, tsk types.TipSetKey) (*api.MinerPower, error) { - return c.Internal.StateMinerPower(ctx, a, tsk) -} - -func (c *FullNodeStruct) StateMinerInfo(ctx context.Context, actor address.Address, tsk types.TipSetKey) (miner.MinerInfo, error) { - return c.Internal.StateMinerInfo(ctx, actor, tsk) -} - -func (c *FullNodeStruct) StateMinerDeadlines(ctx context.Context, m address.Address, tsk types.TipSetKey) (*miner.Deadlines, error) { - return c.Internal.StateMinerDeadlines(ctx, m, tsk) -} - -func (c *FullNodeStruct) StateMinerFaults(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*abi.BitField, error) { - return c.Internal.StateMinerFaults(ctx, actor, tsk) -} - -func (c *FullNodeStruct) StateAllMinerFaults(ctx context.Context, cutoff abi.ChainEpoch, endTsk types.TipSetKey) ([]*api.Fault, error) { - return c.Internal.StateAllMinerFaults(ctx, cutoff, endTsk) -} - -func (c *FullNodeStruct) StateMinerRecoveries(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*abi.BitField, error) { - return c.Internal.StateMinerRecoveries(ctx, actor, tsk) -} - -func (c *FullNodeStruct) StateMinerInitialPledgeCollateral(ctx context.Context, maddr address.Address, snum abi.SectorNumber, tsk types.TipSetKey) (types.BigInt, error) { - return c.Internal.StateMinerInitialPledgeCollateral(ctx, maddr, snum, tsk) -} - -func (c *FullNodeStruct) StateMinerAvailableBalance(ctx context.Context, maddr address.Address, tsk types.TipSetKey) (types.BigInt, error) { - return c.Internal.StateMinerAvailableBalance(ctx, maddr, tsk) -} - -func (c *FullNodeStruct) StateSectorPreCommitInfo(ctx context.Context, maddr address.Address, n abi.SectorNumber, tsk types.TipSetKey) (miner.SectorPreCommitOnChainInfo, error) { - return c.Internal.StateSectorPreCommitInfo(ctx, maddr, n, tsk) -} - -func (c *FullNodeStruct) StateCall(ctx context.Context, msg *types.Message, tsk types.TipSetKey) (*api.InvocResult, error) { - return c.Internal.StateCall(ctx, msg, tsk) -} - -func (c *FullNodeStruct) StateReplay(ctx context.Context, tsk types.TipSetKey, mc cid.Cid) (*api.InvocResult, error) { - return c.Internal.StateReplay(ctx, tsk, mc) -} - -func (c *FullNodeStruct) StateGetActor(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*types.Actor, error) { - return c.Internal.StateGetActor(ctx, actor, tsk) -} - -func (c *FullNodeStruct) StateReadState(ctx context.Context, act *types.Actor, tsk types.TipSetKey) (*api.ActorState, error) { - return c.Internal.StateReadState(ctx, act, tsk) -} - -func (c *FullNodeStruct) StatePledgeCollateral(ctx context.Context, tsk types.TipSetKey) (types.BigInt, error) { - return c.Internal.StatePledgeCollateral(ctx, tsk) -} - -func (c *FullNodeStruct) StateWaitMsg(ctx context.Context, msgc cid.Cid) (*api.MsgLookup, error) { - return c.Internal.StateWaitMsg(ctx, msgc) -} - -func (c *FullNodeStruct) StateSearchMsg(ctx context.Context, msgc cid.Cid) (*api.MsgLookup, error) { - return c.Internal.StateSearchMsg(ctx, msgc) -} - -func (c *FullNodeStruct) StateListMiners(ctx context.Context, tsk types.TipSetKey) ([]address.Address, error) { - return c.Internal.StateListMiners(ctx, tsk) -} - -func (c *FullNodeStruct) StateListActors(ctx context.Context, tsk types.TipSetKey) ([]address.Address, error) { - return c.Internal.StateListActors(ctx, tsk) -} - -func (c *FullNodeStruct) StateMarketBalance(ctx context.Context, addr address.Address, tsk types.TipSetKey) (api.MarketBalance, error) { - return c.Internal.StateMarketBalance(ctx, addr, tsk) -} - -func (c *FullNodeStruct) StateMarketParticipants(ctx context.Context, tsk types.TipSetKey) (map[string]api.MarketBalance, error) { - return c.Internal.StateMarketParticipants(ctx, tsk) -} - -func (c *FullNodeStruct) StateMarketDeals(ctx context.Context, tsk types.TipSetKey) (map[string]api.MarketDeal, error) { - return c.Internal.StateMarketDeals(ctx, tsk) -} - -func (c *FullNodeStruct) StateMarketStorageDeal(ctx context.Context, dealid abi.DealID, tsk types.TipSetKey) (*api.MarketDeal, error) { - return c.Internal.StateMarketStorageDeal(ctx, dealid, tsk) -} - -func (c *FullNodeStruct) StateLookupID(ctx context.Context, addr address.Address, tsk types.TipSetKey) (address.Address, error) { - return c.Internal.StateLookupID(ctx, addr, tsk) -} - -func (c *FullNodeStruct) StateAccountKey(ctx context.Context, addr address.Address, tsk types.TipSetKey) (address.Address, error) { - return c.Internal.StateAccountKey(ctx, addr, tsk) -} - -func (c *FullNodeStruct) StateChangedActors(ctx context.Context, olnstate cid.Cid, newstate cid.Cid) (map[string]types.Actor, error) { - return c.Internal.StateChangedActors(ctx, olnstate, newstate) -} - -func (c *FullNodeStruct) StateGetReceipt(ctx context.Context, msg cid.Cid, tsk types.TipSetKey) (*types.MessageReceipt, error) { - return c.Internal.StateGetReceipt(ctx, msg, tsk) -} - -func (c *FullNodeStruct) StateListMessages(ctx context.Context, match *types.Message, tsk types.TipSetKey, toht abi.ChainEpoch) ([]cid.Cid, error) { - return c.Internal.StateListMessages(ctx, match, tsk, toht) -} - -func (c *FullNodeStruct) StateCompute(ctx context.Context, height abi.ChainEpoch, msgs []*types.Message, tsk types.TipSetKey) (*api.ComputeStateOutput, error) { - return c.Internal.StateCompute(ctx, height, msgs, tsk) -} - -func (c *FullNodeStruct) MsigGetAvailableBalance(ctx context.Context, a address.Address, tsk types.TipSetKey) (types.BigInt, error) { - return c.Internal.MsigGetAvailableBalance(ctx, a, tsk) -} - -func (c *FullNodeStruct) MsigCreate(ctx context.Context, req int64, addrs []address.Address, val types.BigInt, src address.Address, gp types.BigInt) (cid.Cid, error) { - return c.Internal.MsigCreate(ctx, req, addrs, val, src, gp) -} - -func (c *FullNodeStruct) MsigPropose(ctx context.Context, msig address.Address, to address.Address, amt types.BigInt, src address.Address, method uint64, params []byte) (cid.Cid, error) { - return c.Internal.MsigPropose(ctx, msig, to, amt, src, method, params) -} - -func (c *FullNodeStruct) MsigApprove(ctx context.Context, msig address.Address, txID uint64, proposer address.Address, to address.Address, amt types.BigInt, src address.Address, method uint64, params []byte) (cid.Cid, error) { - return c.Internal.MsigApprove(ctx, msig, txID, proposer, to, amt, src, method, params) -} - -func (c *FullNodeStruct) MsigCancel(ctx context.Context, msig address.Address, txID uint64, proposer address.Address, to address.Address, amt types.BigInt, src address.Address, method uint64, params []byte) (cid.Cid, error) { - return c.Internal.MsigCancel(ctx, msig, txID, proposer, to, amt, src, method, params) -} - -func (c *FullNodeStruct) MarketEnsureAvailable(ctx context.Context, addr, wallet address.Address, amt types.BigInt) (cid.Cid, error) { - return c.Internal.MarketEnsureAvailable(ctx, addr, wallet, amt) -} - -func (c *FullNodeStruct) PaychGet(ctx context.Context, from, to address.Address, ensureFunds types.BigInt) (*api.ChannelInfo, error) { - return c.Internal.PaychGet(ctx, from, to, ensureFunds) -} - -func (c *FullNodeStruct) PaychList(ctx context.Context) ([]address.Address, error) { - return c.Internal.PaychList(ctx) -} - -func (c *FullNodeStruct) PaychStatus(ctx context.Context, pch address.Address) (*api.PaychStatus, error) { - return c.Internal.PaychStatus(ctx, pch) -} - -func (c *FullNodeStruct) PaychVoucherCheckValid(ctx context.Context, addr address.Address, sv *paych.SignedVoucher) error { - return c.Internal.PaychVoucherCheckValid(ctx, addr, sv) -} - -func (c *FullNodeStruct) PaychVoucherCheckSpendable(ctx context.Context, addr address.Address, sv *paych.SignedVoucher, secret []byte, proof []byte) (bool, error) { - return c.Internal.PaychVoucherCheckSpendable(ctx, addr, sv, secret, proof) -} - -func (c *FullNodeStruct) PaychVoucherAdd(ctx context.Context, addr address.Address, sv *paych.SignedVoucher, proof []byte, minDelta types.BigInt) (types.BigInt, error) { - return c.Internal.PaychVoucherAdd(ctx, addr, sv, proof, minDelta) -} - -func (c *FullNodeStruct) PaychVoucherCreate(ctx context.Context, pch address.Address, amt types.BigInt, lane uint64) (*paych.SignedVoucher, error) { - return c.Internal.PaychVoucherCreate(ctx, pch, amt, lane) -} - -func (c *FullNodeStruct) PaychVoucherList(ctx context.Context, pch address.Address) ([]*paych.SignedVoucher, error) { - return c.Internal.PaychVoucherList(ctx, pch) -} - -func (c *FullNodeStruct) PaychClose(ctx context.Context, a address.Address) (cid.Cid, error) { - return c.Internal.PaychClose(ctx, a) -} - -func (c *FullNodeStruct) PaychAllocateLane(ctx context.Context, ch address.Address) (uint64, error) { - return c.Internal.PaychAllocateLane(ctx, ch) -} - -func (c *FullNodeStruct) PaychNewPayment(ctx context.Context, from, to address.Address, vouchers []api.VoucherSpec) (*api.PaymentInfo, error) { - return c.Internal.PaychNewPayment(ctx, from, to, vouchers) -} - -func (c *FullNodeStruct) PaychVoucherSubmit(ctx context.Context, ch address.Address, sv *paych.SignedVoucher) (cid.Cid, error) { - return c.Internal.PaychVoucherSubmit(ctx, ch, sv) -} - -// StorageMinerStruct - -func (c *StorageMinerStruct) ActorAddress(ctx context.Context) (address.Address, error) { - return c.Internal.ActorAddress(ctx) -} - -func (c *StorageMinerStruct) MiningBase(ctx context.Context) (*types.TipSet, error) { - return c.Internal.MiningBase(ctx) -} - -func (c *StorageMinerStruct) ActorSectorSize(ctx context.Context, addr address.Address) (abi.SectorSize, error) { - return c.Internal.ActorSectorSize(ctx, addr) -} - -func (c *StorageMinerStruct) PledgeSector(ctx context.Context) error { - return c.Internal.PledgeSector(ctx) -} - -// Get the status of a given sector by ID -func (c *StorageMinerStruct) SectorsStatus(ctx context.Context, sid abi.SectorNumber) (api.SectorInfo, error) { - return c.Internal.SectorsStatus(ctx, sid) -} - -// List all staged sectors -func (c *StorageMinerStruct) SectorsList(ctx context.Context) ([]abi.SectorNumber, error) { - return c.Internal.SectorsList(ctx) -} - -func (c *StorageMinerStruct) SectorsRefs(ctx context.Context) (map[string][]api.SealedRef, error) { - return c.Internal.SectorsRefs(ctx) -} - -func (c *StorageMinerStruct) SectorsUpdate(ctx context.Context, id abi.SectorNumber, state api.SectorState) error { - return c.Internal.SectorsUpdate(ctx, id, state) -} - -func (c *StorageMinerStruct) WorkerConnect(ctx context.Context, url string) error { - return c.Internal.WorkerConnect(ctx, url) -} - -func (c *StorageMinerStruct) WorkerStats(ctx context.Context) (map[uint64]storiface.WorkerStats, error) { - return c.Internal.WorkerStats(ctx) -} - -func (c *StorageMinerStruct) StorageAttach(ctx context.Context, si stores.StorageInfo, st stores.FsStat) error { - return c.Internal.StorageAttach(ctx, si, st) -} - -func (c *StorageMinerStruct) StorageDeclareSector(ctx context.Context, storageId stores.ID, s abi.SectorID, ft stores.SectorFileType) error { - return c.Internal.StorageDeclareSector(ctx, storageId, s, ft) -} - -func (c *StorageMinerStruct) StorageDropSector(ctx context.Context, storageId stores.ID, s abi.SectorID, ft stores.SectorFileType) error { - return c.Internal.StorageDropSector(ctx, storageId, s, ft) -} - -func (c *StorageMinerStruct) StorageFindSector(ctx context.Context, si abi.SectorID, types stores.SectorFileType, allowFetch bool) ([]stores.StorageInfo, error) { - return c.Internal.StorageFindSector(ctx, si, types, allowFetch) -} - -func (c *StorageMinerStruct) StorageList(ctx context.Context) (map[stores.ID][]stores.Decl, error) { - return c.Internal.StorageList(ctx) -} - -func (c *StorageMinerStruct) StorageLocal(ctx context.Context) (map[stores.ID]string, error) { - return c.Internal.StorageLocal(ctx) -} - -func (c *StorageMinerStruct) StorageStat(ctx context.Context, id stores.ID) (stores.FsStat, error) { - return c.Internal.StorageStat(ctx, id) -} - -func (c *StorageMinerStruct) StorageInfo(ctx context.Context, id stores.ID) (stores.StorageInfo, error) { - return c.Internal.StorageInfo(ctx, id) -} - -func (c *StorageMinerStruct) StorageBestAlloc(ctx context.Context, allocate stores.SectorFileType, spt abi.RegisteredProof, sealing bool) ([]stores.StorageInfo, error) { - return c.Internal.StorageBestAlloc(ctx, allocate, spt, sealing) -} - -func (c *StorageMinerStruct) StorageReportHealth(ctx context.Context, id stores.ID, report stores.HealthReport) error { - return c.Internal.StorageReportHealth(ctx, id, report) -} - -func (c *StorageMinerStruct) MarketImportDealData(ctx context.Context, propcid cid.Cid, path string) error { - return c.Internal.MarketImportDealData(ctx, propcid, path) -} - -func (c *StorageMinerStruct) MarketListDeals(ctx context.Context) ([]storagemarket.StorageDeal, error) { - return c.Internal.MarketListDeals(ctx) -} - -func (c *StorageMinerStruct) MarketListIncompleteDeals(ctx context.Context) ([]storagemarket.MinerDeal, error) { - return c.Internal.MarketListIncompleteDeals(ctx) -} - -func (c *StorageMinerStruct) MarketSetPrice(ctx context.Context, p types.BigInt) error { - return c.Internal.MarketSetPrice(ctx, p) -} - -func (c *StorageMinerStruct) DealsImportData(ctx context.Context, dealPropCid cid.Cid, file string) error { - return c.Internal.DealsImportData(ctx, dealPropCid, file) -} - -func (c *StorageMinerStruct) DealsList(ctx context.Context) ([]storagemarket.StorageDeal, error) { - return c.Internal.DealsList(ctx) -} - -func (c *StorageMinerStruct) StorageAddLocal(ctx context.Context, path string) error { - return c.Internal.StorageAddLocal(ctx, path) -} - -// WorkerStruct - -func (w *WorkerStruct) Version(ctx context.Context) (build.Version, error) { - return w.Internal.Version(ctx) -} - -func (w *WorkerStruct) TaskTypes(ctx context.Context) (map[sealtasks.TaskType]struct{}, error) { - return w.Internal.TaskTypes(ctx) -} - -func (w *WorkerStruct) Paths(ctx context.Context) ([]stores.StoragePath, error) { - return w.Internal.Paths(ctx) -} - -func (w *WorkerStruct) Info(ctx context.Context) (storiface.WorkerInfo, error) { - return w.Internal.Info(ctx) -} - -func (w *WorkerStruct) SealPreCommit1(ctx context.Context, sector abi.SectorID, ticket abi.SealRandomness, pieces []abi.PieceInfo) (storage.PreCommit1Out, error) { - return w.Internal.SealPreCommit1(ctx, sector, ticket, pieces) -} - -func (w *WorkerStruct) SealPreCommit2(ctx context.Context, sector abi.SectorID, p1o storage.PreCommit1Out) (storage.SectorCids, error) { - return w.Internal.SealPreCommit2(ctx, sector, p1o) -} - -func (w *WorkerStruct) SealCommit1(ctx context.Context, sector abi.SectorID, ticket abi.SealRandomness, seed abi.InteractiveSealRandomness, pieces []abi.PieceInfo, cids storage.SectorCids) (storage.Commit1Out, error) { - return w.Internal.SealCommit1(ctx, sector, ticket, seed, pieces, cids) -} - -func (w *WorkerStruct) SealCommit2(ctx context.Context, sector abi.SectorID, c1o storage.Commit1Out) (storage.Proof, error) { - return w.Internal.SealCommit2(ctx, sector, c1o) -} - -func (w *WorkerStruct) FinalizeSector(ctx context.Context, sector abi.SectorID) error { - return w.Internal.FinalizeSector(ctx, sector) -} - -func (w *WorkerStruct) Fetch(ctx context.Context, id abi.SectorID, fileType stores.SectorFileType, b bool) error { - return w.Internal.Fetch(ctx, id, fileType, b) -} - -func (w *WorkerStruct) Closing(ctx context.Context) (<-chan struct{}, error) { - return w.Internal.Closing(ctx) -} - -var _ api.Common = &CommonStruct{} -var _ api.FullNode = &FullNodeStruct{} -var _ api.StorageMiner = &StorageMinerStruct{} -var _ api.WorkerApi = &WorkerStruct{} diff --git a/api/apistruct/struct_test.go b/api/apistruct/struct_test.go deleted file mode 100644 index 9f5f58360..000000000 --- a/api/apistruct/struct_test.go +++ /dev/null @@ -1,9 +0,0 @@ -package apistruct - -import "testing" - -func TestPermTags(t *testing.T) { - _ = PermissionedFullAPI(&FullNodeStruct{}) - _ = PermissionedStorMinerAPI(&StorageMinerStruct{}) - _ = PermissionedWorkerAPI(&WorkerStruct{}) -} diff --git a/api/cbor_gen.go b/api/cbor_gen.go index 85079d39a..80392b212 100644 --- a/api/cbor_gen.go +++ b/api/cbor_gen.go @@ -5,73 +5,60 @@ package api import ( "fmt" "io" + "math" + "sort" - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/builtin/paych" + cid "github.com/ipfs/go-cid" cbg "github.com/whyrusleeping/cbor-gen" xerrors "golang.org/x/xerrors" + + abi "github.com/filecoin-project/go-state-types/abi" + paych "github.com/filecoin-project/go-state-types/builtin/v8/paych" + market "github.com/filecoin-project/go-state-types/builtin/v9/market" ) var _ = xerrors.Errorf +var _ = cid.Undef +var _ = math.E +var _ = sort.Sort func (t *PaymentInfo) MarshalCBOR(w io.Writer) error { if t == nil { _, err := w.Write(cbg.CborNull) return err } - if _, err := w.Write([]byte{163}); err != nil { + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write([]byte{163}); err != nil { return err } - scratch := make([]byte, 9) - // t.Channel (address.Address) (struct) if len("Channel") > cbg.MaxLength { return xerrors.Errorf("Value in field \"Channel\" was too long") } - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Channel"))); err != nil { + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("Channel"))); err != nil { return err } - if _, err := io.WriteString(w, "Channel"); err != nil { + if _, err := io.WriteString(w, string("Channel")); err != nil { return err } - if err := t.Channel.MarshalCBOR(w); err != nil { + if err := t.Channel.MarshalCBOR(cw); err != nil { return err } - // t.ChannelMessage (cid.Cid) (struct) - if len("ChannelMessage") > cbg.MaxLength { - return xerrors.Errorf("Value in field \"ChannelMessage\" was too long") - } - - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("ChannelMessage"))); err != nil { - return err - } - if _, err := io.WriteString(w, "ChannelMessage"); err != nil { - return err - } - - if t.ChannelMessage == nil { - if _, err := w.Write(cbg.CborNull); err != nil { - return err - } - } else { - if err := cbg.WriteCidBuf(scratch, w, *t.ChannelMessage); err != nil { - return xerrors.Errorf("failed to write cid field t.ChannelMessage: %w", err) - } - } - // t.Vouchers ([]*paych.SignedVoucher) (slice) if len("Vouchers") > cbg.MaxLength { return xerrors.Errorf("Value in field \"Vouchers\" was too long") } - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Vouchers"))); err != nil { + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("Vouchers"))); err != nil { return err } - if _, err := io.WriteString(w, "Vouchers"); err != nil { + if _, err := io.WriteString(w, string("Vouchers")); err != nil { return err } @@ -79,25 +66,49 @@ func (t *PaymentInfo) MarshalCBOR(w io.Writer) error { return xerrors.Errorf("Slice value in field t.Vouchers was too long") } - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.Vouchers))); err != nil { + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Vouchers))); err != nil { return err } for _, v := range t.Vouchers { - if err := v.MarshalCBOR(w); err != nil { + if err := v.MarshalCBOR(cw); err != nil { return err } } + + // t.WaitSentinel (cid.Cid) (struct) + if len("WaitSentinel") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"WaitSentinel\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("WaitSentinel"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("WaitSentinel")); err != nil { + return err + } + + if err := cbg.WriteCid(cw, t.WaitSentinel); err != nil { + return xerrors.Errorf("failed to write cid field t.WaitSentinel: %w", err) + } + return nil } -func (t *PaymentInfo) UnmarshalCBOR(r io.Reader) error { - br := cbg.GetPeeker(r) - scratch := make([]byte, 8) +func (t *PaymentInfo) UnmarshalCBOR(r io.Reader) (err error) { + *t = PaymentInfo{} - maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() if err != nil { return err } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + if maj != cbg.MajMap { return fmt.Errorf("cbor input should be of type map") } @@ -112,7 +123,7 @@ func (t *PaymentInfo) UnmarshalCBOR(r io.Reader) error { for i := uint64(0); i < n; i++ { { - sval, err := cbg.ReadStringBuf(br, scratch) + sval, err := cbg.ReadString(cr) if err != nil { return err } @@ -126,40 +137,15 @@ func (t *PaymentInfo) UnmarshalCBOR(r io.Reader) error { { - if err := t.Channel.UnmarshalCBOR(br); err != nil { + if err := t.Channel.UnmarshalCBOR(cr); err != nil { return xerrors.Errorf("unmarshaling t.Channel: %w", err) } - } - // t.ChannelMessage (cid.Cid) (struct) - case "ChannelMessage": - - { - - pb, err := br.PeekByte() - if err != nil { - return err - } - if pb == cbg.CborNull[0] { - var nbuf [1]byte - if _, err := br.Read(nbuf[:]); err != nil { - return err - } - } else { - - c, err := cbg.ReadCid(br) - if err != nil { - return xerrors.Errorf("failed to read cid field t.ChannelMessage: %w", err) - } - - t.ChannelMessage = &c - } - } // t.Vouchers ([]*paych.SignedVoucher) (slice) case "Vouchers": - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + maj, extra, err = cr.ReadHeader() if err != nil { return err } @@ -179,15 +165,30 @@ func (t *PaymentInfo) UnmarshalCBOR(r io.Reader) error { for i := 0; i < int(extra); i++ { var v paych.SignedVoucher - if err := v.UnmarshalCBOR(br); err != nil { + if err := v.UnmarshalCBOR(cr); err != nil { return err } t.Vouchers[i] = &v } + // t.WaitSentinel (cid.Cid) (struct) + case "WaitSentinel": + + { + + c, err := cbg.ReadCid(cr) + if err != nil { + return xerrors.Errorf("failed to read cid field t.WaitSentinel: %w", err) + } + + t.WaitSentinel = c + + } + default: - return fmt.Errorf("unknown struct field %d: '%s'", i, name) + // Field doesn't exist on this type, so ignore it + cbg.ScanForLinks(r, func(cid.Cid) {}) } } @@ -198,41 +199,10 @@ func (t *SealedRef) MarshalCBOR(w io.Writer) error { _, err := w.Write(cbg.CborNull) return err } - if _, err := w.Write([]byte{163}); err != nil { - return err - } - scratch := make([]byte, 9) + cw := cbg.NewCborWriter(w) - // t.SectorID (abi.SectorNumber) (uint64) - if len("SectorID") > cbg.MaxLength { - return xerrors.Errorf("Value in field \"SectorID\" was too long") - } - - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("SectorID"))); err != nil { - return err - } - if _, err := io.WriteString(w, "SectorID"); err != nil { - return err - } - - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.SectorID)); err != nil { - return err - } - - // t.Offset (uint64) (uint64) - if len("Offset") > cbg.MaxLength { - return xerrors.Errorf("Value in field \"Offset\" was too long") - } - - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Offset"))); err != nil { - return err - } - if _, err := io.WriteString(w, "Offset"); err != nil { - return err - } - - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Offset)); err != nil { + if _, err := cw.Write([]byte{163}); err != nil { return err } @@ -241,28 +211,67 @@ func (t *SealedRef) MarshalCBOR(w io.Writer) error { return xerrors.Errorf("Value in field \"Size\" was too long") } - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Size"))); err != nil { + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("Size"))); err != nil { return err } - if _, err := io.WriteString(w, "Size"); err != nil { + if _, err := io.WriteString(w, string("Size")); err != nil { return err } - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Size)); err != nil { + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Size)); err != nil { + return err + } + + // t.Offset (abi.PaddedPieceSize) (uint64) + if len("Offset") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Offset\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("Offset"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Offset")); err != nil { + return err + } + + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Offset)); err != nil { + return err + } + + // t.SectorID (abi.SectorNumber) (uint64) + if len("SectorID") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"SectorID\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("SectorID"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("SectorID")); err != nil { + return err + } + + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.SectorID)); err != nil { return err } return nil } -func (t *SealedRef) UnmarshalCBOR(r io.Reader) error { - br := cbg.GetPeeker(r) - scratch := make([]byte, 8) +func (t *SealedRef) UnmarshalCBOR(r io.Reader) (err error) { + *t = SealedRef{} - maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() if err != nil { return err } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + if maj != cbg.MajMap { return fmt.Errorf("cbor input should be of type map") } @@ -277,7 +286,7 @@ func (t *SealedRef) UnmarshalCBOR(r io.Reader) error { for i := uint64(0); i < n; i++ { { - sval, err := cbg.ReadStringBuf(br, scratch) + sval, err := cbg.ReadString(cr) if err != nil { return err } @@ -286,42 +295,12 @@ func (t *SealedRef) UnmarshalCBOR(r io.Reader) error { } switch name { - // t.SectorID (abi.SectorNumber) (uint64) - case "SectorID": - - { - - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) - if err != nil { - return err - } - if maj != cbg.MajUnsignedInt { - return fmt.Errorf("wrong type for uint64 field") - } - t.SectorID = abi.SectorNumber(extra) - - } - // t.Offset (uint64) (uint64) - case "Offset": - - { - - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) - if err != nil { - return err - } - if maj != cbg.MajUnsignedInt { - return fmt.Errorf("wrong type for uint64 field") - } - t.Offset = uint64(extra) - - } - // t.Size (abi.UnpaddedPieceSize) (uint64) + // t.Size (abi.UnpaddedPieceSize) (uint64) case "Size": { - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + maj, extra, err = cr.ReadHeader() if err != nil { return err } @@ -331,9 +310,40 @@ func (t *SealedRef) UnmarshalCBOR(r io.Reader) error { t.Size = abi.UnpaddedPieceSize(extra) } + // t.Offset (abi.PaddedPieceSize) (uint64) + case "Offset": + + { + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.Offset = abi.PaddedPieceSize(extra) + + } + // t.SectorID (abi.SectorNumber) (uint64) + case "SectorID": + + { + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.SectorID = abi.SectorNumber(extra) + + } default: - return fmt.Errorf("unknown struct field %d: '%s'", i, name) + // Field doesn't exist on this type, so ignore it + cbg.ScanForLinks(r, func(cid.Cid) {}) } } @@ -344,21 +354,22 @@ func (t *SealedRefs) MarshalCBOR(w io.Writer) error { _, err := w.Write(cbg.CborNull) return err } - if _, err := w.Write([]byte{161}); err != nil { + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write([]byte{161}); err != nil { return err } - scratch := make([]byte, 9) - // t.Refs ([]api.SealedRef) (slice) if len("Refs") > cbg.MaxLength { return xerrors.Errorf("Value in field \"Refs\" was too long") } - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Refs"))); err != nil { + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("Refs"))); err != nil { return err } - if _, err := io.WriteString(w, "Refs"); err != nil { + if _, err := io.WriteString(w, string("Refs")); err != nil { return err } @@ -366,25 +377,32 @@ func (t *SealedRefs) MarshalCBOR(w io.Writer) error { return xerrors.Errorf("Slice value in field t.Refs was too long") } - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.Refs))); err != nil { + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Refs))); err != nil { return err } for _, v := range t.Refs { - if err := v.MarshalCBOR(w); err != nil { + if err := v.MarshalCBOR(cw); err != nil { return err } } return nil } -func (t *SealedRefs) UnmarshalCBOR(r io.Reader) error { - br := cbg.GetPeeker(r) - scratch := make([]byte, 8) +func (t *SealedRefs) UnmarshalCBOR(r io.Reader) (err error) { + *t = SealedRefs{} - maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() if err != nil { return err } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + if maj != cbg.MajMap { return fmt.Errorf("cbor input should be of type map") } @@ -399,7 +417,7 @@ func (t *SealedRefs) UnmarshalCBOR(r io.Reader) error { for i := uint64(0); i < n; i++ { { - sval, err := cbg.ReadStringBuf(br, scratch) + sval, err := cbg.ReadString(cr) if err != nil { return err } @@ -411,7 +429,7 @@ func (t *SealedRefs) UnmarshalCBOR(r io.Reader) error { // t.Refs ([]api.SealedRef) (slice) case "Refs": - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + maj, extra, err = cr.ReadHeader() if err != nil { return err } @@ -431,7 +449,7 @@ func (t *SealedRefs) UnmarshalCBOR(r io.Reader) error { for i := 0; i < int(extra); i++ { var v SealedRef - if err := v.UnmarshalCBOR(br); err != nil { + if err := v.UnmarshalCBOR(cr); err != nil { return err } @@ -439,7 +457,8 @@ func (t *SealedRefs) UnmarshalCBOR(r io.Reader) error { } default: - return fmt.Errorf("unknown struct field %d: '%s'", i, name) + // Field doesn't exist on this type, so ignore it + cbg.ScanForLinks(r, func(cid.Cid) {}) } } @@ -450,33 +469,10 @@ func (t *SealTicket) MarshalCBOR(w io.Writer) error { _, err := w.Write(cbg.CborNull) return err } - if _, err := w.Write([]byte{162}); err != nil { - return err - } - scratch := make([]byte, 9) + cw := cbg.NewCborWriter(w) - // t.Value (abi.SealRandomness) (slice) - if len("Value") > cbg.MaxLength { - return xerrors.Errorf("Value in field \"Value\" was too long") - } - - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Value"))); err != nil { - return err - } - if _, err := io.WriteString(w, "Value"); err != nil { - return err - } - - if len(t.Value) > cbg.ByteArrayMaxLen { - return xerrors.Errorf("Byte array in field t.Value was too long") - } - - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajByteString, uint64(len(t.Value))); err != nil { - return err - } - - if _, err := w.Write(t.Value); err != nil { + if _, err := cw.Write([]byte{162}); err != nil { return err } @@ -485,33 +481,64 @@ func (t *SealTicket) MarshalCBOR(w io.Writer) error { return xerrors.Errorf("Value in field \"Epoch\" was too long") } - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Epoch"))); err != nil { + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("Epoch"))); err != nil { return err } - if _, err := io.WriteString(w, "Epoch"); err != nil { + if _, err := io.WriteString(w, string("Epoch")); err != nil { return err } if t.Epoch >= 0 { - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Epoch)); err != nil { + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Epoch)); err != nil { return err } } else { - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajNegativeInt, uint64(-t.Epoch-1)); err != nil { + if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.Epoch-1)); err != nil { return err } } + + // t.Value (abi.SealRandomness) (slice) + if len("Value") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Value\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("Value"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Value")); err != nil { + return err + } + + if len(t.Value) > cbg.ByteArrayMaxLen { + return xerrors.Errorf("Byte array in field t.Value was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajByteString, uint64(len(t.Value))); err != nil { + return err + } + + if _, err := cw.Write(t.Value[:]); err != nil { + return err + } return nil } -func (t *SealTicket) UnmarshalCBOR(r io.Reader) error { - br := cbg.GetPeeker(r) - scratch := make([]byte, 8) +func (t *SealTicket) UnmarshalCBOR(r io.Reader) (err error) { + *t = SealTicket{} - maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() if err != nil { return err } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + if maj != cbg.MajMap { return fmt.Errorf("cbor input should be of type map") } @@ -526,7 +553,7 @@ func (t *SealTicket) UnmarshalCBOR(r io.Reader) error { for i := uint64(0); i < n; i++ { { - sval, err := cbg.ReadStringBuf(br, scratch) + sval, err := cbg.ReadString(cr) if err != nil { return err } @@ -535,28 +562,10 @@ func (t *SealTicket) UnmarshalCBOR(r io.Reader) error { } switch name { - // t.Value (abi.SealRandomness) (slice) - case "Value": - - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) - if err != nil { - return err - } - - if extra > cbg.ByteArrayMaxLen { - return fmt.Errorf("t.Value: byte array too large (%d)", extra) - } - if maj != cbg.MajByteString { - return fmt.Errorf("expected byte array") - } - t.Value = make([]byte, extra) - if _, err := io.ReadFull(br, t.Value); err != nil { - return err - } - // t.Epoch (abi.ChainEpoch) (int64) + // t.Epoch (abi.ChainEpoch) (int64) case "Epoch": { - maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + maj, extra, err := cr.ReadHeader() var extraI int64 if err != nil { return err @@ -570,7 +579,7 @@ func (t *SealTicket) UnmarshalCBOR(r io.Reader) error { case cbg.MajNegativeInt: extraI = int64(extra) if extraI < 0 { - return fmt.Errorf("int64 negative oveflow") + return fmt.Errorf("int64 negative overflow") } extraI = -1 - extraI default: @@ -579,9 +588,32 @@ func (t *SealTicket) UnmarshalCBOR(r io.Reader) error { t.Epoch = abi.ChainEpoch(extraI) } + // t.Value (abi.SealRandomness) (slice) + case "Value": + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > cbg.ByteArrayMaxLen { + return fmt.Errorf("t.Value: byte array too large (%d)", extra) + } + if maj != cbg.MajByteString { + return fmt.Errorf("expected byte array") + } + + if extra > 0 { + t.Value = make([]uint8, extra) + } + + if _, err := io.ReadFull(cr, t.Value[:]); err != nil { + return err + } default: - return fmt.Errorf("unknown struct field %d: '%s'", i, name) + // Field doesn't exist on this type, so ignore it + cbg.ScanForLinks(r, func(cid.Cid) {}) } } @@ -592,33 +624,10 @@ func (t *SealSeed) MarshalCBOR(w io.Writer) error { _, err := w.Write(cbg.CborNull) return err } - if _, err := w.Write([]byte{162}); err != nil { - return err - } - scratch := make([]byte, 9) + cw := cbg.NewCborWriter(w) - // t.Value (abi.InteractiveSealRandomness) (slice) - if len("Value") > cbg.MaxLength { - return xerrors.Errorf("Value in field \"Value\" was too long") - } - - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Value"))); err != nil { - return err - } - if _, err := io.WriteString(w, "Value"); err != nil { - return err - } - - if len(t.Value) > cbg.ByteArrayMaxLen { - return xerrors.Errorf("Byte array in field t.Value was too long") - } - - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajByteString, uint64(len(t.Value))); err != nil { - return err - } - - if _, err := w.Write(t.Value); err != nil { + if _, err := cw.Write([]byte{162}); err != nil { return err } @@ -627,33 +636,64 @@ func (t *SealSeed) MarshalCBOR(w io.Writer) error { return xerrors.Errorf("Value in field \"Epoch\" was too long") } - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len("Epoch"))); err != nil { + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("Epoch"))); err != nil { return err } - if _, err := io.WriteString(w, "Epoch"); err != nil { + if _, err := io.WriteString(w, string("Epoch")); err != nil { return err } if t.Epoch >= 0 { - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Epoch)); err != nil { + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Epoch)); err != nil { return err } } else { - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajNegativeInt, uint64(-t.Epoch-1)); err != nil { + if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.Epoch-1)); err != nil { return err } } + + // t.Value (abi.InteractiveSealRandomness) (slice) + if len("Value") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Value\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("Value"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Value")); err != nil { + return err + } + + if len(t.Value) > cbg.ByteArrayMaxLen { + return xerrors.Errorf("Byte array in field t.Value was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajByteString, uint64(len(t.Value))); err != nil { + return err + } + + if _, err := cw.Write(t.Value[:]); err != nil { + return err + } return nil } -func (t *SealSeed) UnmarshalCBOR(r io.Reader) error { - br := cbg.GetPeeker(r) - scratch := make([]byte, 8) +func (t *SealSeed) UnmarshalCBOR(r io.Reader) (err error) { + *t = SealSeed{} - maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() if err != nil { return err } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + if maj != cbg.MajMap { return fmt.Errorf("cbor input should be of type map") } @@ -668,7 +708,7 @@ func (t *SealSeed) UnmarshalCBOR(r io.Reader) error { for i := uint64(0); i < n; i++ { { - sval, err := cbg.ReadStringBuf(br, scratch) + sval, err := cbg.ReadString(cr) if err != nil { return err } @@ -677,28 +717,10 @@ func (t *SealSeed) UnmarshalCBOR(r io.Reader) error { } switch name { - // t.Value (abi.InteractiveSealRandomness) (slice) - case "Value": - - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) - if err != nil { - return err - } - - if extra > cbg.ByteArrayMaxLen { - return fmt.Errorf("t.Value: byte array too large (%d)", extra) - } - if maj != cbg.MajByteString { - return fmt.Errorf("expected byte array") - } - t.Value = make([]byte, extra) - if _, err := io.ReadFull(br, t.Value); err != nil { - return err - } - // t.Epoch (abi.ChainEpoch) (int64) + // t.Epoch (abi.ChainEpoch) (int64) case "Epoch": { - maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + maj, extra, err := cr.ReadHeader() var extraI int64 if err != nil { return err @@ -712,7 +734,7 @@ func (t *SealSeed) UnmarshalCBOR(r io.Reader) error { case cbg.MajNegativeInt: extraI = int64(extra) if extraI < 0 { - return fmt.Errorf("int64 negative oveflow") + return fmt.Errorf("int64 negative overflow") } extraI = -1 - extraI default: @@ -721,9 +743,545 @@ func (t *SealSeed) UnmarshalCBOR(r io.Reader) error { t.Epoch = abi.ChainEpoch(extraI) } + // t.Value (abi.InteractiveSealRandomness) (slice) + case "Value": + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > cbg.ByteArrayMaxLen { + return fmt.Errorf("t.Value: byte array too large (%d)", extra) + } + if maj != cbg.MajByteString { + return fmt.Errorf("expected byte array") + } + + if extra > 0 { + t.Value = make([]uint8, extra) + } + + if _, err := io.ReadFull(cr, t.Value[:]); err != nil { + return err + } default: - return fmt.Errorf("unknown struct field %d: '%s'", i, name) + // Field doesn't exist on this type, so ignore it + cbg.ScanForLinks(r, func(cid.Cid) {}) + } + } + + return nil +} +func (t *PieceDealInfo) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write([]byte{165}); err != nil { + return err + } + + // t.DealID (abi.DealID) (uint64) + if len("DealID") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"DealID\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("DealID"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("DealID")); err != nil { + return err + } + + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.DealID)); err != nil { + return err + } + + // t.PublishCid (cid.Cid) (struct) + if len("PublishCid") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"PublishCid\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("PublishCid"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("PublishCid")); err != nil { + return err + } + + if t.PublishCid == nil { + if _, err := cw.Write(cbg.CborNull); err != nil { + return err + } + } else { + if err := cbg.WriteCid(cw, *t.PublishCid); err != nil { + return xerrors.Errorf("failed to write cid field t.PublishCid: %w", err) + } + } + + // t.DealProposal (market.DealProposal) (struct) + if len("DealProposal") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"DealProposal\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("DealProposal"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("DealProposal")); err != nil { + return err + } + + if err := t.DealProposal.MarshalCBOR(cw); err != nil { + return err + } + + // t.DealSchedule (api.DealSchedule) (struct) + if len("DealSchedule") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"DealSchedule\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("DealSchedule"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("DealSchedule")); err != nil { + return err + } + + if err := t.DealSchedule.MarshalCBOR(cw); err != nil { + return err + } + + // t.KeepUnsealed (bool) (bool) + if len("KeepUnsealed") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"KeepUnsealed\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("KeepUnsealed"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("KeepUnsealed")); err != nil { + return err + } + + if err := cbg.WriteBool(w, t.KeepUnsealed); err != nil { + return err + } + return nil +} + +func (t *PieceDealInfo) UnmarshalCBOR(r io.Reader) (err error) { + *t = PieceDealInfo{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajMap { + return fmt.Errorf("cbor input should be of type map") + } + + if extra > cbg.MaxLength { + return fmt.Errorf("PieceDealInfo: map struct too large (%d)", extra) + } + + var name string + n := extra + + for i := uint64(0); i < n; i++ { + + { + sval, err := cbg.ReadString(cr) + if err != nil { + return err + } + + name = string(sval) + } + + switch name { + // t.DealID (abi.DealID) (uint64) + case "DealID": + + { + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.DealID = abi.DealID(extra) + + } + // t.PublishCid (cid.Cid) (struct) + case "PublishCid": + + { + + b, err := cr.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { + return err + } + + c, err := cbg.ReadCid(cr) + if err != nil { + return xerrors.Errorf("failed to read cid field t.PublishCid: %w", err) + } + + t.PublishCid = &c + } + + } + // t.DealProposal (market.DealProposal) (struct) + case "DealProposal": + + { + + b, err := cr.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { + return err + } + t.DealProposal = new(market.DealProposal) + if err := t.DealProposal.UnmarshalCBOR(cr); err != nil { + return xerrors.Errorf("unmarshaling t.DealProposal pointer: %w", err) + } + } + + } + // t.DealSchedule (api.DealSchedule) (struct) + case "DealSchedule": + + { + + if err := t.DealSchedule.UnmarshalCBOR(cr); err != nil { + return xerrors.Errorf("unmarshaling t.DealSchedule: %w", err) + } + + } + // t.KeepUnsealed (bool) (bool) + case "KeepUnsealed": + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + if maj != cbg.MajOther { + return fmt.Errorf("booleans must be major type 7") + } + switch extra { + case 20: + t.KeepUnsealed = false + case 21: + t.KeepUnsealed = true + default: + return fmt.Errorf("booleans are either major type 7, value 20 or 21 (got %d)", extra) + } + + default: + // Field doesn't exist on this type, so ignore it + cbg.ScanForLinks(r, func(cid.Cid) {}) + } + } + + return nil +} +func (t *SectorPiece) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write([]byte{162}); err != nil { + return err + } + + // t.Piece (abi.PieceInfo) (struct) + if len("Piece") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"Piece\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("Piece"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("Piece")); err != nil { + return err + } + + if err := t.Piece.MarshalCBOR(cw); err != nil { + return err + } + + // t.DealInfo (api.PieceDealInfo) (struct) + if len("DealInfo") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"DealInfo\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("DealInfo"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("DealInfo")); err != nil { + return err + } + + if err := t.DealInfo.MarshalCBOR(cw); err != nil { + return err + } + return nil +} + +func (t *SectorPiece) UnmarshalCBOR(r io.Reader) (err error) { + *t = SectorPiece{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajMap { + return fmt.Errorf("cbor input should be of type map") + } + + if extra > cbg.MaxLength { + return fmt.Errorf("SectorPiece: map struct too large (%d)", extra) + } + + var name string + n := extra + + for i := uint64(0); i < n; i++ { + + { + sval, err := cbg.ReadString(cr) + if err != nil { + return err + } + + name = string(sval) + } + + switch name { + // t.Piece (abi.PieceInfo) (struct) + case "Piece": + + { + + if err := t.Piece.UnmarshalCBOR(cr); err != nil { + return xerrors.Errorf("unmarshaling t.Piece: %w", err) + } + + } + // t.DealInfo (api.PieceDealInfo) (struct) + case "DealInfo": + + { + + b, err := cr.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { + return err + } + t.DealInfo = new(PieceDealInfo) + if err := t.DealInfo.UnmarshalCBOR(cr); err != nil { + return xerrors.Errorf("unmarshaling t.DealInfo pointer: %w", err) + } + } + + } + + default: + // Field doesn't exist on this type, so ignore it + cbg.ScanForLinks(r, func(cid.Cid) {}) + } + } + + return nil +} +func (t *DealSchedule) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write([]byte{162}); err != nil { + return err + } + + // t.EndEpoch (abi.ChainEpoch) (int64) + if len("EndEpoch") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"EndEpoch\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("EndEpoch"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("EndEpoch")); err != nil { + return err + } + + if t.EndEpoch >= 0 { + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.EndEpoch)); err != nil { + return err + } + } else { + if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.EndEpoch-1)); err != nil { + return err + } + } + + // t.StartEpoch (abi.ChainEpoch) (int64) + if len("StartEpoch") > cbg.MaxLength { + return xerrors.Errorf("Value in field \"StartEpoch\" was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("StartEpoch"))); err != nil { + return err + } + if _, err := io.WriteString(w, string("StartEpoch")); err != nil { + return err + } + + if t.StartEpoch >= 0 { + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.StartEpoch)); err != nil { + return err + } + } else { + if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.StartEpoch-1)); err != nil { + return err + } + } + return nil +} + +func (t *DealSchedule) UnmarshalCBOR(r io.Reader) (err error) { + *t = DealSchedule{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajMap { + return fmt.Errorf("cbor input should be of type map") + } + + if extra > cbg.MaxLength { + return fmt.Errorf("DealSchedule: map struct too large (%d)", extra) + } + + var name string + n := extra + + for i := uint64(0); i < n; i++ { + + { + sval, err := cbg.ReadString(cr) + if err != nil { + return err + } + + name = string(sval) + } + + switch name { + // t.EndEpoch (abi.ChainEpoch) (int64) + case "EndEpoch": + { + maj, extra, err := cr.ReadHeader() + var extraI int64 + if err != nil { + return err + } + switch maj { + case cbg.MajUnsignedInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 positive overflow") + } + case cbg.MajNegativeInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 negative overflow") + } + extraI = -1 - extraI + default: + return fmt.Errorf("wrong type for int64 field: %d", maj) + } + + t.EndEpoch = abi.ChainEpoch(extraI) + } + // t.StartEpoch (abi.ChainEpoch) (int64) + case "StartEpoch": + { + maj, extra, err := cr.ReadHeader() + var extraI int64 + if err != nil { + return err + } + switch maj { + case cbg.MajUnsignedInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 positive overflow") + } + case cbg.MajNegativeInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 negative overflow") + } + extraI = -1 - extraI + default: + return fmt.Errorf("wrong type for int64 field: %d", maj) + } + + t.StartEpoch = abi.ChainEpoch(extraI) + } + + default: + // Field doesn't exist on this type, so ignore it + cbg.ScanForLinks(r, func(cid.Cid) {}) } } diff --git a/api/checkstatuscode_string.go b/api/checkstatuscode_string.go new file mode 100644 index 000000000..072f77989 --- /dev/null +++ b/api/checkstatuscode_string.go @@ -0,0 +1,35 @@ +// Code generated by "stringer -type=CheckStatusCode -trimprefix=CheckStatus"; DO NOT EDIT. + +package api + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[CheckStatusMessageSerialize-1] + _ = x[CheckStatusMessageSize-2] + _ = x[CheckStatusMessageValidity-3] + _ = x[CheckStatusMessageMinGas-4] + _ = x[CheckStatusMessageMinBaseFee-5] + _ = x[CheckStatusMessageBaseFee-6] + _ = x[CheckStatusMessageBaseFeeLowerBound-7] + _ = x[CheckStatusMessageBaseFeeUpperBound-8] + _ = x[CheckStatusMessageGetStateNonce-9] + _ = x[CheckStatusMessageNonce-10] + _ = x[CheckStatusMessageGetStateBalance-11] + _ = x[CheckStatusMessageBalance-12] +} + +const _CheckStatusCode_name = "MessageSerializeMessageSizeMessageValidityMessageMinGasMessageMinBaseFeeMessageBaseFeeMessageBaseFeeLowerBoundMessageBaseFeeUpperBoundMessageGetStateNonceMessageNonceMessageGetStateBalanceMessageBalance" + +var _CheckStatusCode_index = [...]uint8{0, 16, 27, 42, 55, 72, 86, 110, 134, 154, 166, 188, 202} + +func (i CheckStatusCode) String() string { + i -= 1 + if i < 0 || i >= CheckStatusCode(len(_CheckStatusCode_index)-1) { + return "CheckStatusCode(" + strconv.FormatInt(int64(i+1), 10) + ")" + } + return _CheckStatusCode_name[_CheckStatusCode_index[i]:_CheckStatusCode_index[i+1]] +} diff --git a/api/client/client.go b/api/client/client.go index d3eceb24a..8b159c5b1 100644 --- a/api/client/client.go +++ b/api/client/client.go @@ -1,60 +1,132 @@ package client import ( + "context" "net/http" + "net/url" + "path" + "time" "github.com/filecoin-project/go-jsonrpc" "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/api/apistruct" + "github.com/filecoin-project/lotus/api/v0api" + "github.com/filecoin-project/lotus/api/v1api" + "github.com/filecoin-project/lotus/lib/rpcenc" ) -// NewCommonRPC creates a new http jsonrpc client. -func NewCommonRPC(addr string, requestHeader http.Header) (api.Common, jsonrpc.ClientCloser, error) { - var res apistruct.CommonStruct - closer, err := jsonrpc.NewMergeClient(addr, "Filecoin", - []interface{}{ - &res.Internal, - }, +// NewCommonRPCV0 creates a new http jsonrpc client. +func NewCommonRPCV0(ctx context.Context, addr string, requestHeader http.Header) (api.CommonNet, jsonrpc.ClientCloser, error) { + var res v0api.CommonNetStruct + closer, err := jsonrpc.NewMergeClient(ctx, addr, "Filecoin", + api.GetInternalStructs(&res), requestHeader, jsonrpc.WithErrors(api.RPCErrors)) + + return &res, closer, err +} + +// NewFullNodeRPCV0 creates a new http jsonrpc client. +func NewFullNodeRPCV0(ctx context.Context, addr string, requestHeader http.Header) (v0api.FullNode, jsonrpc.ClientCloser, error) { + var res v0api.FullNodeStruct + + closer, err := jsonrpc.NewMergeClient(ctx, addr, "Filecoin", + api.GetInternalStructs(&res), requestHeader, jsonrpc.WithErrors(api.RPCErrors)) + + return &res, closer, err +} + +// NewFullNodeRPCV1 creates a new http jsonrpc client. +func NewFullNodeRPCV1(ctx context.Context, addr string, requestHeader http.Header, opts ...jsonrpc.Option) (api.FullNode, jsonrpc.ClientCloser, error) { + var res v1api.FullNodeStruct + closer, err := jsonrpc.NewMergeClient(ctx, addr, "Filecoin", + api.GetInternalStructs(&res), requestHeader, append([]jsonrpc.Option{jsonrpc.WithErrors(api.RPCErrors)}, opts...)...) + + return &res, closer, err +} + +func getPushUrl(addr string) (string, error) { + pushUrl, err := url.Parse(addr) + if err != nil { + return "", err + } + switch pushUrl.Scheme { + case "ws": + pushUrl.Scheme = "http" + case "wss": + pushUrl.Scheme = "https" + } + ///rpc/v0 -> /rpc/streams/v0/push + + pushUrl.Path = path.Join(pushUrl.Path, "../streams/v0/push") + return pushUrl.String(), nil +} + +// NewStorageMinerRPCV0 creates a new http jsonrpc client for miner +func NewStorageMinerRPCV0(ctx context.Context, addr string, requestHeader http.Header, opts ...jsonrpc.Option) (v0api.StorageMiner, jsonrpc.ClientCloser, error) { + pushUrl, err := getPushUrl(addr) + if err != nil { + return nil, nil, err + } + + var res v0api.StorageMinerStruct + closer, err := jsonrpc.NewMergeClient(ctx, addr, "Filecoin", + api.GetInternalStructs(&res), requestHeader, + append([]jsonrpc.Option{ + rpcenc.ReaderParamEncoder(pushUrl), + jsonrpc.WithErrors(api.RPCErrors), + }, opts...)...) + + return &res, closer, err +} + +func NewWorkerRPCV0(ctx context.Context, addr string, requestHeader http.Header) (v0api.Worker, jsonrpc.ClientCloser, error) { + pushUrl, err := getPushUrl(addr) + if err != nil { + return nil, nil, err + } + + var res api.WorkerStruct + closer, err := jsonrpc.NewMergeClient(ctx, addr, "Filecoin", + api.GetInternalStructs(&res), requestHeader, + rpcenc.ReaderParamEncoder(pushUrl), + jsonrpc.WithNoReconnect(), + jsonrpc.WithTimeout(30*time.Second), + jsonrpc.WithErrors(api.RPCErrors), ) return &res, closer, err } -// NewFullNodeRPC creates a new http jsonrpc client. -func NewFullNodeRPC(addr string, requestHeader http.Header) (api.FullNode, jsonrpc.ClientCloser, error) { - var res apistruct.FullNodeStruct - closer, err := jsonrpc.NewMergeClient(addr, "Filecoin", - []interface{}{ - &res.CommonStruct.Internal, - &res.Internal, - }, requestHeader) - - return &res, closer, err -} - -// NewStorageMinerRPC creates a new http jsonrpc client for storage miner -func NewStorageMinerRPC(addr string, requestHeader http.Header) (api.StorageMiner, jsonrpc.ClientCloser, error) { - var res apistruct.StorageMinerStruct - closer, err := jsonrpc.NewMergeClient(addr, "Filecoin", - []interface{}{ - &res.CommonStruct.Internal, - &res.Internal, - }, +// NewGatewayRPCV1 creates a new http jsonrpc client for a gateway node. +func NewGatewayRPCV1(ctx context.Context, addr string, requestHeader http.Header, opts ...jsonrpc.Option) (api.Gateway, jsonrpc.ClientCloser, error) { + var res api.GatewayStruct + closer, err := jsonrpc.NewMergeClient(ctx, addr, "Filecoin", + api.GetInternalStructs(&res), requestHeader, + append(opts, jsonrpc.WithErrors(api.RPCErrors))..., ) return &res, closer, err } -func NewWorkerRPC(addr string, requestHeader http.Header) (api.WorkerApi, jsonrpc.ClientCloser, error) { - var res apistruct.WorkerStruct - closer, err := jsonrpc.NewMergeClient(addr, "Filecoin", - []interface{}{ - &res.Internal, - }, +// NewGatewayRPCV0 creates a new http jsonrpc client for a gateway node. +func NewGatewayRPCV0(ctx context.Context, addr string, requestHeader http.Header, opts ...jsonrpc.Option) (v0api.Gateway, jsonrpc.ClientCloser, error) { + var res v0api.GatewayStruct + closer, err := jsonrpc.NewMergeClient(ctx, addr, "Filecoin", + api.GetInternalStructs(&res), requestHeader, + append(opts, jsonrpc.WithErrors(api.RPCErrors))..., + ) + + return &res, closer, err +} + +func NewWalletRPCV0(ctx context.Context, addr string, requestHeader http.Header) (api.Wallet, jsonrpc.ClientCloser, error) { + var res api.WalletStruct + closer, err := jsonrpc.NewMergeClient(ctx, addr, "Filecoin", + api.GetInternalStructs(&res), + requestHeader, + jsonrpc.WithErrors(api.RPCErrors), ) return &res, closer, err diff --git a/api/docgen-openrpc/cmd/docgen_openrpc.go b/api/docgen-openrpc/cmd/docgen_openrpc.go new file mode 100644 index 000000000..5f6bc566f --- /dev/null +++ b/api/docgen-openrpc/cmd/docgen_openrpc.go @@ -0,0 +1,73 @@ +package main + +import ( + "compress/gzip" + "encoding/json" + "io" + "log" + "os" + + "github.com/filecoin-project/lotus/api/docgen" + docgen_openrpc "github.com/filecoin-project/lotus/api/docgen-openrpc" +) + +/* +main defines a small program that writes an OpenRPC document describing +a Lotus API to stdout. + +If the first argument is "miner", the document will describe the StorageMiner API. +If not (no, or any other args), the document will describe the Full API. + +Use: + + go run ./api/openrpc/cmd ["api/api_full.go"|"api/api_storage.go"|"api/api_worker.go"] ["FullNode"|"StorageMiner"|"Worker"] + + With gzip compression: a '-gzip' flag is made available as an optional third argument. Note that position matters. + + go run ./api/openrpc/cmd ["api/api_full.go"|"api/api_storage.go"|"api/api_worker.go"] ["FullNode"|"StorageMiner"|"Worker"] -gzip + +*/ + +func main() { + Comments, GroupDocs := docgen.ParseApiASTInfo(os.Args[1], os.Args[2], os.Args[3], os.Args[4]) + + doc := docgen_openrpc.NewLotusOpenRPCDocument(Comments, GroupDocs) + + i, _, _ := docgen.GetAPIType(os.Args[2], os.Args[3]) + doc.RegisterReceiverName("Filecoin", i) + + out, err := doc.Discover() + if err != nil { + log.Fatalln(err) + } + + var jsonOut []byte + var writer io.WriteCloser + + // Use os.Args to handle a somewhat hacky flag for the gzip option. + // Could use flags package to handle this more cleanly, but that requires changes elsewhere + // the scope of which just isn't warranted by this one use case which will usually be run + // programmatically anyways. + if len(os.Args) > 5 && os.Args[5] == "-gzip" { + jsonOut, err = json.Marshal(out) + if err != nil { + log.Fatalln(err) + } + writer = gzip.NewWriter(os.Stdout) + } else { + jsonOut, err = json.MarshalIndent(out, "", " ") + if err != nil { + log.Fatalln(err) + } + writer = os.Stdout + } + + _, err = writer.Write(jsonOut) + if err != nil { + log.Fatalln(err) + } + err = writer.Close() + if err != nil { + log.Fatalln(err) + } +} diff --git a/api/docgen-openrpc/openrpc.go b/api/docgen-openrpc/openrpc.go new file mode 100644 index 000000000..c9504ba89 --- /dev/null +++ b/api/docgen-openrpc/openrpc.go @@ -0,0 +1,162 @@ +package docgenopenrpc + +import ( + "encoding/json" + "go/ast" + "net" + "reflect" + + "github.com/alecthomas/jsonschema" + go_openrpc_reflect "github.com/etclabscore/go-openrpc-reflect" + "github.com/ipfs/go-cid" + meta_schema "github.com/open-rpc/meta-schema" + + "github.com/filecoin-project/lotus/api/docgen" + "github.com/filecoin-project/lotus/build" +) + +// schemaDictEntry represents a type association passed to the jsonschema reflector. +type schemaDictEntry struct { + example interface{} + rawJson string +} + +const integerD = `{ + "title": "number", + "type": "number", + "description": "Number is a number" + }` + +const cidCidD = `{"title": "Content Identifier", "type": "string", "description": "Cid represents a self-describing content addressed identifier. It is formed by a Version, a Codec (which indicates a multicodec-packed content type) and a Multihash."}` + +func OpenRPCSchemaTypeMapper(ty reflect.Type) *jsonschema.Type { + unmarshalJSONToJSONSchemaType := func(input string) *jsonschema.Type { + var js jsonschema.Type + err := json.Unmarshal([]byte(input), &js) + if err != nil { + panic(err) + } + return &js + } + + if ty.Kind() == reflect.Ptr { + ty = ty.Elem() + } + + if ty == reflect.TypeOf((*interface{})(nil)).Elem() { + return &jsonschema.Type{Type: "object", AdditionalProperties: []byte("true")} + } + + // Second, handle other types. + // Use a slice instead of a map because it preserves order, as a logic safeguard/fallback. + dict := []schemaDictEntry{ + {cid.Cid{}, cidCidD}, + } + + for _, d := range dict { + if reflect.TypeOf(d.example) == ty { + tt := unmarshalJSONToJSONSchemaType(d.rawJson) + + return tt + } + } + + // Handle primitive types in case there are generic cases + // specific to our services. + switch ty.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + // Return all integer types as the hex representation integer schemea. + ret := unmarshalJSONToJSONSchemaType(integerD) + return ret + case reflect.Uintptr: + return &jsonschema.Type{Type: "number", Title: "uintptr-title"} + case reflect.Struct: + case reflect.Map: + case reflect.Slice, reflect.Array: + case reflect.Float32, reflect.Float64: + case reflect.Bool: + case reflect.String: + case reflect.Ptr, reflect.Interface: + default: + } + + return nil +} + +// NewLotusOpenRPCDocument defines application-specific documentation and configuration for its OpenRPC document. +func NewLotusOpenRPCDocument(Comments, GroupDocs map[string]string) *go_openrpc_reflect.Document { + d := &go_openrpc_reflect.Document{} + + // Register "Meta" document fields. + // These include getters for + // - Servers object + // - Info object + // - ExternalDocs object + // + // These objects represent server-specific data that cannot be + // reflected. + d.WithMeta(&go_openrpc_reflect.MetaT{ + GetServersFn: func() func(listeners []net.Listener) (*meta_schema.Servers, error) { + return func(listeners []net.Listener) (*meta_schema.Servers, error) { + return nil, nil + } + }, + GetInfoFn: func() (info *meta_schema.InfoObject) { + info = &meta_schema.InfoObject{} + title := "Lotus RPC API" + info.Title = (*meta_schema.InfoObjectProperties)(&title) + + version := build.BuildVersion + info.Version = (*meta_schema.InfoObjectVersion)(&version) + return info + }, + GetExternalDocsFn: func() (exdocs *meta_schema.ExternalDocumentationObject) { + return nil // FIXME + }, + }) + + // Use a provided Ethereum default configuration as a base. + appReflector := &go_openrpc_reflect.EthereumReflectorT{} + + // Install overrides for the json schema->type map fn used by the jsonschema reflect package. + appReflector.FnSchemaTypeMap = func() func(ty reflect.Type) *jsonschema.Type { + return OpenRPCSchemaTypeMapper + } + + appReflector.FnIsMethodEligible = func(m reflect.Method) bool { + for i := 0; i < m.Func.Type().NumOut(); i++ { + if m.Func.Type().Out(i).Kind() == reflect.Chan { + return false + } + } + return go_openrpc_reflect.EthereumReflector.IsMethodEligible(m) + } + appReflector.FnGetMethodName = func(moduleName string, r reflect.Value, m reflect.Method, funcDecl *ast.FuncDecl) (string, error) { + if m.Name == "ID" { + return moduleName + "_ID", nil + } + if moduleName == "rpc" && m.Name == "Discover" { + return "rpc.discover", nil + } + + return moduleName + "." + m.Name, nil + } + + appReflector.FnGetMethodSummary = func(r reflect.Value, m reflect.Method, funcDecl *ast.FuncDecl) (string, error) { + if v, ok := Comments[m.Name]; ok { + return v, nil + } + return "", nil // noComment + } + + appReflector.FnSchemaExamples = func(ty reflect.Type) (examples *meta_schema.Examples, err error) { + v := docgen.ExampleValue("unknown", ty, ty) // This isn't ideal, but seems to work well enough. + return &meta_schema.Examples{ + meta_schema.AlwaysTrue(v), + }, nil + } + + // Finally, register the configured reflector to the document. + d.WithReflector(appReflector) + return d +} diff --git a/api/docgen/cmd/docgen.go b/api/docgen/cmd/docgen.go new file mode 100644 index 000000000..9ae2df2e7 --- /dev/null +++ b/api/docgen/cmd/docgen.go @@ -0,0 +1,121 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + "reflect" + "sort" + "strings" + + "github.com/filecoin-project/lotus/api/docgen" +) + +func main() { + comments, groupComments := docgen.ParseApiASTInfo(os.Args[1], os.Args[2], os.Args[3], os.Args[4]) + + groups := make(map[string]*docgen.MethodGroup) + + _, t, permStruct := docgen.GetAPIType(os.Args[2], os.Args[3]) + + for i := 0; i < t.NumMethod(); i++ { + m := t.Method(i) + + groupName := docgen.MethodGroupFromName(m.Name) + + g, ok := groups[groupName] + if !ok { + g = new(docgen.MethodGroup) + g.Header = groupComments[groupName] + g.GroupName = groupName + groups[groupName] = g + } + + var args []interface{} + ft := m.Func.Type() + for j := 2; j < ft.NumIn(); j++ { + inp := ft.In(j) + args = append(args, docgen.ExampleValue(m.Name, inp, nil)) + } + + v, err := json.MarshalIndent(args, "", " ") + if err != nil { + panic(err) + } + + outv := docgen.ExampleValue(m.Name, ft.Out(0), nil) + + ov, err := json.MarshalIndent(outv, "", " ") + if err != nil { + panic(err) + } + + g.Methods = append(g.Methods, &docgen.Method{ + Name: m.Name, + Comment: comments[m.Name], + InputExample: string(v), + ResponseExample: string(ov), + }) + } + + var groupslice []*docgen.MethodGroup + for _, g := range groups { + groupslice = append(groupslice, g) + } + + sort.Slice(groupslice, func(i, j int) bool { + return groupslice[i].GroupName < groupslice[j].GroupName + }) + + fmt.Printf("# Groups\n") + + for _, g := range groupslice { + fmt.Printf("* [%s](#%s)\n", g.GroupName, g.GroupName) + for _, method := range g.Methods { + fmt.Printf(" * [%s](#%s)\n", method.Name, method.Name) + } + } + + for _, g := range groupslice { + g := g + fmt.Printf("## %s\n", g.GroupName) + fmt.Printf("%s\n\n", g.Header) + + sort.Slice(g.Methods, func(i, j int) bool { + return g.Methods[i].Name < g.Methods[j].Name + }) + + for _, m := range g.Methods { + fmt.Printf("### %s\n", m.Name) + fmt.Printf("%s\n\n", m.Comment) + + var meth reflect.StructField + var ok bool + for _, ps := range permStruct { + meth, ok = ps.FieldByName(m.Name) + if ok { + break + } + } + if !ok { + panic("no perms for method: " + m.Name) + } + + perms := meth.Tag.Get("perm") + + fmt.Printf("Perms: %s\n\n", perms) + + if strings.Count(m.InputExample, "\n") > 0 { + fmt.Printf("Inputs:\n```json\n%s\n```\n\n", m.InputExample) + } else { + fmt.Printf("Inputs: `%s`\n\n", m.InputExample) + } + + if strings.Count(m.ResponseExample, "\n") > 0 { + fmt.Printf("Response:\n```json\n%s\n```\n\n", m.ResponseExample) + } else { + fmt.Printf("Response: `%s`\n\n", m.ResponseExample) + } + } + } +} diff --git a/api/docgen/docgen.go b/api/docgen/docgen.go index 0ad3b02bb..7a9993bb7 100644 --- a/api/docgen/docgen.go +++ b/api/docgen/docgen.go @@ -1,4 +1,4 @@ -package main +package docgen import ( "encoding/json" @@ -6,35 +6,56 @@ import ( "go/ast" "go/parser" "go/token" + "net/http" + "path/filepath" "reflect" - "sort" "strings" "time" "unicode" + "github.com/google/uuid" + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + "github.com/ipfs/go-graphsync" + textselector "github.com/ipld/go-ipld-selector-text-lite" + pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/libp2p/go-libp2p/core/metrics" + "github.com/libp2p/go-libp2p/core/network" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/protocol" + "github.com/multiformats/go-multiaddr" + "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-bitfield" + datatransfer "github.com/filecoin-project/go-data-transfer/v2" + "github.com/filecoin-project/go-fil-markets/filestore" + "github.com/filecoin-project/go-fil-markets/retrievalmarket" "github.com/filecoin-project/go-jsonrpc/auth" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/go-state-types/exitcode" + "github.com/filecoin-project/lotus/api" + apitypes "github.com/filecoin-project/lotus/api/types" + "github.com/filecoin-project/lotus/api/v0api" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/ethtypes" "github.com/filecoin-project/lotus/node/modules/dtypes" - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/crypto" - "github.com/filecoin-project/specs-actors/actors/runtime/exitcode" - "github.com/ipfs/go-cid" - "github.com/ipfs/go-filestore" - "github.com/libp2p/go-libp2p-core/network" - peer "github.com/libp2p/go-libp2p-peer" - "github.com/multiformats/go-multiaddr" + "github.com/filecoin-project/lotus/node/repo/imports" + sealing "github.com/filecoin-project/lotus/storage/pipeline" + "github.com/filecoin-project/lotus/storage/sealer/sealtasks" + "github.com/filecoin-project/lotus/storage/sealer/storiface" ) var ExampleValues = map[reflect.Type]interface{}{ - reflect.TypeOf(auth.Permission("")): auth.Permission("write"), - reflect.TypeOf(""): "string value", - reflect.TypeOf(uint64(42)): uint64(42), - reflect.TypeOf(byte(7)): byte(7), - reflect.TypeOf([]byte{}): []byte("byte array"), + reflect.TypeOf(api.MinerSubsystem(0)): api.MinerSubsystem(1), + reflect.TypeOf(auth.Permission("")): auth.Permission("write"), + reflect.TypeOf(""): "string value", + reflect.TypeOf(uint64(42)): uint64(42), + reflect.TypeOf(byte(7)): byte(7), + reflect.TypeOf([]byte{}): []byte("byte array"), } func addExample(v interface{}) { @@ -48,6 +69,7 @@ func init() { } ExampleValues[reflect.TypeOf(c)] = c + ExampleValues[reflect.TypeOf(&c)] = &c c2, err := cid.Decode("bafy2bzacebp3shtrn43k7g3unredz7fxn4gj533d3o43tqn2p2ipxxhrvchve") if err != nil { @@ -65,17 +87,31 @@ func init() { ExampleValues[reflect.TypeOf(addr)] = addr - pid, err := peer.IDB58Decode("12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf") + pid, err := peer.Decode("12D3KooWGzxzKZYveHXtpG6AsrUJBcWxHBFS2HsEoGTxrMLvKXtf") if err != nil { panic(err) } addExample(pid) + addExample(&pid) + + storeIDExample := imports.ID(50) + textSelExample := textselector.Expression("Links/21/Hash/Links/42/Hash") + apiSelExample := api.Selector("Links/21/Hash/Links/42/Hash") + clientEvent := retrievalmarket.ClientEventDealAccepted + + block := blocks.Block(&blocks.BasicBlock{}) + ExampleValues[reflect.TypeOf(&block).Elem()] = block addExample(bitfield.NewFromSet([]uint64{5})) - addExample(abi.RegisteredProof_StackedDRG32GiBPoSt) + addExample(abi.RegisteredSealProof_StackedDrg32GiBV1_1) + addExample(abi.RegisteredPoStProof_StackedDrgWindow32GiBV1) addExample(abi.ChainEpoch(10101)) addExample(crypto.SigTypeBLS) + addExample(types.KTBLS) addExample(int64(9)) + addExample(12.3) + addExample(123) + addExample(uintptr(0)) addExample(abi.MethodNum(1)) addExample(exitcode.ExitCode(0)) addExample(crypto.DomainSeparationTag_ElectionProofProduction) @@ -83,28 +119,78 @@ func init() { addExample(abi.UnpaddedPieceSize(1024)) addExample(abi.UnpaddedPieceSize(1024).Padded()) addExample(abi.DealID(5432)) - addExample(filestore.StatusFileChanged) addExample(abi.SectorNumber(9)) addExample(abi.SectorSize(32 * 1024 * 1024 * 1024)) addExample(api.MpoolChange(0)) addExample(network.Connected) addExample(dtypes.NetworkName("lotus")) addExample(api.SyncStateStage(1)) - addExample(build.APIVersion) + addExample(api.FullAPIVersion1) addExample(api.PCHInbound) addExample(time.Minute) - addExample(&types.ExecutionResult{ - Msg: exampleValue(reflect.TypeOf(&types.Message{})).(*types.Message), - MsgRct: exampleValue(reflect.TypeOf(&types.MessageReceipt{})).(*types.MessageReceipt), + addExample(graphsync.NewRequestID()) + addExample(datatransfer.TransferID(3)) + addExample(datatransfer.Ongoing) + addExample(storeIDExample) + addExample(&storeIDExample) + addExample(clientEvent) + addExample(&clientEvent) + addExample(retrievalmarket.ClientEventDealAccepted) + addExample(retrievalmarket.DealStatusNew) + addExample(&textSelExample) + addExample(&apiSelExample) + addExample(network.ReachabilityPublic) + addExample(build.TestNetworkVersion) + allocationId := verifreg.AllocationId(0) + addExample(allocationId) + addExample(&allocationId) + addExample(map[verifreg.AllocationId]verifreg.Allocation{}) + claimId := verifreg.ClaimId(0) + addExample(claimId) + addExample(&claimId) + addExample(map[verifreg.ClaimId]verifreg.Claim{}) + addExample(map[string]int{"name": 42}) + addExample(map[string]time.Time{"name": time.Unix(1615243938, 0).UTC()}) + addExample(&types.ExecutionTrace{ + Msg: ExampleValue("init", reflect.TypeOf(types.MessageTrace{}), nil).(types.MessageTrace), + MsgRct: ExampleValue("init", reflect.TypeOf(types.ReturnTrace{}), nil).(types.ReturnTrace), }) addExample(map[string]types.Actor{ - "t01236": exampleValue(reflect.TypeOf(types.Actor{})).(types.Actor), + "t01236": ExampleValue("init", reflect.TypeOf(types.Actor{}), nil).(types.Actor), }) addExample(map[string]api.MarketDeal{ - "t026363": exampleValue(reflect.TypeOf(api.MarketDeal{})).(api.MarketDeal), + "t026363": ExampleValue("init", reflect.TypeOf(api.MarketDeal{}), nil).(api.MarketDeal), }) + addExample(map[string]*api.MarketDeal{ + "t026363": ExampleValue("init", reflect.TypeOf(&api.MarketDeal{}), nil).(*api.MarketDeal), + }) + addExample(map[string]api.MarketBalance{ - "t026363": exampleValue(reflect.TypeOf(api.MarketBalance{})).(api.MarketBalance), + "t026363": ExampleValue("init", reflect.TypeOf(api.MarketBalance{}), nil).(api.MarketBalance), + }) + addExample(map[string]*pubsub.TopicScoreSnapshot{ + "/blocks": { + TimeInMesh: time.Minute, + FirstMessageDeliveries: 122, + MeshMessageDeliveries: 1234, + InvalidMessageDeliveries: 3, + }, + }) + addExample(map[string]metrics.Stats{ + "12D3KooWSXmXLJmBR1M7i9RW9GQPNUhZSzXKzxDHWtAgNuJAbyEJ": { + RateIn: 100, + RateOut: 50, + TotalIn: 174000, + TotalOut: 12500, + }, + }) + addExample(map[protocol.ID]metrics.Stats{ + "/fil/hello/1.0.0": { + RateIn: 100, + RateOut: 50, + TotalIn: 174000, + TotalOut: 12500, + }, }) maddr, err := multiaddr.NewMultiaddr("/ip4/52.36.61.156/tcp/1347/p2p/12D3KooWFETiESTf1v4PGUvtnxMAcEFMzLZbJGg4tjWfGEimYior") @@ -115,9 +201,255 @@ func init() { // because reflect.TypeOf(maddr) returns the concrete type... ExampleValues[reflect.TypeOf(struct{ A multiaddr.Multiaddr }{}).Field(0).Type] = maddr + // miner specific + addExample(filestore.Path(".lotusminer/fstmp123")) + si := uint64(12) + addExample(&si) + addExample(retrievalmarket.DealID(5)) + addExample(abi.ActorID(1000)) + addExample(map[string]cid.Cid{}) + addExample(map[string][]api.SealedRef{ + "98000": { + api.SealedRef{ + SectorID: 100, + Offset: 10 << 20, + Size: 1 << 20, + }, + }, + }) + addExample(api.SectorState(sealing.Proving)) + addExample(storiface.ID("76f1988b-ef30-4d7e-b3ec-9a627f4ba5a8")) + addExample(storiface.FTUnsealed) + addExample(storiface.PathSealing) + addExample(map[storiface.ID][]storiface.Decl{ + "76f1988b-ef30-4d7e-b3ec-9a627f4ba5a8": { + { + SectorID: abi.SectorID{Miner: 1000, Number: 100}, + SectorFileType: storiface.FTSealed, + }, + }, + }) + addExample(map[storiface.ID]string{ + "76f1988b-ef30-4d7e-b3ec-9a627f4ba5a8": "/data/path", + }) + addExample(map[uuid.UUID][]storiface.WorkerJob{ + uuid.MustParse("ef8d99a2-6865-4189-8ffa-9fef0f806eee"): { + { + ID: storiface.CallID{ + Sector: abi.SectorID{Miner: 1000, Number: 100}, + ID: uuid.MustParse("76081ba0-61bd-45a5-bc08-af05f1c26e5d"), + }, + Sector: abi.SectorID{Miner: 1000, Number: 100}, + Task: sealtasks.TTPreCommit2, + RunWait: 0, + Start: time.Unix(1605172927, 0).UTC(), + Hostname: "host", + }, + }, + }) + addExample(map[uuid.UUID]storiface.WorkerStats{ + uuid.MustParse("ef8d99a2-6865-4189-8ffa-9fef0f806eee"): { + Info: storiface.WorkerInfo{ + Hostname: "host", + Resources: storiface.WorkerResources{ + MemPhysical: 256 << 30, + MemUsed: 2 << 30, + MemSwap: 120 << 30, + MemSwapUsed: 2 << 30, + CPUs: 64, + GPUs: []string{"aGPU 1337"}, + Resources: storiface.ResourceTable, + }, + }, + Enabled: true, + MemUsedMin: 0, + MemUsedMax: 0, + GpuUsed: 0, + CpuUse: 0, + }, + }) + addExample(storiface.ErrorCode(0)) + addExample(map[abi.SectorNumber]string{ + 123: "can't acquire read lock", + }) + addExample(json.RawMessage(`"json raw message"`)) + addExample(map[api.SectorState]int{ + api.SectorState(sealing.Proving): 120, + }) + addExample([]abi.SectorNumber{123, 124}) + addExample([]storiface.SectorLock{ + { + Sector: abi.SectorID{Number: 123, Miner: 1000}, + Write: [storiface.FileTypes]uint{0, 0, 1}, + Read: [storiface.FileTypes]uint{2, 3, 0}, + }, + }) + storifaceid := storiface.ID("1399aa04-2625-44b1-bad4-bd07b59b22c4") + addExample(&storifaceid) + + // worker specific + addExample(storiface.AcquireMove) + addExample(storiface.UnpaddedByteIndex(abi.PaddedPieceSize(1 << 20).Unpadded())) + addExample(map[sealtasks.TaskType]struct{}{ + sealtasks.TTPreCommit2: {}, + }) + addExample(sealtasks.TTCommit2) + addExample(apitypes.OpenRPCDocument{ + "openrpc": "1.2.6", + "info": map[string]interface{}{ + "title": "Lotus RPC API", + "version": "1.2.1/generated=2020-11-22T08:22:42-06:00", + }, + "methods": []interface{}{}, + }, + ) + + addExample(api.CheckStatusCode(0)) + addExample(map[string]interface{}{"abc": 123}) + addExample(api.MinerSubsystems{ + api.SubsystemMining, + api.SubsystemSealing, + api.SubsystemSectorStorage, + api.SubsystemMarkets, + }) + addExample(api.DagstoreShardResult{ + Key: "baga6ea4seaqecmtz7iak33dsfshi627abz4i4665dfuzr3qfs4bmad6dx3iigdq", + Error: "", + }) + addExample(api.DagstoreShardInfo{ + Key: "baga6ea4seaqecmtz7iak33dsfshi627abz4i4665dfuzr3qfs4bmad6dx3iigdq", + State: "ShardStateAvailable", + Error: "", + }) + addExample(storiface.ResourceTable) + addExample(network.ScopeStat{ + Memory: 123, + NumStreamsInbound: 1, + NumStreamsOutbound: 2, + NumConnsInbound: 3, + NumConnsOutbound: 4, + NumFD: 5, + }) + addExample(map[string]network.ScopeStat{ + "abc": { + Memory: 123, + NumStreamsInbound: 1, + NumStreamsOutbound: 2, + NumConnsInbound: 3, + NumConnsOutbound: 4, + NumFD: 5, + }, + }) + addExample(api.NetLimit{ + Memory: 123, + StreamsInbound: 1, + StreamsOutbound: 2, + Streams: 3, + ConnsInbound: 3, + ConnsOutbound: 4, + Conns: 4, + FD: 5, + }) + + addExample(map[string]bitfield.BitField{ + "": bitfield.NewFromSet([]uint64{5, 6, 7, 10}), + }) + addExample(&api.RaftStateData{ + NonceMap: make(map[address.Address]uint64), + MsgUuids: make(map[uuid.UUID]*types.SignedMessage), + }) + + addExample(http.Header{ + "Authorization": []string{"Bearer ey.."}, + }) + + addExample(map[storiface.SectorFileType]storiface.SectorLocation{ + storiface.FTSealed: { + Local: false, + URL: "https://example.com/sealingservice/sectors/s-f0123-12345", + Headers: nil, + }, + }) + + ethint := ethtypes.EthUint64(5) + addExample(ethint) + addExample(ðint) + + ethaddr, _ := ethtypes.ParseEthAddress("0x5CbEeCF99d3fDB3f25E309Cc264f240bb0664031") + addExample(ethaddr) + addExample(ðaddr) + + ethhash, _ := ethtypes.EthHashFromCid(c) + addExample(ethhash) + addExample(ðhash) + + ethFeeHistoryReward := [][]ethtypes.EthBigInt{} + addExample(ðFeeHistoryReward) + + addExample(&uuid.UUID{}) + + filterid := ethtypes.EthFilterID(ethhash) + addExample(filterid) + addExample(&filterid) + + subid := ethtypes.EthSubscriptionID(ethhash) + addExample(subid) + addExample(&subid) + + pstring := func(s string) *string { return &s } + addExample(ðtypes.EthFilterSpec{ + FromBlock: pstring("2301220"), + Address: []ethtypes.EthAddress{ethaddr}, + }) + + percent := types.Percent(123) + addExample(percent) + addExample(&percent) } -func exampleValue(t reflect.Type) interface{} { +func GetAPIType(name, pkg string) (i interface{}, t reflect.Type, permStruct []reflect.Type) { + switch pkg { + case "api": // latest + switch name { + case "FullNode": + i = &api.FullNodeStruct{} + t = reflect.TypeOf(new(struct{ api.FullNode })).Elem() + permStruct = append(permStruct, reflect.TypeOf(api.FullNodeStruct{}.Internal)) + permStruct = append(permStruct, reflect.TypeOf(api.CommonStruct{}.Internal)) + permStruct = append(permStruct, reflect.TypeOf(api.NetStruct{}.Internal)) + case "StorageMiner": + i = &api.StorageMinerStruct{} + t = reflect.TypeOf(new(struct{ api.StorageMiner })).Elem() + permStruct = append(permStruct, reflect.TypeOf(api.StorageMinerStruct{}.Internal)) + permStruct = append(permStruct, reflect.TypeOf(api.CommonStruct{}.Internal)) + permStruct = append(permStruct, reflect.TypeOf(api.NetStruct{}.Internal)) + case "Worker": + i = &api.WorkerStruct{} + t = reflect.TypeOf(new(struct{ api.Worker })).Elem() + permStruct = append(permStruct, reflect.TypeOf(api.WorkerStruct{}.Internal)) + case "Gateway": + i = &api.GatewayStruct{} + t = reflect.TypeOf(new(struct{ api.Gateway })).Elem() + permStruct = append(permStruct, reflect.TypeOf(api.GatewayStruct{}.Internal)) + default: + panic("unknown type") + } + case "v0api": + switch name { + case "FullNode": + i = v0api.FullNodeStruct{} + t = reflect.TypeOf(new(struct{ v0api.FullNode })).Elem() + permStruct = append(permStruct, reflect.TypeOf(v0api.FullNodeStruct{}.Internal)) + permStruct = append(permStruct, reflect.TypeOf(v0api.CommonStruct{}.Internal)) + permStruct = append(permStruct, reflect.TypeOf(v0api.NetStruct{}.Internal)) + default: + panic("unknown type") + } + } + return +} + +func ExampleValue(method string, t, parent reflect.Type) interface{} { v, ok := ExampleValues[t] if ok { return v @@ -126,41 +458,45 @@ func exampleValue(t reflect.Type) interface{} { switch t.Kind() { case reflect.Slice: out := reflect.New(t).Elem() - reflect.Append(out, reflect.ValueOf(exampleValue(t.Elem()))) + out = reflect.Append(out, reflect.ValueOf(ExampleValue(method, t.Elem(), t))) return out.Interface() case reflect.Chan: - return exampleValue(t.Elem()) + return ExampleValue(method, t.Elem(), nil) case reflect.Struct: - es := exampleStruct(t) + es := exampleStruct(method, t, parent) v := reflect.ValueOf(es).Elem().Interface() ExampleValues[t] = v return v case reflect.Array: out := reflect.New(t).Elem() for i := 0; i < t.Len(); i++ { - out.Index(i).Set(reflect.ValueOf(exampleValue(t.Elem()))) + out.Index(i).Set(reflect.ValueOf(ExampleValue(method, t.Elem(), t))) } return out.Interface() case reflect.Ptr: if t.Elem().Kind() == reflect.Struct { - es := exampleStruct(t.Elem()) - //ExampleValues[t] = es + es := exampleStruct(method, t.Elem(), t) + ExampleValues[t] = es return es } case reflect.Interface: return struct{}{} } - panic(fmt.Sprintf("No example value for type: %s", t)) + panic(fmt.Sprintf("No example value for type: %s (method '%s')", t, method)) } -func exampleStruct(t reflect.Type) interface{} { +func exampleStruct(method string, t, parent reflect.Type) interface{} { ns := reflect.New(t) for i := 0; i < t.NumField(); i++ { f := t.Field(i) - if strings.Title(f.Name) == f.Name { - ns.Elem().Field(i).Set(reflect.ValueOf(exampleValue(f.Type))) + if f.Type == parent { + continue + } + + if f.IsExported() { + ns.Elem().Field(i).Set(reflect.ValueOf(ExampleValue(method, f.Type, t))) } } @@ -168,6 +504,7 @@ func exampleStruct(t reflect.Type) interface{} { } type Visitor struct { + Root string Methods map[string]ast.Node } @@ -177,7 +514,7 @@ func (v *Visitor) Visit(node ast.Node) ast.Visitor { return v } - if st.Name.Name != "FullNode" { + if st.Name.Name != v.Root { return nil } @@ -191,33 +528,43 @@ func (v *Visitor) Visit(node ast.Node) ast.Visitor { return v } -const noComment = "There are not yet any comments for this method." - -func parseApiASTInfo() (map[string]string, map[string]string) { +const NoComment = "There are not yet any comments for this method." +func ParseApiASTInfo(apiFile, iface, pkg, dir string) (comments map[string]string, groupDocs map[string]string) { //nolint:golint fset := token.NewFileSet() - pkgs, err := parser.ParseDir(fset, "./api", nil, parser.AllErrors|parser.ParseComments) + apiDir, err := filepath.Abs(dir) + if err != nil { + fmt.Println("./api filepath absolute error: ", err) + return + } + apiFile, err = filepath.Abs(apiFile) + if err != nil { + fmt.Println("filepath absolute error: ", err, "file:", apiFile) + return + } + pkgs, err := parser.ParseDir(fset, apiDir, nil, parser.AllErrors|parser.ParseComments) if err != nil { fmt.Println("parse error: ", err) + return } - ap := pkgs["api"] + ap := pkgs[pkg] - f := ap.Files["api/api_full.go"] + f := ap.Files[apiFile] cmap := ast.NewCommentMap(fset, f, f.Comments) - v := &Visitor{make(map[string]ast.Node)} - ast.Walk(v, pkgs["api"]) + v := &Visitor{iface, make(map[string]ast.Node)} + ast.Walk(v, ap) - groupDocs := make(map[string]string) - out := make(map[string]string) + comments = make(map[string]string) + groupDocs = make(map[string]string) for mn, node := range v.Methods { - cs := cmap.Filter(node).Comments() - if len(cs) == 0 { - out[mn] = noComment + filteredComments := cmap.Filter(node).Comments() + if len(filteredComments) == 0 { + comments[mn] = NoComment } else { - for _, c := range cs { + for _, c := range filteredComments { if strings.HasPrefix(c.Text(), "MethodGroup:") { parts := strings.Split(c.Text(), "\n") groupName := strings.TrimSpace(parts[0][12:]) @@ -228,15 +575,19 @@ func parseApiASTInfo() (map[string]string, map[string]string) { } } - last := cs[len(cs)-1].Text() + l := len(filteredComments) - 1 + if len(filteredComments) > 1 { + l = len(filteredComments) - 2 + } + last := filteredComments[l].Text() if !strings.HasPrefix(last, "MethodGroup:") { - out[mn] = last + comments[mn] = last } else { - out[mn] = noComment + comments[mn] = NoComment } } } - return out, groupDocs + return comments, groupDocs } type MethodGroup struct { @@ -252,7 +603,7 @@ type Method struct { ResponseExample string } -func methodGroupFromName(mn string) string { +func MethodGroupFromName(mn string) string { i := strings.IndexFunc(mn[1:], func(r rune) bool { return unicode.IsUpper(r) }) @@ -261,78 +612,3 @@ func methodGroupFromName(mn string) string { } return mn[:i+1] } - -func main() { - - comments, groupComments := parseApiASTInfo() - - groups := make(map[string]*MethodGroup) - - var api struct{ api.FullNode } - t := reflect.TypeOf(api) - for i := 0; i < t.NumMethod(); i++ { - m := t.Method(i) - - groupName := methodGroupFromName(m.Name) - - g, ok := groups[groupName] - if !ok { - g = new(MethodGroup) - g.Header = groupComments[groupName] - g.GroupName = groupName - groups[groupName] = g - } - - var args []interface{} - ft := m.Func.Type() - for j := 2; j < ft.NumIn(); j++ { - inp := ft.In(j) - args = append(args, exampleValue(inp)) - } - - v, err := json.Marshal(args) - if err != nil { - panic(err) - } - - outv := exampleValue(ft.Out(0)) - - ov, err := json.Marshal(outv) - if err != nil { - panic(err) - } - - g.Methods = append(g.Methods, &Method{ - Name: m.Name, - Comment: comments[m.Name], - InputExample: string(v), - ResponseExample: string(ov), - }) - } - - var groupslice []*MethodGroup - for _, g := range groups { - groupslice = append(groupslice, g) - } - - sort.Slice(groupslice, func(i, j int) bool { - return groupslice[i].GroupName < groupslice[j].GroupName - }) - - for _, g := range groupslice { - fmt.Printf("## %s\n", g.GroupName) - fmt.Printf("%s\n\n", g.Header) - - sort.Slice(g.Methods, func(i, j int) bool { - return g.Methods[i].Name < g.Methods[j].Name - }) - - for _, m := range g.Methods { - fmt.Printf("### %s\n", m.Name) - fmt.Printf("%s\n\n", m.Comment) - - fmt.Printf("Inputs: `%s`\n\n", m.InputExample) - fmt.Printf("Response: `%s`\n\n", m.ResponseExample) - } - } -} diff --git a/api/eth_aliases.go b/api/eth_aliases.go new file mode 100644 index 000000000..ca0f861ac --- /dev/null +++ b/api/eth_aliases.go @@ -0,0 +1,47 @@ +package api + +import apitypes "github.com/filecoin-project/lotus/api/types" + +func CreateEthRPCAliases(as apitypes.Aliaser) { + // TODO: maybe use reflect to automatically register all the eth aliases + as.AliasMethod("eth_accounts", "Filecoin.EthAccounts") + as.AliasMethod("eth_blockNumber", "Filecoin.EthBlockNumber") + as.AliasMethod("eth_getBlockTransactionCountByNumber", "Filecoin.EthGetBlockTransactionCountByNumber") + as.AliasMethod("eth_getBlockTransactionCountByHash", "Filecoin.EthGetBlockTransactionCountByHash") + + as.AliasMethod("eth_getBlockByHash", "Filecoin.EthGetBlockByHash") + as.AliasMethod("eth_getBlockByNumber", "Filecoin.EthGetBlockByNumber") + as.AliasMethod("eth_getTransactionByHash", "Filecoin.EthGetTransactionByHash") + as.AliasMethod("eth_getTransactionCount", "Filecoin.EthGetTransactionCount") + as.AliasMethod("eth_getTransactionReceipt", "Filecoin.EthGetTransactionReceipt") + as.AliasMethod("eth_getTransactionByBlockHashAndIndex", "Filecoin.EthGetTransactionByBlockHashAndIndex") + as.AliasMethod("eth_getTransactionByBlockNumberAndIndex", "Filecoin.EthGetTransactionByBlockNumberAndIndex") + + as.AliasMethod("eth_getCode", "Filecoin.EthGetCode") + as.AliasMethod("eth_getStorageAt", "Filecoin.EthGetStorageAt") + as.AliasMethod("eth_getBalance", "Filecoin.EthGetBalance") + as.AliasMethod("eth_chainId", "Filecoin.EthChainId") + as.AliasMethod("eth_syncing", "Filecoin.EthSyncing") + as.AliasMethod("eth_feeHistory", "Filecoin.EthFeeHistory") + as.AliasMethod("eth_protocolVersion", "Filecoin.EthProtocolVersion") + as.AliasMethod("eth_maxPriorityFeePerGas", "Filecoin.EthMaxPriorityFeePerGas") + as.AliasMethod("eth_gasPrice", "Filecoin.EthGasPrice") + as.AliasMethod("eth_sendRawTransaction", "Filecoin.EthSendRawTransaction") + as.AliasMethod("eth_estimateGas", "Filecoin.EthEstimateGas") + as.AliasMethod("eth_call", "Filecoin.EthCall") + + as.AliasMethod("eth_getLogs", "Filecoin.EthGetLogs") + as.AliasMethod("eth_getFilterChanges", "Filecoin.EthGetFilterChanges") + as.AliasMethod("eth_getFilterLogs", "Filecoin.EthGetFilterLogs") + as.AliasMethod("eth_newFilter", "Filecoin.EthNewFilter") + as.AliasMethod("eth_newBlockFilter", "Filecoin.EthNewBlockFilter") + as.AliasMethod("eth_newPendingTransactionFilter", "Filecoin.EthNewPendingTransactionFilter") + as.AliasMethod("eth_uninstallFilter", "Filecoin.EthUninstallFilter") + as.AliasMethod("eth_subscribe", "Filecoin.EthSubscribe") + as.AliasMethod("eth_unsubscribe", "Filecoin.EthUnsubscribe") + + as.AliasMethod("net_version", "Filecoin.NetVersion") + as.AliasMethod("net_listening", "Filecoin.NetListening") + + as.AliasMethod("web3_clientVersion", "Filecoin.Web3ClientVersion") +} diff --git a/api/miner_subsystems.go b/api/miner_subsystems.go new file mode 100644 index 000000000..a77de7e3c --- /dev/null +++ b/api/miner_subsystems.go @@ -0,0 +1,79 @@ +package api + +import ( + "encoding/json" +) + +// MinerSubsystem represents a miner subsystem. Int and string values are not +// guaranteed to be stable over time is not +// guaranteed to be stable over time. +type MinerSubsystem int + +const ( + // SubsystemUnknown is a placeholder for the zero value. It should never + // be used. + SubsystemUnknown MinerSubsystem = iota + // SubsystemMarkets signifies the storage and retrieval + // deal-making subsystem. + SubsystemMarkets + // SubsystemMining signifies the mining subsystem. + SubsystemMining + // SubsystemSealing signifies the sealing subsystem. + SubsystemSealing + // SubsystemSectorStorage signifies the sector storage subsystem. + SubsystemSectorStorage +) + +var MinerSubsystemToString = map[MinerSubsystem]string{ + SubsystemUnknown: "Unknown", + SubsystemMarkets: "Markets", + SubsystemMining: "Mining", + SubsystemSealing: "Sealing", + SubsystemSectorStorage: "SectorStorage", +} + +var MinerSubsystemToID = map[string]MinerSubsystem{ + "Unknown": SubsystemUnknown, + "Markets": SubsystemMarkets, + "Mining": SubsystemMining, + "Sealing": SubsystemSealing, + "SectorStorage": SubsystemSectorStorage, +} + +func (ms MinerSubsystem) MarshalJSON() ([]byte, error) { + return json.Marshal(MinerSubsystemToString[ms]) +} + +func (ms *MinerSubsystem) UnmarshalJSON(b []byte) error { + var j string + err := json.Unmarshal(b, &j) + if err != nil { + return err + } + s, ok := MinerSubsystemToID[j] + if !ok { + *ms = SubsystemUnknown + } else { + *ms = s + } + return nil +} + +type MinerSubsystems []MinerSubsystem + +func (ms MinerSubsystems) Has(entry MinerSubsystem) bool { + for _, v := range ms { + if v == entry { + return true + } + } + return false +} + +func (ms MinerSubsystem) String() string { + s, ok := MinerSubsystemToString[ms] + if !ok { + return MinerSubsystemToString[SubsystemUnknown] + } + return s +} diff --git a/api/mocks/mock_full.go b/api/mocks/mock_full.go new file mode 100644 index 000000000..0b0e3ca4c --- /dev/null +++ b/api/mocks/mock_full.go @@ -0,0 +1,4217 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/filecoin-project/lotus/api (interfaces: FullNode) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + json "encoding/json" + reflect "reflect" + time "time" + + gomock "github.com/golang/mock/gomock" + uuid "github.com/google/uuid" + blocks "github.com/ipfs/go-block-format" + cid "github.com/ipfs/go-cid" + metrics "github.com/libp2p/go-libp2p/core/metrics" + network0 "github.com/libp2p/go-libp2p/core/network" + peer "github.com/libp2p/go-libp2p/core/peer" + protocol "github.com/libp2p/go-libp2p/core/protocol" + + address "github.com/filecoin-project/go-address" + bitfield "github.com/filecoin-project/go-bitfield" + datatransfer "github.com/filecoin-project/go-data-transfer/v2" + retrievalmarket "github.com/filecoin-project/go-fil-markets/retrievalmarket" + jsonrpc "github.com/filecoin-project/go-jsonrpc" + auth "github.com/filecoin-project/go-jsonrpc/auth" + abi "github.com/filecoin-project/go-state-types/abi" + big "github.com/filecoin-project/go-state-types/big" + paych "github.com/filecoin-project/go-state-types/builtin/v8/paych" + miner "github.com/filecoin-project/go-state-types/builtin/v9/miner" + verifreg "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" + crypto "github.com/filecoin-project/go-state-types/crypto" + dline "github.com/filecoin-project/go-state-types/dline" + network "github.com/filecoin-project/go-state-types/network" + + api "github.com/filecoin-project/lotus/api" + apitypes "github.com/filecoin-project/lotus/api/types" + miner0 "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + types "github.com/filecoin-project/lotus/chain/types" + ethtypes "github.com/filecoin-project/lotus/chain/types/ethtypes" + alerting "github.com/filecoin-project/lotus/journal/alerting" + dtypes "github.com/filecoin-project/lotus/node/modules/dtypes" + imports "github.com/filecoin-project/lotus/node/repo/imports" +) + +// MockFullNode is a mock of FullNode interface. +type MockFullNode struct { + ctrl *gomock.Controller + recorder *MockFullNodeMockRecorder +} + +// MockFullNodeMockRecorder is the mock recorder for MockFullNode. +type MockFullNodeMockRecorder struct { + mock *MockFullNode +} + +// NewMockFullNode creates a new mock instance. +func NewMockFullNode(ctrl *gomock.Controller) *MockFullNode { + mock := &MockFullNode{ctrl: ctrl} + mock.recorder = &MockFullNodeMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockFullNode) EXPECT() *MockFullNodeMockRecorder { + return m.recorder +} + +// AuthNew mocks base method. +func (m *MockFullNode) AuthNew(arg0 context.Context, arg1 []auth.Permission) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AuthNew", arg0, arg1) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AuthNew indicates an expected call of AuthNew. +func (mr *MockFullNodeMockRecorder) AuthNew(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthNew", reflect.TypeOf((*MockFullNode)(nil).AuthNew), arg0, arg1) +} + +// AuthVerify mocks base method. +func (m *MockFullNode) AuthVerify(arg0 context.Context, arg1 string) ([]auth.Permission, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AuthVerify", arg0, arg1) + ret0, _ := ret[0].([]auth.Permission) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AuthVerify indicates an expected call of AuthVerify. +func (mr *MockFullNodeMockRecorder) AuthVerify(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthVerify", reflect.TypeOf((*MockFullNode)(nil).AuthVerify), arg0, arg1) +} + +// ChainBlockstoreInfo mocks base method. +func (m *MockFullNode) ChainBlockstoreInfo(arg0 context.Context) (map[string]interface{}, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainBlockstoreInfo", arg0) + ret0, _ := ret[0].(map[string]interface{}) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainBlockstoreInfo indicates an expected call of ChainBlockstoreInfo. +func (mr *MockFullNodeMockRecorder) ChainBlockstoreInfo(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainBlockstoreInfo", reflect.TypeOf((*MockFullNode)(nil).ChainBlockstoreInfo), arg0) +} + +// ChainCheckBlockstore mocks base method. +func (m *MockFullNode) ChainCheckBlockstore(arg0 context.Context) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainCheckBlockstore", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// ChainCheckBlockstore indicates an expected call of ChainCheckBlockstore. +func (mr *MockFullNodeMockRecorder) ChainCheckBlockstore(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainCheckBlockstore", reflect.TypeOf((*MockFullNode)(nil).ChainCheckBlockstore), arg0) +} + +// ChainDeleteObj mocks base method. +func (m *MockFullNode) ChainDeleteObj(arg0 context.Context, arg1 cid.Cid) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainDeleteObj", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// ChainDeleteObj indicates an expected call of ChainDeleteObj. +func (mr *MockFullNodeMockRecorder) ChainDeleteObj(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainDeleteObj", reflect.TypeOf((*MockFullNode)(nil).ChainDeleteObj), arg0, arg1) +} + +// ChainExport mocks base method. +func (m *MockFullNode) ChainExport(arg0 context.Context, arg1 abi.ChainEpoch, arg2 bool, arg3 types.TipSetKey) (<-chan []byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainExport", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(<-chan []byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainExport indicates an expected call of ChainExport. +func (mr *MockFullNodeMockRecorder) ChainExport(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainExport", reflect.TypeOf((*MockFullNode)(nil).ChainExport), arg0, arg1, arg2, arg3) +} + +// ChainExportRangeInternal mocks base method. +func (m *MockFullNode) ChainExportRangeInternal(arg0 context.Context, arg1, arg2 types.TipSetKey, arg3 api.ChainExportConfig) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainExportRangeInternal", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(error) + return ret0 +} + +// ChainExportRangeInternal indicates an expected call of ChainExportRangeInternal. +func (mr *MockFullNodeMockRecorder) ChainExportRangeInternal(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainExportRangeInternal", reflect.TypeOf((*MockFullNode)(nil).ChainExportRangeInternal), arg0, arg1, arg2, arg3) +} + +// ChainGetBlock mocks base method. +func (m *MockFullNode) ChainGetBlock(arg0 context.Context, arg1 cid.Cid) (*types.BlockHeader, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainGetBlock", arg0, arg1) + ret0, _ := ret[0].(*types.BlockHeader) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainGetBlock indicates an expected call of ChainGetBlock. +func (mr *MockFullNodeMockRecorder) ChainGetBlock(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainGetBlock", reflect.TypeOf((*MockFullNode)(nil).ChainGetBlock), arg0, arg1) +} + +// ChainGetBlockMessages mocks base method. +func (m *MockFullNode) ChainGetBlockMessages(arg0 context.Context, arg1 cid.Cid) (*api.BlockMessages, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainGetBlockMessages", arg0, arg1) + ret0, _ := ret[0].(*api.BlockMessages) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainGetBlockMessages indicates an expected call of ChainGetBlockMessages. +func (mr *MockFullNodeMockRecorder) ChainGetBlockMessages(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainGetBlockMessages", reflect.TypeOf((*MockFullNode)(nil).ChainGetBlockMessages), arg0, arg1) +} + +// ChainGetEvents mocks base method. +func (m *MockFullNode) ChainGetEvents(arg0 context.Context, arg1 cid.Cid) ([]types.Event, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainGetEvents", arg0, arg1) + ret0, _ := ret[0].([]types.Event) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainGetEvents indicates an expected call of ChainGetEvents. +func (mr *MockFullNodeMockRecorder) ChainGetEvents(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainGetEvents", reflect.TypeOf((*MockFullNode)(nil).ChainGetEvents), arg0, arg1) +} + +// ChainGetGenesis mocks base method. +func (m *MockFullNode) ChainGetGenesis(arg0 context.Context) (*types.TipSet, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainGetGenesis", arg0) + ret0, _ := ret[0].(*types.TipSet) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainGetGenesis indicates an expected call of ChainGetGenesis. +func (mr *MockFullNodeMockRecorder) ChainGetGenesis(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainGetGenesis", reflect.TypeOf((*MockFullNode)(nil).ChainGetGenesis), arg0) +} + +// ChainGetMessage mocks base method. +func (m *MockFullNode) ChainGetMessage(arg0 context.Context, arg1 cid.Cid) (*types.Message, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainGetMessage", arg0, arg1) + ret0, _ := ret[0].(*types.Message) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainGetMessage indicates an expected call of ChainGetMessage. +func (mr *MockFullNodeMockRecorder) ChainGetMessage(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainGetMessage", reflect.TypeOf((*MockFullNode)(nil).ChainGetMessage), arg0, arg1) +} + +// ChainGetMessagesInTipset mocks base method. +func (m *MockFullNode) ChainGetMessagesInTipset(arg0 context.Context, arg1 types.TipSetKey) ([]api.Message, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainGetMessagesInTipset", arg0, arg1) + ret0, _ := ret[0].([]api.Message) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainGetMessagesInTipset indicates an expected call of ChainGetMessagesInTipset. +func (mr *MockFullNodeMockRecorder) ChainGetMessagesInTipset(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainGetMessagesInTipset", reflect.TypeOf((*MockFullNode)(nil).ChainGetMessagesInTipset), arg0, arg1) +} + +// ChainGetNode mocks base method. +func (m *MockFullNode) ChainGetNode(arg0 context.Context, arg1 string) (*api.IpldObject, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainGetNode", arg0, arg1) + ret0, _ := ret[0].(*api.IpldObject) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainGetNode indicates an expected call of ChainGetNode. +func (mr *MockFullNodeMockRecorder) ChainGetNode(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainGetNode", reflect.TypeOf((*MockFullNode)(nil).ChainGetNode), arg0, arg1) +} + +// ChainGetParentMessages mocks base method. +func (m *MockFullNode) ChainGetParentMessages(arg0 context.Context, arg1 cid.Cid) ([]api.Message, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainGetParentMessages", arg0, arg1) + ret0, _ := ret[0].([]api.Message) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainGetParentMessages indicates an expected call of ChainGetParentMessages. +func (mr *MockFullNodeMockRecorder) ChainGetParentMessages(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainGetParentMessages", reflect.TypeOf((*MockFullNode)(nil).ChainGetParentMessages), arg0, arg1) +} + +// ChainGetParentReceipts mocks base method. +func (m *MockFullNode) ChainGetParentReceipts(arg0 context.Context, arg1 cid.Cid) ([]*types.MessageReceipt, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainGetParentReceipts", arg0, arg1) + ret0, _ := ret[0].([]*types.MessageReceipt) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainGetParentReceipts indicates an expected call of ChainGetParentReceipts. +func (mr *MockFullNodeMockRecorder) ChainGetParentReceipts(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainGetParentReceipts", reflect.TypeOf((*MockFullNode)(nil).ChainGetParentReceipts), arg0, arg1) +} + +// ChainGetPath mocks base method. +func (m *MockFullNode) ChainGetPath(arg0 context.Context, arg1, arg2 types.TipSetKey) ([]*api.HeadChange, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainGetPath", arg0, arg1, arg2) + ret0, _ := ret[0].([]*api.HeadChange) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainGetPath indicates an expected call of ChainGetPath. +func (mr *MockFullNodeMockRecorder) ChainGetPath(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainGetPath", reflect.TypeOf((*MockFullNode)(nil).ChainGetPath), arg0, arg1, arg2) +} + +// ChainGetTipSet mocks base method. +func (m *MockFullNode) ChainGetTipSet(arg0 context.Context, arg1 types.TipSetKey) (*types.TipSet, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainGetTipSet", arg0, arg1) + ret0, _ := ret[0].(*types.TipSet) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainGetTipSet indicates an expected call of ChainGetTipSet. +func (mr *MockFullNodeMockRecorder) ChainGetTipSet(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainGetTipSet", reflect.TypeOf((*MockFullNode)(nil).ChainGetTipSet), arg0, arg1) +} + +// ChainGetTipSetAfterHeight mocks base method. +func (m *MockFullNode) ChainGetTipSetAfterHeight(arg0 context.Context, arg1 abi.ChainEpoch, arg2 types.TipSetKey) (*types.TipSet, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainGetTipSetAfterHeight", arg0, arg1, arg2) + ret0, _ := ret[0].(*types.TipSet) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainGetTipSetAfterHeight indicates an expected call of ChainGetTipSetAfterHeight. +func (mr *MockFullNodeMockRecorder) ChainGetTipSetAfterHeight(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainGetTipSetAfterHeight", reflect.TypeOf((*MockFullNode)(nil).ChainGetTipSetAfterHeight), arg0, arg1, arg2) +} + +// ChainGetTipSetByHeight mocks base method. +func (m *MockFullNode) ChainGetTipSetByHeight(arg0 context.Context, arg1 abi.ChainEpoch, arg2 types.TipSetKey) (*types.TipSet, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainGetTipSetByHeight", arg0, arg1, arg2) + ret0, _ := ret[0].(*types.TipSet) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainGetTipSetByHeight indicates an expected call of ChainGetTipSetByHeight. +func (mr *MockFullNodeMockRecorder) ChainGetTipSetByHeight(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainGetTipSetByHeight", reflect.TypeOf((*MockFullNode)(nil).ChainGetTipSetByHeight), arg0, arg1, arg2) +} + +// ChainHasObj mocks base method. +func (m *MockFullNode) ChainHasObj(arg0 context.Context, arg1 cid.Cid) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainHasObj", arg0, arg1) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainHasObj indicates an expected call of ChainHasObj. +func (mr *MockFullNodeMockRecorder) ChainHasObj(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainHasObj", reflect.TypeOf((*MockFullNode)(nil).ChainHasObj), arg0, arg1) +} + +// ChainHead mocks base method. +func (m *MockFullNode) ChainHead(arg0 context.Context) (*types.TipSet, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainHead", arg0) + ret0, _ := ret[0].(*types.TipSet) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainHead indicates an expected call of ChainHead. +func (mr *MockFullNodeMockRecorder) ChainHead(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainHead", reflect.TypeOf((*MockFullNode)(nil).ChainHead), arg0) +} + +// ChainHotGC mocks base method. +func (m *MockFullNode) ChainHotGC(arg0 context.Context, arg1 api.HotGCOpts) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainHotGC", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// ChainHotGC indicates an expected call of ChainHotGC. +func (mr *MockFullNodeMockRecorder) ChainHotGC(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainHotGC", reflect.TypeOf((*MockFullNode)(nil).ChainHotGC), arg0, arg1) +} + +// ChainNotify mocks base method. +func (m *MockFullNode) ChainNotify(arg0 context.Context) (<-chan []*api.HeadChange, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainNotify", arg0) + ret0, _ := ret[0].(<-chan []*api.HeadChange) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainNotify indicates an expected call of ChainNotify. +func (mr *MockFullNodeMockRecorder) ChainNotify(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainNotify", reflect.TypeOf((*MockFullNode)(nil).ChainNotify), arg0) +} + +// ChainPrune mocks base method. +func (m *MockFullNode) ChainPrune(arg0 context.Context, arg1 api.PruneOpts) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainPrune", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// ChainPrune indicates an expected call of ChainPrune. +func (mr *MockFullNodeMockRecorder) ChainPrune(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainPrune", reflect.TypeOf((*MockFullNode)(nil).ChainPrune), arg0, arg1) +} + +// ChainPutObj mocks base method. +func (m *MockFullNode) ChainPutObj(arg0 context.Context, arg1 blocks.Block) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainPutObj", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// ChainPutObj indicates an expected call of ChainPutObj. +func (mr *MockFullNodeMockRecorder) ChainPutObj(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainPutObj", reflect.TypeOf((*MockFullNode)(nil).ChainPutObj), arg0, arg1) +} + +// ChainReadObj mocks base method. +func (m *MockFullNode) ChainReadObj(arg0 context.Context, arg1 cid.Cid) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainReadObj", arg0, arg1) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainReadObj indicates an expected call of ChainReadObj. +func (mr *MockFullNodeMockRecorder) ChainReadObj(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainReadObj", reflect.TypeOf((*MockFullNode)(nil).ChainReadObj), arg0, arg1) +} + +// ChainSetHead mocks base method. +func (m *MockFullNode) ChainSetHead(arg0 context.Context, arg1 types.TipSetKey) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainSetHead", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// ChainSetHead indicates an expected call of ChainSetHead. +func (mr *MockFullNodeMockRecorder) ChainSetHead(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainSetHead", reflect.TypeOf((*MockFullNode)(nil).ChainSetHead), arg0, arg1) +} + +// ChainStatObj mocks base method. +func (m *MockFullNode) ChainStatObj(arg0 context.Context, arg1, arg2 cid.Cid) (api.ObjStat, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainStatObj", arg0, arg1, arg2) + ret0, _ := ret[0].(api.ObjStat) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainStatObj indicates an expected call of ChainStatObj. +func (mr *MockFullNodeMockRecorder) ChainStatObj(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainStatObj", reflect.TypeOf((*MockFullNode)(nil).ChainStatObj), arg0, arg1, arg2) +} + +// ChainTipSetWeight mocks base method. +func (m *MockFullNode) ChainTipSetWeight(arg0 context.Context, arg1 types.TipSetKey) (big.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainTipSetWeight", arg0, arg1) + ret0, _ := ret[0].(big.Int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainTipSetWeight indicates an expected call of ChainTipSetWeight. +func (mr *MockFullNodeMockRecorder) ChainTipSetWeight(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainTipSetWeight", reflect.TypeOf((*MockFullNode)(nil).ChainTipSetWeight), arg0, arg1) +} + +// ClientCalcCommP mocks base method. +func (m *MockFullNode) ClientCalcCommP(arg0 context.Context, arg1 string) (*api.CommPRet, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientCalcCommP", arg0, arg1) + ret0, _ := ret[0].(*api.CommPRet) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientCalcCommP indicates an expected call of ClientCalcCommP. +func (mr *MockFullNodeMockRecorder) ClientCalcCommP(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientCalcCommP", reflect.TypeOf((*MockFullNode)(nil).ClientCalcCommP), arg0, arg1) +} + +// ClientCancelDataTransfer mocks base method. +func (m *MockFullNode) ClientCancelDataTransfer(arg0 context.Context, arg1 datatransfer.TransferID, arg2 peer.ID, arg3 bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientCancelDataTransfer", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(error) + return ret0 +} + +// ClientCancelDataTransfer indicates an expected call of ClientCancelDataTransfer. +func (mr *MockFullNodeMockRecorder) ClientCancelDataTransfer(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientCancelDataTransfer", reflect.TypeOf((*MockFullNode)(nil).ClientCancelDataTransfer), arg0, arg1, arg2, arg3) +} + +// ClientCancelRetrievalDeal mocks base method. +func (m *MockFullNode) ClientCancelRetrievalDeal(arg0 context.Context, arg1 retrievalmarket.DealID) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientCancelRetrievalDeal", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// ClientCancelRetrievalDeal indicates an expected call of ClientCancelRetrievalDeal. +func (mr *MockFullNodeMockRecorder) ClientCancelRetrievalDeal(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientCancelRetrievalDeal", reflect.TypeOf((*MockFullNode)(nil).ClientCancelRetrievalDeal), arg0, arg1) +} + +// ClientDataTransferUpdates mocks base method. +func (m *MockFullNode) ClientDataTransferUpdates(arg0 context.Context) (<-chan api.DataTransferChannel, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientDataTransferUpdates", arg0) + ret0, _ := ret[0].(<-chan api.DataTransferChannel) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientDataTransferUpdates indicates an expected call of ClientDataTransferUpdates. +func (mr *MockFullNodeMockRecorder) ClientDataTransferUpdates(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientDataTransferUpdates", reflect.TypeOf((*MockFullNode)(nil).ClientDataTransferUpdates), arg0) +} + +// ClientDealPieceCID mocks base method. +func (m *MockFullNode) ClientDealPieceCID(arg0 context.Context, arg1 cid.Cid) (api.DataCIDSize, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientDealPieceCID", arg0, arg1) + ret0, _ := ret[0].(api.DataCIDSize) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientDealPieceCID indicates an expected call of ClientDealPieceCID. +func (mr *MockFullNodeMockRecorder) ClientDealPieceCID(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientDealPieceCID", reflect.TypeOf((*MockFullNode)(nil).ClientDealPieceCID), arg0, arg1) +} + +// ClientDealSize mocks base method. +func (m *MockFullNode) ClientDealSize(arg0 context.Context, arg1 cid.Cid) (api.DataSize, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientDealSize", arg0, arg1) + ret0, _ := ret[0].(api.DataSize) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientDealSize indicates an expected call of ClientDealSize. +func (mr *MockFullNodeMockRecorder) ClientDealSize(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientDealSize", reflect.TypeOf((*MockFullNode)(nil).ClientDealSize), arg0, arg1) +} + +// ClientExport mocks base method. +func (m *MockFullNode) ClientExport(arg0 context.Context, arg1 api.ExportRef, arg2 api.FileRef) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientExport", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// ClientExport indicates an expected call of ClientExport. +func (mr *MockFullNodeMockRecorder) ClientExport(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientExport", reflect.TypeOf((*MockFullNode)(nil).ClientExport), arg0, arg1, arg2) +} + +// ClientFindData mocks base method. +func (m *MockFullNode) ClientFindData(arg0 context.Context, arg1 cid.Cid, arg2 *cid.Cid) ([]api.QueryOffer, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientFindData", arg0, arg1, arg2) + ret0, _ := ret[0].([]api.QueryOffer) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientFindData indicates an expected call of ClientFindData. +func (mr *MockFullNodeMockRecorder) ClientFindData(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientFindData", reflect.TypeOf((*MockFullNode)(nil).ClientFindData), arg0, arg1, arg2) +} + +// ClientGenCar mocks base method. +func (m *MockFullNode) ClientGenCar(arg0 context.Context, arg1 api.FileRef, arg2 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientGenCar", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// ClientGenCar indicates an expected call of ClientGenCar. +func (mr *MockFullNodeMockRecorder) ClientGenCar(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientGenCar", reflect.TypeOf((*MockFullNode)(nil).ClientGenCar), arg0, arg1, arg2) +} + +// ClientGetDealInfo mocks base method. +func (m *MockFullNode) ClientGetDealInfo(arg0 context.Context, arg1 cid.Cid) (*api.DealInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientGetDealInfo", arg0, arg1) + ret0, _ := ret[0].(*api.DealInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientGetDealInfo indicates an expected call of ClientGetDealInfo. +func (mr *MockFullNodeMockRecorder) ClientGetDealInfo(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientGetDealInfo", reflect.TypeOf((*MockFullNode)(nil).ClientGetDealInfo), arg0, arg1) +} + +// ClientGetDealStatus mocks base method. +func (m *MockFullNode) ClientGetDealStatus(arg0 context.Context, arg1 uint64) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientGetDealStatus", arg0, arg1) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientGetDealStatus indicates an expected call of ClientGetDealStatus. +func (mr *MockFullNodeMockRecorder) ClientGetDealStatus(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientGetDealStatus", reflect.TypeOf((*MockFullNode)(nil).ClientGetDealStatus), arg0, arg1) +} + +// ClientGetDealUpdates mocks base method. +func (m *MockFullNode) ClientGetDealUpdates(arg0 context.Context) (<-chan api.DealInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientGetDealUpdates", arg0) + ret0, _ := ret[0].(<-chan api.DealInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientGetDealUpdates indicates an expected call of ClientGetDealUpdates. +func (mr *MockFullNodeMockRecorder) ClientGetDealUpdates(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientGetDealUpdates", reflect.TypeOf((*MockFullNode)(nil).ClientGetDealUpdates), arg0) +} + +// ClientGetRetrievalUpdates mocks base method. +func (m *MockFullNode) ClientGetRetrievalUpdates(arg0 context.Context) (<-chan api.RetrievalInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientGetRetrievalUpdates", arg0) + ret0, _ := ret[0].(<-chan api.RetrievalInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientGetRetrievalUpdates indicates an expected call of ClientGetRetrievalUpdates. +func (mr *MockFullNodeMockRecorder) ClientGetRetrievalUpdates(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientGetRetrievalUpdates", reflect.TypeOf((*MockFullNode)(nil).ClientGetRetrievalUpdates), arg0) +} + +// ClientHasLocal mocks base method. +func (m *MockFullNode) ClientHasLocal(arg0 context.Context, arg1 cid.Cid) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientHasLocal", arg0, arg1) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientHasLocal indicates an expected call of ClientHasLocal. +func (mr *MockFullNodeMockRecorder) ClientHasLocal(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientHasLocal", reflect.TypeOf((*MockFullNode)(nil).ClientHasLocal), arg0, arg1) +} + +// ClientImport mocks base method. +func (m *MockFullNode) ClientImport(arg0 context.Context, arg1 api.FileRef) (*api.ImportRes, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientImport", arg0, arg1) + ret0, _ := ret[0].(*api.ImportRes) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientImport indicates an expected call of ClientImport. +func (mr *MockFullNodeMockRecorder) ClientImport(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientImport", reflect.TypeOf((*MockFullNode)(nil).ClientImport), arg0, arg1) +} + +// ClientListDataTransfers mocks base method. +func (m *MockFullNode) ClientListDataTransfers(arg0 context.Context) ([]api.DataTransferChannel, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientListDataTransfers", arg0) + ret0, _ := ret[0].([]api.DataTransferChannel) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientListDataTransfers indicates an expected call of ClientListDataTransfers. +func (mr *MockFullNodeMockRecorder) ClientListDataTransfers(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientListDataTransfers", reflect.TypeOf((*MockFullNode)(nil).ClientListDataTransfers), arg0) +} + +// ClientListDeals mocks base method. +func (m *MockFullNode) ClientListDeals(arg0 context.Context) ([]api.DealInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientListDeals", arg0) + ret0, _ := ret[0].([]api.DealInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientListDeals indicates an expected call of ClientListDeals. +func (mr *MockFullNodeMockRecorder) ClientListDeals(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientListDeals", reflect.TypeOf((*MockFullNode)(nil).ClientListDeals), arg0) +} + +// ClientListImports mocks base method. +func (m *MockFullNode) ClientListImports(arg0 context.Context) ([]api.Import, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientListImports", arg0) + ret0, _ := ret[0].([]api.Import) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientListImports indicates an expected call of ClientListImports. +func (mr *MockFullNodeMockRecorder) ClientListImports(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientListImports", reflect.TypeOf((*MockFullNode)(nil).ClientListImports), arg0) +} + +// ClientListRetrievals mocks base method. +func (m *MockFullNode) ClientListRetrievals(arg0 context.Context) ([]api.RetrievalInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientListRetrievals", arg0) + ret0, _ := ret[0].([]api.RetrievalInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientListRetrievals indicates an expected call of ClientListRetrievals. +func (mr *MockFullNodeMockRecorder) ClientListRetrievals(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientListRetrievals", reflect.TypeOf((*MockFullNode)(nil).ClientListRetrievals), arg0) +} + +// ClientMinerQueryOffer mocks base method. +func (m *MockFullNode) ClientMinerQueryOffer(arg0 context.Context, arg1 address.Address, arg2 cid.Cid, arg3 *cid.Cid) (api.QueryOffer, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientMinerQueryOffer", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(api.QueryOffer) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientMinerQueryOffer indicates an expected call of ClientMinerQueryOffer. +func (mr *MockFullNodeMockRecorder) ClientMinerQueryOffer(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientMinerQueryOffer", reflect.TypeOf((*MockFullNode)(nil).ClientMinerQueryOffer), arg0, arg1, arg2, arg3) +} + +// ClientQueryAsk mocks base method. +func (m *MockFullNode) ClientQueryAsk(arg0 context.Context, arg1 peer.ID, arg2 address.Address) (*api.StorageAsk, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientQueryAsk", arg0, arg1, arg2) + ret0, _ := ret[0].(*api.StorageAsk) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientQueryAsk indicates an expected call of ClientQueryAsk. +func (mr *MockFullNodeMockRecorder) ClientQueryAsk(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientQueryAsk", reflect.TypeOf((*MockFullNode)(nil).ClientQueryAsk), arg0, arg1, arg2) +} + +// ClientRemoveImport mocks base method. +func (m *MockFullNode) ClientRemoveImport(arg0 context.Context, arg1 imports.ID) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientRemoveImport", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// ClientRemoveImport indicates an expected call of ClientRemoveImport. +func (mr *MockFullNodeMockRecorder) ClientRemoveImport(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientRemoveImport", reflect.TypeOf((*MockFullNode)(nil).ClientRemoveImport), arg0, arg1) +} + +// ClientRestartDataTransfer mocks base method. +func (m *MockFullNode) ClientRestartDataTransfer(arg0 context.Context, arg1 datatransfer.TransferID, arg2 peer.ID, arg3 bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientRestartDataTransfer", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(error) + return ret0 +} + +// ClientRestartDataTransfer indicates an expected call of ClientRestartDataTransfer. +func (mr *MockFullNodeMockRecorder) ClientRestartDataTransfer(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientRestartDataTransfer", reflect.TypeOf((*MockFullNode)(nil).ClientRestartDataTransfer), arg0, arg1, arg2, arg3) +} + +// ClientRetrieve mocks base method. +func (m *MockFullNode) ClientRetrieve(arg0 context.Context, arg1 api.RetrievalOrder) (*api.RestrievalRes, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientRetrieve", arg0, arg1) + ret0, _ := ret[0].(*api.RestrievalRes) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientRetrieve indicates an expected call of ClientRetrieve. +func (mr *MockFullNodeMockRecorder) ClientRetrieve(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientRetrieve", reflect.TypeOf((*MockFullNode)(nil).ClientRetrieve), arg0, arg1) +} + +// ClientRetrieveTryRestartInsufficientFunds mocks base method. +func (m *MockFullNode) ClientRetrieveTryRestartInsufficientFunds(arg0 context.Context, arg1 address.Address) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientRetrieveTryRestartInsufficientFunds", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// ClientRetrieveTryRestartInsufficientFunds indicates an expected call of ClientRetrieveTryRestartInsufficientFunds. +func (mr *MockFullNodeMockRecorder) ClientRetrieveTryRestartInsufficientFunds(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientRetrieveTryRestartInsufficientFunds", reflect.TypeOf((*MockFullNode)(nil).ClientRetrieveTryRestartInsufficientFunds), arg0, arg1) +} + +// ClientRetrieveWait mocks base method. +func (m *MockFullNode) ClientRetrieveWait(arg0 context.Context, arg1 retrievalmarket.DealID) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientRetrieveWait", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// ClientRetrieveWait indicates an expected call of ClientRetrieveWait. +func (mr *MockFullNodeMockRecorder) ClientRetrieveWait(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientRetrieveWait", reflect.TypeOf((*MockFullNode)(nil).ClientRetrieveWait), arg0, arg1) +} + +// ClientStartDeal mocks base method. +func (m *MockFullNode) ClientStartDeal(arg0 context.Context, arg1 *api.StartDealParams) (*cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientStartDeal", arg0, arg1) + ret0, _ := ret[0].(*cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientStartDeal indicates an expected call of ClientStartDeal. +func (mr *MockFullNodeMockRecorder) ClientStartDeal(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientStartDeal", reflect.TypeOf((*MockFullNode)(nil).ClientStartDeal), arg0, arg1) +} + +// ClientStatelessDeal mocks base method. +func (m *MockFullNode) ClientStatelessDeal(arg0 context.Context, arg1 *api.StartDealParams) (*cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientStatelessDeal", arg0, arg1) + ret0, _ := ret[0].(*cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientStatelessDeal indicates an expected call of ClientStatelessDeal. +func (mr *MockFullNodeMockRecorder) ClientStatelessDeal(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientStatelessDeal", reflect.TypeOf((*MockFullNode)(nil).ClientStatelessDeal), arg0, arg1) +} + +// Closing mocks base method. +func (m *MockFullNode) Closing(arg0 context.Context) (<-chan struct{}, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Closing", arg0) + ret0, _ := ret[0].(<-chan struct{}) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Closing indicates an expected call of Closing. +func (mr *MockFullNodeMockRecorder) Closing(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Closing", reflect.TypeOf((*MockFullNode)(nil).Closing), arg0) +} + +// CreateBackup mocks base method. +func (m *MockFullNode) CreateBackup(arg0 context.Context, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateBackup", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateBackup indicates an expected call of CreateBackup. +func (mr *MockFullNodeMockRecorder) CreateBackup(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateBackup", reflect.TypeOf((*MockFullNode)(nil).CreateBackup), arg0, arg1) +} + +// Discover mocks base method. +func (m *MockFullNode) Discover(arg0 context.Context) (apitypes.OpenRPCDocument, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Discover", arg0) + ret0, _ := ret[0].(apitypes.OpenRPCDocument) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Discover indicates an expected call of Discover. +func (mr *MockFullNodeMockRecorder) Discover(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Discover", reflect.TypeOf((*MockFullNode)(nil).Discover), arg0) +} + +// EthAccounts mocks base method. +func (m *MockFullNode) EthAccounts(arg0 context.Context) ([]ethtypes.EthAddress, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthAccounts", arg0) + ret0, _ := ret[0].([]ethtypes.EthAddress) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthAccounts indicates an expected call of EthAccounts. +func (mr *MockFullNodeMockRecorder) EthAccounts(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthAccounts", reflect.TypeOf((*MockFullNode)(nil).EthAccounts), arg0) +} + +// EthAddressToFilecoinAddress mocks base method. +func (m *MockFullNode) EthAddressToFilecoinAddress(arg0 context.Context, arg1 ethtypes.EthAddress) (address.Address, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthAddressToFilecoinAddress", arg0, arg1) + ret0, _ := ret[0].(address.Address) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthAddressToFilecoinAddress indicates an expected call of EthAddressToFilecoinAddress. +func (mr *MockFullNodeMockRecorder) EthAddressToFilecoinAddress(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthAddressToFilecoinAddress", reflect.TypeOf((*MockFullNode)(nil).EthAddressToFilecoinAddress), arg0, arg1) +} + +// EthBlockNumber mocks base method. +func (m *MockFullNode) EthBlockNumber(arg0 context.Context) (ethtypes.EthUint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthBlockNumber", arg0) + ret0, _ := ret[0].(ethtypes.EthUint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthBlockNumber indicates an expected call of EthBlockNumber. +func (mr *MockFullNodeMockRecorder) EthBlockNumber(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthBlockNumber", reflect.TypeOf((*MockFullNode)(nil).EthBlockNumber), arg0) +} + +// EthCall mocks base method. +func (m *MockFullNode) EthCall(arg0 context.Context, arg1 ethtypes.EthCall, arg2 string) (ethtypes.EthBytes, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthCall", arg0, arg1, arg2) + ret0, _ := ret[0].(ethtypes.EthBytes) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthCall indicates an expected call of EthCall. +func (mr *MockFullNodeMockRecorder) EthCall(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthCall", reflect.TypeOf((*MockFullNode)(nil).EthCall), arg0, arg1, arg2) +} + +// EthChainId mocks base method. +func (m *MockFullNode) EthChainId(arg0 context.Context) (ethtypes.EthUint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthChainId", arg0) + ret0, _ := ret[0].(ethtypes.EthUint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthChainId indicates an expected call of EthChainId. +func (mr *MockFullNodeMockRecorder) EthChainId(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthChainId", reflect.TypeOf((*MockFullNode)(nil).EthChainId), arg0) +} + +// EthEstimateGas mocks base method. +func (m *MockFullNode) EthEstimateGas(arg0 context.Context, arg1 ethtypes.EthCall) (ethtypes.EthUint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthEstimateGas", arg0, arg1) + ret0, _ := ret[0].(ethtypes.EthUint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthEstimateGas indicates an expected call of EthEstimateGas. +func (mr *MockFullNodeMockRecorder) EthEstimateGas(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthEstimateGas", reflect.TypeOf((*MockFullNode)(nil).EthEstimateGas), arg0, arg1) +} + +// EthFeeHistory mocks base method. +func (m *MockFullNode) EthFeeHistory(arg0 context.Context, arg1 jsonrpc.RawParams) (ethtypes.EthFeeHistory, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthFeeHistory", arg0, arg1) + ret0, _ := ret[0].(ethtypes.EthFeeHistory) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthFeeHistory indicates an expected call of EthFeeHistory. +func (mr *MockFullNodeMockRecorder) EthFeeHistory(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthFeeHistory", reflect.TypeOf((*MockFullNode)(nil).EthFeeHistory), arg0, arg1) +} + +// EthGasPrice mocks base method. +func (m *MockFullNode) EthGasPrice(arg0 context.Context) (ethtypes.EthBigInt, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGasPrice", arg0) + ret0, _ := ret[0].(ethtypes.EthBigInt) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGasPrice indicates an expected call of EthGasPrice. +func (mr *MockFullNodeMockRecorder) EthGasPrice(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGasPrice", reflect.TypeOf((*MockFullNode)(nil).EthGasPrice), arg0) +} + +// EthGetBalance mocks base method. +func (m *MockFullNode) EthGetBalance(arg0 context.Context, arg1 ethtypes.EthAddress, arg2 string) (ethtypes.EthBigInt, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetBalance", arg0, arg1, arg2) + ret0, _ := ret[0].(ethtypes.EthBigInt) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetBalance indicates an expected call of EthGetBalance. +func (mr *MockFullNodeMockRecorder) EthGetBalance(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetBalance", reflect.TypeOf((*MockFullNode)(nil).EthGetBalance), arg0, arg1, arg2) +} + +// EthGetBlockByHash mocks base method. +func (m *MockFullNode) EthGetBlockByHash(arg0 context.Context, arg1 ethtypes.EthHash, arg2 bool) (ethtypes.EthBlock, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetBlockByHash", arg0, arg1, arg2) + ret0, _ := ret[0].(ethtypes.EthBlock) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetBlockByHash indicates an expected call of EthGetBlockByHash. +func (mr *MockFullNodeMockRecorder) EthGetBlockByHash(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetBlockByHash", reflect.TypeOf((*MockFullNode)(nil).EthGetBlockByHash), arg0, arg1, arg2) +} + +// EthGetBlockByNumber mocks base method. +func (m *MockFullNode) EthGetBlockByNumber(arg0 context.Context, arg1 string, arg2 bool) (ethtypes.EthBlock, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetBlockByNumber", arg0, arg1, arg2) + ret0, _ := ret[0].(ethtypes.EthBlock) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetBlockByNumber indicates an expected call of EthGetBlockByNumber. +func (mr *MockFullNodeMockRecorder) EthGetBlockByNumber(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetBlockByNumber", reflect.TypeOf((*MockFullNode)(nil).EthGetBlockByNumber), arg0, arg1, arg2) +} + +// EthGetBlockTransactionCountByHash mocks base method. +func (m *MockFullNode) EthGetBlockTransactionCountByHash(arg0 context.Context, arg1 ethtypes.EthHash) (ethtypes.EthUint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetBlockTransactionCountByHash", arg0, arg1) + ret0, _ := ret[0].(ethtypes.EthUint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetBlockTransactionCountByHash indicates an expected call of EthGetBlockTransactionCountByHash. +func (mr *MockFullNodeMockRecorder) EthGetBlockTransactionCountByHash(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetBlockTransactionCountByHash", reflect.TypeOf((*MockFullNode)(nil).EthGetBlockTransactionCountByHash), arg0, arg1) +} + +// EthGetBlockTransactionCountByNumber mocks base method. +func (m *MockFullNode) EthGetBlockTransactionCountByNumber(arg0 context.Context, arg1 ethtypes.EthUint64) (ethtypes.EthUint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetBlockTransactionCountByNumber", arg0, arg1) + ret0, _ := ret[0].(ethtypes.EthUint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetBlockTransactionCountByNumber indicates an expected call of EthGetBlockTransactionCountByNumber. +func (mr *MockFullNodeMockRecorder) EthGetBlockTransactionCountByNumber(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetBlockTransactionCountByNumber", reflect.TypeOf((*MockFullNode)(nil).EthGetBlockTransactionCountByNumber), arg0, arg1) +} + +// EthGetCode mocks base method. +func (m *MockFullNode) EthGetCode(arg0 context.Context, arg1 ethtypes.EthAddress, arg2 string) (ethtypes.EthBytes, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetCode", arg0, arg1, arg2) + ret0, _ := ret[0].(ethtypes.EthBytes) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetCode indicates an expected call of EthGetCode. +func (mr *MockFullNodeMockRecorder) EthGetCode(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetCode", reflect.TypeOf((*MockFullNode)(nil).EthGetCode), arg0, arg1, arg2) +} + +// EthGetFilterChanges mocks base method. +func (m *MockFullNode) EthGetFilterChanges(arg0 context.Context, arg1 ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetFilterChanges", arg0, arg1) + ret0, _ := ret[0].(*ethtypes.EthFilterResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetFilterChanges indicates an expected call of EthGetFilterChanges. +func (mr *MockFullNodeMockRecorder) EthGetFilterChanges(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetFilterChanges", reflect.TypeOf((*MockFullNode)(nil).EthGetFilterChanges), arg0, arg1) +} + +// EthGetFilterLogs mocks base method. +func (m *MockFullNode) EthGetFilterLogs(arg0 context.Context, arg1 ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetFilterLogs", arg0, arg1) + ret0, _ := ret[0].(*ethtypes.EthFilterResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetFilterLogs indicates an expected call of EthGetFilterLogs. +func (mr *MockFullNodeMockRecorder) EthGetFilterLogs(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetFilterLogs", reflect.TypeOf((*MockFullNode)(nil).EthGetFilterLogs), arg0, arg1) +} + +// EthGetLogs mocks base method. +func (m *MockFullNode) EthGetLogs(arg0 context.Context, arg1 *ethtypes.EthFilterSpec) (*ethtypes.EthFilterResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetLogs", arg0, arg1) + ret0, _ := ret[0].(*ethtypes.EthFilterResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetLogs indicates an expected call of EthGetLogs. +func (mr *MockFullNodeMockRecorder) EthGetLogs(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetLogs", reflect.TypeOf((*MockFullNode)(nil).EthGetLogs), arg0, arg1) +} + +// EthGetMessageCidByTransactionHash mocks base method. +func (m *MockFullNode) EthGetMessageCidByTransactionHash(arg0 context.Context, arg1 *ethtypes.EthHash) (*cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetMessageCidByTransactionHash", arg0, arg1) + ret0, _ := ret[0].(*cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetMessageCidByTransactionHash indicates an expected call of EthGetMessageCidByTransactionHash. +func (mr *MockFullNodeMockRecorder) EthGetMessageCidByTransactionHash(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetMessageCidByTransactionHash", reflect.TypeOf((*MockFullNode)(nil).EthGetMessageCidByTransactionHash), arg0, arg1) +} + +// EthGetStorageAt mocks base method. +func (m *MockFullNode) EthGetStorageAt(arg0 context.Context, arg1 ethtypes.EthAddress, arg2 ethtypes.EthBytes, arg3 string) (ethtypes.EthBytes, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetStorageAt", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(ethtypes.EthBytes) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetStorageAt indicates an expected call of EthGetStorageAt. +func (mr *MockFullNodeMockRecorder) EthGetStorageAt(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetStorageAt", reflect.TypeOf((*MockFullNode)(nil).EthGetStorageAt), arg0, arg1, arg2, arg3) +} + +// EthGetTransactionByBlockHashAndIndex mocks base method. +func (m *MockFullNode) EthGetTransactionByBlockHashAndIndex(arg0 context.Context, arg1 ethtypes.EthHash, arg2 ethtypes.EthUint64) (ethtypes.EthTx, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetTransactionByBlockHashAndIndex", arg0, arg1, arg2) + ret0, _ := ret[0].(ethtypes.EthTx) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetTransactionByBlockHashAndIndex indicates an expected call of EthGetTransactionByBlockHashAndIndex. +func (mr *MockFullNodeMockRecorder) EthGetTransactionByBlockHashAndIndex(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetTransactionByBlockHashAndIndex", reflect.TypeOf((*MockFullNode)(nil).EthGetTransactionByBlockHashAndIndex), arg0, arg1, arg2) +} + +// EthGetTransactionByBlockNumberAndIndex mocks base method. +func (m *MockFullNode) EthGetTransactionByBlockNumberAndIndex(arg0 context.Context, arg1, arg2 ethtypes.EthUint64) (ethtypes.EthTx, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetTransactionByBlockNumberAndIndex", arg0, arg1, arg2) + ret0, _ := ret[0].(ethtypes.EthTx) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetTransactionByBlockNumberAndIndex indicates an expected call of EthGetTransactionByBlockNumberAndIndex. +func (mr *MockFullNodeMockRecorder) EthGetTransactionByBlockNumberAndIndex(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetTransactionByBlockNumberAndIndex", reflect.TypeOf((*MockFullNode)(nil).EthGetTransactionByBlockNumberAndIndex), arg0, arg1, arg2) +} + +// EthGetTransactionByHash mocks base method. +func (m *MockFullNode) EthGetTransactionByHash(arg0 context.Context, arg1 *ethtypes.EthHash) (*ethtypes.EthTx, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetTransactionByHash", arg0, arg1) + ret0, _ := ret[0].(*ethtypes.EthTx) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetTransactionByHash indicates an expected call of EthGetTransactionByHash. +func (mr *MockFullNodeMockRecorder) EthGetTransactionByHash(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetTransactionByHash", reflect.TypeOf((*MockFullNode)(nil).EthGetTransactionByHash), arg0, arg1) +} + +// EthGetTransactionByHashLimited mocks base method. +func (m *MockFullNode) EthGetTransactionByHashLimited(arg0 context.Context, arg1 *ethtypes.EthHash, arg2 abi.ChainEpoch) (*ethtypes.EthTx, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetTransactionByHashLimited", arg0, arg1, arg2) + ret0, _ := ret[0].(*ethtypes.EthTx) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetTransactionByHashLimited indicates an expected call of EthGetTransactionByHashLimited. +func (mr *MockFullNodeMockRecorder) EthGetTransactionByHashLimited(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetTransactionByHashLimited", reflect.TypeOf((*MockFullNode)(nil).EthGetTransactionByHashLimited), arg0, arg1, arg2) +} + +// EthGetTransactionCount mocks base method. +func (m *MockFullNode) EthGetTransactionCount(arg0 context.Context, arg1 ethtypes.EthAddress, arg2 string) (ethtypes.EthUint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetTransactionCount", arg0, arg1, arg2) + ret0, _ := ret[0].(ethtypes.EthUint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetTransactionCount indicates an expected call of EthGetTransactionCount. +func (mr *MockFullNodeMockRecorder) EthGetTransactionCount(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetTransactionCount", reflect.TypeOf((*MockFullNode)(nil).EthGetTransactionCount), arg0, arg1, arg2) +} + +// EthGetTransactionHashByCid mocks base method. +func (m *MockFullNode) EthGetTransactionHashByCid(arg0 context.Context, arg1 cid.Cid) (*ethtypes.EthHash, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetTransactionHashByCid", arg0, arg1) + ret0, _ := ret[0].(*ethtypes.EthHash) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetTransactionHashByCid indicates an expected call of EthGetTransactionHashByCid. +func (mr *MockFullNodeMockRecorder) EthGetTransactionHashByCid(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetTransactionHashByCid", reflect.TypeOf((*MockFullNode)(nil).EthGetTransactionHashByCid), arg0, arg1) +} + +// EthGetTransactionReceipt mocks base method. +func (m *MockFullNode) EthGetTransactionReceipt(arg0 context.Context, arg1 ethtypes.EthHash) (*api.EthTxReceipt, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetTransactionReceipt", arg0, arg1) + ret0, _ := ret[0].(*api.EthTxReceipt) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetTransactionReceipt indicates an expected call of EthGetTransactionReceipt. +func (mr *MockFullNodeMockRecorder) EthGetTransactionReceipt(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetTransactionReceipt", reflect.TypeOf((*MockFullNode)(nil).EthGetTransactionReceipt), arg0, arg1) +} + +// EthGetTransactionReceiptLimited mocks base method. +func (m *MockFullNode) EthGetTransactionReceiptLimited(arg0 context.Context, arg1 ethtypes.EthHash, arg2 abi.ChainEpoch) (*api.EthTxReceipt, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetTransactionReceiptLimited", arg0, arg1, arg2) + ret0, _ := ret[0].(*api.EthTxReceipt) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetTransactionReceiptLimited indicates an expected call of EthGetTransactionReceiptLimited. +func (mr *MockFullNodeMockRecorder) EthGetTransactionReceiptLimited(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetTransactionReceiptLimited", reflect.TypeOf((*MockFullNode)(nil).EthGetTransactionReceiptLimited), arg0, arg1, arg2) +} + +// EthMaxPriorityFeePerGas mocks base method. +func (m *MockFullNode) EthMaxPriorityFeePerGas(arg0 context.Context) (ethtypes.EthBigInt, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthMaxPriorityFeePerGas", arg0) + ret0, _ := ret[0].(ethtypes.EthBigInt) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthMaxPriorityFeePerGas indicates an expected call of EthMaxPriorityFeePerGas. +func (mr *MockFullNodeMockRecorder) EthMaxPriorityFeePerGas(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthMaxPriorityFeePerGas", reflect.TypeOf((*MockFullNode)(nil).EthMaxPriorityFeePerGas), arg0) +} + +// EthNewBlockFilter mocks base method. +func (m *MockFullNode) EthNewBlockFilter(arg0 context.Context) (ethtypes.EthFilterID, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthNewBlockFilter", arg0) + ret0, _ := ret[0].(ethtypes.EthFilterID) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthNewBlockFilter indicates an expected call of EthNewBlockFilter. +func (mr *MockFullNodeMockRecorder) EthNewBlockFilter(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthNewBlockFilter", reflect.TypeOf((*MockFullNode)(nil).EthNewBlockFilter), arg0) +} + +// EthNewFilter mocks base method. +func (m *MockFullNode) EthNewFilter(arg0 context.Context, arg1 *ethtypes.EthFilterSpec) (ethtypes.EthFilterID, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthNewFilter", arg0, arg1) + ret0, _ := ret[0].(ethtypes.EthFilterID) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthNewFilter indicates an expected call of EthNewFilter. +func (mr *MockFullNodeMockRecorder) EthNewFilter(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthNewFilter", reflect.TypeOf((*MockFullNode)(nil).EthNewFilter), arg0, arg1) +} + +// EthNewPendingTransactionFilter mocks base method. +func (m *MockFullNode) EthNewPendingTransactionFilter(arg0 context.Context) (ethtypes.EthFilterID, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthNewPendingTransactionFilter", arg0) + ret0, _ := ret[0].(ethtypes.EthFilterID) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthNewPendingTransactionFilter indicates an expected call of EthNewPendingTransactionFilter. +func (mr *MockFullNodeMockRecorder) EthNewPendingTransactionFilter(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthNewPendingTransactionFilter", reflect.TypeOf((*MockFullNode)(nil).EthNewPendingTransactionFilter), arg0) +} + +// EthProtocolVersion mocks base method. +func (m *MockFullNode) EthProtocolVersion(arg0 context.Context) (ethtypes.EthUint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthProtocolVersion", arg0) + ret0, _ := ret[0].(ethtypes.EthUint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthProtocolVersion indicates an expected call of EthProtocolVersion. +func (mr *MockFullNodeMockRecorder) EthProtocolVersion(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthProtocolVersion", reflect.TypeOf((*MockFullNode)(nil).EthProtocolVersion), arg0) +} + +// EthSendRawTransaction mocks base method. +func (m *MockFullNode) EthSendRawTransaction(arg0 context.Context, arg1 ethtypes.EthBytes) (ethtypes.EthHash, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthSendRawTransaction", arg0, arg1) + ret0, _ := ret[0].(ethtypes.EthHash) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthSendRawTransaction indicates an expected call of EthSendRawTransaction. +func (mr *MockFullNodeMockRecorder) EthSendRawTransaction(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthSendRawTransaction", reflect.TypeOf((*MockFullNode)(nil).EthSendRawTransaction), arg0, arg1) +} + +// EthSubscribe mocks base method. +func (m *MockFullNode) EthSubscribe(arg0 context.Context, arg1 jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthSubscribe", arg0, arg1) + ret0, _ := ret[0].(ethtypes.EthSubscriptionID) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthSubscribe indicates an expected call of EthSubscribe. +func (mr *MockFullNodeMockRecorder) EthSubscribe(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthSubscribe", reflect.TypeOf((*MockFullNode)(nil).EthSubscribe), arg0, arg1) +} + +// EthSyncing mocks base method. +func (m *MockFullNode) EthSyncing(arg0 context.Context) (ethtypes.EthSyncingResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthSyncing", arg0) + ret0, _ := ret[0].(ethtypes.EthSyncingResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthSyncing indicates an expected call of EthSyncing. +func (mr *MockFullNodeMockRecorder) EthSyncing(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthSyncing", reflect.TypeOf((*MockFullNode)(nil).EthSyncing), arg0) +} + +// EthUninstallFilter mocks base method. +func (m *MockFullNode) EthUninstallFilter(arg0 context.Context, arg1 ethtypes.EthFilterID) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthUninstallFilter", arg0, arg1) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthUninstallFilter indicates an expected call of EthUninstallFilter. +func (mr *MockFullNodeMockRecorder) EthUninstallFilter(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthUninstallFilter", reflect.TypeOf((*MockFullNode)(nil).EthUninstallFilter), arg0, arg1) +} + +// EthUnsubscribe mocks base method. +func (m *MockFullNode) EthUnsubscribe(arg0 context.Context, arg1 ethtypes.EthSubscriptionID) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthUnsubscribe", arg0, arg1) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthUnsubscribe indicates an expected call of EthUnsubscribe. +func (mr *MockFullNodeMockRecorder) EthUnsubscribe(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthUnsubscribe", reflect.TypeOf((*MockFullNode)(nil).EthUnsubscribe), arg0, arg1) +} + +// FilecoinAddressToEthAddress mocks base method. +func (m *MockFullNode) FilecoinAddressToEthAddress(arg0 context.Context, arg1 address.Address) (ethtypes.EthAddress, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FilecoinAddressToEthAddress", arg0, arg1) + ret0, _ := ret[0].(ethtypes.EthAddress) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FilecoinAddressToEthAddress indicates an expected call of FilecoinAddressToEthAddress. +func (mr *MockFullNodeMockRecorder) FilecoinAddressToEthAddress(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FilecoinAddressToEthAddress", reflect.TypeOf((*MockFullNode)(nil).FilecoinAddressToEthAddress), arg0, arg1) +} + +// GasEstimateFeeCap mocks base method. +func (m *MockFullNode) GasEstimateFeeCap(arg0 context.Context, arg1 *types.Message, arg2 int64, arg3 types.TipSetKey) (big.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GasEstimateFeeCap", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(big.Int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GasEstimateFeeCap indicates an expected call of GasEstimateFeeCap. +func (mr *MockFullNodeMockRecorder) GasEstimateFeeCap(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GasEstimateFeeCap", reflect.TypeOf((*MockFullNode)(nil).GasEstimateFeeCap), arg0, arg1, arg2, arg3) +} + +// GasEstimateGasLimit mocks base method. +func (m *MockFullNode) GasEstimateGasLimit(arg0 context.Context, arg1 *types.Message, arg2 types.TipSetKey) (int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GasEstimateGasLimit", arg0, arg1, arg2) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GasEstimateGasLimit indicates an expected call of GasEstimateGasLimit. +func (mr *MockFullNodeMockRecorder) GasEstimateGasLimit(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GasEstimateGasLimit", reflect.TypeOf((*MockFullNode)(nil).GasEstimateGasLimit), arg0, arg1, arg2) +} + +// GasEstimateGasPremium mocks base method. +func (m *MockFullNode) GasEstimateGasPremium(arg0 context.Context, arg1 uint64, arg2 address.Address, arg3 int64, arg4 types.TipSetKey) (big.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GasEstimateGasPremium", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(big.Int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GasEstimateGasPremium indicates an expected call of GasEstimateGasPremium. +func (mr *MockFullNodeMockRecorder) GasEstimateGasPremium(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GasEstimateGasPremium", reflect.TypeOf((*MockFullNode)(nil).GasEstimateGasPremium), arg0, arg1, arg2, arg3, arg4) +} + +// GasEstimateMessageGas mocks base method. +func (m *MockFullNode) GasEstimateMessageGas(arg0 context.Context, arg1 *types.Message, arg2 *api.MessageSendSpec, arg3 types.TipSetKey) (*types.Message, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GasEstimateMessageGas", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*types.Message) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GasEstimateMessageGas indicates an expected call of GasEstimateMessageGas. +func (mr *MockFullNodeMockRecorder) GasEstimateMessageGas(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GasEstimateMessageGas", reflect.TypeOf((*MockFullNode)(nil).GasEstimateMessageGas), arg0, arg1, arg2, arg3) +} + +// ID mocks base method. +func (m *MockFullNode) ID(arg0 context.Context) (peer.ID, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ID", arg0) + ret0, _ := ret[0].(peer.ID) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ID indicates an expected call of ID. +func (mr *MockFullNodeMockRecorder) ID(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ID", reflect.TypeOf((*MockFullNode)(nil).ID), arg0) +} + +// LogAlerts mocks base method. +func (m *MockFullNode) LogAlerts(arg0 context.Context) ([]alerting.Alert, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LogAlerts", arg0) + ret0, _ := ret[0].([]alerting.Alert) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LogAlerts indicates an expected call of LogAlerts. +func (mr *MockFullNodeMockRecorder) LogAlerts(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogAlerts", reflect.TypeOf((*MockFullNode)(nil).LogAlerts), arg0) +} + +// LogList mocks base method. +func (m *MockFullNode) LogList(arg0 context.Context) ([]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LogList", arg0) + ret0, _ := ret[0].([]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LogList indicates an expected call of LogList. +func (mr *MockFullNodeMockRecorder) LogList(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogList", reflect.TypeOf((*MockFullNode)(nil).LogList), arg0) +} + +// LogSetLevel mocks base method. +func (m *MockFullNode) LogSetLevel(arg0 context.Context, arg1, arg2 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LogSetLevel", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// LogSetLevel indicates an expected call of LogSetLevel. +func (mr *MockFullNodeMockRecorder) LogSetLevel(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogSetLevel", reflect.TypeOf((*MockFullNode)(nil).LogSetLevel), arg0, arg1, arg2) +} + +// MarketAddBalance mocks base method. +func (m *MockFullNode) MarketAddBalance(arg0 context.Context, arg1, arg2 address.Address, arg3 big.Int) (cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MarketAddBalance", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MarketAddBalance indicates an expected call of MarketAddBalance. +func (mr *MockFullNodeMockRecorder) MarketAddBalance(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarketAddBalance", reflect.TypeOf((*MockFullNode)(nil).MarketAddBalance), arg0, arg1, arg2, arg3) +} + +// MarketGetReserved mocks base method. +func (m *MockFullNode) MarketGetReserved(arg0 context.Context, arg1 address.Address) (big.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MarketGetReserved", arg0, arg1) + ret0, _ := ret[0].(big.Int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MarketGetReserved indicates an expected call of MarketGetReserved. +func (mr *MockFullNodeMockRecorder) MarketGetReserved(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarketGetReserved", reflect.TypeOf((*MockFullNode)(nil).MarketGetReserved), arg0, arg1) +} + +// MarketReleaseFunds mocks base method. +func (m *MockFullNode) MarketReleaseFunds(arg0 context.Context, arg1 address.Address, arg2 big.Int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MarketReleaseFunds", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// MarketReleaseFunds indicates an expected call of MarketReleaseFunds. +func (mr *MockFullNodeMockRecorder) MarketReleaseFunds(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarketReleaseFunds", reflect.TypeOf((*MockFullNode)(nil).MarketReleaseFunds), arg0, arg1, arg2) +} + +// MarketReserveFunds mocks base method. +func (m *MockFullNode) MarketReserveFunds(arg0 context.Context, arg1, arg2 address.Address, arg3 big.Int) (cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MarketReserveFunds", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MarketReserveFunds indicates an expected call of MarketReserveFunds. +func (mr *MockFullNodeMockRecorder) MarketReserveFunds(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarketReserveFunds", reflect.TypeOf((*MockFullNode)(nil).MarketReserveFunds), arg0, arg1, arg2, arg3) +} + +// MarketWithdraw mocks base method. +func (m *MockFullNode) MarketWithdraw(arg0 context.Context, arg1, arg2 address.Address, arg3 big.Int) (cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MarketWithdraw", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MarketWithdraw indicates an expected call of MarketWithdraw. +func (mr *MockFullNodeMockRecorder) MarketWithdraw(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarketWithdraw", reflect.TypeOf((*MockFullNode)(nil).MarketWithdraw), arg0, arg1, arg2, arg3) +} + +// MinerCreateBlock mocks base method. +func (m *MockFullNode) MinerCreateBlock(arg0 context.Context, arg1 *api.BlockTemplate) (*types.BlockMsg, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MinerCreateBlock", arg0, arg1) + ret0, _ := ret[0].(*types.BlockMsg) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MinerCreateBlock indicates an expected call of MinerCreateBlock. +func (mr *MockFullNodeMockRecorder) MinerCreateBlock(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MinerCreateBlock", reflect.TypeOf((*MockFullNode)(nil).MinerCreateBlock), arg0, arg1) +} + +// MinerGetBaseInfo mocks base method. +func (m *MockFullNode) MinerGetBaseInfo(arg0 context.Context, arg1 address.Address, arg2 abi.ChainEpoch, arg3 types.TipSetKey) (*api.MiningBaseInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MinerGetBaseInfo", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*api.MiningBaseInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MinerGetBaseInfo indicates an expected call of MinerGetBaseInfo. +func (mr *MockFullNodeMockRecorder) MinerGetBaseInfo(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MinerGetBaseInfo", reflect.TypeOf((*MockFullNode)(nil).MinerGetBaseInfo), arg0, arg1, arg2, arg3) +} + +// MpoolBatchPush mocks base method. +func (m *MockFullNode) MpoolBatchPush(arg0 context.Context, arg1 []*types.SignedMessage) ([]cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MpoolBatchPush", arg0, arg1) + ret0, _ := ret[0].([]cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MpoolBatchPush indicates an expected call of MpoolBatchPush. +func (mr *MockFullNodeMockRecorder) MpoolBatchPush(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MpoolBatchPush", reflect.TypeOf((*MockFullNode)(nil).MpoolBatchPush), arg0, arg1) +} + +// MpoolBatchPushMessage mocks base method. +func (m *MockFullNode) MpoolBatchPushMessage(arg0 context.Context, arg1 []*types.Message, arg2 *api.MessageSendSpec) ([]*types.SignedMessage, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MpoolBatchPushMessage", arg0, arg1, arg2) + ret0, _ := ret[0].([]*types.SignedMessage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MpoolBatchPushMessage indicates an expected call of MpoolBatchPushMessage. +func (mr *MockFullNodeMockRecorder) MpoolBatchPushMessage(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MpoolBatchPushMessage", reflect.TypeOf((*MockFullNode)(nil).MpoolBatchPushMessage), arg0, arg1, arg2) +} + +// MpoolBatchPushUntrusted mocks base method. +func (m *MockFullNode) MpoolBatchPushUntrusted(arg0 context.Context, arg1 []*types.SignedMessage) ([]cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MpoolBatchPushUntrusted", arg0, arg1) + ret0, _ := ret[0].([]cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MpoolBatchPushUntrusted indicates an expected call of MpoolBatchPushUntrusted. +func (mr *MockFullNodeMockRecorder) MpoolBatchPushUntrusted(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MpoolBatchPushUntrusted", reflect.TypeOf((*MockFullNode)(nil).MpoolBatchPushUntrusted), arg0, arg1) +} + +// MpoolCheckMessages mocks base method. +func (m *MockFullNode) MpoolCheckMessages(arg0 context.Context, arg1 []*api.MessagePrototype) ([][]api.MessageCheckStatus, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MpoolCheckMessages", arg0, arg1) + ret0, _ := ret[0].([][]api.MessageCheckStatus) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MpoolCheckMessages indicates an expected call of MpoolCheckMessages. +func (mr *MockFullNodeMockRecorder) MpoolCheckMessages(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MpoolCheckMessages", reflect.TypeOf((*MockFullNode)(nil).MpoolCheckMessages), arg0, arg1) +} + +// MpoolCheckPendingMessages mocks base method. +func (m *MockFullNode) MpoolCheckPendingMessages(arg0 context.Context, arg1 address.Address) ([][]api.MessageCheckStatus, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MpoolCheckPendingMessages", arg0, arg1) + ret0, _ := ret[0].([][]api.MessageCheckStatus) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MpoolCheckPendingMessages indicates an expected call of MpoolCheckPendingMessages. +func (mr *MockFullNodeMockRecorder) MpoolCheckPendingMessages(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MpoolCheckPendingMessages", reflect.TypeOf((*MockFullNode)(nil).MpoolCheckPendingMessages), arg0, arg1) +} + +// MpoolCheckReplaceMessages mocks base method. +func (m *MockFullNode) MpoolCheckReplaceMessages(arg0 context.Context, arg1 []*types.Message) ([][]api.MessageCheckStatus, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MpoolCheckReplaceMessages", arg0, arg1) + ret0, _ := ret[0].([][]api.MessageCheckStatus) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MpoolCheckReplaceMessages indicates an expected call of MpoolCheckReplaceMessages. +func (mr *MockFullNodeMockRecorder) MpoolCheckReplaceMessages(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MpoolCheckReplaceMessages", reflect.TypeOf((*MockFullNode)(nil).MpoolCheckReplaceMessages), arg0, arg1) +} + +// MpoolClear mocks base method. +func (m *MockFullNode) MpoolClear(arg0 context.Context, arg1 bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MpoolClear", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// MpoolClear indicates an expected call of MpoolClear. +func (mr *MockFullNodeMockRecorder) MpoolClear(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MpoolClear", reflect.TypeOf((*MockFullNode)(nil).MpoolClear), arg0, arg1) +} + +// MpoolGetConfig mocks base method. +func (m *MockFullNode) MpoolGetConfig(arg0 context.Context) (*types.MpoolConfig, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MpoolGetConfig", arg0) + ret0, _ := ret[0].(*types.MpoolConfig) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MpoolGetConfig indicates an expected call of MpoolGetConfig. +func (mr *MockFullNodeMockRecorder) MpoolGetConfig(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MpoolGetConfig", reflect.TypeOf((*MockFullNode)(nil).MpoolGetConfig), arg0) +} + +// MpoolGetNonce mocks base method. +func (m *MockFullNode) MpoolGetNonce(arg0 context.Context, arg1 address.Address) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MpoolGetNonce", arg0, arg1) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MpoolGetNonce indicates an expected call of MpoolGetNonce. +func (mr *MockFullNodeMockRecorder) MpoolGetNonce(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MpoolGetNonce", reflect.TypeOf((*MockFullNode)(nil).MpoolGetNonce), arg0, arg1) +} + +// MpoolPending mocks base method. +func (m *MockFullNode) MpoolPending(arg0 context.Context, arg1 types.TipSetKey) ([]*types.SignedMessage, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MpoolPending", arg0, arg1) + ret0, _ := ret[0].([]*types.SignedMessage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MpoolPending indicates an expected call of MpoolPending. +func (mr *MockFullNodeMockRecorder) MpoolPending(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MpoolPending", reflect.TypeOf((*MockFullNode)(nil).MpoolPending), arg0, arg1) +} + +// MpoolPush mocks base method. +func (m *MockFullNode) MpoolPush(arg0 context.Context, arg1 *types.SignedMessage) (cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MpoolPush", arg0, arg1) + ret0, _ := ret[0].(cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MpoolPush indicates an expected call of MpoolPush. +func (mr *MockFullNodeMockRecorder) MpoolPush(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MpoolPush", reflect.TypeOf((*MockFullNode)(nil).MpoolPush), arg0, arg1) +} + +// MpoolPushMessage mocks base method. +func (m *MockFullNode) MpoolPushMessage(arg0 context.Context, arg1 *types.Message, arg2 *api.MessageSendSpec) (*types.SignedMessage, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MpoolPushMessage", arg0, arg1, arg2) + ret0, _ := ret[0].(*types.SignedMessage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MpoolPushMessage indicates an expected call of MpoolPushMessage. +func (mr *MockFullNodeMockRecorder) MpoolPushMessage(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MpoolPushMessage", reflect.TypeOf((*MockFullNode)(nil).MpoolPushMessage), arg0, arg1, arg2) +} + +// MpoolPushUntrusted mocks base method. +func (m *MockFullNode) MpoolPushUntrusted(arg0 context.Context, arg1 *types.SignedMessage) (cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MpoolPushUntrusted", arg0, arg1) + ret0, _ := ret[0].(cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MpoolPushUntrusted indicates an expected call of MpoolPushUntrusted. +func (mr *MockFullNodeMockRecorder) MpoolPushUntrusted(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MpoolPushUntrusted", reflect.TypeOf((*MockFullNode)(nil).MpoolPushUntrusted), arg0, arg1) +} + +// MpoolSelect mocks base method. +func (m *MockFullNode) MpoolSelect(arg0 context.Context, arg1 types.TipSetKey, arg2 float64) ([]*types.SignedMessage, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MpoolSelect", arg0, arg1, arg2) + ret0, _ := ret[0].([]*types.SignedMessage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MpoolSelect indicates an expected call of MpoolSelect. +func (mr *MockFullNodeMockRecorder) MpoolSelect(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MpoolSelect", reflect.TypeOf((*MockFullNode)(nil).MpoolSelect), arg0, arg1, arg2) +} + +// MpoolSetConfig mocks base method. +func (m *MockFullNode) MpoolSetConfig(arg0 context.Context, arg1 *types.MpoolConfig) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MpoolSetConfig", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// MpoolSetConfig indicates an expected call of MpoolSetConfig. +func (mr *MockFullNodeMockRecorder) MpoolSetConfig(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MpoolSetConfig", reflect.TypeOf((*MockFullNode)(nil).MpoolSetConfig), arg0, arg1) +} + +// MpoolSub mocks base method. +func (m *MockFullNode) MpoolSub(arg0 context.Context) (<-chan api.MpoolUpdate, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MpoolSub", arg0) + ret0, _ := ret[0].(<-chan api.MpoolUpdate) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MpoolSub indicates an expected call of MpoolSub. +func (mr *MockFullNodeMockRecorder) MpoolSub(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MpoolSub", reflect.TypeOf((*MockFullNode)(nil).MpoolSub), arg0) +} + +// MsigAddApprove mocks base method. +func (m *MockFullNode) MsigAddApprove(arg0 context.Context, arg1, arg2 address.Address, arg3 uint64, arg4, arg5 address.Address, arg6 bool) (*api.MessagePrototype, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MsigAddApprove", arg0, arg1, arg2, arg3, arg4, arg5, arg6) + ret0, _ := ret[0].(*api.MessagePrototype) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MsigAddApprove indicates an expected call of MsigAddApprove. +func (mr *MockFullNodeMockRecorder) MsigAddApprove(arg0, arg1, arg2, arg3, arg4, arg5, arg6 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MsigAddApprove", reflect.TypeOf((*MockFullNode)(nil).MsigAddApprove), arg0, arg1, arg2, arg3, arg4, arg5, arg6) +} + +// MsigAddCancel mocks base method. +func (m *MockFullNode) MsigAddCancel(arg0 context.Context, arg1, arg2 address.Address, arg3 uint64, arg4 address.Address, arg5 bool) (*api.MessagePrototype, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MsigAddCancel", arg0, arg1, arg2, arg3, arg4, arg5) + ret0, _ := ret[0].(*api.MessagePrototype) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MsigAddCancel indicates an expected call of MsigAddCancel. +func (mr *MockFullNodeMockRecorder) MsigAddCancel(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MsigAddCancel", reflect.TypeOf((*MockFullNode)(nil).MsigAddCancel), arg0, arg1, arg2, arg3, arg4, arg5) +} + +// MsigAddPropose mocks base method. +func (m *MockFullNode) MsigAddPropose(arg0 context.Context, arg1, arg2, arg3 address.Address, arg4 bool) (*api.MessagePrototype, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MsigAddPropose", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(*api.MessagePrototype) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MsigAddPropose indicates an expected call of MsigAddPropose. +func (mr *MockFullNodeMockRecorder) MsigAddPropose(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MsigAddPropose", reflect.TypeOf((*MockFullNode)(nil).MsigAddPropose), arg0, arg1, arg2, arg3, arg4) +} + +// MsigApprove mocks base method. +func (m *MockFullNode) MsigApprove(arg0 context.Context, arg1 address.Address, arg2 uint64, arg3 address.Address) (*api.MessagePrototype, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MsigApprove", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*api.MessagePrototype) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MsigApprove indicates an expected call of MsigApprove. +func (mr *MockFullNodeMockRecorder) MsigApprove(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MsigApprove", reflect.TypeOf((*MockFullNode)(nil).MsigApprove), arg0, arg1, arg2, arg3) +} + +// MsigApproveTxnHash mocks base method. +func (m *MockFullNode) MsigApproveTxnHash(arg0 context.Context, arg1 address.Address, arg2 uint64, arg3, arg4 address.Address, arg5 big.Int, arg6 address.Address, arg7 uint64, arg8 []byte) (*api.MessagePrototype, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MsigApproveTxnHash", arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) + ret0, _ := ret[0].(*api.MessagePrototype) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MsigApproveTxnHash indicates an expected call of MsigApproveTxnHash. +func (mr *MockFullNodeMockRecorder) MsigApproveTxnHash(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MsigApproveTxnHash", reflect.TypeOf((*MockFullNode)(nil).MsigApproveTxnHash), arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) +} + +// MsigCancel mocks base method. +func (m *MockFullNode) MsigCancel(arg0 context.Context, arg1 address.Address, arg2 uint64, arg3 address.Address) (*api.MessagePrototype, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MsigCancel", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*api.MessagePrototype) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MsigCancel indicates an expected call of MsigCancel. +func (mr *MockFullNodeMockRecorder) MsigCancel(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MsigCancel", reflect.TypeOf((*MockFullNode)(nil).MsigCancel), arg0, arg1, arg2, arg3) +} + +// MsigCancelTxnHash mocks base method. +func (m *MockFullNode) MsigCancelTxnHash(arg0 context.Context, arg1 address.Address, arg2 uint64, arg3 address.Address, arg4 big.Int, arg5 address.Address, arg6 uint64, arg7 []byte) (*api.MessagePrototype, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MsigCancelTxnHash", arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) + ret0, _ := ret[0].(*api.MessagePrototype) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MsigCancelTxnHash indicates an expected call of MsigCancelTxnHash. +func (mr *MockFullNodeMockRecorder) MsigCancelTxnHash(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MsigCancelTxnHash", reflect.TypeOf((*MockFullNode)(nil).MsigCancelTxnHash), arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) +} + +// MsigCreate mocks base method. +func (m *MockFullNode) MsigCreate(arg0 context.Context, arg1 uint64, arg2 []address.Address, arg3 abi.ChainEpoch, arg4 big.Int, arg5 address.Address, arg6 big.Int) (*api.MessagePrototype, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MsigCreate", arg0, arg1, arg2, arg3, arg4, arg5, arg6) + ret0, _ := ret[0].(*api.MessagePrototype) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MsigCreate indicates an expected call of MsigCreate. +func (mr *MockFullNodeMockRecorder) MsigCreate(arg0, arg1, arg2, arg3, arg4, arg5, arg6 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MsigCreate", reflect.TypeOf((*MockFullNode)(nil).MsigCreate), arg0, arg1, arg2, arg3, arg4, arg5, arg6) +} + +// MsigGetAvailableBalance mocks base method. +func (m *MockFullNode) MsigGetAvailableBalance(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (big.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MsigGetAvailableBalance", arg0, arg1, arg2) + ret0, _ := ret[0].(big.Int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MsigGetAvailableBalance indicates an expected call of MsigGetAvailableBalance. +func (mr *MockFullNodeMockRecorder) MsigGetAvailableBalance(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MsigGetAvailableBalance", reflect.TypeOf((*MockFullNode)(nil).MsigGetAvailableBalance), arg0, arg1, arg2) +} + +// MsigGetPending mocks base method. +func (m *MockFullNode) MsigGetPending(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) ([]*api.MsigTransaction, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MsigGetPending", arg0, arg1, arg2) + ret0, _ := ret[0].([]*api.MsigTransaction) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MsigGetPending indicates an expected call of MsigGetPending. +func (mr *MockFullNodeMockRecorder) MsigGetPending(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MsigGetPending", reflect.TypeOf((*MockFullNode)(nil).MsigGetPending), arg0, arg1, arg2) +} + +// MsigGetVested mocks base method. +func (m *MockFullNode) MsigGetVested(arg0 context.Context, arg1 address.Address, arg2, arg3 types.TipSetKey) (big.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MsigGetVested", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(big.Int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MsigGetVested indicates an expected call of MsigGetVested. +func (mr *MockFullNodeMockRecorder) MsigGetVested(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MsigGetVested", reflect.TypeOf((*MockFullNode)(nil).MsigGetVested), arg0, arg1, arg2, arg3) +} + +// MsigGetVestingSchedule mocks base method. +func (m *MockFullNode) MsigGetVestingSchedule(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (api.MsigVesting, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MsigGetVestingSchedule", arg0, arg1, arg2) + ret0, _ := ret[0].(api.MsigVesting) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MsigGetVestingSchedule indicates an expected call of MsigGetVestingSchedule. +func (mr *MockFullNodeMockRecorder) MsigGetVestingSchedule(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MsigGetVestingSchedule", reflect.TypeOf((*MockFullNode)(nil).MsigGetVestingSchedule), arg0, arg1, arg2) +} + +// MsigPropose mocks base method. +func (m *MockFullNode) MsigPropose(arg0 context.Context, arg1, arg2 address.Address, arg3 big.Int, arg4 address.Address, arg5 uint64, arg6 []byte) (*api.MessagePrototype, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MsigPropose", arg0, arg1, arg2, arg3, arg4, arg5, arg6) + ret0, _ := ret[0].(*api.MessagePrototype) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MsigPropose indicates an expected call of MsigPropose. +func (mr *MockFullNodeMockRecorder) MsigPropose(arg0, arg1, arg2, arg3, arg4, arg5, arg6 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MsigPropose", reflect.TypeOf((*MockFullNode)(nil).MsigPropose), arg0, arg1, arg2, arg3, arg4, arg5, arg6) +} + +// MsigRemoveSigner mocks base method. +func (m *MockFullNode) MsigRemoveSigner(arg0 context.Context, arg1, arg2, arg3 address.Address, arg4 bool) (*api.MessagePrototype, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MsigRemoveSigner", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(*api.MessagePrototype) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MsigRemoveSigner indicates an expected call of MsigRemoveSigner. +func (mr *MockFullNodeMockRecorder) MsigRemoveSigner(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MsigRemoveSigner", reflect.TypeOf((*MockFullNode)(nil).MsigRemoveSigner), arg0, arg1, arg2, arg3, arg4) +} + +// MsigSwapApprove mocks base method. +func (m *MockFullNode) MsigSwapApprove(arg0 context.Context, arg1, arg2 address.Address, arg3 uint64, arg4, arg5, arg6 address.Address) (*api.MessagePrototype, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MsigSwapApprove", arg0, arg1, arg2, arg3, arg4, arg5, arg6) + ret0, _ := ret[0].(*api.MessagePrototype) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MsigSwapApprove indicates an expected call of MsigSwapApprove. +func (mr *MockFullNodeMockRecorder) MsigSwapApprove(arg0, arg1, arg2, arg3, arg4, arg5, arg6 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MsigSwapApprove", reflect.TypeOf((*MockFullNode)(nil).MsigSwapApprove), arg0, arg1, arg2, arg3, arg4, arg5, arg6) +} + +// MsigSwapCancel mocks base method. +func (m *MockFullNode) MsigSwapCancel(arg0 context.Context, arg1, arg2 address.Address, arg3 uint64, arg4, arg5 address.Address) (*api.MessagePrototype, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MsigSwapCancel", arg0, arg1, arg2, arg3, arg4, arg5) + ret0, _ := ret[0].(*api.MessagePrototype) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MsigSwapCancel indicates an expected call of MsigSwapCancel. +func (mr *MockFullNodeMockRecorder) MsigSwapCancel(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MsigSwapCancel", reflect.TypeOf((*MockFullNode)(nil).MsigSwapCancel), arg0, arg1, arg2, arg3, arg4, arg5) +} + +// MsigSwapPropose mocks base method. +func (m *MockFullNode) MsigSwapPropose(arg0 context.Context, arg1, arg2, arg3, arg4 address.Address) (*api.MessagePrototype, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MsigSwapPropose", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(*api.MessagePrototype) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MsigSwapPropose indicates an expected call of MsigSwapPropose. +func (mr *MockFullNodeMockRecorder) MsigSwapPropose(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MsigSwapPropose", reflect.TypeOf((*MockFullNode)(nil).MsigSwapPropose), arg0, arg1, arg2, arg3, arg4) +} + +// NetAddrsListen mocks base method. +func (m *MockFullNode) NetAddrsListen(arg0 context.Context) (peer.AddrInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetAddrsListen", arg0) + ret0, _ := ret[0].(peer.AddrInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetAddrsListen indicates an expected call of NetAddrsListen. +func (mr *MockFullNodeMockRecorder) NetAddrsListen(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetAddrsListen", reflect.TypeOf((*MockFullNode)(nil).NetAddrsListen), arg0) +} + +// NetAgentVersion mocks base method. +func (m *MockFullNode) NetAgentVersion(arg0 context.Context, arg1 peer.ID) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetAgentVersion", arg0, arg1) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetAgentVersion indicates an expected call of NetAgentVersion. +func (mr *MockFullNodeMockRecorder) NetAgentVersion(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetAgentVersion", reflect.TypeOf((*MockFullNode)(nil).NetAgentVersion), arg0, arg1) +} + +// NetAutoNatStatus mocks base method. +func (m *MockFullNode) NetAutoNatStatus(arg0 context.Context) (api.NatInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetAutoNatStatus", arg0) + ret0, _ := ret[0].(api.NatInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetAutoNatStatus indicates an expected call of NetAutoNatStatus. +func (mr *MockFullNodeMockRecorder) NetAutoNatStatus(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetAutoNatStatus", reflect.TypeOf((*MockFullNode)(nil).NetAutoNatStatus), arg0) +} + +// NetBandwidthStats mocks base method. +func (m *MockFullNode) NetBandwidthStats(arg0 context.Context) (metrics.Stats, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetBandwidthStats", arg0) + ret0, _ := ret[0].(metrics.Stats) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetBandwidthStats indicates an expected call of NetBandwidthStats. +func (mr *MockFullNodeMockRecorder) NetBandwidthStats(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetBandwidthStats", reflect.TypeOf((*MockFullNode)(nil).NetBandwidthStats), arg0) +} + +// NetBandwidthStatsByPeer mocks base method. +func (m *MockFullNode) NetBandwidthStatsByPeer(arg0 context.Context) (map[string]metrics.Stats, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetBandwidthStatsByPeer", arg0) + ret0, _ := ret[0].(map[string]metrics.Stats) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetBandwidthStatsByPeer indicates an expected call of NetBandwidthStatsByPeer. +func (mr *MockFullNodeMockRecorder) NetBandwidthStatsByPeer(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetBandwidthStatsByPeer", reflect.TypeOf((*MockFullNode)(nil).NetBandwidthStatsByPeer), arg0) +} + +// NetBandwidthStatsByProtocol mocks base method. +func (m *MockFullNode) NetBandwidthStatsByProtocol(arg0 context.Context) (map[protocol.ID]metrics.Stats, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetBandwidthStatsByProtocol", arg0) + ret0, _ := ret[0].(map[protocol.ID]metrics.Stats) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetBandwidthStatsByProtocol indicates an expected call of NetBandwidthStatsByProtocol. +func (mr *MockFullNodeMockRecorder) NetBandwidthStatsByProtocol(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetBandwidthStatsByProtocol", reflect.TypeOf((*MockFullNode)(nil).NetBandwidthStatsByProtocol), arg0) +} + +// NetBlockAdd mocks base method. +func (m *MockFullNode) NetBlockAdd(arg0 context.Context, arg1 api.NetBlockList) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetBlockAdd", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// NetBlockAdd indicates an expected call of NetBlockAdd. +func (mr *MockFullNodeMockRecorder) NetBlockAdd(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetBlockAdd", reflect.TypeOf((*MockFullNode)(nil).NetBlockAdd), arg0, arg1) +} + +// NetBlockList mocks base method. +func (m *MockFullNode) NetBlockList(arg0 context.Context) (api.NetBlockList, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetBlockList", arg0) + ret0, _ := ret[0].(api.NetBlockList) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetBlockList indicates an expected call of NetBlockList. +func (mr *MockFullNodeMockRecorder) NetBlockList(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetBlockList", reflect.TypeOf((*MockFullNode)(nil).NetBlockList), arg0) +} + +// NetBlockRemove mocks base method. +func (m *MockFullNode) NetBlockRemove(arg0 context.Context, arg1 api.NetBlockList) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetBlockRemove", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// NetBlockRemove indicates an expected call of NetBlockRemove. +func (mr *MockFullNodeMockRecorder) NetBlockRemove(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetBlockRemove", reflect.TypeOf((*MockFullNode)(nil).NetBlockRemove), arg0, arg1) +} + +// NetConnect mocks base method. +func (m *MockFullNode) NetConnect(arg0 context.Context, arg1 peer.AddrInfo) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetConnect", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// NetConnect indicates an expected call of NetConnect. +func (mr *MockFullNodeMockRecorder) NetConnect(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetConnect", reflect.TypeOf((*MockFullNode)(nil).NetConnect), arg0, arg1) +} + +// NetConnectedness mocks base method. +func (m *MockFullNode) NetConnectedness(arg0 context.Context, arg1 peer.ID) (network0.Connectedness, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetConnectedness", arg0, arg1) + ret0, _ := ret[0].(network0.Connectedness) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetConnectedness indicates an expected call of NetConnectedness. +func (mr *MockFullNodeMockRecorder) NetConnectedness(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetConnectedness", reflect.TypeOf((*MockFullNode)(nil).NetConnectedness), arg0, arg1) +} + +// NetDisconnect mocks base method. +func (m *MockFullNode) NetDisconnect(arg0 context.Context, arg1 peer.ID) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetDisconnect", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// NetDisconnect indicates an expected call of NetDisconnect. +func (mr *MockFullNodeMockRecorder) NetDisconnect(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetDisconnect", reflect.TypeOf((*MockFullNode)(nil).NetDisconnect), arg0, arg1) +} + +// NetFindPeer mocks base method. +func (m *MockFullNode) NetFindPeer(arg0 context.Context, arg1 peer.ID) (peer.AddrInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetFindPeer", arg0, arg1) + ret0, _ := ret[0].(peer.AddrInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetFindPeer indicates an expected call of NetFindPeer. +func (mr *MockFullNodeMockRecorder) NetFindPeer(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetFindPeer", reflect.TypeOf((*MockFullNode)(nil).NetFindPeer), arg0, arg1) +} + +// NetLimit mocks base method. +func (m *MockFullNode) NetLimit(arg0 context.Context, arg1 string) (api.NetLimit, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetLimit", arg0, arg1) + ret0, _ := ret[0].(api.NetLimit) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetLimit indicates an expected call of NetLimit. +func (mr *MockFullNodeMockRecorder) NetLimit(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetLimit", reflect.TypeOf((*MockFullNode)(nil).NetLimit), arg0, arg1) +} + +// NetListening mocks base method. +func (m *MockFullNode) NetListening(arg0 context.Context) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetListening", arg0) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetListening indicates an expected call of NetListening. +func (mr *MockFullNodeMockRecorder) NetListening(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetListening", reflect.TypeOf((*MockFullNode)(nil).NetListening), arg0) +} + +// NetPeerInfo mocks base method. +func (m *MockFullNode) NetPeerInfo(arg0 context.Context, arg1 peer.ID) (*api.ExtendedPeerInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetPeerInfo", arg0, arg1) + ret0, _ := ret[0].(*api.ExtendedPeerInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetPeerInfo indicates an expected call of NetPeerInfo. +func (mr *MockFullNodeMockRecorder) NetPeerInfo(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetPeerInfo", reflect.TypeOf((*MockFullNode)(nil).NetPeerInfo), arg0, arg1) +} + +// NetPeers mocks base method. +func (m *MockFullNode) NetPeers(arg0 context.Context) ([]peer.AddrInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetPeers", arg0) + ret0, _ := ret[0].([]peer.AddrInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetPeers indicates an expected call of NetPeers. +func (mr *MockFullNodeMockRecorder) NetPeers(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetPeers", reflect.TypeOf((*MockFullNode)(nil).NetPeers), arg0) +} + +// NetPing mocks base method. +func (m *MockFullNode) NetPing(arg0 context.Context, arg1 peer.ID) (time.Duration, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetPing", arg0, arg1) + ret0, _ := ret[0].(time.Duration) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetPing indicates an expected call of NetPing. +func (mr *MockFullNodeMockRecorder) NetPing(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetPing", reflect.TypeOf((*MockFullNode)(nil).NetPing), arg0, arg1) +} + +// NetProtectAdd mocks base method. +func (m *MockFullNode) NetProtectAdd(arg0 context.Context, arg1 []peer.ID) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetProtectAdd", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// NetProtectAdd indicates an expected call of NetProtectAdd. +func (mr *MockFullNodeMockRecorder) NetProtectAdd(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetProtectAdd", reflect.TypeOf((*MockFullNode)(nil).NetProtectAdd), arg0, arg1) +} + +// NetProtectList mocks base method. +func (m *MockFullNode) NetProtectList(arg0 context.Context) ([]peer.ID, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetProtectList", arg0) + ret0, _ := ret[0].([]peer.ID) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetProtectList indicates an expected call of NetProtectList. +func (mr *MockFullNodeMockRecorder) NetProtectList(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetProtectList", reflect.TypeOf((*MockFullNode)(nil).NetProtectList), arg0) +} + +// NetProtectRemove mocks base method. +func (m *MockFullNode) NetProtectRemove(arg0 context.Context, arg1 []peer.ID) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetProtectRemove", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// NetProtectRemove indicates an expected call of NetProtectRemove. +func (mr *MockFullNodeMockRecorder) NetProtectRemove(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetProtectRemove", reflect.TypeOf((*MockFullNode)(nil).NetProtectRemove), arg0, arg1) +} + +// NetPubsubScores mocks base method. +func (m *MockFullNode) NetPubsubScores(arg0 context.Context) ([]api.PubsubScore, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetPubsubScores", arg0) + ret0, _ := ret[0].([]api.PubsubScore) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetPubsubScores indicates an expected call of NetPubsubScores. +func (mr *MockFullNodeMockRecorder) NetPubsubScores(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetPubsubScores", reflect.TypeOf((*MockFullNode)(nil).NetPubsubScores), arg0) +} + +// NetSetLimit mocks base method. +func (m *MockFullNode) NetSetLimit(arg0 context.Context, arg1 string, arg2 api.NetLimit) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetSetLimit", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// NetSetLimit indicates an expected call of NetSetLimit. +func (mr *MockFullNodeMockRecorder) NetSetLimit(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetSetLimit", reflect.TypeOf((*MockFullNode)(nil).NetSetLimit), arg0, arg1, arg2) +} + +// NetStat mocks base method. +func (m *MockFullNode) NetStat(arg0 context.Context, arg1 string) (api.NetStat, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetStat", arg0, arg1) + ret0, _ := ret[0].(api.NetStat) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetStat indicates an expected call of NetStat. +func (mr *MockFullNodeMockRecorder) NetStat(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetStat", reflect.TypeOf((*MockFullNode)(nil).NetStat), arg0, arg1) +} + +// NetVersion mocks base method. +func (m *MockFullNode) NetVersion(arg0 context.Context) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetVersion", arg0) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetVersion indicates an expected call of NetVersion. +func (mr *MockFullNodeMockRecorder) NetVersion(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetVersion", reflect.TypeOf((*MockFullNode)(nil).NetVersion), arg0) +} + +// NodeStatus mocks base method. +func (m *MockFullNode) NodeStatus(arg0 context.Context, arg1 bool) (api.NodeStatus, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NodeStatus", arg0, arg1) + ret0, _ := ret[0].(api.NodeStatus) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NodeStatus indicates an expected call of NodeStatus. +func (mr *MockFullNodeMockRecorder) NodeStatus(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NodeStatus", reflect.TypeOf((*MockFullNode)(nil).NodeStatus), arg0, arg1) +} + +// PaychAllocateLane mocks base method. +func (m *MockFullNode) PaychAllocateLane(arg0 context.Context, arg1 address.Address) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaychAllocateLane", arg0, arg1) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PaychAllocateLane indicates an expected call of PaychAllocateLane. +func (mr *MockFullNodeMockRecorder) PaychAllocateLane(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychAllocateLane", reflect.TypeOf((*MockFullNode)(nil).PaychAllocateLane), arg0, arg1) +} + +// PaychAvailableFunds mocks base method. +func (m *MockFullNode) PaychAvailableFunds(arg0 context.Context, arg1 address.Address) (*api.ChannelAvailableFunds, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaychAvailableFunds", arg0, arg1) + ret0, _ := ret[0].(*api.ChannelAvailableFunds) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PaychAvailableFunds indicates an expected call of PaychAvailableFunds. +func (mr *MockFullNodeMockRecorder) PaychAvailableFunds(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychAvailableFunds", reflect.TypeOf((*MockFullNode)(nil).PaychAvailableFunds), arg0, arg1) +} + +// PaychAvailableFundsByFromTo mocks base method. +func (m *MockFullNode) PaychAvailableFundsByFromTo(arg0 context.Context, arg1, arg2 address.Address) (*api.ChannelAvailableFunds, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaychAvailableFundsByFromTo", arg0, arg1, arg2) + ret0, _ := ret[0].(*api.ChannelAvailableFunds) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PaychAvailableFundsByFromTo indicates an expected call of PaychAvailableFundsByFromTo. +func (mr *MockFullNodeMockRecorder) PaychAvailableFundsByFromTo(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychAvailableFundsByFromTo", reflect.TypeOf((*MockFullNode)(nil).PaychAvailableFundsByFromTo), arg0, arg1, arg2) +} + +// PaychCollect mocks base method. +func (m *MockFullNode) PaychCollect(arg0 context.Context, arg1 address.Address) (cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaychCollect", arg0, arg1) + ret0, _ := ret[0].(cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PaychCollect indicates an expected call of PaychCollect. +func (mr *MockFullNodeMockRecorder) PaychCollect(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychCollect", reflect.TypeOf((*MockFullNode)(nil).PaychCollect), arg0, arg1) +} + +// PaychFund mocks base method. +func (m *MockFullNode) PaychFund(arg0 context.Context, arg1, arg2 address.Address, arg3 big.Int) (*api.ChannelInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaychFund", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*api.ChannelInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PaychFund indicates an expected call of PaychFund. +func (mr *MockFullNodeMockRecorder) PaychFund(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychFund", reflect.TypeOf((*MockFullNode)(nil).PaychFund), arg0, arg1, arg2, arg3) +} + +// PaychGet mocks base method. +func (m *MockFullNode) PaychGet(arg0 context.Context, arg1, arg2 address.Address, arg3 big.Int, arg4 api.PaychGetOpts) (*api.ChannelInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaychGet", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(*api.ChannelInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PaychGet indicates an expected call of PaychGet. +func (mr *MockFullNodeMockRecorder) PaychGet(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychGet", reflect.TypeOf((*MockFullNode)(nil).PaychGet), arg0, arg1, arg2, arg3, arg4) +} + +// PaychGetWaitReady mocks base method. +func (m *MockFullNode) PaychGetWaitReady(arg0 context.Context, arg1 cid.Cid) (address.Address, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaychGetWaitReady", arg0, arg1) + ret0, _ := ret[0].(address.Address) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PaychGetWaitReady indicates an expected call of PaychGetWaitReady. +func (mr *MockFullNodeMockRecorder) PaychGetWaitReady(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychGetWaitReady", reflect.TypeOf((*MockFullNode)(nil).PaychGetWaitReady), arg0, arg1) +} + +// PaychList mocks base method. +func (m *MockFullNode) PaychList(arg0 context.Context) ([]address.Address, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaychList", arg0) + ret0, _ := ret[0].([]address.Address) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PaychList indicates an expected call of PaychList. +func (mr *MockFullNodeMockRecorder) PaychList(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychList", reflect.TypeOf((*MockFullNode)(nil).PaychList), arg0) +} + +// PaychNewPayment mocks base method. +func (m *MockFullNode) PaychNewPayment(arg0 context.Context, arg1, arg2 address.Address, arg3 []api.VoucherSpec) (*api.PaymentInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaychNewPayment", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*api.PaymentInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PaychNewPayment indicates an expected call of PaychNewPayment. +func (mr *MockFullNodeMockRecorder) PaychNewPayment(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychNewPayment", reflect.TypeOf((*MockFullNode)(nil).PaychNewPayment), arg0, arg1, arg2, arg3) +} + +// PaychSettle mocks base method. +func (m *MockFullNode) PaychSettle(arg0 context.Context, arg1 address.Address) (cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaychSettle", arg0, arg1) + ret0, _ := ret[0].(cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PaychSettle indicates an expected call of PaychSettle. +func (mr *MockFullNodeMockRecorder) PaychSettle(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychSettle", reflect.TypeOf((*MockFullNode)(nil).PaychSettle), arg0, arg1) +} + +// PaychStatus mocks base method. +func (m *MockFullNode) PaychStatus(arg0 context.Context, arg1 address.Address) (*api.PaychStatus, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaychStatus", arg0, arg1) + ret0, _ := ret[0].(*api.PaychStatus) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PaychStatus indicates an expected call of PaychStatus. +func (mr *MockFullNodeMockRecorder) PaychStatus(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychStatus", reflect.TypeOf((*MockFullNode)(nil).PaychStatus), arg0, arg1) +} + +// PaychVoucherAdd mocks base method. +func (m *MockFullNode) PaychVoucherAdd(arg0 context.Context, arg1 address.Address, arg2 *paych.SignedVoucher, arg3 []byte, arg4 big.Int) (big.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaychVoucherAdd", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(big.Int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PaychVoucherAdd indicates an expected call of PaychVoucherAdd. +func (mr *MockFullNodeMockRecorder) PaychVoucherAdd(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychVoucherAdd", reflect.TypeOf((*MockFullNode)(nil).PaychVoucherAdd), arg0, arg1, arg2, arg3, arg4) +} + +// PaychVoucherCheckSpendable mocks base method. +func (m *MockFullNode) PaychVoucherCheckSpendable(arg0 context.Context, arg1 address.Address, arg2 *paych.SignedVoucher, arg3, arg4 []byte) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaychVoucherCheckSpendable", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PaychVoucherCheckSpendable indicates an expected call of PaychVoucherCheckSpendable. +func (mr *MockFullNodeMockRecorder) PaychVoucherCheckSpendable(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychVoucherCheckSpendable", reflect.TypeOf((*MockFullNode)(nil).PaychVoucherCheckSpendable), arg0, arg1, arg2, arg3, arg4) +} + +// PaychVoucherCheckValid mocks base method. +func (m *MockFullNode) PaychVoucherCheckValid(arg0 context.Context, arg1 address.Address, arg2 *paych.SignedVoucher) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaychVoucherCheckValid", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// PaychVoucherCheckValid indicates an expected call of PaychVoucherCheckValid. +func (mr *MockFullNodeMockRecorder) PaychVoucherCheckValid(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychVoucherCheckValid", reflect.TypeOf((*MockFullNode)(nil).PaychVoucherCheckValid), arg0, arg1, arg2) +} + +// PaychVoucherCreate mocks base method. +func (m *MockFullNode) PaychVoucherCreate(arg0 context.Context, arg1 address.Address, arg2 big.Int, arg3 uint64) (*api.VoucherCreateResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaychVoucherCreate", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*api.VoucherCreateResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PaychVoucherCreate indicates an expected call of PaychVoucherCreate. +func (mr *MockFullNodeMockRecorder) PaychVoucherCreate(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychVoucherCreate", reflect.TypeOf((*MockFullNode)(nil).PaychVoucherCreate), arg0, arg1, arg2, arg3) +} + +// PaychVoucherList mocks base method. +func (m *MockFullNode) PaychVoucherList(arg0 context.Context, arg1 address.Address) ([]*paych.SignedVoucher, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaychVoucherList", arg0, arg1) + ret0, _ := ret[0].([]*paych.SignedVoucher) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PaychVoucherList indicates an expected call of PaychVoucherList. +func (mr *MockFullNodeMockRecorder) PaychVoucherList(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychVoucherList", reflect.TypeOf((*MockFullNode)(nil).PaychVoucherList), arg0, arg1) +} + +// PaychVoucherSubmit mocks base method. +func (m *MockFullNode) PaychVoucherSubmit(arg0 context.Context, arg1 address.Address, arg2 *paych.SignedVoucher, arg3, arg4 []byte) (cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaychVoucherSubmit", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PaychVoucherSubmit indicates an expected call of PaychVoucherSubmit. +func (mr *MockFullNodeMockRecorder) PaychVoucherSubmit(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychVoucherSubmit", reflect.TypeOf((*MockFullNode)(nil).PaychVoucherSubmit), arg0, arg1, arg2, arg3, arg4) +} + +// RaftLeader mocks base method. +func (m *MockFullNode) RaftLeader(arg0 context.Context) (peer.ID, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RaftLeader", arg0) + ret0, _ := ret[0].(peer.ID) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// RaftLeader indicates an expected call of RaftLeader. +func (mr *MockFullNodeMockRecorder) RaftLeader(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RaftLeader", reflect.TypeOf((*MockFullNode)(nil).RaftLeader), arg0) +} + +// RaftState mocks base method. +func (m *MockFullNode) RaftState(arg0 context.Context) (*api.RaftStateData, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RaftState", arg0) + ret0, _ := ret[0].(*api.RaftStateData) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// RaftState indicates an expected call of RaftState. +func (mr *MockFullNodeMockRecorder) RaftState(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RaftState", reflect.TypeOf((*MockFullNode)(nil).RaftState), arg0) +} + +// Session mocks base method. +func (m *MockFullNode) Session(arg0 context.Context) (uuid.UUID, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Session", arg0) + ret0, _ := ret[0].(uuid.UUID) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Session indicates an expected call of Session. +func (mr *MockFullNodeMockRecorder) Session(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Session", reflect.TypeOf((*MockFullNode)(nil).Session), arg0) +} + +// Shutdown mocks base method. +func (m *MockFullNode) Shutdown(arg0 context.Context) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Shutdown", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// Shutdown indicates an expected call of Shutdown. +func (mr *MockFullNodeMockRecorder) Shutdown(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Shutdown", reflect.TypeOf((*MockFullNode)(nil).Shutdown), arg0) +} + +// StartTime mocks base method. +func (m *MockFullNode) StartTime(arg0 context.Context) (time.Time, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StartTime", arg0) + ret0, _ := ret[0].(time.Time) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StartTime indicates an expected call of StartTime. +func (mr *MockFullNodeMockRecorder) StartTime(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartTime", reflect.TypeOf((*MockFullNode)(nil).StartTime), arg0) +} + +// StateAccountKey mocks base method. +func (m *MockFullNode) StateAccountKey(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (address.Address, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateAccountKey", arg0, arg1, arg2) + ret0, _ := ret[0].(address.Address) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateAccountKey indicates an expected call of StateAccountKey. +func (mr *MockFullNodeMockRecorder) StateAccountKey(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateAccountKey", reflect.TypeOf((*MockFullNode)(nil).StateAccountKey), arg0, arg1, arg2) +} + +// StateActorCodeCIDs mocks base method. +func (m *MockFullNode) StateActorCodeCIDs(arg0 context.Context, arg1 network.Version) (map[string]cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateActorCodeCIDs", arg0, arg1) + ret0, _ := ret[0].(map[string]cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateActorCodeCIDs indicates an expected call of StateActorCodeCIDs. +func (mr *MockFullNodeMockRecorder) StateActorCodeCIDs(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateActorCodeCIDs", reflect.TypeOf((*MockFullNode)(nil).StateActorCodeCIDs), arg0, arg1) +} + +// StateActorManifestCID mocks base method. +func (m *MockFullNode) StateActorManifestCID(arg0 context.Context, arg1 network.Version) (cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateActorManifestCID", arg0, arg1) + ret0, _ := ret[0].(cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateActorManifestCID indicates an expected call of StateActorManifestCID. +func (mr *MockFullNodeMockRecorder) StateActorManifestCID(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateActorManifestCID", reflect.TypeOf((*MockFullNode)(nil).StateActorManifestCID), arg0, arg1) +} + +// StateAllMinerFaults mocks base method. +func (m *MockFullNode) StateAllMinerFaults(arg0 context.Context, arg1 abi.ChainEpoch, arg2 types.TipSetKey) ([]*api.Fault, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateAllMinerFaults", arg0, arg1, arg2) + ret0, _ := ret[0].([]*api.Fault) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateAllMinerFaults indicates an expected call of StateAllMinerFaults. +func (mr *MockFullNodeMockRecorder) StateAllMinerFaults(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateAllMinerFaults", reflect.TypeOf((*MockFullNode)(nil).StateAllMinerFaults), arg0, arg1, arg2) +} + +// StateCall mocks base method. +func (m *MockFullNode) StateCall(arg0 context.Context, arg1 *types.Message, arg2 types.TipSetKey) (*api.InvocResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateCall", arg0, arg1, arg2) + ret0, _ := ret[0].(*api.InvocResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateCall indicates an expected call of StateCall. +func (mr *MockFullNodeMockRecorder) StateCall(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateCall", reflect.TypeOf((*MockFullNode)(nil).StateCall), arg0, arg1, arg2) +} + +// StateChangedActors mocks base method. +func (m *MockFullNode) StateChangedActors(arg0 context.Context, arg1, arg2 cid.Cid) (map[string]types.ActorV5, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateChangedActors", arg0, arg1, arg2) + ret0, _ := ret[0].(map[string]types.ActorV5) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateChangedActors indicates an expected call of StateChangedActors. +func (mr *MockFullNodeMockRecorder) StateChangedActors(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateChangedActors", reflect.TypeOf((*MockFullNode)(nil).StateChangedActors), arg0, arg1, arg2) +} + +// StateCirculatingSupply mocks base method. +func (m *MockFullNode) StateCirculatingSupply(arg0 context.Context, arg1 types.TipSetKey) (big.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateCirculatingSupply", arg0, arg1) + ret0, _ := ret[0].(big.Int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateCirculatingSupply indicates an expected call of StateCirculatingSupply. +func (mr *MockFullNodeMockRecorder) StateCirculatingSupply(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateCirculatingSupply", reflect.TypeOf((*MockFullNode)(nil).StateCirculatingSupply), arg0, arg1) +} + +// StateCompute mocks base method. +func (m *MockFullNode) StateCompute(arg0 context.Context, arg1 abi.ChainEpoch, arg2 []*types.Message, arg3 types.TipSetKey) (*api.ComputeStateOutput, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateCompute", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*api.ComputeStateOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateCompute indicates an expected call of StateCompute. +func (mr *MockFullNodeMockRecorder) StateCompute(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateCompute", reflect.TypeOf((*MockFullNode)(nil).StateCompute), arg0, arg1, arg2, arg3) +} + +// StateComputeDataCID mocks base method. +func (m *MockFullNode) StateComputeDataCID(arg0 context.Context, arg1 address.Address, arg2 abi.RegisteredSealProof, arg3 []abi.DealID, arg4 types.TipSetKey) (cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateComputeDataCID", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateComputeDataCID indicates an expected call of StateComputeDataCID. +func (mr *MockFullNodeMockRecorder) StateComputeDataCID(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateComputeDataCID", reflect.TypeOf((*MockFullNode)(nil).StateComputeDataCID), arg0, arg1, arg2, arg3, arg4) +} + +// StateDealProviderCollateralBounds mocks base method. +func (m *MockFullNode) StateDealProviderCollateralBounds(arg0 context.Context, arg1 abi.PaddedPieceSize, arg2 bool, arg3 types.TipSetKey) (api.DealCollateralBounds, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateDealProviderCollateralBounds", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(api.DealCollateralBounds) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateDealProviderCollateralBounds indicates an expected call of StateDealProviderCollateralBounds. +func (mr *MockFullNodeMockRecorder) StateDealProviderCollateralBounds(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateDealProviderCollateralBounds", reflect.TypeOf((*MockFullNode)(nil).StateDealProviderCollateralBounds), arg0, arg1, arg2, arg3) +} + +// StateDecodeParams mocks base method. +func (m *MockFullNode) StateDecodeParams(arg0 context.Context, arg1 address.Address, arg2 abi.MethodNum, arg3 []byte, arg4 types.TipSetKey) (interface{}, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateDecodeParams", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(interface{}) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateDecodeParams indicates an expected call of StateDecodeParams. +func (mr *MockFullNodeMockRecorder) StateDecodeParams(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateDecodeParams", reflect.TypeOf((*MockFullNode)(nil).StateDecodeParams), arg0, arg1, arg2, arg3, arg4) +} + +// StateEncodeParams mocks base method. +func (m *MockFullNode) StateEncodeParams(arg0 context.Context, arg1 cid.Cid, arg2 abi.MethodNum, arg3 json.RawMessage) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateEncodeParams", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateEncodeParams indicates an expected call of StateEncodeParams. +func (mr *MockFullNodeMockRecorder) StateEncodeParams(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateEncodeParams", reflect.TypeOf((*MockFullNode)(nil).StateEncodeParams), arg0, arg1, arg2, arg3) +} + +// StateGetActor mocks base method. +func (m *MockFullNode) StateGetActor(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (*types.ActorV5, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateGetActor", arg0, arg1, arg2) + ret0, _ := ret[0].(*types.ActorV5) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateGetActor indicates an expected call of StateGetActor. +func (mr *MockFullNodeMockRecorder) StateGetActor(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateGetActor", reflect.TypeOf((*MockFullNode)(nil).StateGetActor), arg0, arg1, arg2) +} + +// StateGetAllocation mocks base method. +func (m *MockFullNode) StateGetAllocation(arg0 context.Context, arg1 address.Address, arg2 verifreg.AllocationId, arg3 types.TipSetKey) (*verifreg.Allocation, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateGetAllocation", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*verifreg.Allocation) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateGetAllocation indicates an expected call of StateGetAllocation. +func (mr *MockFullNodeMockRecorder) StateGetAllocation(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateGetAllocation", reflect.TypeOf((*MockFullNode)(nil).StateGetAllocation), arg0, arg1, arg2, arg3) +} + +// StateGetAllocationForPendingDeal mocks base method. +func (m *MockFullNode) StateGetAllocationForPendingDeal(arg0 context.Context, arg1 abi.DealID, arg2 types.TipSetKey) (*verifreg.Allocation, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateGetAllocationForPendingDeal", arg0, arg1, arg2) + ret0, _ := ret[0].(*verifreg.Allocation) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateGetAllocationForPendingDeal indicates an expected call of StateGetAllocationForPendingDeal. +func (mr *MockFullNodeMockRecorder) StateGetAllocationForPendingDeal(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateGetAllocationForPendingDeal", reflect.TypeOf((*MockFullNode)(nil).StateGetAllocationForPendingDeal), arg0, arg1, arg2) +} + +// StateGetAllocations mocks base method. +func (m *MockFullNode) StateGetAllocations(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (map[verifreg.AllocationId]verifreg.Allocation, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateGetAllocations", arg0, arg1, arg2) + ret0, _ := ret[0].(map[verifreg.AllocationId]verifreg.Allocation) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateGetAllocations indicates an expected call of StateGetAllocations. +func (mr *MockFullNodeMockRecorder) StateGetAllocations(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateGetAllocations", reflect.TypeOf((*MockFullNode)(nil).StateGetAllocations), arg0, arg1, arg2) +} + +// StateGetBeaconEntry mocks base method. +func (m *MockFullNode) StateGetBeaconEntry(arg0 context.Context, arg1 abi.ChainEpoch) (*types.BeaconEntry, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateGetBeaconEntry", arg0, arg1) + ret0, _ := ret[0].(*types.BeaconEntry) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateGetBeaconEntry indicates an expected call of StateGetBeaconEntry. +func (mr *MockFullNodeMockRecorder) StateGetBeaconEntry(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateGetBeaconEntry", reflect.TypeOf((*MockFullNode)(nil).StateGetBeaconEntry), arg0, arg1) +} + +// StateGetClaim mocks base method. +func (m *MockFullNode) StateGetClaim(arg0 context.Context, arg1 address.Address, arg2 verifreg.ClaimId, arg3 types.TipSetKey) (*verifreg.Claim, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateGetClaim", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*verifreg.Claim) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateGetClaim indicates an expected call of StateGetClaim. +func (mr *MockFullNodeMockRecorder) StateGetClaim(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateGetClaim", reflect.TypeOf((*MockFullNode)(nil).StateGetClaim), arg0, arg1, arg2, arg3) +} + +// StateGetClaims mocks base method. +func (m *MockFullNode) StateGetClaims(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (map[verifreg.ClaimId]verifreg.Claim, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateGetClaims", arg0, arg1, arg2) + ret0, _ := ret[0].(map[verifreg.ClaimId]verifreg.Claim) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateGetClaims indicates an expected call of StateGetClaims. +func (mr *MockFullNodeMockRecorder) StateGetClaims(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateGetClaims", reflect.TypeOf((*MockFullNode)(nil).StateGetClaims), arg0, arg1, arg2) +} + +// StateGetNetworkParams mocks base method. +func (m *MockFullNode) StateGetNetworkParams(arg0 context.Context) (*api.NetworkParams, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateGetNetworkParams", arg0) + ret0, _ := ret[0].(*api.NetworkParams) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateGetNetworkParams indicates an expected call of StateGetNetworkParams. +func (mr *MockFullNodeMockRecorder) StateGetNetworkParams(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateGetNetworkParams", reflect.TypeOf((*MockFullNode)(nil).StateGetNetworkParams), arg0) +} + +// StateGetRandomnessFromBeacon mocks base method. +func (m *MockFullNode) StateGetRandomnessFromBeacon(arg0 context.Context, arg1 crypto.DomainSeparationTag, arg2 abi.ChainEpoch, arg3 []byte, arg4 types.TipSetKey) (abi.Randomness, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateGetRandomnessFromBeacon", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(abi.Randomness) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateGetRandomnessFromBeacon indicates an expected call of StateGetRandomnessFromBeacon. +func (mr *MockFullNodeMockRecorder) StateGetRandomnessFromBeacon(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateGetRandomnessFromBeacon", reflect.TypeOf((*MockFullNode)(nil).StateGetRandomnessFromBeacon), arg0, arg1, arg2, arg3, arg4) +} + +// StateGetRandomnessFromTickets mocks base method. +func (m *MockFullNode) StateGetRandomnessFromTickets(arg0 context.Context, arg1 crypto.DomainSeparationTag, arg2 abi.ChainEpoch, arg3 []byte, arg4 types.TipSetKey) (abi.Randomness, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateGetRandomnessFromTickets", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(abi.Randomness) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateGetRandomnessFromTickets indicates an expected call of StateGetRandomnessFromTickets. +func (mr *MockFullNodeMockRecorder) StateGetRandomnessFromTickets(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateGetRandomnessFromTickets", reflect.TypeOf((*MockFullNode)(nil).StateGetRandomnessFromTickets), arg0, arg1, arg2, arg3, arg4) +} + +// StateListActors mocks base method. +func (m *MockFullNode) StateListActors(arg0 context.Context, arg1 types.TipSetKey) ([]address.Address, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateListActors", arg0, arg1) + ret0, _ := ret[0].([]address.Address) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateListActors indicates an expected call of StateListActors. +func (mr *MockFullNodeMockRecorder) StateListActors(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateListActors", reflect.TypeOf((*MockFullNode)(nil).StateListActors), arg0, arg1) +} + +// StateListMessages mocks base method. +func (m *MockFullNode) StateListMessages(arg0 context.Context, arg1 *api.MessageMatch, arg2 types.TipSetKey, arg3 abi.ChainEpoch) ([]cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateListMessages", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].([]cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateListMessages indicates an expected call of StateListMessages. +func (mr *MockFullNodeMockRecorder) StateListMessages(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateListMessages", reflect.TypeOf((*MockFullNode)(nil).StateListMessages), arg0, arg1, arg2, arg3) +} + +// StateListMiners mocks base method. +func (m *MockFullNode) StateListMiners(arg0 context.Context, arg1 types.TipSetKey) ([]address.Address, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateListMiners", arg0, arg1) + ret0, _ := ret[0].([]address.Address) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateListMiners indicates an expected call of StateListMiners. +func (mr *MockFullNodeMockRecorder) StateListMiners(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateListMiners", reflect.TypeOf((*MockFullNode)(nil).StateListMiners), arg0, arg1) +} + +// StateLookupID mocks base method. +func (m *MockFullNode) StateLookupID(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (address.Address, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateLookupID", arg0, arg1, arg2) + ret0, _ := ret[0].(address.Address) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateLookupID indicates an expected call of StateLookupID. +func (mr *MockFullNodeMockRecorder) StateLookupID(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateLookupID", reflect.TypeOf((*MockFullNode)(nil).StateLookupID), arg0, arg1, arg2) +} + +// StateLookupRobustAddress mocks base method. +func (m *MockFullNode) StateLookupRobustAddress(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (address.Address, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateLookupRobustAddress", arg0, arg1, arg2) + ret0, _ := ret[0].(address.Address) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateLookupRobustAddress indicates an expected call of StateLookupRobustAddress. +func (mr *MockFullNodeMockRecorder) StateLookupRobustAddress(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateLookupRobustAddress", reflect.TypeOf((*MockFullNode)(nil).StateLookupRobustAddress), arg0, arg1, arg2) +} + +// StateMarketBalance mocks base method. +func (m *MockFullNode) StateMarketBalance(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (api.MarketBalance, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMarketBalance", arg0, arg1, arg2) + ret0, _ := ret[0].(api.MarketBalance) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMarketBalance indicates an expected call of StateMarketBalance. +func (mr *MockFullNodeMockRecorder) StateMarketBalance(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMarketBalance", reflect.TypeOf((*MockFullNode)(nil).StateMarketBalance), arg0, arg1, arg2) +} + +// StateMarketDeals mocks base method. +func (m *MockFullNode) StateMarketDeals(arg0 context.Context, arg1 types.TipSetKey) (map[string]*api.MarketDeal, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMarketDeals", arg0, arg1) + ret0, _ := ret[0].(map[string]*api.MarketDeal) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMarketDeals indicates an expected call of StateMarketDeals. +func (mr *MockFullNodeMockRecorder) StateMarketDeals(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMarketDeals", reflect.TypeOf((*MockFullNode)(nil).StateMarketDeals), arg0, arg1) +} + +// StateMarketParticipants mocks base method. +func (m *MockFullNode) StateMarketParticipants(arg0 context.Context, arg1 types.TipSetKey) (map[string]api.MarketBalance, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMarketParticipants", arg0, arg1) + ret0, _ := ret[0].(map[string]api.MarketBalance) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMarketParticipants indicates an expected call of StateMarketParticipants. +func (mr *MockFullNodeMockRecorder) StateMarketParticipants(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMarketParticipants", reflect.TypeOf((*MockFullNode)(nil).StateMarketParticipants), arg0, arg1) +} + +// StateMarketStorageDeal mocks base method. +func (m *MockFullNode) StateMarketStorageDeal(arg0 context.Context, arg1 abi.DealID, arg2 types.TipSetKey) (*api.MarketDeal, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMarketStorageDeal", arg0, arg1, arg2) + ret0, _ := ret[0].(*api.MarketDeal) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMarketStorageDeal indicates an expected call of StateMarketStorageDeal. +func (mr *MockFullNodeMockRecorder) StateMarketStorageDeal(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMarketStorageDeal", reflect.TypeOf((*MockFullNode)(nil).StateMarketStorageDeal), arg0, arg1, arg2) +} + +// StateMinerActiveSectors mocks base method. +func (m *MockFullNode) StateMinerActiveSectors(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) ([]*miner.SectorOnChainInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerActiveSectors", arg0, arg1, arg2) + ret0, _ := ret[0].([]*miner.SectorOnChainInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerActiveSectors indicates an expected call of StateMinerActiveSectors. +func (mr *MockFullNodeMockRecorder) StateMinerActiveSectors(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerActiveSectors", reflect.TypeOf((*MockFullNode)(nil).StateMinerActiveSectors), arg0, arg1, arg2) +} + +// StateMinerAllocated mocks base method. +func (m *MockFullNode) StateMinerAllocated(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (*bitfield.BitField, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerAllocated", arg0, arg1, arg2) + ret0, _ := ret[0].(*bitfield.BitField) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerAllocated indicates an expected call of StateMinerAllocated. +func (mr *MockFullNodeMockRecorder) StateMinerAllocated(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerAllocated", reflect.TypeOf((*MockFullNode)(nil).StateMinerAllocated), arg0, arg1, arg2) +} + +// StateMinerAvailableBalance mocks base method. +func (m *MockFullNode) StateMinerAvailableBalance(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (big.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerAvailableBalance", arg0, arg1, arg2) + ret0, _ := ret[0].(big.Int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerAvailableBalance indicates an expected call of StateMinerAvailableBalance. +func (mr *MockFullNodeMockRecorder) StateMinerAvailableBalance(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerAvailableBalance", reflect.TypeOf((*MockFullNode)(nil).StateMinerAvailableBalance), arg0, arg1, arg2) +} + +// StateMinerDeadlines mocks base method. +func (m *MockFullNode) StateMinerDeadlines(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) ([]api.Deadline, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerDeadlines", arg0, arg1, arg2) + ret0, _ := ret[0].([]api.Deadline) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerDeadlines indicates an expected call of StateMinerDeadlines. +func (mr *MockFullNodeMockRecorder) StateMinerDeadlines(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerDeadlines", reflect.TypeOf((*MockFullNode)(nil).StateMinerDeadlines), arg0, arg1, arg2) +} + +// StateMinerFaults mocks base method. +func (m *MockFullNode) StateMinerFaults(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (bitfield.BitField, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerFaults", arg0, arg1, arg2) + ret0, _ := ret[0].(bitfield.BitField) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerFaults indicates an expected call of StateMinerFaults. +func (mr *MockFullNodeMockRecorder) StateMinerFaults(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerFaults", reflect.TypeOf((*MockFullNode)(nil).StateMinerFaults), arg0, arg1, arg2) +} + +// StateMinerInfo mocks base method. +func (m *MockFullNode) StateMinerInfo(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (api.MinerInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerInfo", arg0, arg1, arg2) + ret0, _ := ret[0].(api.MinerInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerInfo indicates an expected call of StateMinerInfo. +func (mr *MockFullNodeMockRecorder) StateMinerInfo(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerInfo", reflect.TypeOf((*MockFullNode)(nil).StateMinerInfo), arg0, arg1, arg2) +} + +// StateMinerInitialPledgeCollateral mocks base method. +func (m *MockFullNode) StateMinerInitialPledgeCollateral(arg0 context.Context, arg1 address.Address, arg2 miner.SectorPreCommitInfo, arg3 types.TipSetKey) (big.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerInitialPledgeCollateral", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(big.Int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerInitialPledgeCollateral indicates an expected call of StateMinerInitialPledgeCollateral. +func (mr *MockFullNodeMockRecorder) StateMinerInitialPledgeCollateral(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerInitialPledgeCollateral", reflect.TypeOf((*MockFullNode)(nil).StateMinerInitialPledgeCollateral), arg0, arg1, arg2, arg3) +} + +// StateMinerPartitions mocks base method. +func (m *MockFullNode) StateMinerPartitions(arg0 context.Context, arg1 address.Address, arg2 uint64, arg3 types.TipSetKey) ([]api.Partition, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerPartitions", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].([]api.Partition) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerPartitions indicates an expected call of StateMinerPartitions. +func (mr *MockFullNodeMockRecorder) StateMinerPartitions(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerPartitions", reflect.TypeOf((*MockFullNode)(nil).StateMinerPartitions), arg0, arg1, arg2, arg3) +} + +// StateMinerPower mocks base method. +func (m *MockFullNode) StateMinerPower(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (*api.MinerPower, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerPower", arg0, arg1, arg2) + ret0, _ := ret[0].(*api.MinerPower) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerPower indicates an expected call of StateMinerPower. +func (mr *MockFullNodeMockRecorder) StateMinerPower(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerPower", reflect.TypeOf((*MockFullNode)(nil).StateMinerPower), arg0, arg1, arg2) +} + +// StateMinerPreCommitDepositForPower mocks base method. +func (m *MockFullNode) StateMinerPreCommitDepositForPower(arg0 context.Context, arg1 address.Address, arg2 miner.SectorPreCommitInfo, arg3 types.TipSetKey) (big.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerPreCommitDepositForPower", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(big.Int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerPreCommitDepositForPower indicates an expected call of StateMinerPreCommitDepositForPower. +func (mr *MockFullNodeMockRecorder) StateMinerPreCommitDepositForPower(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerPreCommitDepositForPower", reflect.TypeOf((*MockFullNode)(nil).StateMinerPreCommitDepositForPower), arg0, arg1, arg2, arg3) +} + +// StateMinerProvingDeadline mocks base method. +func (m *MockFullNode) StateMinerProvingDeadline(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (*dline.Info, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerProvingDeadline", arg0, arg1, arg2) + ret0, _ := ret[0].(*dline.Info) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerProvingDeadline indicates an expected call of StateMinerProvingDeadline. +func (mr *MockFullNodeMockRecorder) StateMinerProvingDeadline(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerProvingDeadline", reflect.TypeOf((*MockFullNode)(nil).StateMinerProvingDeadline), arg0, arg1, arg2) +} + +// StateMinerRecoveries mocks base method. +func (m *MockFullNode) StateMinerRecoveries(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (bitfield.BitField, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerRecoveries", arg0, arg1, arg2) + ret0, _ := ret[0].(bitfield.BitField) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerRecoveries indicates an expected call of StateMinerRecoveries. +func (mr *MockFullNodeMockRecorder) StateMinerRecoveries(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerRecoveries", reflect.TypeOf((*MockFullNode)(nil).StateMinerRecoveries), arg0, arg1, arg2) +} + +// StateMinerSectorAllocated mocks base method. +func (m *MockFullNode) StateMinerSectorAllocated(arg0 context.Context, arg1 address.Address, arg2 abi.SectorNumber, arg3 types.TipSetKey) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerSectorAllocated", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerSectorAllocated indicates an expected call of StateMinerSectorAllocated. +func (mr *MockFullNodeMockRecorder) StateMinerSectorAllocated(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerSectorAllocated", reflect.TypeOf((*MockFullNode)(nil).StateMinerSectorAllocated), arg0, arg1, arg2, arg3) +} + +// StateMinerSectorCount mocks base method. +func (m *MockFullNode) StateMinerSectorCount(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (api.MinerSectors, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerSectorCount", arg0, arg1, arg2) + ret0, _ := ret[0].(api.MinerSectors) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerSectorCount indicates an expected call of StateMinerSectorCount. +func (mr *MockFullNodeMockRecorder) StateMinerSectorCount(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerSectorCount", reflect.TypeOf((*MockFullNode)(nil).StateMinerSectorCount), arg0, arg1, arg2) +} + +// StateMinerSectors mocks base method. +func (m *MockFullNode) StateMinerSectors(arg0 context.Context, arg1 address.Address, arg2 *bitfield.BitField, arg3 types.TipSetKey) ([]*miner.SectorOnChainInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerSectors", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].([]*miner.SectorOnChainInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerSectors indicates an expected call of StateMinerSectors. +func (mr *MockFullNodeMockRecorder) StateMinerSectors(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerSectors", reflect.TypeOf((*MockFullNode)(nil).StateMinerSectors), arg0, arg1, arg2, arg3) +} + +// StateNetworkName mocks base method. +func (m *MockFullNode) StateNetworkName(arg0 context.Context) (dtypes.NetworkName, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateNetworkName", arg0) + ret0, _ := ret[0].(dtypes.NetworkName) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateNetworkName indicates an expected call of StateNetworkName. +func (mr *MockFullNodeMockRecorder) StateNetworkName(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateNetworkName", reflect.TypeOf((*MockFullNode)(nil).StateNetworkName), arg0) +} + +// StateNetworkVersion mocks base method. +func (m *MockFullNode) StateNetworkVersion(arg0 context.Context, arg1 types.TipSetKey) (network.Version, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateNetworkVersion", arg0, arg1) + ret0, _ := ret[0].(network.Version) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateNetworkVersion indicates an expected call of StateNetworkVersion. +func (mr *MockFullNodeMockRecorder) StateNetworkVersion(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateNetworkVersion", reflect.TypeOf((*MockFullNode)(nil).StateNetworkVersion), arg0, arg1) +} + +// StateReadState mocks base method. +func (m *MockFullNode) StateReadState(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (*api.ActorState, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateReadState", arg0, arg1, arg2) + ret0, _ := ret[0].(*api.ActorState) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateReadState indicates an expected call of StateReadState. +func (mr *MockFullNodeMockRecorder) StateReadState(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateReadState", reflect.TypeOf((*MockFullNode)(nil).StateReadState), arg0, arg1, arg2) +} + +// StateReplay mocks base method. +func (m *MockFullNode) StateReplay(arg0 context.Context, arg1 types.TipSetKey, arg2 cid.Cid) (*api.InvocResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateReplay", arg0, arg1, arg2) + ret0, _ := ret[0].(*api.InvocResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateReplay indicates an expected call of StateReplay. +func (mr *MockFullNodeMockRecorder) StateReplay(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateReplay", reflect.TypeOf((*MockFullNode)(nil).StateReplay), arg0, arg1, arg2) +} + +// StateSearchMsg mocks base method. +func (m *MockFullNode) StateSearchMsg(arg0 context.Context, arg1 types.TipSetKey, arg2 cid.Cid, arg3 abi.ChainEpoch, arg4 bool) (*api.MsgLookup, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateSearchMsg", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(*api.MsgLookup) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateSearchMsg indicates an expected call of StateSearchMsg. +func (mr *MockFullNodeMockRecorder) StateSearchMsg(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateSearchMsg", reflect.TypeOf((*MockFullNode)(nil).StateSearchMsg), arg0, arg1, arg2, arg3, arg4) +} + +// StateSectorExpiration mocks base method. +func (m *MockFullNode) StateSectorExpiration(arg0 context.Context, arg1 address.Address, arg2 abi.SectorNumber, arg3 types.TipSetKey) (*miner0.SectorExpiration, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateSectorExpiration", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*miner0.SectorExpiration) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateSectorExpiration indicates an expected call of StateSectorExpiration. +func (mr *MockFullNodeMockRecorder) StateSectorExpiration(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateSectorExpiration", reflect.TypeOf((*MockFullNode)(nil).StateSectorExpiration), arg0, arg1, arg2, arg3) +} + +// StateSectorGetInfo mocks base method. +func (m *MockFullNode) StateSectorGetInfo(arg0 context.Context, arg1 address.Address, arg2 abi.SectorNumber, arg3 types.TipSetKey) (*miner.SectorOnChainInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateSectorGetInfo", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*miner.SectorOnChainInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateSectorGetInfo indicates an expected call of StateSectorGetInfo. +func (mr *MockFullNodeMockRecorder) StateSectorGetInfo(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateSectorGetInfo", reflect.TypeOf((*MockFullNode)(nil).StateSectorGetInfo), arg0, arg1, arg2, arg3) +} + +// StateSectorPartition mocks base method. +func (m *MockFullNode) StateSectorPartition(arg0 context.Context, arg1 address.Address, arg2 abi.SectorNumber, arg3 types.TipSetKey) (*miner0.SectorLocation, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateSectorPartition", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*miner0.SectorLocation) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateSectorPartition indicates an expected call of StateSectorPartition. +func (mr *MockFullNodeMockRecorder) StateSectorPartition(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateSectorPartition", reflect.TypeOf((*MockFullNode)(nil).StateSectorPartition), arg0, arg1, arg2, arg3) +} + +// StateSectorPreCommitInfo mocks base method. +func (m *MockFullNode) StateSectorPreCommitInfo(arg0 context.Context, arg1 address.Address, arg2 abi.SectorNumber, arg3 types.TipSetKey) (*miner.SectorPreCommitOnChainInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateSectorPreCommitInfo", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*miner.SectorPreCommitOnChainInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateSectorPreCommitInfo indicates an expected call of StateSectorPreCommitInfo. +func (mr *MockFullNodeMockRecorder) StateSectorPreCommitInfo(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateSectorPreCommitInfo", reflect.TypeOf((*MockFullNode)(nil).StateSectorPreCommitInfo), arg0, arg1, arg2, arg3) +} + +// StateVMCirculatingSupplyInternal mocks base method. +func (m *MockFullNode) StateVMCirculatingSupplyInternal(arg0 context.Context, arg1 types.TipSetKey) (api.CirculatingSupply, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateVMCirculatingSupplyInternal", arg0, arg1) + ret0, _ := ret[0].(api.CirculatingSupply) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateVMCirculatingSupplyInternal indicates an expected call of StateVMCirculatingSupplyInternal. +func (mr *MockFullNodeMockRecorder) StateVMCirculatingSupplyInternal(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateVMCirculatingSupplyInternal", reflect.TypeOf((*MockFullNode)(nil).StateVMCirculatingSupplyInternal), arg0, arg1) +} + +// StateVerifiedClientStatus mocks base method. +func (m *MockFullNode) StateVerifiedClientStatus(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (*big.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateVerifiedClientStatus", arg0, arg1, arg2) + ret0, _ := ret[0].(*big.Int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateVerifiedClientStatus indicates an expected call of StateVerifiedClientStatus. +func (mr *MockFullNodeMockRecorder) StateVerifiedClientStatus(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateVerifiedClientStatus", reflect.TypeOf((*MockFullNode)(nil).StateVerifiedClientStatus), arg0, arg1, arg2) +} + +// StateVerifiedRegistryRootKey mocks base method. +func (m *MockFullNode) StateVerifiedRegistryRootKey(arg0 context.Context, arg1 types.TipSetKey) (address.Address, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateVerifiedRegistryRootKey", arg0, arg1) + ret0, _ := ret[0].(address.Address) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateVerifiedRegistryRootKey indicates an expected call of StateVerifiedRegistryRootKey. +func (mr *MockFullNodeMockRecorder) StateVerifiedRegistryRootKey(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateVerifiedRegistryRootKey", reflect.TypeOf((*MockFullNode)(nil).StateVerifiedRegistryRootKey), arg0, arg1) +} + +// StateVerifierStatus mocks base method. +func (m *MockFullNode) StateVerifierStatus(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (*big.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateVerifierStatus", arg0, arg1, arg2) + ret0, _ := ret[0].(*big.Int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateVerifierStatus indicates an expected call of StateVerifierStatus. +func (mr *MockFullNodeMockRecorder) StateVerifierStatus(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateVerifierStatus", reflect.TypeOf((*MockFullNode)(nil).StateVerifierStatus), arg0, arg1, arg2) +} + +// StateWaitMsg mocks base method. +func (m *MockFullNode) StateWaitMsg(arg0 context.Context, arg1 cid.Cid, arg2 uint64, arg3 abi.ChainEpoch, arg4 bool) (*api.MsgLookup, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateWaitMsg", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(*api.MsgLookup) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateWaitMsg indicates an expected call of StateWaitMsg. +func (mr *MockFullNodeMockRecorder) StateWaitMsg(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateWaitMsg", reflect.TypeOf((*MockFullNode)(nil).StateWaitMsg), arg0, arg1, arg2, arg3, arg4) +} + +// SyncCheckBad mocks base method. +func (m *MockFullNode) SyncCheckBad(arg0 context.Context, arg1 cid.Cid) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SyncCheckBad", arg0, arg1) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SyncCheckBad indicates an expected call of SyncCheckBad. +func (mr *MockFullNodeMockRecorder) SyncCheckBad(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncCheckBad", reflect.TypeOf((*MockFullNode)(nil).SyncCheckBad), arg0, arg1) +} + +// SyncCheckpoint mocks base method. +func (m *MockFullNode) SyncCheckpoint(arg0 context.Context, arg1 types.TipSetKey) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SyncCheckpoint", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// SyncCheckpoint indicates an expected call of SyncCheckpoint. +func (mr *MockFullNodeMockRecorder) SyncCheckpoint(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncCheckpoint", reflect.TypeOf((*MockFullNode)(nil).SyncCheckpoint), arg0, arg1) +} + +// SyncIncomingBlocks mocks base method. +func (m *MockFullNode) SyncIncomingBlocks(arg0 context.Context) (<-chan *types.BlockHeader, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SyncIncomingBlocks", arg0) + ret0, _ := ret[0].(<-chan *types.BlockHeader) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SyncIncomingBlocks indicates an expected call of SyncIncomingBlocks. +func (mr *MockFullNodeMockRecorder) SyncIncomingBlocks(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncIncomingBlocks", reflect.TypeOf((*MockFullNode)(nil).SyncIncomingBlocks), arg0) +} + +// SyncMarkBad mocks base method. +func (m *MockFullNode) SyncMarkBad(arg0 context.Context, arg1 cid.Cid) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SyncMarkBad", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// SyncMarkBad indicates an expected call of SyncMarkBad. +func (mr *MockFullNodeMockRecorder) SyncMarkBad(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncMarkBad", reflect.TypeOf((*MockFullNode)(nil).SyncMarkBad), arg0, arg1) +} + +// SyncState mocks base method. +func (m *MockFullNode) SyncState(arg0 context.Context) (*api.SyncState, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SyncState", arg0) + ret0, _ := ret[0].(*api.SyncState) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SyncState indicates an expected call of SyncState. +func (mr *MockFullNodeMockRecorder) SyncState(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncState", reflect.TypeOf((*MockFullNode)(nil).SyncState), arg0) +} + +// SyncSubmitBlock mocks base method. +func (m *MockFullNode) SyncSubmitBlock(arg0 context.Context, arg1 *types.BlockMsg) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SyncSubmitBlock", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// SyncSubmitBlock indicates an expected call of SyncSubmitBlock. +func (mr *MockFullNodeMockRecorder) SyncSubmitBlock(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncSubmitBlock", reflect.TypeOf((*MockFullNode)(nil).SyncSubmitBlock), arg0, arg1) +} + +// SyncUnmarkAllBad mocks base method. +func (m *MockFullNode) SyncUnmarkAllBad(arg0 context.Context) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SyncUnmarkAllBad", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// SyncUnmarkAllBad indicates an expected call of SyncUnmarkAllBad. +func (mr *MockFullNodeMockRecorder) SyncUnmarkAllBad(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncUnmarkAllBad", reflect.TypeOf((*MockFullNode)(nil).SyncUnmarkAllBad), arg0) +} + +// SyncUnmarkBad mocks base method. +func (m *MockFullNode) SyncUnmarkBad(arg0 context.Context, arg1 cid.Cid) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SyncUnmarkBad", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// SyncUnmarkBad indicates an expected call of SyncUnmarkBad. +func (mr *MockFullNodeMockRecorder) SyncUnmarkBad(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncUnmarkBad", reflect.TypeOf((*MockFullNode)(nil).SyncUnmarkBad), arg0, arg1) +} + +// SyncValidateTipset mocks base method. +func (m *MockFullNode) SyncValidateTipset(arg0 context.Context, arg1 types.TipSetKey) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SyncValidateTipset", arg0, arg1) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SyncValidateTipset indicates an expected call of SyncValidateTipset. +func (mr *MockFullNodeMockRecorder) SyncValidateTipset(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncValidateTipset", reflect.TypeOf((*MockFullNode)(nil).SyncValidateTipset), arg0, arg1) +} + +// Version mocks base method. +func (m *MockFullNode) Version(arg0 context.Context) (api.APIVersion, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Version", arg0) + ret0, _ := ret[0].(api.APIVersion) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Version indicates an expected call of Version. +func (mr *MockFullNodeMockRecorder) Version(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Version", reflect.TypeOf((*MockFullNode)(nil).Version), arg0) +} + +// WalletBalance mocks base method. +func (m *MockFullNode) WalletBalance(arg0 context.Context, arg1 address.Address) (big.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WalletBalance", arg0, arg1) + ret0, _ := ret[0].(big.Int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WalletBalance indicates an expected call of WalletBalance. +func (mr *MockFullNodeMockRecorder) WalletBalance(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WalletBalance", reflect.TypeOf((*MockFullNode)(nil).WalletBalance), arg0, arg1) +} + +// WalletDefaultAddress mocks base method. +func (m *MockFullNode) WalletDefaultAddress(arg0 context.Context) (address.Address, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WalletDefaultAddress", arg0) + ret0, _ := ret[0].(address.Address) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WalletDefaultAddress indicates an expected call of WalletDefaultAddress. +func (mr *MockFullNodeMockRecorder) WalletDefaultAddress(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WalletDefaultAddress", reflect.TypeOf((*MockFullNode)(nil).WalletDefaultAddress), arg0) +} + +// WalletDelete mocks base method. +func (m *MockFullNode) WalletDelete(arg0 context.Context, arg1 address.Address) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WalletDelete", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// WalletDelete indicates an expected call of WalletDelete. +func (mr *MockFullNodeMockRecorder) WalletDelete(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WalletDelete", reflect.TypeOf((*MockFullNode)(nil).WalletDelete), arg0, arg1) +} + +// WalletExport mocks base method. +func (m *MockFullNode) WalletExport(arg0 context.Context, arg1 address.Address) (*types.KeyInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WalletExport", arg0, arg1) + ret0, _ := ret[0].(*types.KeyInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WalletExport indicates an expected call of WalletExport. +func (mr *MockFullNodeMockRecorder) WalletExport(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WalletExport", reflect.TypeOf((*MockFullNode)(nil).WalletExport), arg0, arg1) +} + +// WalletHas mocks base method. +func (m *MockFullNode) WalletHas(arg0 context.Context, arg1 address.Address) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WalletHas", arg0, arg1) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WalletHas indicates an expected call of WalletHas. +func (mr *MockFullNodeMockRecorder) WalletHas(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WalletHas", reflect.TypeOf((*MockFullNode)(nil).WalletHas), arg0, arg1) +} + +// WalletImport mocks base method. +func (m *MockFullNode) WalletImport(arg0 context.Context, arg1 *types.KeyInfo) (address.Address, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WalletImport", arg0, arg1) + ret0, _ := ret[0].(address.Address) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WalletImport indicates an expected call of WalletImport. +func (mr *MockFullNodeMockRecorder) WalletImport(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WalletImport", reflect.TypeOf((*MockFullNode)(nil).WalletImport), arg0, arg1) +} + +// WalletList mocks base method. +func (m *MockFullNode) WalletList(arg0 context.Context) ([]address.Address, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WalletList", arg0) + ret0, _ := ret[0].([]address.Address) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WalletList indicates an expected call of WalletList. +func (mr *MockFullNodeMockRecorder) WalletList(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WalletList", reflect.TypeOf((*MockFullNode)(nil).WalletList), arg0) +} + +// WalletNew mocks base method. +func (m *MockFullNode) WalletNew(arg0 context.Context, arg1 types.KeyType) (address.Address, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WalletNew", arg0, arg1) + ret0, _ := ret[0].(address.Address) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WalletNew indicates an expected call of WalletNew. +func (mr *MockFullNodeMockRecorder) WalletNew(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WalletNew", reflect.TypeOf((*MockFullNode)(nil).WalletNew), arg0, arg1) +} + +// WalletSetDefault mocks base method. +func (m *MockFullNode) WalletSetDefault(arg0 context.Context, arg1 address.Address) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WalletSetDefault", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// WalletSetDefault indicates an expected call of WalletSetDefault. +func (mr *MockFullNodeMockRecorder) WalletSetDefault(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WalletSetDefault", reflect.TypeOf((*MockFullNode)(nil).WalletSetDefault), arg0, arg1) +} + +// WalletSign mocks base method. +func (m *MockFullNode) WalletSign(arg0 context.Context, arg1 address.Address, arg2 []byte) (*crypto.Signature, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WalletSign", arg0, arg1, arg2) + ret0, _ := ret[0].(*crypto.Signature) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WalletSign indicates an expected call of WalletSign. +func (mr *MockFullNodeMockRecorder) WalletSign(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WalletSign", reflect.TypeOf((*MockFullNode)(nil).WalletSign), arg0, arg1, arg2) +} + +// WalletSignMessage mocks base method. +func (m *MockFullNode) WalletSignMessage(arg0 context.Context, arg1 address.Address, arg2 *types.Message) (*types.SignedMessage, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WalletSignMessage", arg0, arg1, arg2) + ret0, _ := ret[0].(*types.SignedMessage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WalletSignMessage indicates an expected call of WalletSignMessage. +func (mr *MockFullNodeMockRecorder) WalletSignMessage(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WalletSignMessage", reflect.TypeOf((*MockFullNode)(nil).WalletSignMessage), arg0, arg1, arg2) +} + +// WalletValidateAddress mocks base method. +func (m *MockFullNode) WalletValidateAddress(arg0 context.Context, arg1 string) (address.Address, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WalletValidateAddress", arg0, arg1) + ret0, _ := ret[0].(address.Address) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WalletValidateAddress indicates an expected call of WalletValidateAddress. +func (mr *MockFullNodeMockRecorder) WalletValidateAddress(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WalletValidateAddress", reflect.TypeOf((*MockFullNode)(nil).WalletValidateAddress), arg0, arg1) +} + +// WalletVerify mocks base method. +func (m *MockFullNode) WalletVerify(arg0 context.Context, arg1 address.Address, arg2 []byte, arg3 *crypto.Signature) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WalletVerify", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WalletVerify indicates an expected call of WalletVerify. +func (mr *MockFullNodeMockRecorder) WalletVerify(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WalletVerify", reflect.TypeOf((*MockFullNode)(nil).WalletVerify), arg0, arg1, arg2, arg3) +} + +// Web3ClientVersion mocks base method. +func (m *MockFullNode) Web3ClientVersion(arg0 context.Context) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Web3ClientVersion", arg0) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Web3ClientVersion indicates an expected call of Web3ClientVersion. +func (mr *MockFullNodeMockRecorder) Web3ClientVersion(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Web3ClientVersion", reflect.TypeOf((*MockFullNode)(nil).Web3ClientVersion), arg0) +} diff --git a/api/permissioned.go b/api/permissioned.go new file mode 100644 index 000000000..72d2239ee --- /dev/null +++ b/api/permissioned.go @@ -0,0 +1,48 @@ +package api + +import ( + "github.com/filecoin-project/go-jsonrpc/auth" +) + +const ( + // When changing these, update docs/API.md too + + PermRead auth.Permission = "read" // default + PermWrite auth.Permission = "write" + PermSign auth.Permission = "sign" // Use wallet keys for signing + PermAdmin auth.Permission = "admin" // Manage permissions +) + +var AllPermissions = []auth.Permission{PermRead, PermWrite, PermSign, PermAdmin} +var DefaultPerms = []auth.Permission{PermRead} + +func permissionedProxies(in, out interface{}) { + outs := GetInternalStructs(out) + for _, o := range outs { + auth.PermissionedProxy(AllPermissions, DefaultPerms, in, o) + } +} + +func PermissionedStorMinerAPI(a StorageMiner) StorageMiner { + var out StorageMinerStruct + permissionedProxies(a, &out) + return &out +} + +func PermissionedFullAPI(a FullNode) FullNode { + var out FullNodeStruct + permissionedProxies(a, &out) + return &out +} + +func PermissionedWorkerAPI(a Worker) Worker { + var out WorkerStruct + permissionedProxies(a, &out) + return &out +} + +func PermissionedWalletAPI(a Wallet) Wallet { + var out WalletStruct + permissionedProxies(a, &out) + return &out +} diff --git a/api/proxy_gen.go b/api/proxy_gen.go new file mode 100644 index 000000000..459fd5864 --- /dev/null +++ b/api/proxy_gen.go @@ -0,0 +1,7268 @@ +// Code generated by github.com/filecoin-project/lotus/gen/api. DO NOT EDIT. + +package api + +import ( + "context" + "encoding/json" + "time" + + "github.com/google/uuid" + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + "github.com/libp2p/go-libp2p/core/metrics" + "github.com/libp2p/go-libp2p/core/network" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/protocol" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-bitfield" + datatransfer "github.com/filecoin-project/go-data-transfer/v2" + "github.com/filecoin-project/go-fil-markets/piecestore" + "github.com/filecoin-project/go-fil-markets/retrievalmarket" + "github.com/filecoin-project/go-fil-markets/storagemarket" + "github.com/filecoin-project/go-jsonrpc" + "github.com/filecoin-project/go-jsonrpc/auth" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/builtin/v8/paych" + "github.com/filecoin-project/go-state-types/builtin/v9/miner" + verifregtypes "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/go-state-types/dline" + abinetwork "github.com/filecoin-project/go-state-types/network" + "github.com/filecoin-project/go-state-types/proof" + + apitypes "github.com/filecoin-project/lotus/api/types" + builtinactors "github.com/filecoin-project/lotus/chain/actors/builtin" + lminer "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/ethtypes" + "github.com/filecoin-project/lotus/journal/alerting" + "github.com/filecoin-project/lotus/node/modules/dtypes" + "github.com/filecoin-project/lotus/node/repo/imports" + "github.com/filecoin-project/lotus/storage/pipeline/sealiface" + "github.com/filecoin-project/lotus/storage/sealer/fsutil" + "github.com/filecoin-project/lotus/storage/sealer/sealtasks" + "github.com/filecoin-project/lotus/storage/sealer/storiface" +) + +var ErrNotSupported = xerrors.New("method not supported") + +type ChainIOStruct struct { + Internal ChainIOMethods +} + +type ChainIOMethods struct { + ChainHasObj func(p0 context.Context, p1 cid.Cid) (bool, error) `` + + ChainPutObj func(p0 context.Context, p1 blocks.Block) error `` + + ChainReadObj func(p0 context.Context, p1 cid.Cid) ([]byte, error) `` +} + +type ChainIOStub struct { +} + +type CommonStruct struct { + Internal CommonMethods +} + +type CommonMethods struct { + AuthNew func(p0 context.Context, p1 []auth.Permission) ([]byte, error) `perm:"admin"` + + AuthVerify func(p0 context.Context, p1 string) ([]auth.Permission, error) `perm:"read"` + + Closing func(p0 context.Context) (<-chan struct{}, error) `perm:"read"` + + Discover func(p0 context.Context) (apitypes.OpenRPCDocument, error) `perm:"read"` + + LogAlerts func(p0 context.Context) ([]alerting.Alert, error) `perm:"admin"` + + LogList func(p0 context.Context) ([]string, error) `perm:"write"` + + LogSetLevel func(p0 context.Context, p1 string, p2 string) error `perm:"write"` + + Session func(p0 context.Context) (uuid.UUID, error) `perm:"read"` + + Shutdown func(p0 context.Context) error `perm:"admin"` + + StartTime func(p0 context.Context) (time.Time, error) `perm:"read"` + + Version func(p0 context.Context) (APIVersion, error) `perm:"read"` +} + +type CommonStub struct { +} + +type CommonNetStruct struct { + CommonStruct + + NetStruct + + Internal CommonNetMethods +} + +type CommonNetMethods struct { +} + +type CommonNetStub struct { + CommonStub + + NetStub +} + +type EthSubscriberStruct struct { + Internal EthSubscriberMethods +} + +type EthSubscriberMethods struct { + EthSubscription func(p0 context.Context, p1 jsonrpc.RawParams) error `notify:"true" rpc_method:"eth_subscription"` +} + +type EthSubscriberStub struct { +} + +type FullNodeStruct struct { + CommonStruct + + NetStruct + + Internal FullNodeMethods +} + +type FullNodeMethods struct { + ChainBlockstoreInfo func(p0 context.Context) (map[string]interface{}, error) `perm:"read"` + + ChainCheckBlockstore func(p0 context.Context) error `perm:"admin"` + + ChainDeleteObj func(p0 context.Context, p1 cid.Cid) error `perm:"admin"` + + ChainExport func(p0 context.Context, p1 abi.ChainEpoch, p2 bool, p3 types.TipSetKey) (<-chan []byte, error) `perm:"read"` + + ChainExportRangeInternal func(p0 context.Context, p1 types.TipSetKey, p2 types.TipSetKey, p3 ChainExportConfig) error `perm:"admin"` + + ChainGetBlock func(p0 context.Context, p1 cid.Cid) (*types.BlockHeader, error) `perm:"read"` + + ChainGetBlockMessages func(p0 context.Context, p1 cid.Cid) (*BlockMessages, error) `perm:"read"` + + ChainGetEvents func(p0 context.Context, p1 cid.Cid) ([]types.Event, error) `perm:"read"` + + ChainGetGenesis func(p0 context.Context) (*types.TipSet, error) `perm:"read"` + + ChainGetMessage func(p0 context.Context, p1 cid.Cid) (*types.Message, error) `perm:"read"` + + ChainGetMessagesInTipset func(p0 context.Context, p1 types.TipSetKey) ([]Message, error) `perm:"read"` + + ChainGetNode func(p0 context.Context, p1 string) (*IpldObject, error) `perm:"read"` + + ChainGetParentMessages func(p0 context.Context, p1 cid.Cid) ([]Message, error) `perm:"read"` + + ChainGetParentReceipts func(p0 context.Context, p1 cid.Cid) ([]*types.MessageReceipt, error) `perm:"read"` + + ChainGetPath func(p0 context.Context, p1 types.TipSetKey, p2 types.TipSetKey) ([]*HeadChange, error) `perm:"read"` + + ChainGetTipSet func(p0 context.Context, p1 types.TipSetKey) (*types.TipSet, error) `perm:"read"` + + ChainGetTipSetAfterHeight func(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) `perm:"read"` + + ChainGetTipSetByHeight func(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) `perm:"read"` + + ChainHasObj func(p0 context.Context, p1 cid.Cid) (bool, error) `perm:"read"` + + ChainHead func(p0 context.Context) (*types.TipSet, error) `perm:"read"` + + ChainHotGC func(p0 context.Context, p1 HotGCOpts) error `perm:"admin"` + + ChainNotify func(p0 context.Context) (<-chan []*HeadChange, error) `perm:"read"` + + ChainPrune func(p0 context.Context, p1 PruneOpts) error `perm:"admin"` + + ChainPutObj func(p0 context.Context, p1 blocks.Block) error `perm:"admin"` + + ChainReadObj func(p0 context.Context, p1 cid.Cid) ([]byte, error) `perm:"read"` + + ChainSetHead func(p0 context.Context, p1 types.TipSetKey) error `perm:"admin"` + + ChainStatObj func(p0 context.Context, p1 cid.Cid, p2 cid.Cid) (ObjStat, error) `perm:"read"` + + ChainTipSetWeight func(p0 context.Context, p1 types.TipSetKey) (types.BigInt, error) `perm:"read"` + + ClientCalcCommP func(p0 context.Context, p1 string) (*CommPRet, error) `perm:"write"` + + ClientCancelDataTransfer func(p0 context.Context, p1 datatransfer.TransferID, p2 peer.ID, p3 bool) error `perm:"write"` + + ClientCancelRetrievalDeal func(p0 context.Context, p1 retrievalmarket.DealID) error `perm:"write"` + + ClientDataTransferUpdates func(p0 context.Context) (<-chan DataTransferChannel, error) `perm:"write"` + + ClientDealPieceCID func(p0 context.Context, p1 cid.Cid) (DataCIDSize, error) `perm:"read"` + + ClientDealSize func(p0 context.Context, p1 cid.Cid) (DataSize, error) `perm:"read"` + + ClientExport func(p0 context.Context, p1 ExportRef, p2 FileRef) error `perm:"admin"` + + ClientFindData func(p0 context.Context, p1 cid.Cid, p2 *cid.Cid) ([]QueryOffer, error) `perm:"read"` + + ClientGenCar func(p0 context.Context, p1 FileRef, p2 string) error `perm:"write"` + + ClientGetDealInfo func(p0 context.Context, p1 cid.Cid) (*DealInfo, error) `perm:"read"` + + ClientGetDealStatus func(p0 context.Context, p1 uint64) (string, error) `perm:"read"` + + ClientGetDealUpdates func(p0 context.Context) (<-chan DealInfo, error) `perm:"write"` + + ClientGetRetrievalUpdates func(p0 context.Context) (<-chan RetrievalInfo, error) `perm:"write"` + + ClientHasLocal func(p0 context.Context, p1 cid.Cid) (bool, error) `perm:"write"` + + ClientImport func(p0 context.Context, p1 FileRef) (*ImportRes, error) `perm:"admin"` + + ClientListDataTransfers func(p0 context.Context) ([]DataTransferChannel, error) `perm:"write"` + + ClientListDeals func(p0 context.Context) ([]DealInfo, error) `perm:"write"` + + ClientListImports func(p0 context.Context) ([]Import, error) `perm:"write"` + + ClientListRetrievals func(p0 context.Context) ([]RetrievalInfo, error) `perm:"write"` + + ClientMinerQueryOffer func(p0 context.Context, p1 address.Address, p2 cid.Cid, p3 *cid.Cid) (QueryOffer, error) `perm:"read"` + + ClientQueryAsk func(p0 context.Context, p1 peer.ID, p2 address.Address) (*StorageAsk, error) `perm:"read"` + + ClientRemoveImport func(p0 context.Context, p1 imports.ID) error `perm:"admin"` + + ClientRestartDataTransfer func(p0 context.Context, p1 datatransfer.TransferID, p2 peer.ID, p3 bool) error `perm:"write"` + + ClientRetrieve func(p0 context.Context, p1 RetrievalOrder) (*RestrievalRes, error) `perm:"admin"` + + ClientRetrieveTryRestartInsufficientFunds func(p0 context.Context, p1 address.Address) error `perm:"write"` + + ClientRetrieveWait func(p0 context.Context, p1 retrievalmarket.DealID) error `perm:"admin"` + + ClientStartDeal func(p0 context.Context, p1 *StartDealParams) (*cid.Cid, error) `perm:"admin"` + + ClientStatelessDeal func(p0 context.Context, p1 *StartDealParams) (*cid.Cid, error) `perm:"write"` + + CreateBackup func(p0 context.Context, p1 string) error `perm:"admin"` + + EthAccounts func(p0 context.Context) ([]ethtypes.EthAddress, error) `perm:"read"` + + EthAddressToFilecoinAddress func(p0 context.Context, p1 ethtypes.EthAddress) (address.Address, error) `perm:"read"` + + EthBlockNumber func(p0 context.Context) (ethtypes.EthUint64, error) `perm:"read"` + + EthCall func(p0 context.Context, p1 ethtypes.EthCall, p2 string) (ethtypes.EthBytes, error) `perm:"read"` + + EthChainId func(p0 context.Context) (ethtypes.EthUint64, error) `perm:"read"` + + EthEstimateGas func(p0 context.Context, p1 ethtypes.EthCall) (ethtypes.EthUint64, error) `perm:"read"` + + EthFeeHistory func(p0 context.Context, p1 jsonrpc.RawParams) (ethtypes.EthFeeHistory, error) `perm:"read"` + + EthGasPrice func(p0 context.Context) (ethtypes.EthBigInt, error) `perm:"read"` + + EthGetBalance func(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthBigInt, error) `perm:"read"` + + EthGetBlockByHash func(p0 context.Context, p1 ethtypes.EthHash, p2 bool) (ethtypes.EthBlock, error) `perm:"read"` + + EthGetBlockByNumber func(p0 context.Context, p1 string, p2 bool) (ethtypes.EthBlock, error) `perm:"read"` + + EthGetBlockTransactionCountByHash func(p0 context.Context, p1 ethtypes.EthHash) (ethtypes.EthUint64, error) `perm:"read"` + + EthGetBlockTransactionCountByNumber func(p0 context.Context, p1 ethtypes.EthUint64) (ethtypes.EthUint64, error) `perm:"read"` + + EthGetCode func(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthBytes, error) `perm:"read"` + + EthGetFilterChanges func(p0 context.Context, p1 ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) `perm:"read"` + + EthGetFilterLogs func(p0 context.Context, p1 ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) `perm:"read"` + + EthGetLogs func(p0 context.Context, p1 *ethtypes.EthFilterSpec) (*ethtypes.EthFilterResult, error) `perm:"read"` + + EthGetMessageCidByTransactionHash func(p0 context.Context, p1 *ethtypes.EthHash) (*cid.Cid, error) `perm:"read"` + + EthGetStorageAt func(p0 context.Context, p1 ethtypes.EthAddress, p2 ethtypes.EthBytes, p3 string) (ethtypes.EthBytes, error) `perm:"read"` + + EthGetTransactionByBlockHashAndIndex func(p0 context.Context, p1 ethtypes.EthHash, p2 ethtypes.EthUint64) (ethtypes.EthTx, error) `perm:"read"` + + EthGetTransactionByBlockNumberAndIndex func(p0 context.Context, p1 ethtypes.EthUint64, p2 ethtypes.EthUint64) (ethtypes.EthTx, error) `perm:"read"` + + EthGetTransactionByHash func(p0 context.Context, p1 *ethtypes.EthHash) (*ethtypes.EthTx, error) `perm:"read"` + + EthGetTransactionByHashLimited func(p0 context.Context, p1 *ethtypes.EthHash, p2 abi.ChainEpoch) (*ethtypes.EthTx, error) `perm:"read"` + + EthGetTransactionCount func(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthUint64, error) `perm:"read"` + + EthGetTransactionHashByCid func(p0 context.Context, p1 cid.Cid) (*ethtypes.EthHash, error) `perm:"read"` + + EthGetTransactionReceipt func(p0 context.Context, p1 ethtypes.EthHash) (*EthTxReceipt, error) `perm:"read"` + + EthGetTransactionReceiptLimited func(p0 context.Context, p1 ethtypes.EthHash, p2 abi.ChainEpoch) (*EthTxReceipt, error) `perm:"read"` + + EthMaxPriorityFeePerGas func(p0 context.Context) (ethtypes.EthBigInt, error) `perm:"read"` + + EthNewBlockFilter func(p0 context.Context) (ethtypes.EthFilterID, error) `perm:"read"` + + EthNewFilter func(p0 context.Context, p1 *ethtypes.EthFilterSpec) (ethtypes.EthFilterID, error) `perm:"read"` + + EthNewPendingTransactionFilter func(p0 context.Context) (ethtypes.EthFilterID, error) `perm:"read"` + + EthProtocolVersion func(p0 context.Context) (ethtypes.EthUint64, error) `perm:"read"` + + EthSendRawTransaction func(p0 context.Context, p1 ethtypes.EthBytes) (ethtypes.EthHash, error) `perm:"read"` + + EthSubscribe func(p0 context.Context, p1 jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error) `perm:"read"` + + EthSyncing func(p0 context.Context) (ethtypes.EthSyncingResult, error) `perm:"read"` + + EthUninstallFilter func(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) `perm:"read"` + + EthUnsubscribe func(p0 context.Context, p1 ethtypes.EthSubscriptionID) (bool, error) `perm:"read"` + + FilecoinAddressToEthAddress func(p0 context.Context, p1 address.Address) (ethtypes.EthAddress, error) `perm:"read"` + + GasEstimateFeeCap func(p0 context.Context, p1 *types.Message, p2 int64, p3 types.TipSetKey) (types.BigInt, error) `perm:"read"` + + GasEstimateGasLimit func(p0 context.Context, p1 *types.Message, p2 types.TipSetKey) (int64, error) `perm:"read"` + + GasEstimateGasPremium func(p0 context.Context, p1 uint64, p2 address.Address, p3 int64, p4 types.TipSetKey) (types.BigInt, error) `perm:"read"` + + GasEstimateMessageGas func(p0 context.Context, p1 *types.Message, p2 *MessageSendSpec, p3 types.TipSetKey) (*types.Message, error) `perm:"read"` + + MarketAddBalance func(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt) (cid.Cid, error) `perm:"sign"` + + MarketGetReserved func(p0 context.Context, p1 address.Address) (types.BigInt, error) `perm:"sign"` + + MarketReleaseFunds func(p0 context.Context, p1 address.Address, p2 types.BigInt) error `perm:"sign"` + + MarketReserveFunds func(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt) (cid.Cid, error) `perm:"sign"` + + MarketWithdraw func(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt) (cid.Cid, error) `perm:"sign"` + + MinerCreateBlock func(p0 context.Context, p1 *BlockTemplate) (*types.BlockMsg, error) `perm:"write"` + + MinerGetBaseInfo func(p0 context.Context, p1 address.Address, p2 abi.ChainEpoch, p3 types.TipSetKey) (*MiningBaseInfo, error) `perm:"read"` + + MpoolBatchPush func(p0 context.Context, p1 []*types.SignedMessage) ([]cid.Cid, error) `perm:"write"` + + MpoolBatchPushMessage func(p0 context.Context, p1 []*types.Message, p2 *MessageSendSpec) ([]*types.SignedMessage, error) `perm:"sign"` + + MpoolBatchPushUntrusted func(p0 context.Context, p1 []*types.SignedMessage) ([]cid.Cid, error) `perm:"write"` + + MpoolCheckMessages func(p0 context.Context, p1 []*MessagePrototype) ([][]MessageCheckStatus, error) `perm:"read"` + + MpoolCheckPendingMessages func(p0 context.Context, p1 address.Address) ([][]MessageCheckStatus, error) `perm:"read"` + + MpoolCheckReplaceMessages func(p0 context.Context, p1 []*types.Message) ([][]MessageCheckStatus, error) `perm:"read"` + + MpoolClear func(p0 context.Context, p1 bool) error `perm:"write"` + + MpoolGetConfig func(p0 context.Context) (*types.MpoolConfig, error) `perm:"read"` + + MpoolGetNonce func(p0 context.Context, p1 address.Address) (uint64, error) `perm:"read"` + + MpoolPending func(p0 context.Context, p1 types.TipSetKey) ([]*types.SignedMessage, error) `perm:"read"` + + MpoolPush func(p0 context.Context, p1 *types.SignedMessage) (cid.Cid, error) `perm:"write"` + + MpoolPushMessage func(p0 context.Context, p1 *types.Message, p2 *MessageSendSpec) (*types.SignedMessage, error) `perm:"sign"` + + MpoolPushUntrusted func(p0 context.Context, p1 *types.SignedMessage) (cid.Cid, error) `perm:"write"` + + MpoolSelect func(p0 context.Context, p1 types.TipSetKey, p2 float64) ([]*types.SignedMessage, error) `perm:"read"` + + MpoolSetConfig func(p0 context.Context, p1 *types.MpoolConfig) error `perm:"admin"` + + MpoolSub func(p0 context.Context) (<-chan MpoolUpdate, error) `perm:"read"` + + MsigAddApprove func(p0 context.Context, p1 address.Address, p2 address.Address, p3 uint64, p4 address.Address, p5 address.Address, p6 bool) (*MessagePrototype, error) `perm:"sign"` + + MsigAddCancel func(p0 context.Context, p1 address.Address, p2 address.Address, p3 uint64, p4 address.Address, p5 bool) (*MessagePrototype, error) `perm:"sign"` + + MsigAddPropose func(p0 context.Context, p1 address.Address, p2 address.Address, p3 address.Address, p4 bool) (*MessagePrototype, error) `perm:"sign"` + + MsigApprove func(p0 context.Context, p1 address.Address, p2 uint64, p3 address.Address) (*MessagePrototype, error) `perm:"sign"` + + MsigApproveTxnHash func(p0 context.Context, p1 address.Address, p2 uint64, p3 address.Address, p4 address.Address, p5 types.BigInt, p6 address.Address, p7 uint64, p8 []byte) (*MessagePrototype, error) `perm:"sign"` + + MsigCancel func(p0 context.Context, p1 address.Address, p2 uint64, p3 address.Address) (*MessagePrototype, error) `perm:"sign"` + + MsigCancelTxnHash func(p0 context.Context, p1 address.Address, p2 uint64, p3 address.Address, p4 types.BigInt, p5 address.Address, p6 uint64, p7 []byte) (*MessagePrototype, error) `perm:"sign"` + + MsigCreate func(p0 context.Context, p1 uint64, p2 []address.Address, p3 abi.ChainEpoch, p4 types.BigInt, p5 address.Address, p6 types.BigInt) (*MessagePrototype, error) `perm:"sign"` + + MsigGetAvailableBalance func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (types.BigInt, error) `perm:"read"` + + MsigGetPending func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) ([]*MsigTransaction, error) `perm:"read"` + + MsigGetVested func(p0 context.Context, p1 address.Address, p2 types.TipSetKey, p3 types.TipSetKey) (types.BigInt, error) `perm:"read"` + + MsigGetVestingSchedule func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (MsigVesting, error) `perm:"read"` + + MsigPropose func(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt, p4 address.Address, p5 uint64, p6 []byte) (*MessagePrototype, error) `perm:"sign"` + + MsigRemoveSigner func(p0 context.Context, p1 address.Address, p2 address.Address, p3 address.Address, p4 bool) (*MessagePrototype, error) `perm:"sign"` + + MsigSwapApprove func(p0 context.Context, p1 address.Address, p2 address.Address, p3 uint64, p4 address.Address, p5 address.Address, p6 address.Address) (*MessagePrototype, error) `perm:"sign"` + + MsigSwapCancel func(p0 context.Context, p1 address.Address, p2 address.Address, p3 uint64, p4 address.Address, p5 address.Address) (*MessagePrototype, error) `perm:"sign"` + + MsigSwapPropose func(p0 context.Context, p1 address.Address, p2 address.Address, p3 address.Address, p4 address.Address) (*MessagePrototype, error) `perm:"sign"` + + NetListening func(p0 context.Context) (bool, error) `perm:"read"` + + NetVersion func(p0 context.Context) (string, error) `perm:"read"` + + NodeStatus func(p0 context.Context, p1 bool) (NodeStatus, error) `perm:"read"` + + PaychAllocateLane func(p0 context.Context, p1 address.Address) (uint64, error) `perm:"sign"` + + PaychAvailableFunds func(p0 context.Context, p1 address.Address) (*ChannelAvailableFunds, error) `perm:"sign"` + + PaychAvailableFundsByFromTo func(p0 context.Context, p1 address.Address, p2 address.Address) (*ChannelAvailableFunds, error) `perm:"sign"` + + PaychCollect func(p0 context.Context, p1 address.Address) (cid.Cid, error) `perm:"sign"` + + PaychFund func(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt) (*ChannelInfo, error) `perm:"sign"` + + PaychGet func(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt, p4 PaychGetOpts) (*ChannelInfo, error) `perm:"sign"` + + PaychGetWaitReady func(p0 context.Context, p1 cid.Cid) (address.Address, error) `perm:"sign"` + + PaychList func(p0 context.Context) ([]address.Address, error) `perm:"read"` + + PaychNewPayment func(p0 context.Context, p1 address.Address, p2 address.Address, p3 []VoucherSpec) (*PaymentInfo, error) `perm:"sign"` + + PaychSettle func(p0 context.Context, p1 address.Address) (cid.Cid, error) `perm:"sign"` + + PaychStatus func(p0 context.Context, p1 address.Address) (*PaychStatus, error) `perm:"read"` + + PaychVoucherAdd func(p0 context.Context, p1 address.Address, p2 *paych.SignedVoucher, p3 []byte, p4 types.BigInt) (types.BigInt, error) `perm:"write"` + + PaychVoucherCheckSpendable func(p0 context.Context, p1 address.Address, p2 *paych.SignedVoucher, p3 []byte, p4 []byte) (bool, error) `perm:"read"` + + PaychVoucherCheckValid func(p0 context.Context, p1 address.Address, p2 *paych.SignedVoucher) error `perm:"read"` + + PaychVoucherCreate func(p0 context.Context, p1 address.Address, p2 types.BigInt, p3 uint64) (*VoucherCreateResult, error) `perm:"sign"` + + PaychVoucherList func(p0 context.Context, p1 address.Address) ([]*paych.SignedVoucher, error) `perm:"write"` + + PaychVoucherSubmit func(p0 context.Context, p1 address.Address, p2 *paych.SignedVoucher, p3 []byte, p4 []byte) (cid.Cid, error) `perm:"sign"` + + RaftLeader func(p0 context.Context) (peer.ID, error) `perm:"read"` + + RaftState func(p0 context.Context) (*RaftStateData, error) `perm:"read"` + + StateAccountKey func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) `perm:"read"` + + StateActorCodeCIDs func(p0 context.Context, p1 abinetwork.Version) (map[string]cid.Cid, error) `perm:"read"` + + StateActorManifestCID func(p0 context.Context, p1 abinetwork.Version) (cid.Cid, error) `perm:"read"` + + StateAllMinerFaults func(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) ([]*Fault, error) `perm:"read"` + + StateCall func(p0 context.Context, p1 *types.Message, p2 types.TipSetKey) (*InvocResult, error) `perm:"read"` + + StateChangedActors func(p0 context.Context, p1 cid.Cid, p2 cid.Cid) (map[string]types.Actor, error) `perm:"read"` + + StateCirculatingSupply func(p0 context.Context, p1 types.TipSetKey) (abi.TokenAmount, error) `perm:"read"` + + StateCompute func(p0 context.Context, p1 abi.ChainEpoch, p2 []*types.Message, p3 types.TipSetKey) (*ComputeStateOutput, error) `perm:"read"` + + StateComputeDataCID func(p0 context.Context, p1 address.Address, p2 abi.RegisteredSealProof, p3 []abi.DealID, p4 types.TipSetKey) (cid.Cid, error) `perm:"read"` + + StateDealProviderCollateralBounds func(p0 context.Context, p1 abi.PaddedPieceSize, p2 bool, p3 types.TipSetKey) (DealCollateralBounds, error) `perm:"read"` + + StateDecodeParams func(p0 context.Context, p1 address.Address, p2 abi.MethodNum, p3 []byte, p4 types.TipSetKey) (interface{}, error) `perm:"read"` + + StateEncodeParams func(p0 context.Context, p1 cid.Cid, p2 abi.MethodNum, p3 json.RawMessage) ([]byte, error) `perm:"read"` + + StateGetActor func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*types.Actor, error) `perm:"read"` + + StateGetAllocation func(p0 context.Context, p1 address.Address, p2 verifregtypes.AllocationId, p3 types.TipSetKey) (*verifregtypes.Allocation, error) `perm:"read"` + + StateGetAllocationForPendingDeal func(p0 context.Context, p1 abi.DealID, p2 types.TipSetKey) (*verifregtypes.Allocation, error) `perm:"read"` + + StateGetAllocations func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (map[verifregtypes.AllocationId]verifregtypes.Allocation, error) `perm:"read"` + + StateGetBeaconEntry func(p0 context.Context, p1 abi.ChainEpoch) (*types.BeaconEntry, error) `perm:"read"` + + StateGetClaim func(p0 context.Context, p1 address.Address, p2 verifregtypes.ClaimId, p3 types.TipSetKey) (*verifregtypes.Claim, error) `perm:"read"` + + StateGetClaims func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (map[verifregtypes.ClaimId]verifregtypes.Claim, error) `perm:"read"` + + StateGetNetworkParams func(p0 context.Context) (*NetworkParams, error) `perm:"read"` + + StateGetRandomnessFromBeacon func(p0 context.Context, p1 crypto.DomainSeparationTag, p2 abi.ChainEpoch, p3 []byte, p4 types.TipSetKey) (abi.Randomness, error) `perm:"read"` + + StateGetRandomnessFromTickets func(p0 context.Context, p1 crypto.DomainSeparationTag, p2 abi.ChainEpoch, p3 []byte, p4 types.TipSetKey) (abi.Randomness, error) `perm:"read"` + + StateListActors func(p0 context.Context, p1 types.TipSetKey) ([]address.Address, error) `perm:"read"` + + StateListMessages func(p0 context.Context, p1 *MessageMatch, p2 types.TipSetKey, p3 abi.ChainEpoch) ([]cid.Cid, error) `perm:"read"` + + StateListMiners func(p0 context.Context, p1 types.TipSetKey) ([]address.Address, error) `perm:"read"` + + StateLookupID func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) `perm:"read"` + + StateLookupRobustAddress func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) `perm:"read"` + + StateMarketBalance func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (MarketBalance, error) `perm:"read"` + + StateMarketDeals func(p0 context.Context, p1 types.TipSetKey) (map[string]*MarketDeal, error) `perm:"read"` + + StateMarketParticipants func(p0 context.Context, p1 types.TipSetKey) (map[string]MarketBalance, error) `perm:"read"` + + StateMarketStorageDeal func(p0 context.Context, p1 abi.DealID, p2 types.TipSetKey) (*MarketDeal, error) `perm:"read"` + + StateMinerActiveSectors func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) ([]*miner.SectorOnChainInfo, error) `perm:"read"` + + StateMinerAllocated func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*bitfield.BitField, error) `perm:"read"` + + StateMinerAvailableBalance func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (types.BigInt, error) `perm:"read"` + + StateMinerDeadlines func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) ([]Deadline, error) `perm:"read"` + + StateMinerFaults func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (bitfield.BitField, error) `perm:"read"` + + StateMinerInfo func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (MinerInfo, error) `perm:"read"` + + StateMinerInitialPledgeCollateral func(p0 context.Context, p1 address.Address, p2 miner.SectorPreCommitInfo, p3 types.TipSetKey) (types.BigInt, error) `perm:"read"` + + StateMinerPartitions func(p0 context.Context, p1 address.Address, p2 uint64, p3 types.TipSetKey) ([]Partition, error) `perm:"read"` + + StateMinerPower func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*MinerPower, error) `perm:"read"` + + StateMinerPreCommitDepositForPower func(p0 context.Context, p1 address.Address, p2 miner.SectorPreCommitInfo, p3 types.TipSetKey) (types.BigInt, error) `perm:"read"` + + StateMinerProvingDeadline func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*dline.Info, error) `perm:"read"` + + StateMinerRecoveries func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (bitfield.BitField, error) `perm:"read"` + + StateMinerSectorAllocated func(p0 context.Context, p1 address.Address, p2 abi.SectorNumber, p3 types.TipSetKey) (bool, error) `perm:"read"` + + StateMinerSectorCount func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (MinerSectors, error) `perm:"read"` + + StateMinerSectors func(p0 context.Context, p1 address.Address, p2 *bitfield.BitField, p3 types.TipSetKey) ([]*miner.SectorOnChainInfo, error) `perm:"read"` + + StateNetworkName func(p0 context.Context) (dtypes.NetworkName, error) `perm:"read"` + + StateNetworkVersion func(p0 context.Context, p1 types.TipSetKey) (apitypes.NetworkVersion, error) `perm:"read"` + + StateReadState func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*ActorState, error) `perm:"read"` + + StateReplay func(p0 context.Context, p1 types.TipSetKey, p2 cid.Cid) (*InvocResult, error) `perm:"read"` + + StateSearchMsg func(p0 context.Context, p1 types.TipSetKey, p2 cid.Cid, p3 abi.ChainEpoch, p4 bool) (*MsgLookup, error) `perm:"read"` + + StateSectorExpiration func(p0 context.Context, p1 address.Address, p2 abi.SectorNumber, p3 types.TipSetKey) (*lminer.SectorExpiration, error) `perm:"read"` + + StateSectorGetInfo func(p0 context.Context, p1 address.Address, p2 abi.SectorNumber, p3 types.TipSetKey) (*miner.SectorOnChainInfo, error) `perm:"read"` + + StateSectorPartition func(p0 context.Context, p1 address.Address, p2 abi.SectorNumber, p3 types.TipSetKey) (*lminer.SectorLocation, error) `perm:"read"` + + StateSectorPreCommitInfo func(p0 context.Context, p1 address.Address, p2 abi.SectorNumber, p3 types.TipSetKey) (*miner.SectorPreCommitOnChainInfo, error) `perm:"read"` + + StateVMCirculatingSupplyInternal func(p0 context.Context, p1 types.TipSetKey) (CirculatingSupply, error) `perm:"read"` + + StateVerifiedClientStatus func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*abi.StoragePower, error) `perm:"read"` + + StateVerifiedRegistryRootKey func(p0 context.Context, p1 types.TipSetKey) (address.Address, error) `perm:"read"` + + StateVerifierStatus func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*abi.StoragePower, error) `perm:"read"` + + StateWaitMsg func(p0 context.Context, p1 cid.Cid, p2 uint64, p3 abi.ChainEpoch, p4 bool) (*MsgLookup, error) `perm:"read"` + + SyncCheckBad func(p0 context.Context, p1 cid.Cid) (string, error) `perm:"read"` + + SyncCheckpoint func(p0 context.Context, p1 types.TipSetKey) error `perm:"admin"` + + SyncIncomingBlocks func(p0 context.Context) (<-chan *types.BlockHeader, error) `perm:"read"` + + SyncMarkBad func(p0 context.Context, p1 cid.Cid) error `perm:"admin"` + + SyncState func(p0 context.Context) (*SyncState, error) `perm:"read"` + + SyncSubmitBlock func(p0 context.Context, p1 *types.BlockMsg) error `perm:"write"` + + SyncUnmarkAllBad func(p0 context.Context) error `perm:"admin"` + + SyncUnmarkBad func(p0 context.Context, p1 cid.Cid) error `perm:"admin"` + + SyncValidateTipset func(p0 context.Context, p1 types.TipSetKey) (bool, error) `perm:"read"` + + WalletBalance func(p0 context.Context, p1 address.Address) (types.BigInt, error) `perm:"read"` + + WalletDefaultAddress func(p0 context.Context) (address.Address, error) `perm:"write"` + + WalletDelete func(p0 context.Context, p1 address.Address) error `perm:"admin"` + + WalletExport func(p0 context.Context, p1 address.Address) (*types.KeyInfo, error) `perm:"admin"` + + WalletHas func(p0 context.Context, p1 address.Address) (bool, error) `perm:"write"` + + WalletImport func(p0 context.Context, p1 *types.KeyInfo) (address.Address, error) `perm:"admin"` + + WalletList func(p0 context.Context) ([]address.Address, error) `perm:"write"` + + WalletNew func(p0 context.Context, p1 types.KeyType) (address.Address, error) `perm:"write"` + + WalletSetDefault func(p0 context.Context, p1 address.Address) error `perm:"write"` + + WalletSign func(p0 context.Context, p1 address.Address, p2 []byte) (*crypto.Signature, error) `perm:"sign"` + + WalletSignMessage func(p0 context.Context, p1 address.Address, p2 *types.Message) (*types.SignedMessage, error) `perm:"sign"` + + WalletValidateAddress func(p0 context.Context, p1 string) (address.Address, error) `perm:"read"` + + WalletVerify func(p0 context.Context, p1 address.Address, p2 []byte, p3 *crypto.Signature) (bool, error) `perm:"read"` + + Web3ClientVersion func(p0 context.Context) (string, error) `perm:"read"` +} + +type FullNodeStub struct { + CommonStub + + NetStub +} + +type GatewayStruct struct { + Internal GatewayMethods +} + +type GatewayMethods struct { + ChainGetBlockMessages func(p0 context.Context, p1 cid.Cid) (*BlockMessages, error) `` + + ChainGetGenesis func(p0 context.Context) (*types.TipSet, error) `` + + ChainGetMessage func(p0 context.Context, p1 cid.Cid) (*types.Message, error) `` + + ChainGetParentMessages func(p0 context.Context, p1 cid.Cid) ([]Message, error) `` + + ChainGetParentReceipts func(p0 context.Context, p1 cid.Cid) ([]*types.MessageReceipt, error) `` + + ChainGetPath func(p0 context.Context, p1 types.TipSetKey, p2 types.TipSetKey) ([]*HeadChange, error) `` + + ChainGetTipSet func(p0 context.Context, p1 types.TipSetKey) (*types.TipSet, error) `` + + ChainGetTipSetAfterHeight func(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) `` + + ChainGetTipSetByHeight func(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) `` + + ChainHasObj func(p0 context.Context, p1 cid.Cid) (bool, error) `` + + ChainHead func(p0 context.Context) (*types.TipSet, error) `` + + ChainNotify func(p0 context.Context) (<-chan []*HeadChange, error) `` + + ChainPutObj func(p0 context.Context, p1 blocks.Block) error `` + + ChainReadObj func(p0 context.Context, p1 cid.Cid) ([]byte, error) `` + + Discover func(p0 context.Context) (apitypes.OpenRPCDocument, error) `` + + EthAccounts func(p0 context.Context) ([]ethtypes.EthAddress, error) `` + + EthBlockNumber func(p0 context.Context) (ethtypes.EthUint64, error) `` + + EthCall func(p0 context.Context, p1 ethtypes.EthCall, p2 string) (ethtypes.EthBytes, error) `` + + EthChainId func(p0 context.Context) (ethtypes.EthUint64, error) `` + + EthEstimateGas func(p0 context.Context, p1 ethtypes.EthCall) (ethtypes.EthUint64, error) `` + + EthFeeHistory func(p0 context.Context, p1 jsonrpc.RawParams) (ethtypes.EthFeeHistory, error) `` + + EthGasPrice func(p0 context.Context) (ethtypes.EthBigInt, error) `` + + EthGetBalance func(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthBigInt, error) `` + + EthGetBlockByHash func(p0 context.Context, p1 ethtypes.EthHash, p2 bool) (ethtypes.EthBlock, error) `` + + EthGetBlockByNumber func(p0 context.Context, p1 string, p2 bool) (ethtypes.EthBlock, error) `` + + EthGetBlockTransactionCountByHash func(p0 context.Context, p1 ethtypes.EthHash) (ethtypes.EthUint64, error) `` + + EthGetBlockTransactionCountByNumber func(p0 context.Context, p1 ethtypes.EthUint64) (ethtypes.EthUint64, error) `` + + EthGetCode func(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthBytes, error) `` + + EthGetFilterChanges func(p0 context.Context, p1 ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) `` + + EthGetFilterLogs func(p0 context.Context, p1 ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) `` + + EthGetLogs func(p0 context.Context, p1 *ethtypes.EthFilterSpec) (*ethtypes.EthFilterResult, error) `` + + EthGetMessageCidByTransactionHash func(p0 context.Context, p1 *ethtypes.EthHash) (*cid.Cid, error) `` + + EthGetStorageAt func(p0 context.Context, p1 ethtypes.EthAddress, p2 ethtypes.EthBytes, p3 string) (ethtypes.EthBytes, error) `` + + EthGetTransactionByHash func(p0 context.Context, p1 *ethtypes.EthHash) (*ethtypes.EthTx, error) `` + + EthGetTransactionByHashLimited func(p0 context.Context, p1 *ethtypes.EthHash, p2 abi.ChainEpoch) (*ethtypes.EthTx, error) `` + + EthGetTransactionCount func(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthUint64, error) `` + + EthGetTransactionHashByCid func(p0 context.Context, p1 cid.Cid) (*ethtypes.EthHash, error) `` + + EthGetTransactionReceipt func(p0 context.Context, p1 ethtypes.EthHash) (*EthTxReceipt, error) `` + + EthGetTransactionReceiptLimited func(p0 context.Context, p1 ethtypes.EthHash, p2 abi.ChainEpoch) (*EthTxReceipt, error) `` + + EthMaxPriorityFeePerGas func(p0 context.Context) (ethtypes.EthBigInt, error) `` + + EthNewBlockFilter func(p0 context.Context) (ethtypes.EthFilterID, error) `` + + EthNewFilter func(p0 context.Context, p1 *ethtypes.EthFilterSpec) (ethtypes.EthFilterID, error) `` + + EthNewPendingTransactionFilter func(p0 context.Context) (ethtypes.EthFilterID, error) `` + + EthProtocolVersion func(p0 context.Context) (ethtypes.EthUint64, error) `` + + EthSendRawTransaction func(p0 context.Context, p1 ethtypes.EthBytes) (ethtypes.EthHash, error) `` + + EthSubscribe func(p0 context.Context, p1 jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error) `` + + EthSyncing func(p0 context.Context) (ethtypes.EthSyncingResult, error) `` + + EthUninstallFilter func(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) `` + + EthUnsubscribe func(p0 context.Context, p1 ethtypes.EthSubscriptionID) (bool, error) `` + + GasEstimateGasPremium func(p0 context.Context, p1 uint64, p2 address.Address, p3 int64, p4 types.TipSetKey) (types.BigInt, error) `` + + GasEstimateMessageGas func(p0 context.Context, p1 *types.Message, p2 *MessageSendSpec, p3 types.TipSetKey) (*types.Message, error) `` + + MpoolGetNonce func(p0 context.Context, p1 address.Address) (uint64, error) `` + + MpoolPush func(p0 context.Context, p1 *types.SignedMessage) (cid.Cid, error) `` + + MsigGetAvailableBalance func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (types.BigInt, error) `` + + MsigGetPending func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) ([]*MsigTransaction, error) `` + + MsigGetVested func(p0 context.Context, p1 address.Address, p2 types.TipSetKey, p3 types.TipSetKey) (types.BigInt, error) `` + + MsigGetVestingSchedule func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (MsigVesting, error) `` + + NetListening func(p0 context.Context) (bool, error) `` + + NetVersion func(p0 context.Context) (string, error) `` + + StateAccountKey func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) `` + + StateCall func(p0 context.Context, p1 *types.Message, p2 types.TipSetKey) (*InvocResult, error) `` + + StateDealProviderCollateralBounds func(p0 context.Context, p1 abi.PaddedPieceSize, p2 bool, p3 types.TipSetKey) (DealCollateralBounds, error) `` + + StateDecodeParams func(p0 context.Context, p1 address.Address, p2 abi.MethodNum, p3 []byte, p4 types.TipSetKey) (interface{}, error) `` + + StateGetActor func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*types.Actor, error) `` + + StateListMiners func(p0 context.Context, p1 types.TipSetKey) ([]address.Address, error) `` + + StateLookupID func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) `` + + StateMarketBalance func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (MarketBalance, error) `` + + StateMarketStorageDeal func(p0 context.Context, p1 abi.DealID, p2 types.TipSetKey) (*MarketDeal, error) `` + + StateMinerInfo func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (MinerInfo, error) `` + + StateMinerPower func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*MinerPower, error) `` + + StateMinerProvingDeadline func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*dline.Info, error) `` + + StateMinerSectorCount func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (MinerSectors, error) `` + + StateNetworkName func(p0 context.Context) (dtypes.NetworkName, error) `` + + StateNetworkVersion func(p0 context.Context, p1 types.TipSetKey) (apitypes.NetworkVersion, error) `` + + StateReadState func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*ActorState, error) `` + + StateReplay func(p0 context.Context, p1 types.TipSetKey, p2 cid.Cid) (*InvocResult, error) `` + + StateSearchMsg func(p0 context.Context, p1 types.TipSetKey, p2 cid.Cid, p3 abi.ChainEpoch, p4 bool) (*MsgLookup, error) `` + + StateSectorGetInfo func(p0 context.Context, p1 address.Address, p2 abi.SectorNumber, p3 types.TipSetKey) (*miner.SectorOnChainInfo, error) `` + + StateVerifiedClientStatus func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*abi.StoragePower, error) `` + + StateVerifierStatus func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*abi.StoragePower, error) `` + + 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) `` + + Web3ClientVersion func(p0 context.Context) (string, error) `` +} + +type GatewayStub struct { +} + +type NetStruct struct { + Internal NetMethods +} + +type NetMethods struct { + ID func(p0 context.Context) (peer.ID, error) `perm:"read"` + + NetAddrsListen func(p0 context.Context) (peer.AddrInfo, error) `perm:"read"` + + NetAgentVersion func(p0 context.Context, p1 peer.ID) (string, error) `perm:"read"` + + NetAutoNatStatus func(p0 context.Context) (NatInfo, error) `perm:"read"` + + NetBandwidthStats func(p0 context.Context) (metrics.Stats, error) `perm:"read"` + + NetBandwidthStatsByPeer func(p0 context.Context) (map[string]metrics.Stats, error) `perm:"read"` + + NetBandwidthStatsByProtocol func(p0 context.Context) (map[protocol.ID]metrics.Stats, error) `perm:"read"` + + NetBlockAdd func(p0 context.Context, p1 NetBlockList) error `perm:"admin"` + + NetBlockList func(p0 context.Context) (NetBlockList, error) `perm:"read"` + + NetBlockRemove func(p0 context.Context, p1 NetBlockList) error `perm:"admin"` + + NetConnect func(p0 context.Context, p1 peer.AddrInfo) error `perm:"write"` + + NetConnectedness func(p0 context.Context, p1 peer.ID) (network.Connectedness, error) `perm:"read"` + + NetDisconnect func(p0 context.Context, p1 peer.ID) error `perm:"write"` + + NetFindPeer func(p0 context.Context, p1 peer.ID) (peer.AddrInfo, error) `perm:"read"` + + NetLimit func(p0 context.Context, p1 string) (NetLimit, error) `perm:"read"` + + NetPeerInfo func(p0 context.Context, p1 peer.ID) (*ExtendedPeerInfo, error) `perm:"read"` + + NetPeers func(p0 context.Context) ([]peer.AddrInfo, error) `perm:"read"` + + NetPing func(p0 context.Context, p1 peer.ID) (time.Duration, error) `perm:"read"` + + NetProtectAdd func(p0 context.Context, p1 []peer.ID) error `perm:"admin"` + + NetProtectList func(p0 context.Context) ([]peer.ID, error) `perm:"read"` + + NetProtectRemove func(p0 context.Context, p1 []peer.ID) error `perm:"admin"` + + NetPubsubScores func(p0 context.Context) ([]PubsubScore, error) `perm:"read"` + + NetSetLimit func(p0 context.Context, p1 string, p2 NetLimit) error `perm:"admin"` + + NetStat func(p0 context.Context, p1 string) (NetStat, error) `perm:"read"` +} + +type NetStub struct { +} + +type SignableStruct struct { + Internal SignableMethods +} + +type SignableMethods struct { + Sign func(p0 context.Context, p1 SignFunc) error `` +} + +type SignableStub struct { +} + +type StorageMinerStruct struct { + CommonStruct + + NetStruct + + Internal StorageMinerMethods +} + +type StorageMinerMethods struct { + ActorAddress func(p0 context.Context) (address.Address, error) `perm:"read"` + + ActorAddressConfig func(p0 context.Context) (AddressConfig, error) `perm:"read"` + + ActorSectorSize func(p0 context.Context, p1 address.Address) (abi.SectorSize, error) `perm:"read"` + + ActorWithdrawBalance func(p0 context.Context, p1 abi.TokenAmount) (cid.Cid, error) `perm:"admin"` + + BeneficiaryWithdrawBalance func(p0 context.Context, p1 abi.TokenAmount) (cid.Cid, error) `perm:"admin"` + + CheckProvable func(p0 context.Context, p1 abi.RegisteredPoStProof, p2 []storiface.SectorRef) (map[abi.SectorNumber]string, error) `perm:"admin"` + + ComputeDataCid func(p0 context.Context, p1 abi.UnpaddedPieceSize, p2 storiface.Data) (abi.PieceInfo, error) `perm:"admin"` + + ComputeProof func(p0 context.Context, p1 []builtinactors.ExtendedSectorInfo, p2 abi.PoStRandomness, p3 abi.ChainEpoch, p4 abinetwork.Version) ([]builtinactors.PoStProof, error) `perm:"read"` + + ComputeWindowPoSt func(p0 context.Context, p1 uint64, p2 types.TipSetKey) ([]miner.SubmitWindowedPoStParams, error) `perm:"admin"` + + CreateBackup func(p0 context.Context, p1 string) error `perm:"admin"` + + DagstoreGC func(p0 context.Context) ([]DagstoreShardResult, error) `perm:"admin"` + + DagstoreInitializeAll func(p0 context.Context, p1 DagstoreInitializeAllParams) (<-chan DagstoreInitializeAllEvent, error) `perm:"write"` + + DagstoreInitializeShard func(p0 context.Context, p1 string) error `perm:"write"` + + DagstoreListShards func(p0 context.Context) ([]DagstoreShardInfo, error) `perm:"read"` + + DagstoreLookupPieces func(p0 context.Context, p1 cid.Cid) ([]DagstoreShardInfo, error) `perm:"admin"` + + DagstoreRecoverShard func(p0 context.Context, p1 string) error `perm:"write"` + + DagstoreRegisterShard func(p0 context.Context, p1 string) error `perm:"admin"` + + DealsConsiderOfflineRetrievalDeals func(p0 context.Context) (bool, error) `perm:"admin"` + + DealsConsiderOfflineStorageDeals func(p0 context.Context) (bool, error) `perm:"admin"` + + DealsConsiderOnlineRetrievalDeals func(p0 context.Context) (bool, error) `perm:"admin"` + + DealsConsiderOnlineStorageDeals func(p0 context.Context) (bool, error) `perm:"admin"` + + DealsConsiderUnverifiedStorageDeals func(p0 context.Context) (bool, error) `perm:"admin"` + + DealsConsiderVerifiedStorageDeals func(p0 context.Context) (bool, error) `perm:"admin"` + + DealsImportData func(p0 context.Context, p1 cid.Cid, p2 string) error `perm:"admin"` + + DealsList func(p0 context.Context) ([]*MarketDeal, error) `perm:"admin"` + + DealsPieceCidBlocklist func(p0 context.Context) ([]cid.Cid, error) `perm:"admin"` + + DealsSetConsiderOfflineRetrievalDeals func(p0 context.Context, p1 bool) error `perm:"admin"` + + DealsSetConsiderOfflineStorageDeals func(p0 context.Context, p1 bool) error `perm:"admin"` + + DealsSetConsiderOnlineRetrievalDeals func(p0 context.Context, p1 bool) error `perm:"admin"` + + DealsSetConsiderOnlineStorageDeals func(p0 context.Context, p1 bool) error `perm:"admin"` + + DealsSetConsiderUnverifiedStorageDeals func(p0 context.Context, p1 bool) error `perm:"admin"` + + DealsSetConsiderVerifiedStorageDeals func(p0 context.Context, p1 bool) error `perm:"admin"` + + DealsSetPieceCidBlocklist func(p0 context.Context, p1 []cid.Cid) error `perm:"admin"` + + IndexerAnnounceAllDeals func(p0 context.Context) error `perm:"admin"` + + IndexerAnnounceDeal func(p0 context.Context, p1 cid.Cid) error `perm:"admin"` + + MarketCancelDataTransfer func(p0 context.Context, p1 datatransfer.TransferID, p2 peer.ID, p3 bool) error `perm:"write"` + + MarketDataTransferDiagnostics func(p0 context.Context, p1 peer.ID) (*TransferDiagnostics, error) `perm:"write"` + + MarketDataTransferUpdates func(p0 context.Context) (<-chan DataTransferChannel, error) `perm:"write"` + + MarketGetAsk func(p0 context.Context) (*storagemarket.SignedStorageAsk, error) `perm:"read"` + + MarketGetDealUpdates func(p0 context.Context) (<-chan storagemarket.MinerDeal, error) `perm:"read"` + + MarketGetRetrievalAsk func(p0 context.Context) (*retrievalmarket.Ask, error) `perm:"read"` + + MarketImportDealData func(p0 context.Context, p1 cid.Cid, p2 string) error `perm:"write"` + + MarketListDataTransfers func(p0 context.Context) ([]DataTransferChannel, error) `perm:"write"` + + MarketListDeals func(p0 context.Context) ([]*MarketDeal, error) `perm:"read"` + + MarketListIncompleteDeals func(p0 context.Context) ([]storagemarket.MinerDeal, error) `perm:"read"` + + MarketListRetrievalDeals func(p0 context.Context) ([]struct{}, error) `perm:"read"` + + MarketPendingDeals func(p0 context.Context) (PendingDealInfo, error) `perm:"write"` + + MarketPublishPendingDeals func(p0 context.Context) error `perm:"admin"` + + MarketRestartDataTransfer func(p0 context.Context, p1 datatransfer.TransferID, p2 peer.ID, p3 bool) error `perm:"write"` + + MarketRetryPublishDeal func(p0 context.Context, p1 cid.Cid) error `perm:"admin"` + + MarketSetAsk func(p0 context.Context, p1 types.BigInt, p2 types.BigInt, p3 abi.ChainEpoch, p4 abi.PaddedPieceSize, p5 abi.PaddedPieceSize) error `perm:"admin"` + + MarketSetRetrievalAsk func(p0 context.Context, p1 *retrievalmarket.Ask) error `perm:"admin"` + + MiningBase func(p0 context.Context) (*types.TipSet, error) `perm:"read"` + + PiecesGetCIDInfo func(p0 context.Context, p1 cid.Cid) (*piecestore.CIDInfo, error) `perm:"read"` + + PiecesGetPieceInfo func(p0 context.Context, p1 cid.Cid) (*piecestore.PieceInfo, error) `perm:"read"` + + PiecesListCidInfos func(p0 context.Context) ([]cid.Cid, error) `perm:"read"` + + PiecesListPieces func(p0 context.Context) ([]cid.Cid, error) `perm:"read"` + + PledgeSector func(p0 context.Context) (abi.SectorID, error) `perm:"write"` + + RecoverFault func(p0 context.Context, p1 []abi.SectorNumber) ([]cid.Cid, error) `perm:"admin"` + + ReturnAddPiece func(p0 context.Context, p1 storiface.CallID, p2 abi.PieceInfo, p3 *storiface.CallError) error `perm:"admin"` + + ReturnDataCid func(p0 context.Context, p1 storiface.CallID, p2 abi.PieceInfo, p3 *storiface.CallError) error `perm:"admin"` + + ReturnDownloadSector func(p0 context.Context, p1 storiface.CallID, p2 *storiface.CallError) error `perm:"admin"` + + ReturnFetch func(p0 context.Context, p1 storiface.CallID, p2 *storiface.CallError) error `perm:"admin"` + + ReturnFinalizeReplicaUpdate func(p0 context.Context, p1 storiface.CallID, p2 *storiface.CallError) error `perm:"admin"` + + ReturnFinalizeSector func(p0 context.Context, p1 storiface.CallID, p2 *storiface.CallError) error `perm:"admin"` + + ReturnGenerateSectorKeyFromData func(p0 context.Context, p1 storiface.CallID, p2 *storiface.CallError) error `perm:"admin"` + + ReturnMoveStorage func(p0 context.Context, p1 storiface.CallID, p2 *storiface.CallError) error `perm:"admin"` + + ReturnProveReplicaUpdate1 func(p0 context.Context, p1 storiface.CallID, p2 storiface.ReplicaVanillaProofs, p3 *storiface.CallError) error `perm:"admin"` + + ReturnProveReplicaUpdate2 func(p0 context.Context, p1 storiface.CallID, p2 storiface.ReplicaUpdateProof, p3 *storiface.CallError) error `perm:"admin"` + + ReturnReadPiece func(p0 context.Context, p1 storiface.CallID, p2 bool, p3 *storiface.CallError) error `perm:"admin"` + + ReturnReleaseUnsealed func(p0 context.Context, p1 storiface.CallID, p2 *storiface.CallError) error `perm:"admin"` + + ReturnReplicaUpdate func(p0 context.Context, p1 storiface.CallID, p2 storiface.ReplicaUpdateOut, p3 *storiface.CallError) error `perm:"admin"` + + ReturnSealCommit1 func(p0 context.Context, p1 storiface.CallID, p2 storiface.Commit1Out, p3 *storiface.CallError) error `perm:"admin"` + + ReturnSealCommit2 func(p0 context.Context, p1 storiface.CallID, p2 storiface.Proof, p3 *storiface.CallError) error `perm:"admin"` + + ReturnSealPreCommit1 func(p0 context.Context, p1 storiface.CallID, p2 storiface.PreCommit1Out, p3 *storiface.CallError) error `perm:"admin"` + + ReturnSealPreCommit2 func(p0 context.Context, p1 storiface.CallID, p2 storiface.SectorCids, p3 *storiface.CallError) error `perm:"admin"` + + ReturnUnsealPiece func(p0 context.Context, p1 storiface.CallID, p2 *storiface.CallError) error `perm:"admin"` + + RuntimeSubsystems func(p0 context.Context) (MinerSubsystems, error) `perm:"read"` + + SealingAbort func(p0 context.Context, p1 storiface.CallID) error `perm:"admin"` + + SealingRemoveRequest func(p0 context.Context, p1 uuid.UUID) error `perm:"admin"` + + SealingSchedDiag func(p0 context.Context, p1 bool) (interface{}, error) `perm:"admin"` + + SectorAbortUpgrade func(p0 context.Context, p1 abi.SectorNumber) error `perm:"admin"` + + SectorAddPieceToAny func(p0 context.Context, p1 abi.UnpaddedPieceSize, p2 storiface.Data, p3 PieceDealInfo) (SectorOffset, error) `perm:"admin"` + + SectorCommitFlush func(p0 context.Context) ([]sealiface.CommitBatchRes, error) `perm:"admin"` + + SectorCommitPending func(p0 context.Context) ([]abi.SectorID, error) `perm:"admin"` + + SectorGetExpectedSealDuration func(p0 context.Context) (time.Duration, error) `perm:"read"` + + SectorGetSealDelay func(p0 context.Context) (time.Duration, error) `perm:"read"` + + SectorMarkForUpgrade func(p0 context.Context, p1 abi.SectorNumber, p2 bool) error `perm:"admin"` + + SectorMatchPendingPiecesToOpenSectors func(p0 context.Context) error `perm:"admin"` + + SectorNumAssignerMeta func(p0 context.Context) (NumAssignerMeta, error) `perm:"read"` + + SectorNumFree func(p0 context.Context, p1 string) error `perm:"admin"` + + SectorNumReservations func(p0 context.Context) (map[string]bitfield.BitField, error) `perm:"read"` + + SectorNumReserve func(p0 context.Context, p1 string, p2 bitfield.BitField, p3 bool) error `perm:"admin"` + + SectorNumReserveCount func(p0 context.Context, p1 string, p2 uint64) (bitfield.BitField, error) `perm:"admin"` + + SectorPreCommitFlush func(p0 context.Context) ([]sealiface.PreCommitBatchRes, error) `perm:"admin"` + + SectorPreCommitPending func(p0 context.Context) ([]abi.SectorID, error) `perm:"admin"` + + SectorReceive func(p0 context.Context, p1 RemoteSectorMeta) error `perm:"admin"` + + SectorRemove func(p0 context.Context, p1 abi.SectorNumber) error `perm:"admin"` + + SectorSetExpectedSealDuration func(p0 context.Context, p1 time.Duration) error `perm:"write"` + + SectorSetSealDelay func(p0 context.Context, p1 time.Duration) error `perm:"write"` + + SectorStartSealing func(p0 context.Context, p1 abi.SectorNumber) error `perm:"write"` + + SectorTerminate func(p0 context.Context, p1 abi.SectorNumber) error `perm:"admin"` + + SectorTerminateFlush func(p0 context.Context) (*cid.Cid, error) `perm:"admin"` + + SectorTerminatePending func(p0 context.Context) ([]abi.SectorID, error) `perm:"admin"` + + SectorUnseal func(p0 context.Context, p1 abi.SectorNumber) error `perm:"admin"` + + SectorsList func(p0 context.Context) ([]abi.SectorNumber, error) `perm:"read"` + + SectorsListInStates func(p0 context.Context, p1 []SectorState) ([]abi.SectorNumber, error) `perm:"read"` + + SectorsRefs func(p0 context.Context) (map[string][]SealedRef, error) `perm:"read"` + + SectorsStatus func(p0 context.Context, p1 abi.SectorNumber, p2 bool) (SectorInfo, error) `perm:"read"` + + SectorsSummary func(p0 context.Context) (map[SectorState]int, error) `perm:"read"` + + SectorsUnsealPiece func(p0 context.Context, p1 storiface.SectorRef, p2 storiface.UnpaddedByteIndex, p3 abi.UnpaddedPieceSize, p4 abi.SealRandomness, p5 *cid.Cid) error `perm:"admin"` + + SectorsUpdate func(p0 context.Context, p1 abi.SectorNumber, p2 SectorState) error `perm:"admin"` + + StorageAddLocal func(p0 context.Context, p1 string) error `perm:"admin"` + + StorageAttach func(p0 context.Context, p1 storiface.StorageInfo, p2 fsutil.FsStat) error `perm:"admin"` + + StorageAuthVerify func(p0 context.Context, p1 string) ([]auth.Permission, error) `perm:"read"` + + StorageBestAlloc func(p0 context.Context, p1 storiface.SectorFileType, p2 abi.SectorSize, p3 storiface.PathType) ([]storiface.StorageInfo, error) `perm:"admin"` + + StorageDeclareSector func(p0 context.Context, p1 storiface.ID, p2 abi.SectorID, p3 storiface.SectorFileType, p4 bool) error `perm:"admin"` + + StorageDetach func(p0 context.Context, p1 storiface.ID, p2 string) error `perm:"admin"` + + StorageDetachLocal func(p0 context.Context, p1 string) error `perm:"admin"` + + StorageDropSector func(p0 context.Context, p1 storiface.ID, p2 abi.SectorID, p3 storiface.SectorFileType) error `perm:"admin"` + + StorageFindSector func(p0 context.Context, p1 abi.SectorID, p2 storiface.SectorFileType, p3 abi.SectorSize, p4 bool) ([]storiface.SectorStorageInfo, error) `perm:"admin"` + + StorageGetLocks func(p0 context.Context) (storiface.SectorLocks, error) `perm:"admin"` + + StorageInfo func(p0 context.Context, p1 storiface.ID) (storiface.StorageInfo, error) `perm:"admin"` + + StorageList func(p0 context.Context) (map[storiface.ID][]storiface.Decl, error) `perm:"admin"` + + StorageLocal func(p0 context.Context) (map[storiface.ID]string, error) `perm:"admin"` + + StorageLock func(p0 context.Context, p1 abi.SectorID, p2 storiface.SectorFileType, p3 storiface.SectorFileType) error `perm:"admin"` + + StorageRedeclareLocal func(p0 context.Context, p1 *storiface.ID, p2 bool) error `perm:"admin"` + + StorageReportHealth func(p0 context.Context, p1 storiface.ID, p2 storiface.HealthReport) error `perm:"admin"` + + StorageStat func(p0 context.Context, p1 storiface.ID) (fsutil.FsStat, error) `perm:"admin"` + + StorageTryLock func(p0 context.Context, p1 abi.SectorID, p2 storiface.SectorFileType, p3 storiface.SectorFileType) (bool, error) `perm:"admin"` + + WorkerConnect func(p0 context.Context, p1 string) error `perm:"admin"` + + WorkerJobs func(p0 context.Context) (map[uuid.UUID][]storiface.WorkerJob, error) `perm:"admin"` + + WorkerStats func(p0 context.Context) (map[uuid.UUID]storiface.WorkerStats, error) `perm:"admin"` +} + +type StorageMinerStub struct { + CommonStub + + NetStub +} + +type WalletStruct struct { + Internal WalletMethods +} + +type WalletMethods struct { + WalletDelete func(p0 context.Context, p1 address.Address) error `perm:"admin"` + + WalletExport func(p0 context.Context, p1 address.Address) (*types.KeyInfo, error) `perm:"admin"` + + WalletHas func(p0 context.Context, p1 address.Address) (bool, error) `perm:"admin"` + + WalletImport func(p0 context.Context, p1 *types.KeyInfo) (address.Address, error) `perm:"admin"` + + WalletList func(p0 context.Context) ([]address.Address, error) `perm:"admin"` + + WalletNew func(p0 context.Context, p1 types.KeyType) (address.Address, error) `perm:"admin"` + + WalletSign func(p0 context.Context, p1 address.Address, p2 []byte, p3 MsgMeta) (*crypto.Signature, error) `perm:"admin"` +} + +type WalletStub struct { +} + +type WorkerStruct struct { + Internal WorkerMethods +} + +type WorkerMethods struct { + AddPiece func(p0 context.Context, p1 storiface.SectorRef, p2 []abi.UnpaddedPieceSize, p3 abi.UnpaddedPieceSize, p4 storiface.Data) (storiface.CallID, error) `perm:"admin"` + + DataCid func(p0 context.Context, p1 abi.UnpaddedPieceSize, p2 storiface.Data) (storiface.CallID, error) `perm:"admin"` + + DownloadSectorData func(p0 context.Context, p1 storiface.SectorRef, p2 bool, p3 map[storiface.SectorFileType]storiface.SectorLocation) (storiface.CallID, error) `perm:"admin"` + + Enabled func(p0 context.Context) (bool, error) `perm:"admin"` + + Fetch func(p0 context.Context, p1 storiface.SectorRef, p2 storiface.SectorFileType, p3 storiface.PathType, p4 storiface.AcquireMode) (storiface.CallID, error) `perm:"admin"` + + FinalizeReplicaUpdate func(p0 context.Context, p1 storiface.SectorRef) (storiface.CallID, error) `perm:"admin"` + + FinalizeSector func(p0 context.Context, p1 storiface.SectorRef) (storiface.CallID, error) `perm:"admin"` + + GenerateSectorKeyFromData func(p0 context.Context, p1 storiface.SectorRef, p2 cid.Cid) (storiface.CallID, error) `perm:"admin"` + + GenerateWindowPoSt func(p0 context.Context, p1 abi.RegisteredPoStProof, p2 abi.ActorID, p3 []storiface.PostSectorChallenge, p4 int, p5 abi.PoStRandomness) (storiface.WindowPoStResult, error) `perm:"admin"` + + GenerateWinningPoSt func(p0 context.Context, p1 abi.RegisteredPoStProof, p2 abi.ActorID, p3 []storiface.PostSectorChallenge, p4 abi.PoStRandomness) ([]proof.PoStProof, error) `perm:"admin"` + + Info func(p0 context.Context) (storiface.WorkerInfo, error) `perm:"admin"` + + MoveStorage func(p0 context.Context, p1 storiface.SectorRef, p2 storiface.SectorFileType) (storiface.CallID, error) `perm:"admin"` + + Paths func(p0 context.Context) ([]storiface.StoragePath, error) `perm:"admin"` + + ProcessSession func(p0 context.Context) (uuid.UUID, error) `perm:"admin"` + + ProveReplicaUpdate1 func(p0 context.Context, p1 storiface.SectorRef, p2 cid.Cid, p3 cid.Cid, p4 cid.Cid) (storiface.CallID, error) `perm:"admin"` + + ProveReplicaUpdate2 func(p0 context.Context, p1 storiface.SectorRef, p2 cid.Cid, p3 cid.Cid, p4 cid.Cid, p5 storiface.ReplicaVanillaProofs) (storiface.CallID, error) `perm:"admin"` + + ReleaseUnsealed func(p0 context.Context, p1 storiface.SectorRef, p2 []storiface.Range) (storiface.CallID, error) `perm:"admin"` + + Remove func(p0 context.Context, p1 abi.SectorID) error `perm:"admin"` + + ReplicaUpdate func(p0 context.Context, p1 storiface.SectorRef, p2 []abi.PieceInfo) (storiface.CallID, error) `perm:"admin"` + + SealCommit1 func(p0 context.Context, p1 storiface.SectorRef, p2 abi.SealRandomness, p3 abi.InteractiveSealRandomness, p4 []abi.PieceInfo, p5 storiface.SectorCids) (storiface.CallID, error) `perm:"admin"` + + SealCommit2 func(p0 context.Context, p1 storiface.SectorRef, p2 storiface.Commit1Out) (storiface.CallID, error) `perm:"admin"` + + SealPreCommit1 func(p0 context.Context, p1 storiface.SectorRef, p2 abi.SealRandomness, p3 []abi.PieceInfo) (storiface.CallID, error) `perm:"admin"` + + SealPreCommit2 func(p0 context.Context, p1 storiface.SectorRef, p2 storiface.PreCommit1Out) (storiface.CallID, error) `perm:"admin"` + + Session func(p0 context.Context) (uuid.UUID, error) `perm:"admin"` + + SetEnabled func(p0 context.Context, p1 bool) error `perm:"admin"` + + Shutdown func(p0 context.Context) error `perm:"admin"` + + StorageAddLocal func(p0 context.Context, p1 string) error `perm:"admin"` + + StorageDetachAll func(p0 context.Context) error `perm:"admin"` + + StorageDetachLocal func(p0 context.Context, p1 string) error `perm:"admin"` + + StorageLocal func(p0 context.Context) (map[storiface.ID]string, error) `perm:"admin"` + + StorageRedeclareLocal func(p0 context.Context, p1 *storiface.ID, p2 bool) error `perm:"admin"` + + TaskDisable func(p0 context.Context, p1 sealtasks.TaskType) error `perm:"admin"` + + TaskEnable func(p0 context.Context, p1 sealtasks.TaskType) error `perm:"admin"` + + TaskTypes func(p0 context.Context) (map[sealtasks.TaskType]struct{}, error) `perm:"admin"` + + UnsealPiece func(p0 context.Context, p1 storiface.SectorRef, p2 storiface.UnpaddedByteIndex, p3 abi.UnpaddedPieceSize, p4 abi.SealRandomness, p5 cid.Cid) (storiface.CallID, error) `perm:"admin"` + + Version func(p0 context.Context) (Version, error) `perm:"admin"` + + WaitQuiet func(p0 context.Context) error `perm:"admin"` +} + +type WorkerStub struct { +} + +func (s *ChainIOStruct) ChainHasObj(p0 context.Context, p1 cid.Cid) (bool, error) { + if s.Internal.ChainHasObj == nil { + return false, ErrNotSupported + } + return s.Internal.ChainHasObj(p0, p1) +} + +func (s *ChainIOStub) ChainHasObj(p0 context.Context, p1 cid.Cid) (bool, error) { + return false, ErrNotSupported +} + +func (s *ChainIOStruct) ChainPutObj(p0 context.Context, p1 blocks.Block) error { + if s.Internal.ChainPutObj == nil { + return ErrNotSupported + } + return s.Internal.ChainPutObj(p0, p1) +} + +func (s *ChainIOStub) ChainPutObj(p0 context.Context, p1 blocks.Block) error { + return ErrNotSupported +} + +func (s *ChainIOStruct) ChainReadObj(p0 context.Context, p1 cid.Cid) ([]byte, error) { + if s.Internal.ChainReadObj == nil { + return *new([]byte), ErrNotSupported + } + return s.Internal.ChainReadObj(p0, p1) +} + +func (s *ChainIOStub) ChainReadObj(p0 context.Context, p1 cid.Cid) ([]byte, error) { + return *new([]byte), ErrNotSupported +} + +func (s *CommonStruct) AuthNew(p0 context.Context, p1 []auth.Permission) ([]byte, error) { + if s.Internal.AuthNew == nil { + return *new([]byte), ErrNotSupported + } + return s.Internal.AuthNew(p0, p1) +} + +func (s *CommonStub) AuthNew(p0 context.Context, p1 []auth.Permission) ([]byte, error) { + return *new([]byte), ErrNotSupported +} + +func (s *CommonStruct) AuthVerify(p0 context.Context, p1 string) ([]auth.Permission, error) { + if s.Internal.AuthVerify == nil { + return *new([]auth.Permission), ErrNotSupported + } + return s.Internal.AuthVerify(p0, p1) +} + +func (s *CommonStub) AuthVerify(p0 context.Context, p1 string) ([]auth.Permission, error) { + return *new([]auth.Permission), ErrNotSupported +} + +func (s *CommonStruct) Closing(p0 context.Context) (<-chan struct{}, error) { + if s.Internal.Closing == nil { + return nil, ErrNotSupported + } + return s.Internal.Closing(p0) +} + +func (s *CommonStub) Closing(p0 context.Context) (<-chan struct{}, error) { + return nil, ErrNotSupported +} + +func (s *CommonStruct) Discover(p0 context.Context) (apitypes.OpenRPCDocument, error) { + if s.Internal.Discover == nil { + return *new(apitypes.OpenRPCDocument), ErrNotSupported + } + return s.Internal.Discover(p0) +} + +func (s *CommonStub) Discover(p0 context.Context) (apitypes.OpenRPCDocument, error) { + return *new(apitypes.OpenRPCDocument), ErrNotSupported +} + +func (s *CommonStruct) LogAlerts(p0 context.Context) ([]alerting.Alert, error) { + if s.Internal.LogAlerts == nil { + return *new([]alerting.Alert), ErrNotSupported + } + return s.Internal.LogAlerts(p0) +} + +func (s *CommonStub) LogAlerts(p0 context.Context) ([]alerting.Alert, error) { + return *new([]alerting.Alert), ErrNotSupported +} + +func (s *CommonStruct) LogList(p0 context.Context) ([]string, error) { + if s.Internal.LogList == nil { + return *new([]string), ErrNotSupported + } + return s.Internal.LogList(p0) +} + +func (s *CommonStub) LogList(p0 context.Context) ([]string, error) { + return *new([]string), ErrNotSupported +} + +func (s *CommonStruct) LogSetLevel(p0 context.Context, p1 string, p2 string) error { + if s.Internal.LogSetLevel == nil { + return ErrNotSupported + } + return s.Internal.LogSetLevel(p0, p1, p2) +} + +func (s *CommonStub) LogSetLevel(p0 context.Context, p1 string, p2 string) error { + return ErrNotSupported +} + +func (s *CommonStruct) Session(p0 context.Context) (uuid.UUID, error) { + if s.Internal.Session == nil { + return *new(uuid.UUID), ErrNotSupported + } + return s.Internal.Session(p0) +} + +func (s *CommonStub) Session(p0 context.Context) (uuid.UUID, error) { + return *new(uuid.UUID), ErrNotSupported +} + +func (s *CommonStruct) Shutdown(p0 context.Context) error { + if s.Internal.Shutdown == nil { + return ErrNotSupported + } + return s.Internal.Shutdown(p0) +} + +func (s *CommonStub) Shutdown(p0 context.Context) error { + return ErrNotSupported +} + +func (s *CommonStruct) StartTime(p0 context.Context) (time.Time, error) { + if s.Internal.StartTime == nil { + return *new(time.Time), ErrNotSupported + } + return s.Internal.StartTime(p0) +} + +func (s *CommonStub) StartTime(p0 context.Context) (time.Time, error) { + return *new(time.Time), ErrNotSupported +} + +func (s *CommonStruct) Version(p0 context.Context) (APIVersion, error) { + if s.Internal.Version == nil { + return *new(APIVersion), ErrNotSupported + } + return s.Internal.Version(p0) +} + +func (s *CommonStub) Version(p0 context.Context) (APIVersion, error) { + return *new(APIVersion), ErrNotSupported +} + +func (s *EthSubscriberStruct) EthSubscription(p0 context.Context, p1 jsonrpc.RawParams) error { + if s.Internal.EthSubscription == nil { + return ErrNotSupported + } + return s.Internal.EthSubscription(p0, p1) +} + +func (s *EthSubscriberStub) EthSubscription(p0 context.Context, p1 jsonrpc.RawParams) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) ChainBlockstoreInfo(p0 context.Context) (map[string]interface{}, error) { + if s.Internal.ChainBlockstoreInfo == nil { + return *new(map[string]interface{}), ErrNotSupported + } + return s.Internal.ChainBlockstoreInfo(p0) +} + +func (s *FullNodeStub) ChainBlockstoreInfo(p0 context.Context) (map[string]interface{}, error) { + return *new(map[string]interface{}), ErrNotSupported +} + +func (s *FullNodeStruct) ChainCheckBlockstore(p0 context.Context) error { + if s.Internal.ChainCheckBlockstore == nil { + return ErrNotSupported + } + return s.Internal.ChainCheckBlockstore(p0) +} + +func (s *FullNodeStub) ChainCheckBlockstore(p0 context.Context) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) ChainDeleteObj(p0 context.Context, p1 cid.Cid) error { + if s.Internal.ChainDeleteObj == nil { + return ErrNotSupported + } + return s.Internal.ChainDeleteObj(p0, p1) +} + +func (s *FullNodeStub) ChainDeleteObj(p0 context.Context, p1 cid.Cid) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) ChainExport(p0 context.Context, p1 abi.ChainEpoch, p2 bool, p3 types.TipSetKey) (<-chan []byte, error) { + if s.Internal.ChainExport == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainExport(p0, p1, p2, p3) +} + +func (s *FullNodeStub) ChainExport(p0 context.Context, p1 abi.ChainEpoch, p2 bool, p3 types.TipSetKey) (<-chan []byte, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ChainExportRangeInternal(p0 context.Context, p1 types.TipSetKey, p2 types.TipSetKey, p3 ChainExportConfig) error { + if s.Internal.ChainExportRangeInternal == nil { + return ErrNotSupported + } + return s.Internal.ChainExportRangeInternal(p0, p1, p2, p3) +} + +func (s *FullNodeStub) ChainExportRangeInternal(p0 context.Context, p1 types.TipSetKey, p2 types.TipSetKey, p3 ChainExportConfig) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) ChainGetBlock(p0 context.Context, p1 cid.Cid) (*types.BlockHeader, error) { + if s.Internal.ChainGetBlock == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainGetBlock(p0, p1) +} + +func (s *FullNodeStub) ChainGetBlock(p0 context.Context, p1 cid.Cid) (*types.BlockHeader, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ChainGetBlockMessages(p0 context.Context, p1 cid.Cid) (*BlockMessages, error) { + if s.Internal.ChainGetBlockMessages == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainGetBlockMessages(p0, p1) +} + +func (s *FullNodeStub) ChainGetBlockMessages(p0 context.Context, p1 cid.Cid) (*BlockMessages, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ChainGetEvents(p0 context.Context, p1 cid.Cid) ([]types.Event, error) { + if s.Internal.ChainGetEvents == nil { + return *new([]types.Event), ErrNotSupported + } + return s.Internal.ChainGetEvents(p0, p1) +} + +func (s *FullNodeStub) ChainGetEvents(p0 context.Context, p1 cid.Cid) ([]types.Event, error) { + return *new([]types.Event), ErrNotSupported +} + +func (s *FullNodeStruct) ChainGetGenesis(p0 context.Context) (*types.TipSet, error) { + if s.Internal.ChainGetGenesis == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainGetGenesis(p0) +} + +func (s *FullNodeStub) ChainGetGenesis(p0 context.Context) (*types.TipSet, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ChainGetMessage(p0 context.Context, p1 cid.Cid) (*types.Message, error) { + if s.Internal.ChainGetMessage == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainGetMessage(p0, p1) +} + +func (s *FullNodeStub) ChainGetMessage(p0 context.Context, p1 cid.Cid) (*types.Message, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ChainGetMessagesInTipset(p0 context.Context, p1 types.TipSetKey) ([]Message, error) { + if s.Internal.ChainGetMessagesInTipset == nil { + return *new([]Message), ErrNotSupported + } + return s.Internal.ChainGetMessagesInTipset(p0, p1) +} + +func (s *FullNodeStub) ChainGetMessagesInTipset(p0 context.Context, p1 types.TipSetKey) ([]Message, error) { + return *new([]Message), ErrNotSupported +} + +func (s *FullNodeStruct) ChainGetNode(p0 context.Context, p1 string) (*IpldObject, error) { + if s.Internal.ChainGetNode == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainGetNode(p0, p1) +} + +func (s *FullNodeStub) ChainGetNode(p0 context.Context, p1 string) (*IpldObject, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ChainGetParentMessages(p0 context.Context, p1 cid.Cid) ([]Message, error) { + if s.Internal.ChainGetParentMessages == nil { + return *new([]Message), ErrNotSupported + } + return s.Internal.ChainGetParentMessages(p0, p1) +} + +func (s *FullNodeStub) ChainGetParentMessages(p0 context.Context, p1 cid.Cid) ([]Message, error) { + return *new([]Message), ErrNotSupported +} + +func (s *FullNodeStruct) ChainGetParentReceipts(p0 context.Context, p1 cid.Cid) ([]*types.MessageReceipt, error) { + if s.Internal.ChainGetParentReceipts == nil { + return *new([]*types.MessageReceipt), ErrNotSupported + } + return s.Internal.ChainGetParentReceipts(p0, p1) +} + +func (s *FullNodeStub) ChainGetParentReceipts(p0 context.Context, p1 cid.Cid) ([]*types.MessageReceipt, error) { + return *new([]*types.MessageReceipt), ErrNotSupported +} + +func (s *FullNodeStruct) ChainGetPath(p0 context.Context, p1 types.TipSetKey, p2 types.TipSetKey) ([]*HeadChange, error) { + if s.Internal.ChainGetPath == nil { + return *new([]*HeadChange), ErrNotSupported + } + return s.Internal.ChainGetPath(p0, p1, p2) +} + +func (s *FullNodeStub) ChainGetPath(p0 context.Context, p1 types.TipSetKey, p2 types.TipSetKey) ([]*HeadChange, error) { + return *new([]*HeadChange), ErrNotSupported +} + +func (s *FullNodeStruct) ChainGetTipSet(p0 context.Context, p1 types.TipSetKey) (*types.TipSet, error) { + if s.Internal.ChainGetTipSet == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainGetTipSet(p0, p1) +} + +func (s *FullNodeStub) ChainGetTipSet(p0 context.Context, p1 types.TipSetKey) (*types.TipSet, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ChainGetTipSetAfterHeight(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) { + if s.Internal.ChainGetTipSetAfterHeight == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainGetTipSetAfterHeight(p0, p1, p2) +} + +func (s *FullNodeStub) ChainGetTipSetAfterHeight(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ChainGetTipSetByHeight(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) { + if s.Internal.ChainGetTipSetByHeight == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainGetTipSetByHeight(p0, p1, p2) +} + +func (s *FullNodeStub) ChainGetTipSetByHeight(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ChainHasObj(p0 context.Context, p1 cid.Cid) (bool, error) { + if s.Internal.ChainHasObj == nil { + return false, ErrNotSupported + } + return s.Internal.ChainHasObj(p0, p1) +} + +func (s *FullNodeStub) ChainHasObj(p0 context.Context, p1 cid.Cid) (bool, error) { + return false, ErrNotSupported +} + +func (s *FullNodeStruct) ChainHead(p0 context.Context) (*types.TipSet, error) { + if s.Internal.ChainHead == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainHead(p0) +} + +func (s *FullNodeStub) ChainHead(p0 context.Context) (*types.TipSet, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ChainHotGC(p0 context.Context, p1 HotGCOpts) error { + if s.Internal.ChainHotGC == nil { + return ErrNotSupported + } + return s.Internal.ChainHotGC(p0, p1) +} + +func (s *FullNodeStub) ChainHotGC(p0 context.Context, p1 HotGCOpts) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) ChainNotify(p0 context.Context) (<-chan []*HeadChange, error) { + if s.Internal.ChainNotify == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainNotify(p0) +} + +func (s *FullNodeStub) ChainNotify(p0 context.Context) (<-chan []*HeadChange, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ChainPrune(p0 context.Context, p1 PruneOpts) error { + if s.Internal.ChainPrune == nil { + return ErrNotSupported + } + return s.Internal.ChainPrune(p0, p1) +} + +func (s *FullNodeStub) ChainPrune(p0 context.Context, p1 PruneOpts) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) ChainPutObj(p0 context.Context, p1 blocks.Block) error { + if s.Internal.ChainPutObj == nil { + return ErrNotSupported + } + return s.Internal.ChainPutObj(p0, p1) +} + +func (s *FullNodeStub) ChainPutObj(p0 context.Context, p1 blocks.Block) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) ChainReadObj(p0 context.Context, p1 cid.Cid) ([]byte, error) { + if s.Internal.ChainReadObj == nil { + return *new([]byte), ErrNotSupported + } + return s.Internal.ChainReadObj(p0, p1) +} + +func (s *FullNodeStub) ChainReadObj(p0 context.Context, p1 cid.Cid) ([]byte, error) { + return *new([]byte), ErrNotSupported +} + +func (s *FullNodeStruct) ChainSetHead(p0 context.Context, p1 types.TipSetKey) error { + if s.Internal.ChainSetHead == nil { + return ErrNotSupported + } + return s.Internal.ChainSetHead(p0, p1) +} + +func (s *FullNodeStub) ChainSetHead(p0 context.Context, p1 types.TipSetKey) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) ChainStatObj(p0 context.Context, p1 cid.Cid, p2 cid.Cid) (ObjStat, error) { + if s.Internal.ChainStatObj == nil { + return *new(ObjStat), ErrNotSupported + } + return s.Internal.ChainStatObj(p0, p1, p2) +} + +func (s *FullNodeStub) ChainStatObj(p0 context.Context, p1 cid.Cid, p2 cid.Cid) (ObjStat, error) { + return *new(ObjStat), ErrNotSupported +} + +func (s *FullNodeStruct) ChainTipSetWeight(p0 context.Context, p1 types.TipSetKey) (types.BigInt, error) { + if s.Internal.ChainTipSetWeight == nil { + return *new(types.BigInt), ErrNotSupported + } + return s.Internal.ChainTipSetWeight(p0, p1) +} + +func (s *FullNodeStub) ChainTipSetWeight(p0 context.Context, p1 types.TipSetKey) (types.BigInt, error) { + return *new(types.BigInt), ErrNotSupported +} + +func (s *FullNodeStruct) ClientCalcCommP(p0 context.Context, p1 string) (*CommPRet, error) { + if s.Internal.ClientCalcCommP == nil { + return nil, ErrNotSupported + } + return s.Internal.ClientCalcCommP(p0, p1) +} + +func (s *FullNodeStub) ClientCalcCommP(p0 context.Context, p1 string) (*CommPRet, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ClientCancelDataTransfer(p0 context.Context, p1 datatransfer.TransferID, p2 peer.ID, p3 bool) error { + if s.Internal.ClientCancelDataTransfer == nil { + return ErrNotSupported + } + return s.Internal.ClientCancelDataTransfer(p0, p1, p2, p3) +} + +func (s *FullNodeStub) ClientCancelDataTransfer(p0 context.Context, p1 datatransfer.TransferID, p2 peer.ID, p3 bool) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) ClientCancelRetrievalDeal(p0 context.Context, p1 retrievalmarket.DealID) error { + if s.Internal.ClientCancelRetrievalDeal == nil { + return ErrNotSupported + } + return s.Internal.ClientCancelRetrievalDeal(p0, p1) +} + +func (s *FullNodeStub) ClientCancelRetrievalDeal(p0 context.Context, p1 retrievalmarket.DealID) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) ClientDataTransferUpdates(p0 context.Context) (<-chan DataTransferChannel, error) { + if s.Internal.ClientDataTransferUpdates == nil { + return nil, ErrNotSupported + } + return s.Internal.ClientDataTransferUpdates(p0) +} + +func (s *FullNodeStub) ClientDataTransferUpdates(p0 context.Context) (<-chan DataTransferChannel, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ClientDealPieceCID(p0 context.Context, p1 cid.Cid) (DataCIDSize, error) { + if s.Internal.ClientDealPieceCID == nil { + return *new(DataCIDSize), ErrNotSupported + } + return s.Internal.ClientDealPieceCID(p0, p1) +} + +func (s *FullNodeStub) ClientDealPieceCID(p0 context.Context, p1 cid.Cid) (DataCIDSize, error) { + return *new(DataCIDSize), ErrNotSupported +} + +func (s *FullNodeStruct) ClientDealSize(p0 context.Context, p1 cid.Cid) (DataSize, error) { + if s.Internal.ClientDealSize == nil { + return *new(DataSize), ErrNotSupported + } + return s.Internal.ClientDealSize(p0, p1) +} + +func (s *FullNodeStub) ClientDealSize(p0 context.Context, p1 cid.Cid) (DataSize, error) { + return *new(DataSize), ErrNotSupported +} + +func (s *FullNodeStruct) ClientExport(p0 context.Context, p1 ExportRef, p2 FileRef) error { + if s.Internal.ClientExport == nil { + return ErrNotSupported + } + return s.Internal.ClientExport(p0, p1, p2) +} + +func (s *FullNodeStub) ClientExport(p0 context.Context, p1 ExportRef, p2 FileRef) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) ClientFindData(p0 context.Context, p1 cid.Cid, p2 *cid.Cid) ([]QueryOffer, error) { + if s.Internal.ClientFindData == nil { + return *new([]QueryOffer), ErrNotSupported + } + return s.Internal.ClientFindData(p0, p1, p2) +} + +func (s *FullNodeStub) ClientFindData(p0 context.Context, p1 cid.Cid, p2 *cid.Cid) ([]QueryOffer, error) { + return *new([]QueryOffer), ErrNotSupported +} + +func (s *FullNodeStruct) ClientGenCar(p0 context.Context, p1 FileRef, p2 string) error { + if s.Internal.ClientGenCar == nil { + return ErrNotSupported + } + return s.Internal.ClientGenCar(p0, p1, p2) +} + +func (s *FullNodeStub) ClientGenCar(p0 context.Context, p1 FileRef, p2 string) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) ClientGetDealInfo(p0 context.Context, p1 cid.Cid) (*DealInfo, error) { + if s.Internal.ClientGetDealInfo == nil { + return nil, ErrNotSupported + } + return s.Internal.ClientGetDealInfo(p0, p1) +} + +func (s *FullNodeStub) ClientGetDealInfo(p0 context.Context, p1 cid.Cid) (*DealInfo, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ClientGetDealStatus(p0 context.Context, p1 uint64) (string, error) { + if s.Internal.ClientGetDealStatus == nil { + return "", ErrNotSupported + } + return s.Internal.ClientGetDealStatus(p0, p1) +} + +func (s *FullNodeStub) ClientGetDealStatus(p0 context.Context, p1 uint64) (string, error) { + return "", ErrNotSupported +} + +func (s *FullNodeStruct) ClientGetDealUpdates(p0 context.Context) (<-chan DealInfo, error) { + if s.Internal.ClientGetDealUpdates == nil { + return nil, ErrNotSupported + } + return s.Internal.ClientGetDealUpdates(p0) +} + +func (s *FullNodeStub) ClientGetDealUpdates(p0 context.Context) (<-chan DealInfo, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ClientGetRetrievalUpdates(p0 context.Context) (<-chan RetrievalInfo, error) { + if s.Internal.ClientGetRetrievalUpdates == nil { + return nil, ErrNotSupported + } + return s.Internal.ClientGetRetrievalUpdates(p0) +} + +func (s *FullNodeStub) ClientGetRetrievalUpdates(p0 context.Context) (<-chan RetrievalInfo, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ClientHasLocal(p0 context.Context, p1 cid.Cid) (bool, error) { + if s.Internal.ClientHasLocal == nil { + return false, ErrNotSupported + } + return s.Internal.ClientHasLocal(p0, p1) +} + +func (s *FullNodeStub) ClientHasLocal(p0 context.Context, p1 cid.Cid) (bool, error) { + return false, ErrNotSupported +} + +func (s *FullNodeStruct) ClientImport(p0 context.Context, p1 FileRef) (*ImportRes, error) { + if s.Internal.ClientImport == nil { + return nil, ErrNotSupported + } + return s.Internal.ClientImport(p0, p1) +} + +func (s *FullNodeStub) ClientImport(p0 context.Context, p1 FileRef) (*ImportRes, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ClientListDataTransfers(p0 context.Context) ([]DataTransferChannel, error) { + if s.Internal.ClientListDataTransfers == nil { + return *new([]DataTransferChannel), ErrNotSupported + } + return s.Internal.ClientListDataTransfers(p0) +} + +func (s *FullNodeStub) ClientListDataTransfers(p0 context.Context) ([]DataTransferChannel, error) { + return *new([]DataTransferChannel), ErrNotSupported +} + +func (s *FullNodeStruct) ClientListDeals(p0 context.Context) ([]DealInfo, error) { + if s.Internal.ClientListDeals == nil { + return *new([]DealInfo), ErrNotSupported + } + return s.Internal.ClientListDeals(p0) +} + +func (s *FullNodeStub) ClientListDeals(p0 context.Context) ([]DealInfo, error) { + return *new([]DealInfo), ErrNotSupported +} + +func (s *FullNodeStruct) ClientListImports(p0 context.Context) ([]Import, error) { + if s.Internal.ClientListImports == nil { + return *new([]Import), ErrNotSupported + } + return s.Internal.ClientListImports(p0) +} + +func (s *FullNodeStub) ClientListImports(p0 context.Context) ([]Import, error) { + return *new([]Import), ErrNotSupported +} + +func (s *FullNodeStruct) ClientListRetrievals(p0 context.Context) ([]RetrievalInfo, error) { + if s.Internal.ClientListRetrievals == nil { + return *new([]RetrievalInfo), ErrNotSupported + } + return s.Internal.ClientListRetrievals(p0) +} + +func (s *FullNodeStub) ClientListRetrievals(p0 context.Context) ([]RetrievalInfo, error) { + return *new([]RetrievalInfo), ErrNotSupported +} + +func (s *FullNodeStruct) ClientMinerQueryOffer(p0 context.Context, p1 address.Address, p2 cid.Cid, p3 *cid.Cid) (QueryOffer, error) { + if s.Internal.ClientMinerQueryOffer == nil { + return *new(QueryOffer), ErrNotSupported + } + return s.Internal.ClientMinerQueryOffer(p0, p1, p2, p3) +} + +func (s *FullNodeStub) ClientMinerQueryOffer(p0 context.Context, p1 address.Address, p2 cid.Cid, p3 *cid.Cid) (QueryOffer, error) { + return *new(QueryOffer), ErrNotSupported +} + +func (s *FullNodeStruct) ClientQueryAsk(p0 context.Context, p1 peer.ID, p2 address.Address) (*StorageAsk, error) { + if s.Internal.ClientQueryAsk == nil { + return nil, ErrNotSupported + } + return s.Internal.ClientQueryAsk(p0, p1, p2) +} + +func (s *FullNodeStub) ClientQueryAsk(p0 context.Context, p1 peer.ID, p2 address.Address) (*StorageAsk, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ClientRemoveImport(p0 context.Context, p1 imports.ID) error { + if s.Internal.ClientRemoveImport == nil { + return ErrNotSupported + } + return s.Internal.ClientRemoveImport(p0, p1) +} + +func (s *FullNodeStub) ClientRemoveImport(p0 context.Context, p1 imports.ID) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) ClientRestartDataTransfer(p0 context.Context, p1 datatransfer.TransferID, p2 peer.ID, p3 bool) error { + if s.Internal.ClientRestartDataTransfer == nil { + return ErrNotSupported + } + return s.Internal.ClientRestartDataTransfer(p0, p1, p2, p3) +} + +func (s *FullNodeStub) ClientRestartDataTransfer(p0 context.Context, p1 datatransfer.TransferID, p2 peer.ID, p3 bool) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) ClientRetrieve(p0 context.Context, p1 RetrievalOrder) (*RestrievalRes, error) { + if s.Internal.ClientRetrieve == nil { + return nil, ErrNotSupported + } + return s.Internal.ClientRetrieve(p0, p1) +} + +func (s *FullNodeStub) ClientRetrieve(p0 context.Context, p1 RetrievalOrder) (*RestrievalRes, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ClientRetrieveTryRestartInsufficientFunds(p0 context.Context, p1 address.Address) error { + if s.Internal.ClientRetrieveTryRestartInsufficientFunds == nil { + return ErrNotSupported + } + return s.Internal.ClientRetrieveTryRestartInsufficientFunds(p0, p1) +} + +func (s *FullNodeStub) ClientRetrieveTryRestartInsufficientFunds(p0 context.Context, p1 address.Address) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) ClientRetrieveWait(p0 context.Context, p1 retrievalmarket.DealID) error { + if s.Internal.ClientRetrieveWait == nil { + return ErrNotSupported + } + return s.Internal.ClientRetrieveWait(p0, p1) +} + +func (s *FullNodeStub) ClientRetrieveWait(p0 context.Context, p1 retrievalmarket.DealID) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) ClientStartDeal(p0 context.Context, p1 *StartDealParams) (*cid.Cid, error) { + if s.Internal.ClientStartDeal == nil { + return nil, ErrNotSupported + } + return s.Internal.ClientStartDeal(p0, p1) +} + +func (s *FullNodeStub) ClientStartDeal(p0 context.Context, p1 *StartDealParams) (*cid.Cid, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ClientStatelessDeal(p0 context.Context, p1 *StartDealParams) (*cid.Cid, error) { + if s.Internal.ClientStatelessDeal == nil { + return nil, ErrNotSupported + } + return s.Internal.ClientStatelessDeal(p0, p1) +} + +func (s *FullNodeStub) ClientStatelessDeal(p0 context.Context, p1 *StartDealParams) (*cid.Cid, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) CreateBackup(p0 context.Context, p1 string) error { + if s.Internal.CreateBackup == nil { + return ErrNotSupported + } + return s.Internal.CreateBackup(p0, p1) +} + +func (s *FullNodeStub) CreateBackup(p0 context.Context, p1 string) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) EthAccounts(p0 context.Context) ([]ethtypes.EthAddress, error) { + if s.Internal.EthAccounts == nil { + return *new([]ethtypes.EthAddress), ErrNotSupported + } + return s.Internal.EthAccounts(p0) +} + +func (s *FullNodeStub) EthAccounts(p0 context.Context) ([]ethtypes.EthAddress, error) { + return *new([]ethtypes.EthAddress), ErrNotSupported +} + +func (s *FullNodeStruct) EthAddressToFilecoinAddress(p0 context.Context, p1 ethtypes.EthAddress) (address.Address, error) { + if s.Internal.EthAddressToFilecoinAddress == nil { + return *new(address.Address), ErrNotSupported + } + return s.Internal.EthAddressToFilecoinAddress(p0, p1) +} + +func (s *FullNodeStub) EthAddressToFilecoinAddress(p0 context.Context, p1 ethtypes.EthAddress) (address.Address, error) { + return *new(address.Address), ErrNotSupported +} + +func (s *FullNodeStruct) EthBlockNumber(p0 context.Context) (ethtypes.EthUint64, error) { + if s.Internal.EthBlockNumber == nil { + return *new(ethtypes.EthUint64), ErrNotSupported + } + return s.Internal.EthBlockNumber(p0) +} + +func (s *FullNodeStub) EthBlockNumber(p0 context.Context) (ethtypes.EthUint64, error) { + return *new(ethtypes.EthUint64), ErrNotSupported +} + +func (s *FullNodeStruct) EthCall(p0 context.Context, p1 ethtypes.EthCall, p2 string) (ethtypes.EthBytes, error) { + if s.Internal.EthCall == nil { + return *new(ethtypes.EthBytes), ErrNotSupported + } + return s.Internal.EthCall(p0, p1, p2) +} + +func (s *FullNodeStub) EthCall(p0 context.Context, p1 ethtypes.EthCall, p2 string) (ethtypes.EthBytes, error) { + return *new(ethtypes.EthBytes), ErrNotSupported +} + +func (s *FullNodeStruct) EthChainId(p0 context.Context) (ethtypes.EthUint64, error) { + if s.Internal.EthChainId == nil { + return *new(ethtypes.EthUint64), ErrNotSupported + } + return s.Internal.EthChainId(p0) +} + +func (s *FullNodeStub) EthChainId(p0 context.Context) (ethtypes.EthUint64, error) { + return *new(ethtypes.EthUint64), ErrNotSupported +} + +func (s *FullNodeStruct) EthEstimateGas(p0 context.Context, p1 ethtypes.EthCall) (ethtypes.EthUint64, error) { + if s.Internal.EthEstimateGas == nil { + return *new(ethtypes.EthUint64), ErrNotSupported + } + return s.Internal.EthEstimateGas(p0, p1) +} + +func (s *FullNodeStub) EthEstimateGas(p0 context.Context, p1 ethtypes.EthCall) (ethtypes.EthUint64, error) { + return *new(ethtypes.EthUint64), ErrNotSupported +} + +func (s *FullNodeStruct) EthFeeHistory(p0 context.Context, p1 jsonrpc.RawParams) (ethtypes.EthFeeHistory, error) { + if s.Internal.EthFeeHistory == nil { + return *new(ethtypes.EthFeeHistory), ErrNotSupported + } + return s.Internal.EthFeeHistory(p0, p1) +} + +func (s *FullNodeStub) EthFeeHistory(p0 context.Context, p1 jsonrpc.RawParams) (ethtypes.EthFeeHistory, error) { + return *new(ethtypes.EthFeeHistory), ErrNotSupported +} + +func (s *FullNodeStruct) EthGasPrice(p0 context.Context) (ethtypes.EthBigInt, error) { + if s.Internal.EthGasPrice == nil { + return *new(ethtypes.EthBigInt), ErrNotSupported + } + return s.Internal.EthGasPrice(p0) +} + +func (s *FullNodeStub) EthGasPrice(p0 context.Context) (ethtypes.EthBigInt, error) { + return *new(ethtypes.EthBigInt), ErrNotSupported +} + +func (s *FullNodeStruct) EthGetBalance(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthBigInt, error) { + if s.Internal.EthGetBalance == nil { + return *new(ethtypes.EthBigInt), ErrNotSupported + } + return s.Internal.EthGetBalance(p0, p1, p2) +} + +func (s *FullNodeStub) EthGetBalance(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthBigInt, error) { + return *new(ethtypes.EthBigInt), ErrNotSupported +} + +func (s *FullNodeStruct) EthGetBlockByHash(p0 context.Context, p1 ethtypes.EthHash, p2 bool) (ethtypes.EthBlock, error) { + if s.Internal.EthGetBlockByHash == nil { + return *new(ethtypes.EthBlock), ErrNotSupported + } + return s.Internal.EthGetBlockByHash(p0, p1, p2) +} + +func (s *FullNodeStub) EthGetBlockByHash(p0 context.Context, p1 ethtypes.EthHash, p2 bool) (ethtypes.EthBlock, error) { + return *new(ethtypes.EthBlock), ErrNotSupported +} + +func (s *FullNodeStruct) EthGetBlockByNumber(p0 context.Context, p1 string, p2 bool) (ethtypes.EthBlock, error) { + if s.Internal.EthGetBlockByNumber == nil { + return *new(ethtypes.EthBlock), ErrNotSupported + } + return s.Internal.EthGetBlockByNumber(p0, p1, p2) +} + +func (s *FullNodeStub) EthGetBlockByNumber(p0 context.Context, p1 string, p2 bool) (ethtypes.EthBlock, error) { + return *new(ethtypes.EthBlock), ErrNotSupported +} + +func (s *FullNodeStruct) EthGetBlockTransactionCountByHash(p0 context.Context, p1 ethtypes.EthHash) (ethtypes.EthUint64, error) { + if s.Internal.EthGetBlockTransactionCountByHash == nil { + return *new(ethtypes.EthUint64), ErrNotSupported + } + return s.Internal.EthGetBlockTransactionCountByHash(p0, p1) +} + +func (s *FullNodeStub) EthGetBlockTransactionCountByHash(p0 context.Context, p1 ethtypes.EthHash) (ethtypes.EthUint64, error) { + return *new(ethtypes.EthUint64), ErrNotSupported +} + +func (s *FullNodeStruct) EthGetBlockTransactionCountByNumber(p0 context.Context, p1 ethtypes.EthUint64) (ethtypes.EthUint64, error) { + if s.Internal.EthGetBlockTransactionCountByNumber == nil { + return *new(ethtypes.EthUint64), ErrNotSupported + } + return s.Internal.EthGetBlockTransactionCountByNumber(p0, p1) +} + +func (s *FullNodeStub) EthGetBlockTransactionCountByNumber(p0 context.Context, p1 ethtypes.EthUint64) (ethtypes.EthUint64, error) { + return *new(ethtypes.EthUint64), ErrNotSupported +} + +func (s *FullNodeStruct) EthGetCode(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthBytes, error) { + if s.Internal.EthGetCode == nil { + return *new(ethtypes.EthBytes), ErrNotSupported + } + return s.Internal.EthGetCode(p0, p1, p2) +} + +func (s *FullNodeStub) EthGetCode(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthBytes, error) { + return *new(ethtypes.EthBytes), ErrNotSupported +} + +func (s *FullNodeStruct) EthGetFilterChanges(p0 context.Context, p1 ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) { + if s.Internal.EthGetFilterChanges == nil { + return nil, ErrNotSupported + } + return s.Internal.EthGetFilterChanges(p0, p1) +} + +func (s *FullNodeStub) EthGetFilterChanges(p0 context.Context, p1 ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) EthGetFilterLogs(p0 context.Context, p1 ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) { + if s.Internal.EthGetFilterLogs == nil { + return nil, ErrNotSupported + } + return s.Internal.EthGetFilterLogs(p0, p1) +} + +func (s *FullNodeStub) EthGetFilterLogs(p0 context.Context, p1 ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) EthGetLogs(p0 context.Context, p1 *ethtypes.EthFilterSpec) (*ethtypes.EthFilterResult, error) { + if s.Internal.EthGetLogs == nil { + return nil, ErrNotSupported + } + return s.Internal.EthGetLogs(p0, p1) +} + +func (s *FullNodeStub) EthGetLogs(p0 context.Context, p1 *ethtypes.EthFilterSpec) (*ethtypes.EthFilterResult, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) EthGetMessageCidByTransactionHash(p0 context.Context, p1 *ethtypes.EthHash) (*cid.Cid, error) { + if s.Internal.EthGetMessageCidByTransactionHash == nil { + return nil, ErrNotSupported + } + return s.Internal.EthGetMessageCidByTransactionHash(p0, p1) +} + +func (s *FullNodeStub) EthGetMessageCidByTransactionHash(p0 context.Context, p1 *ethtypes.EthHash) (*cid.Cid, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) EthGetStorageAt(p0 context.Context, p1 ethtypes.EthAddress, p2 ethtypes.EthBytes, p3 string) (ethtypes.EthBytes, error) { + if s.Internal.EthGetStorageAt == nil { + return *new(ethtypes.EthBytes), ErrNotSupported + } + return s.Internal.EthGetStorageAt(p0, p1, p2, p3) +} + +func (s *FullNodeStub) EthGetStorageAt(p0 context.Context, p1 ethtypes.EthAddress, p2 ethtypes.EthBytes, p3 string) (ethtypes.EthBytes, error) { + return *new(ethtypes.EthBytes), ErrNotSupported +} + +func (s *FullNodeStruct) EthGetTransactionByBlockHashAndIndex(p0 context.Context, p1 ethtypes.EthHash, p2 ethtypes.EthUint64) (ethtypes.EthTx, error) { + if s.Internal.EthGetTransactionByBlockHashAndIndex == nil { + return *new(ethtypes.EthTx), ErrNotSupported + } + return s.Internal.EthGetTransactionByBlockHashAndIndex(p0, p1, p2) +} + +func (s *FullNodeStub) EthGetTransactionByBlockHashAndIndex(p0 context.Context, p1 ethtypes.EthHash, p2 ethtypes.EthUint64) (ethtypes.EthTx, error) { + return *new(ethtypes.EthTx), ErrNotSupported +} + +func (s *FullNodeStruct) EthGetTransactionByBlockNumberAndIndex(p0 context.Context, p1 ethtypes.EthUint64, p2 ethtypes.EthUint64) (ethtypes.EthTx, error) { + if s.Internal.EthGetTransactionByBlockNumberAndIndex == nil { + return *new(ethtypes.EthTx), ErrNotSupported + } + return s.Internal.EthGetTransactionByBlockNumberAndIndex(p0, p1, p2) +} + +func (s *FullNodeStub) EthGetTransactionByBlockNumberAndIndex(p0 context.Context, p1 ethtypes.EthUint64, p2 ethtypes.EthUint64) (ethtypes.EthTx, error) { + return *new(ethtypes.EthTx), ErrNotSupported +} + +func (s *FullNodeStruct) EthGetTransactionByHash(p0 context.Context, p1 *ethtypes.EthHash) (*ethtypes.EthTx, error) { + if s.Internal.EthGetTransactionByHash == nil { + return nil, ErrNotSupported + } + return s.Internal.EthGetTransactionByHash(p0, p1) +} + +func (s *FullNodeStub) EthGetTransactionByHash(p0 context.Context, p1 *ethtypes.EthHash) (*ethtypes.EthTx, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) EthGetTransactionByHashLimited(p0 context.Context, p1 *ethtypes.EthHash, p2 abi.ChainEpoch) (*ethtypes.EthTx, error) { + if s.Internal.EthGetTransactionByHashLimited == nil { + return nil, ErrNotSupported + } + return s.Internal.EthGetTransactionByHashLimited(p0, p1, p2) +} + +func (s *FullNodeStub) EthGetTransactionByHashLimited(p0 context.Context, p1 *ethtypes.EthHash, p2 abi.ChainEpoch) (*ethtypes.EthTx, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) EthGetTransactionCount(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthUint64, error) { + if s.Internal.EthGetTransactionCount == nil { + return *new(ethtypes.EthUint64), ErrNotSupported + } + return s.Internal.EthGetTransactionCount(p0, p1, p2) +} + +func (s *FullNodeStub) EthGetTransactionCount(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthUint64, error) { + return *new(ethtypes.EthUint64), ErrNotSupported +} + +func (s *FullNodeStruct) EthGetTransactionHashByCid(p0 context.Context, p1 cid.Cid) (*ethtypes.EthHash, error) { + if s.Internal.EthGetTransactionHashByCid == nil { + return nil, ErrNotSupported + } + return s.Internal.EthGetTransactionHashByCid(p0, p1) +} + +func (s *FullNodeStub) EthGetTransactionHashByCid(p0 context.Context, p1 cid.Cid) (*ethtypes.EthHash, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) EthGetTransactionReceipt(p0 context.Context, p1 ethtypes.EthHash) (*EthTxReceipt, error) { + if s.Internal.EthGetTransactionReceipt == nil { + return nil, ErrNotSupported + } + return s.Internal.EthGetTransactionReceipt(p0, p1) +} + +func (s *FullNodeStub) EthGetTransactionReceipt(p0 context.Context, p1 ethtypes.EthHash) (*EthTxReceipt, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) EthGetTransactionReceiptLimited(p0 context.Context, p1 ethtypes.EthHash, p2 abi.ChainEpoch) (*EthTxReceipt, error) { + if s.Internal.EthGetTransactionReceiptLimited == nil { + return nil, ErrNotSupported + } + return s.Internal.EthGetTransactionReceiptLimited(p0, p1, p2) +} + +func (s *FullNodeStub) EthGetTransactionReceiptLimited(p0 context.Context, p1 ethtypes.EthHash, p2 abi.ChainEpoch) (*EthTxReceipt, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) EthMaxPriorityFeePerGas(p0 context.Context) (ethtypes.EthBigInt, error) { + if s.Internal.EthMaxPriorityFeePerGas == nil { + return *new(ethtypes.EthBigInt), ErrNotSupported + } + return s.Internal.EthMaxPriorityFeePerGas(p0) +} + +func (s *FullNodeStub) EthMaxPriorityFeePerGas(p0 context.Context) (ethtypes.EthBigInt, error) { + return *new(ethtypes.EthBigInt), ErrNotSupported +} + +func (s *FullNodeStruct) EthNewBlockFilter(p0 context.Context) (ethtypes.EthFilterID, error) { + if s.Internal.EthNewBlockFilter == nil { + return *new(ethtypes.EthFilterID), ErrNotSupported + } + return s.Internal.EthNewBlockFilter(p0) +} + +func (s *FullNodeStub) EthNewBlockFilter(p0 context.Context) (ethtypes.EthFilterID, error) { + return *new(ethtypes.EthFilterID), ErrNotSupported +} + +func (s *FullNodeStruct) EthNewFilter(p0 context.Context, p1 *ethtypes.EthFilterSpec) (ethtypes.EthFilterID, error) { + if s.Internal.EthNewFilter == nil { + return *new(ethtypes.EthFilterID), ErrNotSupported + } + return s.Internal.EthNewFilter(p0, p1) +} + +func (s *FullNodeStub) EthNewFilter(p0 context.Context, p1 *ethtypes.EthFilterSpec) (ethtypes.EthFilterID, error) { + return *new(ethtypes.EthFilterID), ErrNotSupported +} + +func (s *FullNodeStruct) EthNewPendingTransactionFilter(p0 context.Context) (ethtypes.EthFilterID, error) { + if s.Internal.EthNewPendingTransactionFilter == nil { + return *new(ethtypes.EthFilterID), ErrNotSupported + } + return s.Internal.EthNewPendingTransactionFilter(p0) +} + +func (s *FullNodeStub) EthNewPendingTransactionFilter(p0 context.Context) (ethtypes.EthFilterID, error) { + return *new(ethtypes.EthFilterID), ErrNotSupported +} + +func (s *FullNodeStruct) EthProtocolVersion(p0 context.Context) (ethtypes.EthUint64, error) { + if s.Internal.EthProtocolVersion == nil { + return *new(ethtypes.EthUint64), ErrNotSupported + } + return s.Internal.EthProtocolVersion(p0) +} + +func (s *FullNodeStub) EthProtocolVersion(p0 context.Context) (ethtypes.EthUint64, error) { + return *new(ethtypes.EthUint64), ErrNotSupported +} + +func (s *FullNodeStruct) EthSendRawTransaction(p0 context.Context, p1 ethtypes.EthBytes) (ethtypes.EthHash, error) { + if s.Internal.EthSendRawTransaction == nil { + return *new(ethtypes.EthHash), ErrNotSupported + } + return s.Internal.EthSendRawTransaction(p0, p1) +} + +func (s *FullNodeStub) EthSendRawTransaction(p0 context.Context, p1 ethtypes.EthBytes) (ethtypes.EthHash, error) { + return *new(ethtypes.EthHash), ErrNotSupported +} + +func (s *FullNodeStruct) EthSubscribe(p0 context.Context, p1 jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error) { + if s.Internal.EthSubscribe == nil { + return *new(ethtypes.EthSubscriptionID), ErrNotSupported + } + return s.Internal.EthSubscribe(p0, p1) +} + +func (s *FullNodeStub) EthSubscribe(p0 context.Context, p1 jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error) { + return *new(ethtypes.EthSubscriptionID), ErrNotSupported +} + +func (s *FullNodeStruct) EthSyncing(p0 context.Context) (ethtypes.EthSyncingResult, error) { + if s.Internal.EthSyncing == nil { + return *new(ethtypes.EthSyncingResult), ErrNotSupported + } + return s.Internal.EthSyncing(p0) +} + +func (s *FullNodeStub) EthSyncing(p0 context.Context) (ethtypes.EthSyncingResult, error) { + return *new(ethtypes.EthSyncingResult), ErrNotSupported +} + +func (s *FullNodeStruct) EthUninstallFilter(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) { + if s.Internal.EthUninstallFilter == nil { + return false, ErrNotSupported + } + return s.Internal.EthUninstallFilter(p0, p1) +} + +func (s *FullNodeStub) EthUninstallFilter(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) { + return false, ErrNotSupported +} + +func (s *FullNodeStruct) EthUnsubscribe(p0 context.Context, p1 ethtypes.EthSubscriptionID) (bool, error) { + if s.Internal.EthUnsubscribe == nil { + return false, ErrNotSupported + } + return s.Internal.EthUnsubscribe(p0, p1) +} + +func (s *FullNodeStub) EthUnsubscribe(p0 context.Context, p1 ethtypes.EthSubscriptionID) (bool, error) { + return false, ErrNotSupported +} + +func (s *FullNodeStruct) FilecoinAddressToEthAddress(p0 context.Context, p1 address.Address) (ethtypes.EthAddress, error) { + if s.Internal.FilecoinAddressToEthAddress == nil { + return *new(ethtypes.EthAddress), ErrNotSupported + } + return s.Internal.FilecoinAddressToEthAddress(p0, p1) +} + +func (s *FullNodeStub) FilecoinAddressToEthAddress(p0 context.Context, p1 address.Address) (ethtypes.EthAddress, error) { + return *new(ethtypes.EthAddress), ErrNotSupported +} + +func (s *FullNodeStruct) GasEstimateFeeCap(p0 context.Context, p1 *types.Message, p2 int64, p3 types.TipSetKey) (types.BigInt, error) { + if s.Internal.GasEstimateFeeCap == nil { + return *new(types.BigInt), ErrNotSupported + } + return s.Internal.GasEstimateFeeCap(p0, p1, p2, p3) +} + +func (s *FullNodeStub) GasEstimateFeeCap(p0 context.Context, p1 *types.Message, p2 int64, p3 types.TipSetKey) (types.BigInt, error) { + return *new(types.BigInt), ErrNotSupported +} + +func (s *FullNodeStruct) GasEstimateGasLimit(p0 context.Context, p1 *types.Message, p2 types.TipSetKey) (int64, error) { + if s.Internal.GasEstimateGasLimit == nil { + return 0, ErrNotSupported + } + return s.Internal.GasEstimateGasLimit(p0, p1, p2) +} + +func (s *FullNodeStub) GasEstimateGasLimit(p0 context.Context, p1 *types.Message, p2 types.TipSetKey) (int64, error) { + return 0, ErrNotSupported +} + +func (s *FullNodeStruct) GasEstimateGasPremium(p0 context.Context, p1 uint64, p2 address.Address, p3 int64, p4 types.TipSetKey) (types.BigInt, error) { + if s.Internal.GasEstimateGasPremium == nil { + return *new(types.BigInt), ErrNotSupported + } + return s.Internal.GasEstimateGasPremium(p0, p1, p2, p3, p4) +} + +func (s *FullNodeStub) GasEstimateGasPremium(p0 context.Context, p1 uint64, p2 address.Address, p3 int64, p4 types.TipSetKey) (types.BigInt, error) { + return *new(types.BigInt), ErrNotSupported +} + +func (s *FullNodeStruct) GasEstimateMessageGas(p0 context.Context, p1 *types.Message, p2 *MessageSendSpec, p3 types.TipSetKey) (*types.Message, error) { + if s.Internal.GasEstimateMessageGas == nil { + return nil, ErrNotSupported + } + return s.Internal.GasEstimateMessageGas(p0, p1, p2, p3) +} + +func (s *FullNodeStub) GasEstimateMessageGas(p0 context.Context, p1 *types.Message, p2 *MessageSendSpec, p3 types.TipSetKey) (*types.Message, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) MarketAddBalance(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt) (cid.Cid, error) { + if s.Internal.MarketAddBalance == nil { + return *new(cid.Cid), ErrNotSupported + } + return s.Internal.MarketAddBalance(p0, p1, p2, p3) +} + +func (s *FullNodeStub) MarketAddBalance(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt) (cid.Cid, error) { + return *new(cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) MarketGetReserved(p0 context.Context, p1 address.Address) (types.BigInt, error) { + if s.Internal.MarketGetReserved == nil { + return *new(types.BigInt), ErrNotSupported + } + return s.Internal.MarketGetReserved(p0, p1) +} + +func (s *FullNodeStub) MarketGetReserved(p0 context.Context, p1 address.Address) (types.BigInt, error) { + return *new(types.BigInt), ErrNotSupported +} + +func (s *FullNodeStruct) MarketReleaseFunds(p0 context.Context, p1 address.Address, p2 types.BigInt) error { + if s.Internal.MarketReleaseFunds == nil { + return ErrNotSupported + } + return s.Internal.MarketReleaseFunds(p0, p1, p2) +} + +func (s *FullNodeStub) MarketReleaseFunds(p0 context.Context, p1 address.Address, p2 types.BigInt) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) MarketReserveFunds(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt) (cid.Cid, error) { + if s.Internal.MarketReserveFunds == nil { + return *new(cid.Cid), ErrNotSupported + } + return s.Internal.MarketReserveFunds(p0, p1, p2, p3) +} + +func (s *FullNodeStub) MarketReserveFunds(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt) (cid.Cid, error) { + return *new(cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) MarketWithdraw(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt) (cid.Cid, error) { + if s.Internal.MarketWithdraw == nil { + return *new(cid.Cid), ErrNotSupported + } + return s.Internal.MarketWithdraw(p0, p1, p2, p3) +} + +func (s *FullNodeStub) MarketWithdraw(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt) (cid.Cid, error) { + return *new(cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) MinerCreateBlock(p0 context.Context, p1 *BlockTemplate) (*types.BlockMsg, error) { + if s.Internal.MinerCreateBlock == nil { + return nil, ErrNotSupported + } + return s.Internal.MinerCreateBlock(p0, p1) +} + +func (s *FullNodeStub) MinerCreateBlock(p0 context.Context, p1 *BlockTemplate) (*types.BlockMsg, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) MinerGetBaseInfo(p0 context.Context, p1 address.Address, p2 abi.ChainEpoch, p3 types.TipSetKey) (*MiningBaseInfo, error) { + if s.Internal.MinerGetBaseInfo == nil { + return nil, ErrNotSupported + } + return s.Internal.MinerGetBaseInfo(p0, p1, p2, p3) +} + +func (s *FullNodeStub) MinerGetBaseInfo(p0 context.Context, p1 address.Address, p2 abi.ChainEpoch, p3 types.TipSetKey) (*MiningBaseInfo, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) MpoolBatchPush(p0 context.Context, p1 []*types.SignedMessage) ([]cid.Cid, error) { + if s.Internal.MpoolBatchPush == nil { + return *new([]cid.Cid), ErrNotSupported + } + return s.Internal.MpoolBatchPush(p0, p1) +} + +func (s *FullNodeStub) MpoolBatchPush(p0 context.Context, p1 []*types.SignedMessage) ([]cid.Cid, error) { + return *new([]cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) MpoolBatchPushMessage(p0 context.Context, p1 []*types.Message, p2 *MessageSendSpec) ([]*types.SignedMessage, error) { + if s.Internal.MpoolBatchPushMessage == nil { + return *new([]*types.SignedMessage), ErrNotSupported + } + return s.Internal.MpoolBatchPushMessage(p0, p1, p2) +} + +func (s *FullNodeStub) MpoolBatchPushMessage(p0 context.Context, p1 []*types.Message, p2 *MessageSendSpec) ([]*types.SignedMessage, error) { + return *new([]*types.SignedMessage), ErrNotSupported +} + +func (s *FullNodeStruct) MpoolBatchPushUntrusted(p0 context.Context, p1 []*types.SignedMessage) ([]cid.Cid, error) { + if s.Internal.MpoolBatchPushUntrusted == nil { + return *new([]cid.Cid), ErrNotSupported + } + return s.Internal.MpoolBatchPushUntrusted(p0, p1) +} + +func (s *FullNodeStub) MpoolBatchPushUntrusted(p0 context.Context, p1 []*types.SignedMessage) ([]cid.Cid, error) { + return *new([]cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) MpoolCheckMessages(p0 context.Context, p1 []*MessagePrototype) ([][]MessageCheckStatus, error) { + if s.Internal.MpoolCheckMessages == nil { + return *new([][]MessageCheckStatus), ErrNotSupported + } + return s.Internal.MpoolCheckMessages(p0, p1) +} + +func (s *FullNodeStub) MpoolCheckMessages(p0 context.Context, p1 []*MessagePrototype) ([][]MessageCheckStatus, error) { + return *new([][]MessageCheckStatus), ErrNotSupported +} + +func (s *FullNodeStruct) MpoolCheckPendingMessages(p0 context.Context, p1 address.Address) ([][]MessageCheckStatus, error) { + if s.Internal.MpoolCheckPendingMessages == nil { + return *new([][]MessageCheckStatus), ErrNotSupported + } + return s.Internal.MpoolCheckPendingMessages(p0, p1) +} + +func (s *FullNodeStub) MpoolCheckPendingMessages(p0 context.Context, p1 address.Address) ([][]MessageCheckStatus, error) { + return *new([][]MessageCheckStatus), ErrNotSupported +} + +func (s *FullNodeStruct) MpoolCheckReplaceMessages(p0 context.Context, p1 []*types.Message) ([][]MessageCheckStatus, error) { + if s.Internal.MpoolCheckReplaceMessages == nil { + return *new([][]MessageCheckStatus), ErrNotSupported + } + return s.Internal.MpoolCheckReplaceMessages(p0, p1) +} + +func (s *FullNodeStub) MpoolCheckReplaceMessages(p0 context.Context, p1 []*types.Message) ([][]MessageCheckStatus, error) { + return *new([][]MessageCheckStatus), ErrNotSupported +} + +func (s *FullNodeStruct) MpoolClear(p0 context.Context, p1 bool) error { + if s.Internal.MpoolClear == nil { + return ErrNotSupported + } + return s.Internal.MpoolClear(p0, p1) +} + +func (s *FullNodeStub) MpoolClear(p0 context.Context, p1 bool) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) MpoolGetConfig(p0 context.Context) (*types.MpoolConfig, error) { + if s.Internal.MpoolGetConfig == nil { + return nil, ErrNotSupported + } + return s.Internal.MpoolGetConfig(p0) +} + +func (s *FullNodeStub) MpoolGetConfig(p0 context.Context) (*types.MpoolConfig, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) MpoolGetNonce(p0 context.Context, p1 address.Address) (uint64, error) { + if s.Internal.MpoolGetNonce == nil { + return 0, ErrNotSupported + } + return s.Internal.MpoolGetNonce(p0, p1) +} + +func (s *FullNodeStub) MpoolGetNonce(p0 context.Context, p1 address.Address) (uint64, error) { + return 0, ErrNotSupported +} + +func (s *FullNodeStruct) MpoolPending(p0 context.Context, p1 types.TipSetKey) ([]*types.SignedMessage, error) { + if s.Internal.MpoolPending == nil { + return *new([]*types.SignedMessage), ErrNotSupported + } + return s.Internal.MpoolPending(p0, p1) +} + +func (s *FullNodeStub) MpoolPending(p0 context.Context, p1 types.TipSetKey) ([]*types.SignedMessage, error) { + return *new([]*types.SignedMessage), ErrNotSupported +} + +func (s *FullNodeStruct) MpoolPush(p0 context.Context, p1 *types.SignedMessage) (cid.Cid, error) { + if s.Internal.MpoolPush == nil { + return *new(cid.Cid), ErrNotSupported + } + return s.Internal.MpoolPush(p0, p1) +} + +func (s *FullNodeStub) MpoolPush(p0 context.Context, p1 *types.SignedMessage) (cid.Cid, error) { + return *new(cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) MpoolPushMessage(p0 context.Context, p1 *types.Message, p2 *MessageSendSpec) (*types.SignedMessage, error) { + if s.Internal.MpoolPushMessage == nil { + return nil, ErrNotSupported + } + return s.Internal.MpoolPushMessage(p0, p1, p2) +} + +func (s *FullNodeStub) MpoolPushMessage(p0 context.Context, p1 *types.Message, p2 *MessageSendSpec) (*types.SignedMessage, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) MpoolPushUntrusted(p0 context.Context, p1 *types.SignedMessage) (cid.Cid, error) { + if s.Internal.MpoolPushUntrusted == nil { + return *new(cid.Cid), ErrNotSupported + } + return s.Internal.MpoolPushUntrusted(p0, p1) +} + +func (s *FullNodeStub) MpoolPushUntrusted(p0 context.Context, p1 *types.SignedMessage) (cid.Cid, error) { + return *new(cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) MpoolSelect(p0 context.Context, p1 types.TipSetKey, p2 float64) ([]*types.SignedMessage, error) { + if s.Internal.MpoolSelect == nil { + return *new([]*types.SignedMessage), ErrNotSupported + } + return s.Internal.MpoolSelect(p0, p1, p2) +} + +func (s *FullNodeStub) MpoolSelect(p0 context.Context, p1 types.TipSetKey, p2 float64) ([]*types.SignedMessage, error) { + return *new([]*types.SignedMessage), ErrNotSupported +} + +func (s *FullNodeStruct) MpoolSetConfig(p0 context.Context, p1 *types.MpoolConfig) error { + if s.Internal.MpoolSetConfig == nil { + return ErrNotSupported + } + return s.Internal.MpoolSetConfig(p0, p1) +} + +func (s *FullNodeStub) MpoolSetConfig(p0 context.Context, p1 *types.MpoolConfig) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) MpoolSub(p0 context.Context) (<-chan MpoolUpdate, error) { + if s.Internal.MpoolSub == nil { + return nil, ErrNotSupported + } + return s.Internal.MpoolSub(p0) +} + +func (s *FullNodeStub) MpoolSub(p0 context.Context) (<-chan MpoolUpdate, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) MsigAddApprove(p0 context.Context, p1 address.Address, p2 address.Address, p3 uint64, p4 address.Address, p5 address.Address, p6 bool) (*MessagePrototype, error) { + if s.Internal.MsigAddApprove == nil { + return nil, ErrNotSupported + } + return s.Internal.MsigAddApprove(p0, p1, p2, p3, p4, p5, p6) +} + +func (s *FullNodeStub) MsigAddApprove(p0 context.Context, p1 address.Address, p2 address.Address, p3 uint64, p4 address.Address, p5 address.Address, p6 bool) (*MessagePrototype, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) MsigAddCancel(p0 context.Context, p1 address.Address, p2 address.Address, p3 uint64, p4 address.Address, p5 bool) (*MessagePrototype, error) { + if s.Internal.MsigAddCancel == nil { + return nil, ErrNotSupported + } + return s.Internal.MsigAddCancel(p0, p1, p2, p3, p4, p5) +} + +func (s *FullNodeStub) MsigAddCancel(p0 context.Context, p1 address.Address, p2 address.Address, p3 uint64, p4 address.Address, p5 bool) (*MessagePrototype, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) MsigAddPropose(p0 context.Context, p1 address.Address, p2 address.Address, p3 address.Address, p4 bool) (*MessagePrototype, error) { + if s.Internal.MsigAddPropose == nil { + return nil, ErrNotSupported + } + return s.Internal.MsigAddPropose(p0, p1, p2, p3, p4) +} + +func (s *FullNodeStub) MsigAddPropose(p0 context.Context, p1 address.Address, p2 address.Address, p3 address.Address, p4 bool) (*MessagePrototype, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) MsigApprove(p0 context.Context, p1 address.Address, p2 uint64, p3 address.Address) (*MessagePrototype, error) { + if s.Internal.MsigApprove == nil { + return nil, ErrNotSupported + } + return s.Internal.MsigApprove(p0, p1, p2, p3) +} + +func (s *FullNodeStub) MsigApprove(p0 context.Context, p1 address.Address, p2 uint64, p3 address.Address) (*MessagePrototype, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) MsigApproveTxnHash(p0 context.Context, p1 address.Address, p2 uint64, p3 address.Address, p4 address.Address, p5 types.BigInt, p6 address.Address, p7 uint64, p8 []byte) (*MessagePrototype, error) { + if s.Internal.MsigApproveTxnHash == nil { + return nil, ErrNotSupported + } + return s.Internal.MsigApproveTxnHash(p0, p1, p2, p3, p4, p5, p6, p7, p8) +} + +func (s *FullNodeStub) MsigApproveTxnHash(p0 context.Context, p1 address.Address, p2 uint64, p3 address.Address, p4 address.Address, p5 types.BigInt, p6 address.Address, p7 uint64, p8 []byte) (*MessagePrototype, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) MsigCancel(p0 context.Context, p1 address.Address, p2 uint64, p3 address.Address) (*MessagePrototype, error) { + if s.Internal.MsigCancel == nil { + return nil, ErrNotSupported + } + return s.Internal.MsigCancel(p0, p1, p2, p3) +} + +func (s *FullNodeStub) MsigCancel(p0 context.Context, p1 address.Address, p2 uint64, p3 address.Address) (*MessagePrototype, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) MsigCancelTxnHash(p0 context.Context, p1 address.Address, p2 uint64, p3 address.Address, p4 types.BigInt, p5 address.Address, p6 uint64, p7 []byte) (*MessagePrototype, error) { + if s.Internal.MsigCancelTxnHash == nil { + return nil, ErrNotSupported + } + return s.Internal.MsigCancelTxnHash(p0, p1, p2, p3, p4, p5, p6, p7) +} + +func (s *FullNodeStub) MsigCancelTxnHash(p0 context.Context, p1 address.Address, p2 uint64, p3 address.Address, p4 types.BigInt, p5 address.Address, p6 uint64, p7 []byte) (*MessagePrototype, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) MsigCreate(p0 context.Context, p1 uint64, p2 []address.Address, p3 abi.ChainEpoch, p4 types.BigInt, p5 address.Address, p6 types.BigInt) (*MessagePrototype, error) { + if s.Internal.MsigCreate == nil { + return nil, ErrNotSupported + } + return s.Internal.MsigCreate(p0, p1, p2, p3, p4, p5, p6) +} + +func (s *FullNodeStub) MsigCreate(p0 context.Context, p1 uint64, p2 []address.Address, p3 abi.ChainEpoch, p4 types.BigInt, p5 address.Address, p6 types.BigInt) (*MessagePrototype, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) MsigGetAvailableBalance(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (types.BigInt, error) { + if s.Internal.MsigGetAvailableBalance == nil { + return *new(types.BigInt), ErrNotSupported + } + return s.Internal.MsigGetAvailableBalance(p0, p1, p2) +} + +func (s *FullNodeStub) MsigGetAvailableBalance(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (types.BigInt, error) { + return *new(types.BigInt), ErrNotSupported +} + +func (s *FullNodeStruct) MsigGetPending(p0 context.Context, p1 address.Address, p2 types.TipSetKey) ([]*MsigTransaction, error) { + if s.Internal.MsigGetPending == nil { + return *new([]*MsigTransaction), ErrNotSupported + } + return s.Internal.MsigGetPending(p0, p1, p2) +} + +func (s *FullNodeStub) MsigGetPending(p0 context.Context, p1 address.Address, p2 types.TipSetKey) ([]*MsigTransaction, error) { + return *new([]*MsigTransaction), ErrNotSupported +} + +func (s *FullNodeStruct) MsigGetVested(p0 context.Context, p1 address.Address, p2 types.TipSetKey, p3 types.TipSetKey) (types.BigInt, error) { + if s.Internal.MsigGetVested == nil { + return *new(types.BigInt), ErrNotSupported + } + return s.Internal.MsigGetVested(p0, p1, p2, p3) +} + +func (s *FullNodeStub) MsigGetVested(p0 context.Context, p1 address.Address, p2 types.TipSetKey, p3 types.TipSetKey) (types.BigInt, error) { + return *new(types.BigInt), ErrNotSupported +} + +func (s *FullNodeStruct) MsigGetVestingSchedule(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (MsigVesting, error) { + if s.Internal.MsigGetVestingSchedule == nil { + return *new(MsigVesting), ErrNotSupported + } + return s.Internal.MsigGetVestingSchedule(p0, p1, p2) +} + +func (s *FullNodeStub) MsigGetVestingSchedule(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (MsigVesting, error) { + return *new(MsigVesting), ErrNotSupported +} + +func (s *FullNodeStruct) MsigPropose(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt, p4 address.Address, p5 uint64, p6 []byte) (*MessagePrototype, error) { + if s.Internal.MsigPropose == nil { + return nil, ErrNotSupported + } + return s.Internal.MsigPropose(p0, p1, p2, p3, p4, p5, p6) +} + +func (s *FullNodeStub) MsigPropose(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt, p4 address.Address, p5 uint64, p6 []byte) (*MessagePrototype, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) MsigRemoveSigner(p0 context.Context, p1 address.Address, p2 address.Address, p3 address.Address, p4 bool) (*MessagePrototype, error) { + if s.Internal.MsigRemoveSigner == nil { + return nil, ErrNotSupported + } + return s.Internal.MsigRemoveSigner(p0, p1, p2, p3, p4) +} + +func (s *FullNodeStub) MsigRemoveSigner(p0 context.Context, p1 address.Address, p2 address.Address, p3 address.Address, p4 bool) (*MessagePrototype, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) MsigSwapApprove(p0 context.Context, p1 address.Address, p2 address.Address, p3 uint64, p4 address.Address, p5 address.Address, p6 address.Address) (*MessagePrototype, error) { + if s.Internal.MsigSwapApprove == nil { + return nil, ErrNotSupported + } + return s.Internal.MsigSwapApprove(p0, p1, p2, p3, p4, p5, p6) +} + +func (s *FullNodeStub) MsigSwapApprove(p0 context.Context, p1 address.Address, p2 address.Address, p3 uint64, p4 address.Address, p5 address.Address, p6 address.Address) (*MessagePrototype, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) MsigSwapCancel(p0 context.Context, p1 address.Address, p2 address.Address, p3 uint64, p4 address.Address, p5 address.Address) (*MessagePrototype, error) { + if s.Internal.MsigSwapCancel == nil { + return nil, ErrNotSupported + } + return s.Internal.MsigSwapCancel(p0, p1, p2, p3, p4, p5) +} + +func (s *FullNodeStub) MsigSwapCancel(p0 context.Context, p1 address.Address, p2 address.Address, p3 uint64, p4 address.Address, p5 address.Address) (*MessagePrototype, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) MsigSwapPropose(p0 context.Context, p1 address.Address, p2 address.Address, p3 address.Address, p4 address.Address) (*MessagePrototype, error) { + if s.Internal.MsigSwapPropose == nil { + return nil, ErrNotSupported + } + return s.Internal.MsigSwapPropose(p0, p1, p2, p3, p4) +} + +func (s *FullNodeStub) MsigSwapPropose(p0 context.Context, p1 address.Address, p2 address.Address, p3 address.Address, p4 address.Address) (*MessagePrototype, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) NetListening(p0 context.Context) (bool, error) { + if s.Internal.NetListening == nil { + return false, ErrNotSupported + } + return s.Internal.NetListening(p0) +} + +func (s *FullNodeStub) NetListening(p0 context.Context) (bool, error) { + return false, ErrNotSupported +} + +func (s *FullNodeStruct) NetVersion(p0 context.Context) (string, error) { + if s.Internal.NetVersion == nil { + return "", ErrNotSupported + } + return s.Internal.NetVersion(p0) +} + +func (s *FullNodeStub) NetVersion(p0 context.Context) (string, error) { + return "", ErrNotSupported +} + +func (s *FullNodeStruct) NodeStatus(p0 context.Context, p1 bool) (NodeStatus, error) { + if s.Internal.NodeStatus == nil { + return *new(NodeStatus), ErrNotSupported + } + return s.Internal.NodeStatus(p0, p1) +} + +func (s *FullNodeStub) NodeStatus(p0 context.Context, p1 bool) (NodeStatus, error) { + return *new(NodeStatus), ErrNotSupported +} + +func (s *FullNodeStruct) PaychAllocateLane(p0 context.Context, p1 address.Address) (uint64, error) { + if s.Internal.PaychAllocateLane == nil { + return 0, ErrNotSupported + } + return s.Internal.PaychAllocateLane(p0, p1) +} + +func (s *FullNodeStub) PaychAllocateLane(p0 context.Context, p1 address.Address) (uint64, error) { + return 0, ErrNotSupported +} + +func (s *FullNodeStruct) PaychAvailableFunds(p0 context.Context, p1 address.Address) (*ChannelAvailableFunds, error) { + if s.Internal.PaychAvailableFunds == nil { + return nil, ErrNotSupported + } + return s.Internal.PaychAvailableFunds(p0, p1) +} + +func (s *FullNodeStub) PaychAvailableFunds(p0 context.Context, p1 address.Address) (*ChannelAvailableFunds, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) PaychAvailableFundsByFromTo(p0 context.Context, p1 address.Address, p2 address.Address) (*ChannelAvailableFunds, error) { + if s.Internal.PaychAvailableFundsByFromTo == nil { + return nil, ErrNotSupported + } + return s.Internal.PaychAvailableFundsByFromTo(p0, p1, p2) +} + +func (s *FullNodeStub) PaychAvailableFundsByFromTo(p0 context.Context, p1 address.Address, p2 address.Address) (*ChannelAvailableFunds, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) PaychCollect(p0 context.Context, p1 address.Address) (cid.Cid, error) { + if s.Internal.PaychCollect == nil { + return *new(cid.Cid), ErrNotSupported + } + return s.Internal.PaychCollect(p0, p1) +} + +func (s *FullNodeStub) PaychCollect(p0 context.Context, p1 address.Address) (cid.Cid, error) { + return *new(cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) PaychFund(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt) (*ChannelInfo, error) { + if s.Internal.PaychFund == nil { + return nil, ErrNotSupported + } + return s.Internal.PaychFund(p0, p1, p2, p3) +} + +func (s *FullNodeStub) PaychFund(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt) (*ChannelInfo, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) PaychGet(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt, p4 PaychGetOpts) (*ChannelInfo, error) { + if s.Internal.PaychGet == nil { + return nil, ErrNotSupported + } + return s.Internal.PaychGet(p0, p1, p2, p3, p4) +} + +func (s *FullNodeStub) PaychGet(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt, p4 PaychGetOpts) (*ChannelInfo, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) PaychGetWaitReady(p0 context.Context, p1 cid.Cid) (address.Address, error) { + if s.Internal.PaychGetWaitReady == nil { + return *new(address.Address), ErrNotSupported + } + return s.Internal.PaychGetWaitReady(p0, p1) +} + +func (s *FullNodeStub) PaychGetWaitReady(p0 context.Context, p1 cid.Cid) (address.Address, error) { + return *new(address.Address), ErrNotSupported +} + +func (s *FullNodeStruct) PaychList(p0 context.Context) ([]address.Address, error) { + if s.Internal.PaychList == nil { + return *new([]address.Address), ErrNotSupported + } + return s.Internal.PaychList(p0) +} + +func (s *FullNodeStub) PaychList(p0 context.Context) ([]address.Address, error) { + return *new([]address.Address), ErrNotSupported +} + +func (s *FullNodeStruct) PaychNewPayment(p0 context.Context, p1 address.Address, p2 address.Address, p3 []VoucherSpec) (*PaymentInfo, error) { + if s.Internal.PaychNewPayment == nil { + return nil, ErrNotSupported + } + return s.Internal.PaychNewPayment(p0, p1, p2, p3) +} + +func (s *FullNodeStub) PaychNewPayment(p0 context.Context, p1 address.Address, p2 address.Address, p3 []VoucherSpec) (*PaymentInfo, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) PaychSettle(p0 context.Context, p1 address.Address) (cid.Cid, error) { + if s.Internal.PaychSettle == nil { + return *new(cid.Cid), ErrNotSupported + } + return s.Internal.PaychSettle(p0, p1) +} + +func (s *FullNodeStub) PaychSettle(p0 context.Context, p1 address.Address) (cid.Cid, error) { + return *new(cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) PaychStatus(p0 context.Context, p1 address.Address) (*PaychStatus, error) { + if s.Internal.PaychStatus == nil { + return nil, ErrNotSupported + } + return s.Internal.PaychStatus(p0, p1) +} + +func (s *FullNodeStub) PaychStatus(p0 context.Context, p1 address.Address) (*PaychStatus, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) PaychVoucherAdd(p0 context.Context, p1 address.Address, p2 *paych.SignedVoucher, p3 []byte, p4 types.BigInt) (types.BigInt, error) { + if s.Internal.PaychVoucherAdd == nil { + return *new(types.BigInt), ErrNotSupported + } + return s.Internal.PaychVoucherAdd(p0, p1, p2, p3, p4) +} + +func (s *FullNodeStub) PaychVoucherAdd(p0 context.Context, p1 address.Address, p2 *paych.SignedVoucher, p3 []byte, p4 types.BigInt) (types.BigInt, error) { + return *new(types.BigInt), ErrNotSupported +} + +func (s *FullNodeStruct) PaychVoucherCheckSpendable(p0 context.Context, p1 address.Address, p2 *paych.SignedVoucher, p3 []byte, p4 []byte) (bool, error) { + if s.Internal.PaychVoucherCheckSpendable == nil { + return false, ErrNotSupported + } + return s.Internal.PaychVoucherCheckSpendable(p0, p1, p2, p3, p4) +} + +func (s *FullNodeStub) PaychVoucherCheckSpendable(p0 context.Context, p1 address.Address, p2 *paych.SignedVoucher, p3 []byte, p4 []byte) (bool, error) { + return false, ErrNotSupported +} + +func (s *FullNodeStruct) PaychVoucherCheckValid(p0 context.Context, p1 address.Address, p2 *paych.SignedVoucher) error { + if s.Internal.PaychVoucherCheckValid == nil { + return ErrNotSupported + } + return s.Internal.PaychVoucherCheckValid(p0, p1, p2) +} + +func (s *FullNodeStub) PaychVoucherCheckValid(p0 context.Context, p1 address.Address, p2 *paych.SignedVoucher) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) PaychVoucherCreate(p0 context.Context, p1 address.Address, p2 types.BigInt, p3 uint64) (*VoucherCreateResult, error) { + if s.Internal.PaychVoucherCreate == nil { + return nil, ErrNotSupported + } + return s.Internal.PaychVoucherCreate(p0, p1, p2, p3) +} + +func (s *FullNodeStub) PaychVoucherCreate(p0 context.Context, p1 address.Address, p2 types.BigInt, p3 uint64) (*VoucherCreateResult, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) PaychVoucherList(p0 context.Context, p1 address.Address) ([]*paych.SignedVoucher, error) { + if s.Internal.PaychVoucherList == nil { + return *new([]*paych.SignedVoucher), ErrNotSupported + } + return s.Internal.PaychVoucherList(p0, p1) +} + +func (s *FullNodeStub) PaychVoucherList(p0 context.Context, p1 address.Address) ([]*paych.SignedVoucher, error) { + return *new([]*paych.SignedVoucher), ErrNotSupported +} + +func (s *FullNodeStruct) PaychVoucherSubmit(p0 context.Context, p1 address.Address, p2 *paych.SignedVoucher, p3 []byte, p4 []byte) (cid.Cid, error) { + if s.Internal.PaychVoucherSubmit == nil { + return *new(cid.Cid), ErrNotSupported + } + return s.Internal.PaychVoucherSubmit(p0, p1, p2, p3, p4) +} + +func (s *FullNodeStub) PaychVoucherSubmit(p0 context.Context, p1 address.Address, p2 *paych.SignedVoucher, p3 []byte, p4 []byte) (cid.Cid, error) { + return *new(cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) RaftLeader(p0 context.Context) (peer.ID, error) { + if s.Internal.RaftLeader == nil { + return *new(peer.ID), ErrNotSupported + } + return s.Internal.RaftLeader(p0) +} + +func (s *FullNodeStub) RaftLeader(p0 context.Context) (peer.ID, error) { + return *new(peer.ID), ErrNotSupported +} + +func (s *FullNodeStruct) RaftState(p0 context.Context) (*RaftStateData, error) { + if s.Internal.RaftState == nil { + return nil, ErrNotSupported + } + return s.Internal.RaftState(p0) +} + +func (s *FullNodeStub) RaftState(p0 context.Context) (*RaftStateData, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateAccountKey(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) { + if s.Internal.StateAccountKey == nil { + return *new(address.Address), ErrNotSupported + } + return s.Internal.StateAccountKey(p0, p1, p2) +} + +func (s *FullNodeStub) StateAccountKey(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) { + return *new(address.Address), ErrNotSupported +} + +func (s *FullNodeStruct) StateActorCodeCIDs(p0 context.Context, p1 abinetwork.Version) (map[string]cid.Cid, error) { + if s.Internal.StateActorCodeCIDs == nil { + return *new(map[string]cid.Cid), ErrNotSupported + } + return s.Internal.StateActorCodeCIDs(p0, p1) +} + +func (s *FullNodeStub) StateActorCodeCIDs(p0 context.Context, p1 abinetwork.Version) (map[string]cid.Cid, error) { + return *new(map[string]cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) StateActorManifestCID(p0 context.Context, p1 abinetwork.Version) (cid.Cid, error) { + if s.Internal.StateActorManifestCID == nil { + return *new(cid.Cid), ErrNotSupported + } + return s.Internal.StateActorManifestCID(p0, p1) +} + +func (s *FullNodeStub) StateActorManifestCID(p0 context.Context, p1 abinetwork.Version) (cid.Cid, error) { + return *new(cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) StateAllMinerFaults(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) ([]*Fault, error) { + if s.Internal.StateAllMinerFaults == nil { + return *new([]*Fault), ErrNotSupported + } + return s.Internal.StateAllMinerFaults(p0, p1, p2) +} + +func (s *FullNodeStub) StateAllMinerFaults(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) ([]*Fault, error) { + return *new([]*Fault), ErrNotSupported +} + +func (s *FullNodeStruct) StateCall(p0 context.Context, p1 *types.Message, p2 types.TipSetKey) (*InvocResult, error) { + if s.Internal.StateCall == nil { + return nil, ErrNotSupported + } + return s.Internal.StateCall(p0, p1, p2) +} + +func (s *FullNodeStub) StateCall(p0 context.Context, p1 *types.Message, p2 types.TipSetKey) (*InvocResult, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateChangedActors(p0 context.Context, p1 cid.Cid, p2 cid.Cid) (map[string]types.Actor, error) { + if s.Internal.StateChangedActors == nil { + return *new(map[string]types.Actor), ErrNotSupported + } + return s.Internal.StateChangedActors(p0, p1, p2) +} + +func (s *FullNodeStub) StateChangedActors(p0 context.Context, p1 cid.Cid, p2 cid.Cid) (map[string]types.Actor, error) { + return *new(map[string]types.Actor), ErrNotSupported +} + +func (s *FullNodeStruct) StateCirculatingSupply(p0 context.Context, p1 types.TipSetKey) (abi.TokenAmount, error) { + if s.Internal.StateCirculatingSupply == nil { + return *new(abi.TokenAmount), ErrNotSupported + } + return s.Internal.StateCirculatingSupply(p0, p1) +} + +func (s *FullNodeStub) StateCirculatingSupply(p0 context.Context, p1 types.TipSetKey) (abi.TokenAmount, error) { + return *new(abi.TokenAmount), ErrNotSupported +} + +func (s *FullNodeStruct) StateCompute(p0 context.Context, p1 abi.ChainEpoch, p2 []*types.Message, p3 types.TipSetKey) (*ComputeStateOutput, error) { + if s.Internal.StateCompute == nil { + return nil, ErrNotSupported + } + return s.Internal.StateCompute(p0, p1, p2, p3) +} + +func (s *FullNodeStub) StateCompute(p0 context.Context, p1 abi.ChainEpoch, p2 []*types.Message, p3 types.TipSetKey) (*ComputeStateOutput, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateComputeDataCID(p0 context.Context, p1 address.Address, p2 abi.RegisteredSealProof, p3 []abi.DealID, p4 types.TipSetKey) (cid.Cid, error) { + if s.Internal.StateComputeDataCID == nil { + return *new(cid.Cid), ErrNotSupported + } + return s.Internal.StateComputeDataCID(p0, p1, p2, p3, p4) +} + +func (s *FullNodeStub) StateComputeDataCID(p0 context.Context, p1 address.Address, p2 abi.RegisteredSealProof, p3 []abi.DealID, p4 types.TipSetKey) (cid.Cid, error) { + return *new(cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) StateDealProviderCollateralBounds(p0 context.Context, p1 abi.PaddedPieceSize, p2 bool, p3 types.TipSetKey) (DealCollateralBounds, error) { + if s.Internal.StateDealProviderCollateralBounds == nil { + return *new(DealCollateralBounds), ErrNotSupported + } + return s.Internal.StateDealProviderCollateralBounds(p0, p1, p2, p3) +} + +func (s *FullNodeStub) StateDealProviderCollateralBounds(p0 context.Context, p1 abi.PaddedPieceSize, p2 bool, p3 types.TipSetKey) (DealCollateralBounds, error) { + return *new(DealCollateralBounds), ErrNotSupported +} + +func (s *FullNodeStruct) StateDecodeParams(p0 context.Context, p1 address.Address, p2 abi.MethodNum, p3 []byte, p4 types.TipSetKey) (interface{}, error) { + if s.Internal.StateDecodeParams == nil { + return nil, ErrNotSupported + } + return s.Internal.StateDecodeParams(p0, p1, p2, p3, p4) +} + +func (s *FullNodeStub) StateDecodeParams(p0 context.Context, p1 address.Address, p2 abi.MethodNum, p3 []byte, p4 types.TipSetKey) (interface{}, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateEncodeParams(p0 context.Context, p1 cid.Cid, p2 abi.MethodNum, p3 json.RawMessage) ([]byte, error) { + if s.Internal.StateEncodeParams == nil { + return *new([]byte), ErrNotSupported + } + return s.Internal.StateEncodeParams(p0, p1, p2, p3) +} + +func (s *FullNodeStub) StateEncodeParams(p0 context.Context, p1 cid.Cid, p2 abi.MethodNum, p3 json.RawMessage) ([]byte, error) { + return *new([]byte), ErrNotSupported +} + +func (s *FullNodeStruct) StateGetActor(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*types.Actor, error) { + if s.Internal.StateGetActor == nil { + return nil, ErrNotSupported + } + return s.Internal.StateGetActor(p0, p1, p2) +} + +func (s *FullNodeStub) StateGetActor(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*types.Actor, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateGetAllocation(p0 context.Context, p1 address.Address, p2 verifregtypes.AllocationId, p3 types.TipSetKey) (*verifregtypes.Allocation, error) { + if s.Internal.StateGetAllocation == nil { + return nil, ErrNotSupported + } + return s.Internal.StateGetAllocation(p0, p1, p2, p3) +} + +func (s *FullNodeStub) StateGetAllocation(p0 context.Context, p1 address.Address, p2 verifregtypes.AllocationId, p3 types.TipSetKey) (*verifregtypes.Allocation, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateGetAllocationForPendingDeal(p0 context.Context, p1 abi.DealID, p2 types.TipSetKey) (*verifregtypes.Allocation, error) { + if s.Internal.StateGetAllocationForPendingDeal == nil { + return nil, ErrNotSupported + } + return s.Internal.StateGetAllocationForPendingDeal(p0, p1, p2) +} + +func (s *FullNodeStub) StateGetAllocationForPendingDeal(p0 context.Context, p1 abi.DealID, p2 types.TipSetKey) (*verifregtypes.Allocation, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateGetAllocations(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (map[verifregtypes.AllocationId]verifregtypes.Allocation, error) { + if s.Internal.StateGetAllocations == nil { + return *new(map[verifregtypes.AllocationId]verifregtypes.Allocation), ErrNotSupported + } + return s.Internal.StateGetAllocations(p0, p1, p2) +} + +func (s *FullNodeStub) StateGetAllocations(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (map[verifregtypes.AllocationId]verifregtypes.Allocation, error) { + return *new(map[verifregtypes.AllocationId]verifregtypes.Allocation), ErrNotSupported +} + +func (s *FullNodeStruct) StateGetBeaconEntry(p0 context.Context, p1 abi.ChainEpoch) (*types.BeaconEntry, error) { + if s.Internal.StateGetBeaconEntry == nil { + return nil, ErrNotSupported + } + return s.Internal.StateGetBeaconEntry(p0, p1) +} + +func (s *FullNodeStub) StateGetBeaconEntry(p0 context.Context, p1 abi.ChainEpoch) (*types.BeaconEntry, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateGetClaim(p0 context.Context, p1 address.Address, p2 verifregtypes.ClaimId, p3 types.TipSetKey) (*verifregtypes.Claim, error) { + if s.Internal.StateGetClaim == nil { + return nil, ErrNotSupported + } + return s.Internal.StateGetClaim(p0, p1, p2, p3) +} + +func (s *FullNodeStub) StateGetClaim(p0 context.Context, p1 address.Address, p2 verifregtypes.ClaimId, p3 types.TipSetKey) (*verifregtypes.Claim, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateGetClaims(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (map[verifregtypes.ClaimId]verifregtypes.Claim, error) { + if s.Internal.StateGetClaims == nil { + return *new(map[verifregtypes.ClaimId]verifregtypes.Claim), ErrNotSupported + } + return s.Internal.StateGetClaims(p0, p1, p2) +} + +func (s *FullNodeStub) StateGetClaims(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (map[verifregtypes.ClaimId]verifregtypes.Claim, error) { + return *new(map[verifregtypes.ClaimId]verifregtypes.Claim), ErrNotSupported +} + +func (s *FullNodeStruct) StateGetNetworkParams(p0 context.Context) (*NetworkParams, error) { + if s.Internal.StateGetNetworkParams == nil { + return nil, ErrNotSupported + } + return s.Internal.StateGetNetworkParams(p0) +} + +func (s *FullNodeStub) StateGetNetworkParams(p0 context.Context) (*NetworkParams, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateGetRandomnessFromBeacon(p0 context.Context, p1 crypto.DomainSeparationTag, p2 abi.ChainEpoch, p3 []byte, p4 types.TipSetKey) (abi.Randomness, error) { + if s.Internal.StateGetRandomnessFromBeacon == nil { + return *new(abi.Randomness), ErrNotSupported + } + return s.Internal.StateGetRandomnessFromBeacon(p0, p1, p2, p3, p4) +} + +func (s *FullNodeStub) StateGetRandomnessFromBeacon(p0 context.Context, p1 crypto.DomainSeparationTag, p2 abi.ChainEpoch, p3 []byte, p4 types.TipSetKey) (abi.Randomness, error) { + return *new(abi.Randomness), ErrNotSupported +} + +func (s *FullNodeStruct) StateGetRandomnessFromTickets(p0 context.Context, p1 crypto.DomainSeparationTag, p2 abi.ChainEpoch, p3 []byte, p4 types.TipSetKey) (abi.Randomness, error) { + if s.Internal.StateGetRandomnessFromTickets == nil { + return *new(abi.Randomness), ErrNotSupported + } + return s.Internal.StateGetRandomnessFromTickets(p0, p1, p2, p3, p4) +} + +func (s *FullNodeStub) StateGetRandomnessFromTickets(p0 context.Context, p1 crypto.DomainSeparationTag, p2 abi.ChainEpoch, p3 []byte, p4 types.TipSetKey) (abi.Randomness, error) { + return *new(abi.Randomness), ErrNotSupported +} + +func (s *FullNodeStruct) StateListActors(p0 context.Context, p1 types.TipSetKey) ([]address.Address, error) { + if s.Internal.StateListActors == nil { + return *new([]address.Address), ErrNotSupported + } + return s.Internal.StateListActors(p0, p1) +} + +func (s *FullNodeStub) StateListActors(p0 context.Context, p1 types.TipSetKey) ([]address.Address, error) { + return *new([]address.Address), ErrNotSupported +} + +func (s *FullNodeStruct) StateListMessages(p0 context.Context, p1 *MessageMatch, p2 types.TipSetKey, p3 abi.ChainEpoch) ([]cid.Cid, error) { + if s.Internal.StateListMessages == nil { + return *new([]cid.Cid), ErrNotSupported + } + return s.Internal.StateListMessages(p0, p1, p2, p3) +} + +func (s *FullNodeStub) StateListMessages(p0 context.Context, p1 *MessageMatch, p2 types.TipSetKey, p3 abi.ChainEpoch) ([]cid.Cid, error) { + return *new([]cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) StateListMiners(p0 context.Context, p1 types.TipSetKey) ([]address.Address, error) { + if s.Internal.StateListMiners == nil { + return *new([]address.Address), ErrNotSupported + } + return s.Internal.StateListMiners(p0, p1) +} + +func (s *FullNodeStub) StateListMiners(p0 context.Context, p1 types.TipSetKey) ([]address.Address, error) { + return *new([]address.Address), ErrNotSupported +} + +func (s *FullNodeStruct) StateLookupID(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) { + if s.Internal.StateLookupID == nil { + return *new(address.Address), ErrNotSupported + } + return s.Internal.StateLookupID(p0, p1, p2) +} + +func (s *FullNodeStub) StateLookupID(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) { + return *new(address.Address), ErrNotSupported +} + +func (s *FullNodeStruct) StateLookupRobustAddress(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) { + if s.Internal.StateLookupRobustAddress == nil { + return *new(address.Address), ErrNotSupported + } + return s.Internal.StateLookupRobustAddress(p0, p1, p2) +} + +func (s *FullNodeStub) StateLookupRobustAddress(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) { + return *new(address.Address), ErrNotSupported +} + +func (s *FullNodeStruct) StateMarketBalance(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (MarketBalance, error) { + if s.Internal.StateMarketBalance == nil { + return *new(MarketBalance), ErrNotSupported + } + return s.Internal.StateMarketBalance(p0, p1, p2) +} + +func (s *FullNodeStub) StateMarketBalance(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (MarketBalance, error) { + return *new(MarketBalance), ErrNotSupported +} + +func (s *FullNodeStruct) StateMarketDeals(p0 context.Context, p1 types.TipSetKey) (map[string]*MarketDeal, error) { + if s.Internal.StateMarketDeals == nil { + return *new(map[string]*MarketDeal), ErrNotSupported + } + return s.Internal.StateMarketDeals(p0, p1) +} + +func (s *FullNodeStub) StateMarketDeals(p0 context.Context, p1 types.TipSetKey) (map[string]*MarketDeal, error) { + return *new(map[string]*MarketDeal), ErrNotSupported +} + +func (s *FullNodeStruct) StateMarketParticipants(p0 context.Context, p1 types.TipSetKey) (map[string]MarketBalance, error) { + if s.Internal.StateMarketParticipants == nil { + return *new(map[string]MarketBalance), ErrNotSupported + } + return s.Internal.StateMarketParticipants(p0, p1) +} + +func (s *FullNodeStub) StateMarketParticipants(p0 context.Context, p1 types.TipSetKey) (map[string]MarketBalance, error) { + return *new(map[string]MarketBalance), ErrNotSupported +} + +func (s *FullNodeStruct) StateMarketStorageDeal(p0 context.Context, p1 abi.DealID, p2 types.TipSetKey) (*MarketDeal, error) { + if s.Internal.StateMarketStorageDeal == nil { + return nil, ErrNotSupported + } + return s.Internal.StateMarketStorageDeal(p0, p1, p2) +} + +func (s *FullNodeStub) StateMarketStorageDeal(p0 context.Context, p1 abi.DealID, p2 types.TipSetKey) (*MarketDeal, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateMinerActiveSectors(p0 context.Context, p1 address.Address, p2 types.TipSetKey) ([]*miner.SectorOnChainInfo, error) { + if s.Internal.StateMinerActiveSectors == nil { + return *new([]*miner.SectorOnChainInfo), ErrNotSupported + } + return s.Internal.StateMinerActiveSectors(p0, p1, p2) +} + +func (s *FullNodeStub) StateMinerActiveSectors(p0 context.Context, p1 address.Address, p2 types.TipSetKey) ([]*miner.SectorOnChainInfo, error) { + return *new([]*miner.SectorOnChainInfo), ErrNotSupported +} + +func (s *FullNodeStruct) StateMinerAllocated(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*bitfield.BitField, error) { + if s.Internal.StateMinerAllocated == nil { + return nil, ErrNotSupported + } + return s.Internal.StateMinerAllocated(p0, p1, p2) +} + +func (s *FullNodeStub) StateMinerAllocated(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*bitfield.BitField, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateMinerAvailableBalance(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (types.BigInt, error) { + if s.Internal.StateMinerAvailableBalance == nil { + return *new(types.BigInt), ErrNotSupported + } + return s.Internal.StateMinerAvailableBalance(p0, p1, p2) +} + +func (s *FullNodeStub) StateMinerAvailableBalance(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (types.BigInt, error) { + return *new(types.BigInt), ErrNotSupported +} + +func (s *FullNodeStruct) StateMinerDeadlines(p0 context.Context, p1 address.Address, p2 types.TipSetKey) ([]Deadline, error) { + if s.Internal.StateMinerDeadlines == nil { + return *new([]Deadline), ErrNotSupported + } + return s.Internal.StateMinerDeadlines(p0, p1, p2) +} + +func (s *FullNodeStub) StateMinerDeadlines(p0 context.Context, p1 address.Address, p2 types.TipSetKey) ([]Deadline, error) { + return *new([]Deadline), ErrNotSupported +} + +func (s *FullNodeStruct) StateMinerFaults(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (bitfield.BitField, error) { + if s.Internal.StateMinerFaults == nil { + return *new(bitfield.BitField), ErrNotSupported + } + return s.Internal.StateMinerFaults(p0, p1, p2) +} + +func (s *FullNodeStub) StateMinerFaults(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (bitfield.BitField, error) { + return *new(bitfield.BitField), ErrNotSupported +} + +func (s *FullNodeStruct) StateMinerInfo(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (MinerInfo, error) { + if s.Internal.StateMinerInfo == nil { + return *new(MinerInfo), ErrNotSupported + } + return s.Internal.StateMinerInfo(p0, p1, p2) +} + +func (s *FullNodeStub) StateMinerInfo(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (MinerInfo, error) { + return *new(MinerInfo), ErrNotSupported +} + +func (s *FullNodeStruct) StateMinerInitialPledgeCollateral(p0 context.Context, p1 address.Address, p2 miner.SectorPreCommitInfo, p3 types.TipSetKey) (types.BigInt, error) { + if s.Internal.StateMinerInitialPledgeCollateral == nil { + return *new(types.BigInt), ErrNotSupported + } + return s.Internal.StateMinerInitialPledgeCollateral(p0, p1, p2, p3) +} + +func (s *FullNodeStub) StateMinerInitialPledgeCollateral(p0 context.Context, p1 address.Address, p2 miner.SectorPreCommitInfo, p3 types.TipSetKey) (types.BigInt, error) { + return *new(types.BigInt), ErrNotSupported +} + +func (s *FullNodeStruct) StateMinerPartitions(p0 context.Context, p1 address.Address, p2 uint64, p3 types.TipSetKey) ([]Partition, error) { + if s.Internal.StateMinerPartitions == nil { + return *new([]Partition), ErrNotSupported + } + return s.Internal.StateMinerPartitions(p0, p1, p2, p3) +} + +func (s *FullNodeStub) StateMinerPartitions(p0 context.Context, p1 address.Address, p2 uint64, p3 types.TipSetKey) ([]Partition, error) { + return *new([]Partition), ErrNotSupported +} + +func (s *FullNodeStruct) StateMinerPower(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*MinerPower, error) { + if s.Internal.StateMinerPower == nil { + return nil, ErrNotSupported + } + return s.Internal.StateMinerPower(p0, p1, p2) +} + +func (s *FullNodeStub) StateMinerPower(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*MinerPower, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateMinerPreCommitDepositForPower(p0 context.Context, p1 address.Address, p2 miner.SectorPreCommitInfo, p3 types.TipSetKey) (types.BigInt, error) { + if s.Internal.StateMinerPreCommitDepositForPower == nil { + return *new(types.BigInt), ErrNotSupported + } + return s.Internal.StateMinerPreCommitDepositForPower(p0, p1, p2, p3) +} + +func (s *FullNodeStub) StateMinerPreCommitDepositForPower(p0 context.Context, p1 address.Address, p2 miner.SectorPreCommitInfo, p3 types.TipSetKey) (types.BigInt, error) { + return *new(types.BigInt), ErrNotSupported +} + +func (s *FullNodeStruct) StateMinerProvingDeadline(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*dline.Info, error) { + if s.Internal.StateMinerProvingDeadline == nil { + return nil, ErrNotSupported + } + return s.Internal.StateMinerProvingDeadline(p0, p1, p2) +} + +func (s *FullNodeStub) StateMinerProvingDeadline(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*dline.Info, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateMinerRecoveries(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (bitfield.BitField, error) { + if s.Internal.StateMinerRecoveries == nil { + return *new(bitfield.BitField), ErrNotSupported + } + return s.Internal.StateMinerRecoveries(p0, p1, p2) +} + +func (s *FullNodeStub) StateMinerRecoveries(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (bitfield.BitField, error) { + return *new(bitfield.BitField), ErrNotSupported +} + +func (s *FullNodeStruct) StateMinerSectorAllocated(p0 context.Context, p1 address.Address, p2 abi.SectorNumber, p3 types.TipSetKey) (bool, error) { + if s.Internal.StateMinerSectorAllocated == nil { + return false, ErrNotSupported + } + return s.Internal.StateMinerSectorAllocated(p0, p1, p2, p3) +} + +func (s *FullNodeStub) StateMinerSectorAllocated(p0 context.Context, p1 address.Address, p2 abi.SectorNumber, p3 types.TipSetKey) (bool, error) { + return false, ErrNotSupported +} + +func (s *FullNodeStruct) StateMinerSectorCount(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (MinerSectors, error) { + if s.Internal.StateMinerSectorCount == nil { + return *new(MinerSectors), ErrNotSupported + } + return s.Internal.StateMinerSectorCount(p0, p1, p2) +} + +func (s *FullNodeStub) StateMinerSectorCount(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (MinerSectors, error) { + return *new(MinerSectors), ErrNotSupported +} + +func (s *FullNodeStruct) StateMinerSectors(p0 context.Context, p1 address.Address, p2 *bitfield.BitField, p3 types.TipSetKey) ([]*miner.SectorOnChainInfo, error) { + if s.Internal.StateMinerSectors == nil { + return *new([]*miner.SectorOnChainInfo), ErrNotSupported + } + return s.Internal.StateMinerSectors(p0, p1, p2, p3) +} + +func (s *FullNodeStub) StateMinerSectors(p0 context.Context, p1 address.Address, p2 *bitfield.BitField, p3 types.TipSetKey) ([]*miner.SectorOnChainInfo, error) { + return *new([]*miner.SectorOnChainInfo), ErrNotSupported +} + +func (s *FullNodeStruct) StateNetworkName(p0 context.Context) (dtypes.NetworkName, error) { + if s.Internal.StateNetworkName == nil { + return *new(dtypes.NetworkName), ErrNotSupported + } + return s.Internal.StateNetworkName(p0) +} + +func (s *FullNodeStub) StateNetworkName(p0 context.Context) (dtypes.NetworkName, error) { + return *new(dtypes.NetworkName), ErrNotSupported +} + +func (s *FullNodeStruct) StateNetworkVersion(p0 context.Context, p1 types.TipSetKey) (apitypes.NetworkVersion, error) { + if s.Internal.StateNetworkVersion == nil { + return *new(apitypes.NetworkVersion), ErrNotSupported + } + return s.Internal.StateNetworkVersion(p0, p1) +} + +func (s *FullNodeStub) StateNetworkVersion(p0 context.Context, p1 types.TipSetKey) (apitypes.NetworkVersion, error) { + return *new(apitypes.NetworkVersion), ErrNotSupported +} + +func (s *FullNodeStruct) StateReadState(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*ActorState, error) { + if s.Internal.StateReadState == nil { + return nil, ErrNotSupported + } + return s.Internal.StateReadState(p0, p1, p2) +} + +func (s *FullNodeStub) StateReadState(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*ActorState, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateReplay(p0 context.Context, p1 types.TipSetKey, p2 cid.Cid) (*InvocResult, error) { + if s.Internal.StateReplay == nil { + return nil, ErrNotSupported + } + return s.Internal.StateReplay(p0, p1, p2) +} + +func (s *FullNodeStub) StateReplay(p0 context.Context, p1 types.TipSetKey, p2 cid.Cid) (*InvocResult, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateSearchMsg(p0 context.Context, p1 types.TipSetKey, p2 cid.Cid, p3 abi.ChainEpoch, p4 bool) (*MsgLookup, error) { + if s.Internal.StateSearchMsg == nil { + return nil, ErrNotSupported + } + return s.Internal.StateSearchMsg(p0, p1, p2, p3, p4) +} + +func (s *FullNodeStub) StateSearchMsg(p0 context.Context, p1 types.TipSetKey, p2 cid.Cid, p3 abi.ChainEpoch, p4 bool) (*MsgLookup, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateSectorExpiration(p0 context.Context, p1 address.Address, p2 abi.SectorNumber, p3 types.TipSetKey) (*lminer.SectorExpiration, error) { + if s.Internal.StateSectorExpiration == nil { + return nil, ErrNotSupported + } + return s.Internal.StateSectorExpiration(p0, p1, p2, p3) +} + +func (s *FullNodeStub) StateSectorExpiration(p0 context.Context, p1 address.Address, p2 abi.SectorNumber, p3 types.TipSetKey) (*lminer.SectorExpiration, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateSectorGetInfo(p0 context.Context, p1 address.Address, p2 abi.SectorNumber, p3 types.TipSetKey) (*miner.SectorOnChainInfo, error) { + if s.Internal.StateSectorGetInfo == nil { + return nil, ErrNotSupported + } + return s.Internal.StateSectorGetInfo(p0, p1, p2, p3) +} + +func (s *FullNodeStub) StateSectorGetInfo(p0 context.Context, p1 address.Address, p2 abi.SectorNumber, p3 types.TipSetKey) (*miner.SectorOnChainInfo, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateSectorPartition(p0 context.Context, p1 address.Address, p2 abi.SectorNumber, p3 types.TipSetKey) (*lminer.SectorLocation, error) { + if s.Internal.StateSectorPartition == nil { + return nil, ErrNotSupported + } + return s.Internal.StateSectorPartition(p0, p1, p2, p3) +} + +func (s *FullNodeStub) StateSectorPartition(p0 context.Context, p1 address.Address, p2 abi.SectorNumber, p3 types.TipSetKey) (*lminer.SectorLocation, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateSectorPreCommitInfo(p0 context.Context, p1 address.Address, p2 abi.SectorNumber, p3 types.TipSetKey) (*miner.SectorPreCommitOnChainInfo, error) { + if s.Internal.StateSectorPreCommitInfo == nil { + return nil, ErrNotSupported + } + return s.Internal.StateSectorPreCommitInfo(p0, p1, p2, p3) +} + +func (s *FullNodeStub) StateSectorPreCommitInfo(p0 context.Context, p1 address.Address, p2 abi.SectorNumber, p3 types.TipSetKey) (*miner.SectorPreCommitOnChainInfo, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateVMCirculatingSupplyInternal(p0 context.Context, p1 types.TipSetKey) (CirculatingSupply, error) { + if s.Internal.StateVMCirculatingSupplyInternal == nil { + return *new(CirculatingSupply), ErrNotSupported + } + return s.Internal.StateVMCirculatingSupplyInternal(p0, p1) +} + +func (s *FullNodeStub) StateVMCirculatingSupplyInternal(p0 context.Context, p1 types.TipSetKey) (CirculatingSupply, error) { + return *new(CirculatingSupply), ErrNotSupported +} + +func (s *FullNodeStruct) StateVerifiedClientStatus(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*abi.StoragePower, error) { + if s.Internal.StateVerifiedClientStatus == nil { + return nil, ErrNotSupported + } + return s.Internal.StateVerifiedClientStatus(p0, p1, p2) +} + +func (s *FullNodeStub) StateVerifiedClientStatus(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*abi.StoragePower, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateVerifiedRegistryRootKey(p0 context.Context, p1 types.TipSetKey) (address.Address, error) { + if s.Internal.StateVerifiedRegistryRootKey == nil { + return *new(address.Address), ErrNotSupported + } + return s.Internal.StateVerifiedRegistryRootKey(p0, p1) +} + +func (s *FullNodeStub) StateVerifiedRegistryRootKey(p0 context.Context, p1 types.TipSetKey) (address.Address, error) { + return *new(address.Address), ErrNotSupported +} + +func (s *FullNodeStruct) StateVerifierStatus(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*abi.StoragePower, error) { + if s.Internal.StateVerifierStatus == nil { + return nil, ErrNotSupported + } + return s.Internal.StateVerifierStatus(p0, p1, p2) +} + +func (s *FullNodeStub) StateVerifierStatus(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*abi.StoragePower, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateWaitMsg(p0 context.Context, p1 cid.Cid, p2 uint64, p3 abi.ChainEpoch, p4 bool) (*MsgLookup, error) { + if s.Internal.StateWaitMsg == nil { + return nil, ErrNotSupported + } + return s.Internal.StateWaitMsg(p0, p1, p2, p3, p4) +} + +func (s *FullNodeStub) StateWaitMsg(p0 context.Context, p1 cid.Cid, p2 uint64, p3 abi.ChainEpoch, p4 bool) (*MsgLookup, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) SyncCheckBad(p0 context.Context, p1 cid.Cid) (string, error) { + if s.Internal.SyncCheckBad == nil { + return "", ErrNotSupported + } + return s.Internal.SyncCheckBad(p0, p1) +} + +func (s *FullNodeStub) SyncCheckBad(p0 context.Context, p1 cid.Cid) (string, error) { + return "", ErrNotSupported +} + +func (s *FullNodeStruct) SyncCheckpoint(p0 context.Context, p1 types.TipSetKey) error { + if s.Internal.SyncCheckpoint == nil { + return ErrNotSupported + } + return s.Internal.SyncCheckpoint(p0, p1) +} + +func (s *FullNodeStub) SyncCheckpoint(p0 context.Context, p1 types.TipSetKey) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) SyncIncomingBlocks(p0 context.Context) (<-chan *types.BlockHeader, error) { + if s.Internal.SyncIncomingBlocks == nil { + return nil, ErrNotSupported + } + return s.Internal.SyncIncomingBlocks(p0) +} + +func (s *FullNodeStub) SyncIncomingBlocks(p0 context.Context) (<-chan *types.BlockHeader, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) SyncMarkBad(p0 context.Context, p1 cid.Cid) error { + if s.Internal.SyncMarkBad == nil { + return ErrNotSupported + } + return s.Internal.SyncMarkBad(p0, p1) +} + +func (s *FullNodeStub) SyncMarkBad(p0 context.Context, p1 cid.Cid) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) SyncState(p0 context.Context) (*SyncState, error) { + if s.Internal.SyncState == nil { + return nil, ErrNotSupported + } + return s.Internal.SyncState(p0) +} + +func (s *FullNodeStub) SyncState(p0 context.Context) (*SyncState, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) SyncSubmitBlock(p0 context.Context, p1 *types.BlockMsg) error { + if s.Internal.SyncSubmitBlock == nil { + return ErrNotSupported + } + return s.Internal.SyncSubmitBlock(p0, p1) +} + +func (s *FullNodeStub) SyncSubmitBlock(p0 context.Context, p1 *types.BlockMsg) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) SyncUnmarkAllBad(p0 context.Context) error { + if s.Internal.SyncUnmarkAllBad == nil { + return ErrNotSupported + } + return s.Internal.SyncUnmarkAllBad(p0) +} + +func (s *FullNodeStub) SyncUnmarkAllBad(p0 context.Context) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) SyncUnmarkBad(p0 context.Context, p1 cid.Cid) error { + if s.Internal.SyncUnmarkBad == nil { + return ErrNotSupported + } + return s.Internal.SyncUnmarkBad(p0, p1) +} + +func (s *FullNodeStub) SyncUnmarkBad(p0 context.Context, p1 cid.Cid) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) SyncValidateTipset(p0 context.Context, p1 types.TipSetKey) (bool, error) { + if s.Internal.SyncValidateTipset == nil { + return false, ErrNotSupported + } + return s.Internal.SyncValidateTipset(p0, p1) +} + +func (s *FullNodeStub) SyncValidateTipset(p0 context.Context, p1 types.TipSetKey) (bool, error) { + return false, ErrNotSupported +} + +func (s *FullNodeStruct) WalletBalance(p0 context.Context, p1 address.Address) (types.BigInt, error) { + if s.Internal.WalletBalance == nil { + return *new(types.BigInt), ErrNotSupported + } + return s.Internal.WalletBalance(p0, p1) +} + +func (s *FullNodeStub) WalletBalance(p0 context.Context, p1 address.Address) (types.BigInt, error) { + return *new(types.BigInt), ErrNotSupported +} + +func (s *FullNodeStruct) WalletDefaultAddress(p0 context.Context) (address.Address, error) { + if s.Internal.WalletDefaultAddress == nil { + return *new(address.Address), ErrNotSupported + } + return s.Internal.WalletDefaultAddress(p0) +} + +func (s *FullNodeStub) WalletDefaultAddress(p0 context.Context) (address.Address, error) { + return *new(address.Address), ErrNotSupported +} + +func (s *FullNodeStruct) WalletDelete(p0 context.Context, p1 address.Address) error { + if s.Internal.WalletDelete == nil { + return ErrNotSupported + } + return s.Internal.WalletDelete(p0, p1) +} + +func (s *FullNodeStub) WalletDelete(p0 context.Context, p1 address.Address) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) WalletExport(p0 context.Context, p1 address.Address) (*types.KeyInfo, error) { + if s.Internal.WalletExport == nil { + return nil, ErrNotSupported + } + return s.Internal.WalletExport(p0, p1) +} + +func (s *FullNodeStub) WalletExport(p0 context.Context, p1 address.Address) (*types.KeyInfo, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) WalletHas(p0 context.Context, p1 address.Address) (bool, error) { + if s.Internal.WalletHas == nil { + return false, ErrNotSupported + } + return s.Internal.WalletHas(p0, p1) +} + +func (s *FullNodeStub) WalletHas(p0 context.Context, p1 address.Address) (bool, error) { + return false, ErrNotSupported +} + +func (s *FullNodeStruct) WalletImport(p0 context.Context, p1 *types.KeyInfo) (address.Address, error) { + if s.Internal.WalletImport == nil { + return *new(address.Address), ErrNotSupported + } + return s.Internal.WalletImport(p0, p1) +} + +func (s *FullNodeStub) WalletImport(p0 context.Context, p1 *types.KeyInfo) (address.Address, error) { + return *new(address.Address), ErrNotSupported +} + +func (s *FullNodeStruct) WalletList(p0 context.Context) ([]address.Address, error) { + if s.Internal.WalletList == nil { + return *new([]address.Address), ErrNotSupported + } + return s.Internal.WalletList(p0) +} + +func (s *FullNodeStub) WalletList(p0 context.Context) ([]address.Address, error) { + return *new([]address.Address), ErrNotSupported +} + +func (s *FullNodeStruct) WalletNew(p0 context.Context, p1 types.KeyType) (address.Address, error) { + if s.Internal.WalletNew == nil { + return *new(address.Address), ErrNotSupported + } + return s.Internal.WalletNew(p0, p1) +} + +func (s *FullNodeStub) WalletNew(p0 context.Context, p1 types.KeyType) (address.Address, error) { + return *new(address.Address), ErrNotSupported +} + +func (s *FullNodeStruct) WalletSetDefault(p0 context.Context, p1 address.Address) error { + if s.Internal.WalletSetDefault == nil { + return ErrNotSupported + } + return s.Internal.WalletSetDefault(p0, p1) +} + +func (s *FullNodeStub) WalletSetDefault(p0 context.Context, p1 address.Address) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) WalletSign(p0 context.Context, p1 address.Address, p2 []byte) (*crypto.Signature, error) { + if s.Internal.WalletSign == nil { + return nil, ErrNotSupported + } + return s.Internal.WalletSign(p0, p1, p2) +} + +func (s *FullNodeStub) WalletSign(p0 context.Context, p1 address.Address, p2 []byte) (*crypto.Signature, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) WalletSignMessage(p0 context.Context, p1 address.Address, p2 *types.Message) (*types.SignedMessage, error) { + if s.Internal.WalletSignMessage == nil { + return nil, ErrNotSupported + } + return s.Internal.WalletSignMessage(p0, p1, p2) +} + +func (s *FullNodeStub) WalletSignMessage(p0 context.Context, p1 address.Address, p2 *types.Message) (*types.SignedMessage, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) WalletValidateAddress(p0 context.Context, p1 string) (address.Address, error) { + if s.Internal.WalletValidateAddress == nil { + return *new(address.Address), ErrNotSupported + } + return s.Internal.WalletValidateAddress(p0, p1) +} + +func (s *FullNodeStub) WalletValidateAddress(p0 context.Context, p1 string) (address.Address, error) { + return *new(address.Address), ErrNotSupported +} + +func (s *FullNodeStruct) WalletVerify(p0 context.Context, p1 address.Address, p2 []byte, p3 *crypto.Signature) (bool, error) { + if s.Internal.WalletVerify == nil { + return false, ErrNotSupported + } + return s.Internal.WalletVerify(p0, p1, p2, p3) +} + +func (s *FullNodeStub) WalletVerify(p0 context.Context, p1 address.Address, p2 []byte, p3 *crypto.Signature) (bool, error) { + return false, ErrNotSupported +} + +func (s *FullNodeStruct) Web3ClientVersion(p0 context.Context) (string, error) { + if s.Internal.Web3ClientVersion == nil { + return "", ErrNotSupported + } + return s.Internal.Web3ClientVersion(p0) +} + +func (s *FullNodeStub) Web3ClientVersion(p0 context.Context) (string, error) { + return "", ErrNotSupported +} + +func (s *GatewayStruct) ChainGetBlockMessages(p0 context.Context, p1 cid.Cid) (*BlockMessages, error) { + if s.Internal.ChainGetBlockMessages == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainGetBlockMessages(p0, p1) +} + +func (s *GatewayStub) ChainGetBlockMessages(p0 context.Context, p1 cid.Cid) (*BlockMessages, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) ChainGetGenesis(p0 context.Context) (*types.TipSet, error) { + if s.Internal.ChainGetGenesis == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainGetGenesis(p0) +} + +func (s *GatewayStub) ChainGetGenesis(p0 context.Context) (*types.TipSet, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) ChainGetMessage(p0 context.Context, p1 cid.Cid) (*types.Message, error) { + if s.Internal.ChainGetMessage == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainGetMessage(p0, p1) +} + +func (s *GatewayStub) ChainGetMessage(p0 context.Context, p1 cid.Cid) (*types.Message, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) ChainGetParentMessages(p0 context.Context, p1 cid.Cid) ([]Message, error) { + if s.Internal.ChainGetParentMessages == nil { + return *new([]Message), ErrNotSupported + } + return s.Internal.ChainGetParentMessages(p0, p1) +} + +func (s *GatewayStub) ChainGetParentMessages(p0 context.Context, p1 cid.Cid) ([]Message, error) { + return *new([]Message), ErrNotSupported +} + +func (s *GatewayStruct) ChainGetParentReceipts(p0 context.Context, p1 cid.Cid) ([]*types.MessageReceipt, error) { + if s.Internal.ChainGetParentReceipts == nil { + return *new([]*types.MessageReceipt), ErrNotSupported + } + return s.Internal.ChainGetParentReceipts(p0, p1) +} + +func (s *GatewayStub) ChainGetParentReceipts(p0 context.Context, p1 cid.Cid) ([]*types.MessageReceipt, error) { + return *new([]*types.MessageReceipt), ErrNotSupported +} + +func (s *GatewayStruct) ChainGetPath(p0 context.Context, p1 types.TipSetKey, p2 types.TipSetKey) ([]*HeadChange, error) { + if s.Internal.ChainGetPath == nil { + return *new([]*HeadChange), ErrNotSupported + } + return s.Internal.ChainGetPath(p0, p1, p2) +} + +func (s *GatewayStub) ChainGetPath(p0 context.Context, p1 types.TipSetKey, p2 types.TipSetKey) ([]*HeadChange, error) { + return *new([]*HeadChange), ErrNotSupported +} + +func (s *GatewayStruct) ChainGetTipSet(p0 context.Context, p1 types.TipSetKey) (*types.TipSet, error) { + if s.Internal.ChainGetTipSet == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainGetTipSet(p0, p1) +} + +func (s *GatewayStub) ChainGetTipSet(p0 context.Context, p1 types.TipSetKey) (*types.TipSet, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) ChainGetTipSetAfterHeight(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) { + if s.Internal.ChainGetTipSetAfterHeight == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainGetTipSetAfterHeight(p0, p1, p2) +} + +func (s *GatewayStub) ChainGetTipSetAfterHeight(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) ChainGetTipSetByHeight(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) { + if s.Internal.ChainGetTipSetByHeight == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainGetTipSetByHeight(p0, p1, p2) +} + +func (s *GatewayStub) ChainGetTipSetByHeight(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) ChainHasObj(p0 context.Context, p1 cid.Cid) (bool, error) { + if s.Internal.ChainHasObj == nil { + return false, ErrNotSupported + } + return s.Internal.ChainHasObj(p0, p1) +} + +func (s *GatewayStub) ChainHasObj(p0 context.Context, p1 cid.Cid) (bool, error) { + return false, ErrNotSupported +} + +func (s *GatewayStruct) ChainHead(p0 context.Context) (*types.TipSet, error) { + if s.Internal.ChainHead == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainHead(p0) +} + +func (s *GatewayStub) ChainHead(p0 context.Context) (*types.TipSet, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) ChainNotify(p0 context.Context) (<-chan []*HeadChange, error) { + if s.Internal.ChainNotify == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainNotify(p0) +} + +func (s *GatewayStub) ChainNotify(p0 context.Context) (<-chan []*HeadChange, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) ChainPutObj(p0 context.Context, p1 blocks.Block) error { + if s.Internal.ChainPutObj == nil { + return ErrNotSupported + } + return s.Internal.ChainPutObj(p0, p1) +} + +func (s *GatewayStub) ChainPutObj(p0 context.Context, p1 blocks.Block) error { + return ErrNotSupported +} + +func (s *GatewayStruct) ChainReadObj(p0 context.Context, p1 cid.Cid) ([]byte, error) { + if s.Internal.ChainReadObj == nil { + return *new([]byte), ErrNotSupported + } + return s.Internal.ChainReadObj(p0, p1) +} + +func (s *GatewayStub) ChainReadObj(p0 context.Context, p1 cid.Cid) ([]byte, error) { + return *new([]byte), ErrNotSupported +} + +func (s *GatewayStruct) Discover(p0 context.Context) (apitypes.OpenRPCDocument, error) { + if s.Internal.Discover == nil { + return *new(apitypes.OpenRPCDocument), ErrNotSupported + } + return s.Internal.Discover(p0) +} + +func (s *GatewayStub) Discover(p0 context.Context) (apitypes.OpenRPCDocument, error) { + return *new(apitypes.OpenRPCDocument), ErrNotSupported +} + +func (s *GatewayStruct) EthAccounts(p0 context.Context) ([]ethtypes.EthAddress, error) { + if s.Internal.EthAccounts == nil { + return *new([]ethtypes.EthAddress), ErrNotSupported + } + return s.Internal.EthAccounts(p0) +} + +func (s *GatewayStub) EthAccounts(p0 context.Context) ([]ethtypes.EthAddress, error) { + return *new([]ethtypes.EthAddress), ErrNotSupported +} + +func (s *GatewayStruct) EthBlockNumber(p0 context.Context) (ethtypes.EthUint64, error) { + if s.Internal.EthBlockNumber == nil { + return *new(ethtypes.EthUint64), ErrNotSupported + } + return s.Internal.EthBlockNumber(p0) +} + +func (s *GatewayStub) EthBlockNumber(p0 context.Context) (ethtypes.EthUint64, error) { + return *new(ethtypes.EthUint64), ErrNotSupported +} + +func (s *GatewayStruct) EthCall(p0 context.Context, p1 ethtypes.EthCall, p2 string) (ethtypes.EthBytes, error) { + if s.Internal.EthCall == nil { + return *new(ethtypes.EthBytes), ErrNotSupported + } + return s.Internal.EthCall(p0, p1, p2) +} + +func (s *GatewayStub) EthCall(p0 context.Context, p1 ethtypes.EthCall, p2 string) (ethtypes.EthBytes, error) { + return *new(ethtypes.EthBytes), ErrNotSupported +} + +func (s *GatewayStruct) EthChainId(p0 context.Context) (ethtypes.EthUint64, error) { + if s.Internal.EthChainId == nil { + return *new(ethtypes.EthUint64), ErrNotSupported + } + return s.Internal.EthChainId(p0) +} + +func (s *GatewayStub) EthChainId(p0 context.Context) (ethtypes.EthUint64, error) { + return *new(ethtypes.EthUint64), ErrNotSupported +} + +func (s *GatewayStruct) EthEstimateGas(p0 context.Context, p1 ethtypes.EthCall) (ethtypes.EthUint64, error) { + if s.Internal.EthEstimateGas == nil { + return *new(ethtypes.EthUint64), ErrNotSupported + } + return s.Internal.EthEstimateGas(p0, p1) +} + +func (s *GatewayStub) EthEstimateGas(p0 context.Context, p1 ethtypes.EthCall) (ethtypes.EthUint64, error) { + return *new(ethtypes.EthUint64), ErrNotSupported +} + +func (s *GatewayStruct) EthFeeHistory(p0 context.Context, p1 jsonrpc.RawParams) (ethtypes.EthFeeHistory, error) { + if s.Internal.EthFeeHistory == nil { + return *new(ethtypes.EthFeeHistory), ErrNotSupported + } + return s.Internal.EthFeeHistory(p0, p1) +} + +func (s *GatewayStub) EthFeeHistory(p0 context.Context, p1 jsonrpc.RawParams) (ethtypes.EthFeeHistory, error) { + return *new(ethtypes.EthFeeHistory), ErrNotSupported +} + +func (s *GatewayStruct) EthGasPrice(p0 context.Context) (ethtypes.EthBigInt, error) { + if s.Internal.EthGasPrice == nil { + return *new(ethtypes.EthBigInt), ErrNotSupported + } + return s.Internal.EthGasPrice(p0) +} + +func (s *GatewayStub) EthGasPrice(p0 context.Context) (ethtypes.EthBigInt, error) { + return *new(ethtypes.EthBigInt), ErrNotSupported +} + +func (s *GatewayStruct) EthGetBalance(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthBigInt, error) { + if s.Internal.EthGetBalance == nil { + return *new(ethtypes.EthBigInt), ErrNotSupported + } + return s.Internal.EthGetBalance(p0, p1, p2) +} + +func (s *GatewayStub) EthGetBalance(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthBigInt, error) { + return *new(ethtypes.EthBigInt), ErrNotSupported +} + +func (s *GatewayStruct) EthGetBlockByHash(p0 context.Context, p1 ethtypes.EthHash, p2 bool) (ethtypes.EthBlock, error) { + if s.Internal.EthGetBlockByHash == nil { + return *new(ethtypes.EthBlock), ErrNotSupported + } + return s.Internal.EthGetBlockByHash(p0, p1, p2) +} + +func (s *GatewayStub) EthGetBlockByHash(p0 context.Context, p1 ethtypes.EthHash, p2 bool) (ethtypes.EthBlock, error) { + return *new(ethtypes.EthBlock), ErrNotSupported +} + +func (s *GatewayStruct) EthGetBlockByNumber(p0 context.Context, p1 string, p2 bool) (ethtypes.EthBlock, error) { + if s.Internal.EthGetBlockByNumber == nil { + return *new(ethtypes.EthBlock), ErrNotSupported + } + return s.Internal.EthGetBlockByNumber(p0, p1, p2) +} + +func (s *GatewayStub) EthGetBlockByNumber(p0 context.Context, p1 string, p2 bool) (ethtypes.EthBlock, error) { + return *new(ethtypes.EthBlock), ErrNotSupported +} + +func (s *GatewayStruct) EthGetBlockTransactionCountByHash(p0 context.Context, p1 ethtypes.EthHash) (ethtypes.EthUint64, error) { + if s.Internal.EthGetBlockTransactionCountByHash == nil { + return *new(ethtypes.EthUint64), ErrNotSupported + } + return s.Internal.EthGetBlockTransactionCountByHash(p0, p1) +} + +func (s *GatewayStub) EthGetBlockTransactionCountByHash(p0 context.Context, p1 ethtypes.EthHash) (ethtypes.EthUint64, error) { + return *new(ethtypes.EthUint64), ErrNotSupported +} + +func (s *GatewayStruct) EthGetBlockTransactionCountByNumber(p0 context.Context, p1 ethtypes.EthUint64) (ethtypes.EthUint64, error) { + if s.Internal.EthGetBlockTransactionCountByNumber == nil { + return *new(ethtypes.EthUint64), ErrNotSupported + } + return s.Internal.EthGetBlockTransactionCountByNumber(p0, p1) +} + +func (s *GatewayStub) EthGetBlockTransactionCountByNumber(p0 context.Context, p1 ethtypes.EthUint64) (ethtypes.EthUint64, error) { + return *new(ethtypes.EthUint64), ErrNotSupported +} + +func (s *GatewayStruct) EthGetCode(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthBytes, error) { + if s.Internal.EthGetCode == nil { + return *new(ethtypes.EthBytes), ErrNotSupported + } + return s.Internal.EthGetCode(p0, p1, p2) +} + +func (s *GatewayStub) EthGetCode(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthBytes, error) { + return *new(ethtypes.EthBytes), ErrNotSupported +} + +func (s *GatewayStruct) EthGetFilterChanges(p0 context.Context, p1 ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) { + if s.Internal.EthGetFilterChanges == nil { + return nil, ErrNotSupported + } + return s.Internal.EthGetFilterChanges(p0, p1) +} + +func (s *GatewayStub) EthGetFilterChanges(p0 context.Context, p1 ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) EthGetFilterLogs(p0 context.Context, p1 ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) { + if s.Internal.EthGetFilterLogs == nil { + return nil, ErrNotSupported + } + return s.Internal.EthGetFilterLogs(p0, p1) +} + +func (s *GatewayStub) EthGetFilterLogs(p0 context.Context, p1 ethtypes.EthFilterID) (*ethtypes.EthFilterResult, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) EthGetLogs(p0 context.Context, p1 *ethtypes.EthFilterSpec) (*ethtypes.EthFilterResult, error) { + if s.Internal.EthGetLogs == nil { + return nil, ErrNotSupported + } + return s.Internal.EthGetLogs(p0, p1) +} + +func (s *GatewayStub) EthGetLogs(p0 context.Context, p1 *ethtypes.EthFilterSpec) (*ethtypes.EthFilterResult, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) EthGetMessageCidByTransactionHash(p0 context.Context, p1 *ethtypes.EthHash) (*cid.Cid, error) { + if s.Internal.EthGetMessageCidByTransactionHash == nil { + return nil, ErrNotSupported + } + return s.Internal.EthGetMessageCidByTransactionHash(p0, p1) +} + +func (s *GatewayStub) EthGetMessageCidByTransactionHash(p0 context.Context, p1 *ethtypes.EthHash) (*cid.Cid, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) EthGetStorageAt(p0 context.Context, p1 ethtypes.EthAddress, p2 ethtypes.EthBytes, p3 string) (ethtypes.EthBytes, error) { + if s.Internal.EthGetStorageAt == nil { + return *new(ethtypes.EthBytes), ErrNotSupported + } + return s.Internal.EthGetStorageAt(p0, p1, p2, p3) +} + +func (s *GatewayStub) EthGetStorageAt(p0 context.Context, p1 ethtypes.EthAddress, p2 ethtypes.EthBytes, p3 string) (ethtypes.EthBytes, error) { + return *new(ethtypes.EthBytes), ErrNotSupported +} + +func (s *GatewayStruct) EthGetTransactionByHash(p0 context.Context, p1 *ethtypes.EthHash) (*ethtypes.EthTx, error) { + if s.Internal.EthGetTransactionByHash == nil { + return nil, ErrNotSupported + } + return s.Internal.EthGetTransactionByHash(p0, p1) +} + +func (s *GatewayStub) EthGetTransactionByHash(p0 context.Context, p1 *ethtypes.EthHash) (*ethtypes.EthTx, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) EthGetTransactionByHashLimited(p0 context.Context, p1 *ethtypes.EthHash, p2 abi.ChainEpoch) (*ethtypes.EthTx, error) { + if s.Internal.EthGetTransactionByHashLimited == nil { + return nil, ErrNotSupported + } + return s.Internal.EthGetTransactionByHashLimited(p0, p1, p2) +} + +func (s *GatewayStub) EthGetTransactionByHashLimited(p0 context.Context, p1 *ethtypes.EthHash, p2 abi.ChainEpoch) (*ethtypes.EthTx, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) EthGetTransactionCount(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthUint64, error) { + if s.Internal.EthGetTransactionCount == nil { + return *new(ethtypes.EthUint64), ErrNotSupported + } + return s.Internal.EthGetTransactionCount(p0, p1, p2) +} + +func (s *GatewayStub) EthGetTransactionCount(p0 context.Context, p1 ethtypes.EthAddress, p2 string) (ethtypes.EthUint64, error) { + return *new(ethtypes.EthUint64), ErrNotSupported +} + +func (s *GatewayStruct) EthGetTransactionHashByCid(p0 context.Context, p1 cid.Cid) (*ethtypes.EthHash, error) { + if s.Internal.EthGetTransactionHashByCid == nil { + return nil, ErrNotSupported + } + return s.Internal.EthGetTransactionHashByCid(p0, p1) +} + +func (s *GatewayStub) EthGetTransactionHashByCid(p0 context.Context, p1 cid.Cid) (*ethtypes.EthHash, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) EthGetTransactionReceipt(p0 context.Context, p1 ethtypes.EthHash) (*EthTxReceipt, error) { + if s.Internal.EthGetTransactionReceipt == nil { + return nil, ErrNotSupported + } + return s.Internal.EthGetTransactionReceipt(p0, p1) +} + +func (s *GatewayStub) EthGetTransactionReceipt(p0 context.Context, p1 ethtypes.EthHash) (*EthTxReceipt, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) EthGetTransactionReceiptLimited(p0 context.Context, p1 ethtypes.EthHash, p2 abi.ChainEpoch) (*EthTxReceipt, error) { + if s.Internal.EthGetTransactionReceiptLimited == nil { + return nil, ErrNotSupported + } + return s.Internal.EthGetTransactionReceiptLimited(p0, p1, p2) +} + +func (s *GatewayStub) EthGetTransactionReceiptLimited(p0 context.Context, p1 ethtypes.EthHash, p2 abi.ChainEpoch) (*EthTxReceipt, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) EthMaxPriorityFeePerGas(p0 context.Context) (ethtypes.EthBigInt, error) { + if s.Internal.EthMaxPriorityFeePerGas == nil { + return *new(ethtypes.EthBigInt), ErrNotSupported + } + return s.Internal.EthMaxPriorityFeePerGas(p0) +} + +func (s *GatewayStub) EthMaxPriorityFeePerGas(p0 context.Context) (ethtypes.EthBigInt, error) { + return *new(ethtypes.EthBigInt), ErrNotSupported +} + +func (s *GatewayStruct) EthNewBlockFilter(p0 context.Context) (ethtypes.EthFilterID, error) { + if s.Internal.EthNewBlockFilter == nil { + return *new(ethtypes.EthFilterID), ErrNotSupported + } + return s.Internal.EthNewBlockFilter(p0) +} + +func (s *GatewayStub) EthNewBlockFilter(p0 context.Context) (ethtypes.EthFilterID, error) { + return *new(ethtypes.EthFilterID), ErrNotSupported +} + +func (s *GatewayStruct) EthNewFilter(p0 context.Context, p1 *ethtypes.EthFilterSpec) (ethtypes.EthFilterID, error) { + if s.Internal.EthNewFilter == nil { + return *new(ethtypes.EthFilterID), ErrNotSupported + } + return s.Internal.EthNewFilter(p0, p1) +} + +func (s *GatewayStub) EthNewFilter(p0 context.Context, p1 *ethtypes.EthFilterSpec) (ethtypes.EthFilterID, error) { + return *new(ethtypes.EthFilterID), ErrNotSupported +} + +func (s *GatewayStruct) EthNewPendingTransactionFilter(p0 context.Context) (ethtypes.EthFilterID, error) { + if s.Internal.EthNewPendingTransactionFilter == nil { + return *new(ethtypes.EthFilterID), ErrNotSupported + } + return s.Internal.EthNewPendingTransactionFilter(p0) +} + +func (s *GatewayStub) EthNewPendingTransactionFilter(p0 context.Context) (ethtypes.EthFilterID, error) { + return *new(ethtypes.EthFilterID), ErrNotSupported +} + +func (s *GatewayStruct) EthProtocolVersion(p0 context.Context) (ethtypes.EthUint64, error) { + if s.Internal.EthProtocolVersion == nil { + return *new(ethtypes.EthUint64), ErrNotSupported + } + return s.Internal.EthProtocolVersion(p0) +} + +func (s *GatewayStub) EthProtocolVersion(p0 context.Context) (ethtypes.EthUint64, error) { + return *new(ethtypes.EthUint64), ErrNotSupported +} + +func (s *GatewayStruct) EthSendRawTransaction(p0 context.Context, p1 ethtypes.EthBytes) (ethtypes.EthHash, error) { + if s.Internal.EthSendRawTransaction == nil { + return *new(ethtypes.EthHash), ErrNotSupported + } + return s.Internal.EthSendRawTransaction(p0, p1) +} + +func (s *GatewayStub) EthSendRawTransaction(p0 context.Context, p1 ethtypes.EthBytes) (ethtypes.EthHash, error) { + return *new(ethtypes.EthHash), ErrNotSupported +} + +func (s *GatewayStruct) EthSubscribe(p0 context.Context, p1 jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error) { + if s.Internal.EthSubscribe == nil { + return *new(ethtypes.EthSubscriptionID), ErrNotSupported + } + return s.Internal.EthSubscribe(p0, p1) +} + +func (s *GatewayStub) EthSubscribe(p0 context.Context, p1 jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error) { + return *new(ethtypes.EthSubscriptionID), ErrNotSupported +} + +func (s *GatewayStruct) EthSyncing(p0 context.Context) (ethtypes.EthSyncingResult, error) { + if s.Internal.EthSyncing == nil { + return *new(ethtypes.EthSyncingResult), ErrNotSupported + } + return s.Internal.EthSyncing(p0) +} + +func (s *GatewayStub) EthSyncing(p0 context.Context) (ethtypes.EthSyncingResult, error) { + return *new(ethtypes.EthSyncingResult), ErrNotSupported +} + +func (s *GatewayStruct) EthUninstallFilter(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) { + if s.Internal.EthUninstallFilter == nil { + return false, ErrNotSupported + } + return s.Internal.EthUninstallFilter(p0, p1) +} + +func (s *GatewayStub) EthUninstallFilter(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) { + return false, ErrNotSupported +} + +func (s *GatewayStruct) EthUnsubscribe(p0 context.Context, p1 ethtypes.EthSubscriptionID) (bool, error) { + if s.Internal.EthUnsubscribe == nil { + return false, ErrNotSupported + } + return s.Internal.EthUnsubscribe(p0, p1) +} + +func (s *GatewayStub) EthUnsubscribe(p0 context.Context, p1 ethtypes.EthSubscriptionID) (bool, error) { + return false, ErrNotSupported +} + +func (s *GatewayStruct) GasEstimateGasPremium(p0 context.Context, p1 uint64, p2 address.Address, p3 int64, p4 types.TipSetKey) (types.BigInt, error) { + if s.Internal.GasEstimateGasPremium == nil { + return *new(types.BigInt), ErrNotSupported + } + return s.Internal.GasEstimateGasPremium(p0, p1, p2, p3, p4) +} + +func (s *GatewayStub) GasEstimateGasPremium(p0 context.Context, p1 uint64, p2 address.Address, p3 int64, p4 types.TipSetKey) (types.BigInt, error) { + return *new(types.BigInt), ErrNotSupported +} + +func (s *GatewayStruct) GasEstimateMessageGas(p0 context.Context, p1 *types.Message, p2 *MessageSendSpec, p3 types.TipSetKey) (*types.Message, error) { + if s.Internal.GasEstimateMessageGas == nil { + return nil, ErrNotSupported + } + return s.Internal.GasEstimateMessageGas(p0, p1, p2, p3) +} + +func (s *GatewayStub) GasEstimateMessageGas(p0 context.Context, p1 *types.Message, p2 *MessageSendSpec, p3 types.TipSetKey) (*types.Message, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) MpoolGetNonce(p0 context.Context, p1 address.Address) (uint64, error) { + if s.Internal.MpoolGetNonce == nil { + return 0, ErrNotSupported + } + return s.Internal.MpoolGetNonce(p0, p1) +} + +func (s *GatewayStub) MpoolGetNonce(p0 context.Context, p1 address.Address) (uint64, error) { + return 0, ErrNotSupported +} + +func (s *GatewayStruct) MpoolPush(p0 context.Context, p1 *types.SignedMessage) (cid.Cid, error) { + if s.Internal.MpoolPush == nil { + return *new(cid.Cid), ErrNotSupported + } + return s.Internal.MpoolPush(p0, p1) +} + +func (s *GatewayStub) MpoolPush(p0 context.Context, p1 *types.SignedMessage) (cid.Cid, error) { + return *new(cid.Cid), ErrNotSupported +} + +func (s *GatewayStruct) MsigGetAvailableBalance(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (types.BigInt, error) { + if s.Internal.MsigGetAvailableBalance == nil { + return *new(types.BigInt), ErrNotSupported + } + return s.Internal.MsigGetAvailableBalance(p0, p1, p2) +} + +func (s *GatewayStub) MsigGetAvailableBalance(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (types.BigInt, error) { + return *new(types.BigInt), ErrNotSupported +} + +func (s *GatewayStruct) MsigGetPending(p0 context.Context, p1 address.Address, p2 types.TipSetKey) ([]*MsigTransaction, error) { + if s.Internal.MsigGetPending == nil { + return *new([]*MsigTransaction), ErrNotSupported + } + return s.Internal.MsigGetPending(p0, p1, p2) +} + +func (s *GatewayStub) MsigGetPending(p0 context.Context, p1 address.Address, p2 types.TipSetKey) ([]*MsigTransaction, error) { + return *new([]*MsigTransaction), ErrNotSupported +} + +func (s *GatewayStruct) MsigGetVested(p0 context.Context, p1 address.Address, p2 types.TipSetKey, p3 types.TipSetKey) (types.BigInt, error) { + if s.Internal.MsigGetVested == nil { + return *new(types.BigInt), ErrNotSupported + } + return s.Internal.MsigGetVested(p0, p1, p2, p3) +} + +func (s *GatewayStub) MsigGetVested(p0 context.Context, p1 address.Address, p2 types.TipSetKey, p3 types.TipSetKey) (types.BigInt, error) { + return *new(types.BigInt), ErrNotSupported +} + +func (s *GatewayStruct) MsigGetVestingSchedule(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (MsigVesting, error) { + if s.Internal.MsigGetVestingSchedule == nil { + return *new(MsigVesting), ErrNotSupported + } + return s.Internal.MsigGetVestingSchedule(p0, p1, p2) +} + +func (s *GatewayStub) MsigGetVestingSchedule(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (MsigVesting, error) { + return *new(MsigVesting), ErrNotSupported +} + +func (s *GatewayStruct) NetListening(p0 context.Context) (bool, error) { + if s.Internal.NetListening == nil { + return false, ErrNotSupported + } + return s.Internal.NetListening(p0) +} + +func (s *GatewayStub) NetListening(p0 context.Context) (bool, error) { + return false, ErrNotSupported +} + +func (s *GatewayStruct) NetVersion(p0 context.Context) (string, error) { + if s.Internal.NetVersion == nil { + return "", ErrNotSupported + } + return s.Internal.NetVersion(p0) +} + +func (s *GatewayStub) NetVersion(p0 context.Context) (string, error) { + return "", ErrNotSupported +} + +func (s *GatewayStruct) StateAccountKey(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) { + if s.Internal.StateAccountKey == nil { + return *new(address.Address), ErrNotSupported + } + return s.Internal.StateAccountKey(p0, p1, p2) +} + +func (s *GatewayStub) StateAccountKey(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) { + return *new(address.Address), ErrNotSupported +} + +func (s *GatewayStruct) StateCall(p0 context.Context, p1 *types.Message, p2 types.TipSetKey) (*InvocResult, error) { + if s.Internal.StateCall == nil { + return nil, ErrNotSupported + } + return s.Internal.StateCall(p0, p1, p2) +} + +func (s *GatewayStub) StateCall(p0 context.Context, p1 *types.Message, p2 types.TipSetKey) (*InvocResult, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) StateDealProviderCollateralBounds(p0 context.Context, p1 abi.PaddedPieceSize, p2 bool, p3 types.TipSetKey) (DealCollateralBounds, error) { + if s.Internal.StateDealProviderCollateralBounds == nil { + return *new(DealCollateralBounds), ErrNotSupported + } + return s.Internal.StateDealProviderCollateralBounds(p0, p1, p2, p3) +} + +func (s *GatewayStub) StateDealProviderCollateralBounds(p0 context.Context, p1 abi.PaddedPieceSize, p2 bool, p3 types.TipSetKey) (DealCollateralBounds, error) { + return *new(DealCollateralBounds), ErrNotSupported +} + +func (s *GatewayStruct) StateDecodeParams(p0 context.Context, p1 address.Address, p2 abi.MethodNum, p3 []byte, p4 types.TipSetKey) (interface{}, error) { + if s.Internal.StateDecodeParams == nil { + return nil, ErrNotSupported + } + return s.Internal.StateDecodeParams(p0, p1, p2, p3, p4) +} + +func (s *GatewayStub) StateDecodeParams(p0 context.Context, p1 address.Address, p2 abi.MethodNum, p3 []byte, p4 types.TipSetKey) (interface{}, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) StateGetActor(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*types.Actor, error) { + if s.Internal.StateGetActor == nil { + return nil, ErrNotSupported + } + return s.Internal.StateGetActor(p0, p1, p2) +} + +func (s *GatewayStub) StateGetActor(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*types.Actor, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) StateListMiners(p0 context.Context, p1 types.TipSetKey) ([]address.Address, error) { + if s.Internal.StateListMiners == nil { + return *new([]address.Address), ErrNotSupported + } + return s.Internal.StateListMiners(p0, p1) +} + +func (s *GatewayStub) StateListMiners(p0 context.Context, p1 types.TipSetKey) ([]address.Address, error) { + return *new([]address.Address), ErrNotSupported +} + +func (s *GatewayStruct) StateLookupID(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) { + if s.Internal.StateLookupID == nil { + return *new(address.Address), ErrNotSupported + } + return s.Internal.StateLookupID(p0, p1, p2) +} + +func (s *GatewayStub) StateLookupID(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) { + return *new(address.Address), ErrNotSupported +} + +func (s *GatewayStruct) StateMarketBalance(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (MarketBalance, error) { + if s.Internal.StateMarketBalance == nil { + return *new(MarketBalance), ErrNotSupported + } + return s.Internal.StateMarketBalance(p0, p1, p2) +} + +func (s *GatewayStub) StateMarketBalance(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (MarketBalance, error) { + return *new(MarketBalance), ErrNotSupported +} + +func (s *GatewayStruct) StateMarketStorageDeal(p0 context.Context, p1 abi.DealID, p2 types.TipSetKey) (*MarketDeal, error) { + if s.Internal.StateMarketStorageDeal == nil { + return nil, ErrNotSupported + } + return s.Internal.StateMarketStorageDeal(p0, p1, p2) +} + +func (s *GatewayStub) StateMarketStorageDeal(p0 context.Context, p1 abi.DealID, p2 types.TipSetKey) (*MarketDeal, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) StateMinerInfo(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (MinerInfo, error) { + if s.Internal.StateMinerInfo == nil { + return *new(MinerInfo), ErrNotSupported + } + return s.Internal.StateMinerInfo(p0, p1, p2) +} + +func (s *GatewayStub) StateMinerInfo(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (MinerInfo, error) { + return *new(MinerInfo), ErrNotSupported +} + +func (s *GatewayStruct) StateMinerPower(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*MinerPower, error) { + if s.Internal.StateMinerPower == nil { + return nil, ErrNotSupported + } + return s.Internal.StateMinerPower(p0, p1, p2) +} + +func (s *GatewayStub) StateMinerPower(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*MinerPower, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) StateMinerProvingDeadline(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*dline.Info, error) { + if s.Internal.StateMinerProvingDeadline == nil { + return nil, ErrNotSupported + } + return s.Internal.StateMinerProvingDeadline(p0, p1, p2) +} + +func (s *GatewayStub) StateMinerProvingDeadline(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*dline.Info, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) StateMinerSectorCount(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (MinerSectors, error) { + if s.Internal.StateMinerSectorCount == nil { + return *new(MinerSectors), ErrNotSupported + } + return s.Internal.StateMinerSectorCount(p0, p1, p2) +} + +func (s *GatewayStub) StateMinerSectorCount(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (MinerSectors, error) { + return *new(MinerSectors), ErrNotSupported +} + +func (s *GatewayStruct) StateNetworkName(p0 context.Context) (dtypes.NetworkName, error) { + if s.Internal.StateNetworkName == nil { + return *new(dtypes.NetworkName), ErrNotSupported + } + return s.Internal.StateNetworkName(p0) +} + +func (s *GatewayStub) StateNetworkName(p0 context.Context) (dtypes.NetworkName, error) { + return *new(dtypes.NetworkName), ErrNotSupported +} + +func (s *GatewayStruct) StateNetworkVersion(p0 context.Context, p1 types.TipSetKey) (apitypes.NetworkVersion, error) { + if s.Internal.StateNetworkVersion == nil { + return *new(apitypes.NetworkVersion), ErrNotSupported + } + return s.Internal.StateNetworkVersion(p0, p1) +} + +func (s *GatewayStub) StateNetworkVersion(p0 context.Context, p1 types.TipSetKey) (apitypes.NetworkVersion, error) { + return *new(apitypes.NetworkVersion), ErrNotSupported +} + +func (s *GatewayStruct) StateReadState(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*ActorState, error) { + if s.Internal.StateReadState == nil { + return nil, ErrNotSupported + } + return s.Internal.StateReadState(p0, p1, p2) +} + +func (s *GatewayStub) StateReadState(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*ActorState, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) StateReplay(p0 context.Context, p1 types.TipSetKey, p2 cid.Cid) (*InvocResult, error) { + if s.Internal.StateReplay == nil { + return nil, ErrNotSupported + } + return s.Internal.StateReplay(p0, p1, p2) +} + +func (s *GatewayStub) StateReplay(p0 context.Context, p1 types.TipSetKey, p2 cid.Cid) (*InvocResult, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) StateSearchMsg(p0 context.Context, p1 types.TipSetKey, p2 cid.Cid, p3 abi.ChainEpoch, p4 bool) (*MsgLookup, error) { + if s.Internal.StateSearchMsg == nil { + return nil, ErrNotSupported + } + return s.Internal.StateSearchMsg(p0, p1, p2, p3, p4) +} + +func (s *GatewayStub) StateSearchMsg(p0 context.Context, p1 types.TipSetKey, p2 cid.Cid, p3 abi.ChainEpoch, p4 bool) (*MsgLookup, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) StateSectorGetInfo(p0 context.Context, p1 address.Address, p2 abi.SectorNumber, p3 types.TipSetKey) (*miner.SectorOnChainInfo, error) { + if s.Internal.StateSectorGetInfo == nil { + return nil, ErrNotSupported + } + return s.Internal.StateSectorGetInfo(p0, p1, p2, p3) +} + +func (s *GatewayStub) StateSectorGetInfo(p0 context.Context, p1 address.Address, p2 abi.SectorNumber, p3 types.TipSetKey) (*miner.SectorOnChainInfo, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) StateVerifiedClientStatus(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*abi.StoragePower, error) { + if s.Internal.StateVerifiedClientStatus == nil { + return nil, ErrNotSupported + } + return s.Internal.StateVerifiedClientStatus(p0, p1, p2) +} + +func (s *GatewayStub) StateVerifiedClientStatus(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*abi.StoragePower, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) StateVerifierStatus(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*abi.StoragePower, error) { + if s.Internal.StateVerifierStatus == nil { + return nil, ErrNotSupported + } + return s.Internal.StateVerifierStatus(p0, p1, p2) +} + +func (s *GatewayStub) StateVerifierStatus(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*abi.StoragePower, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) StateWaitMsg(p0 context.Context, p1 cid.Cid, p2 uint64, p3 abi.ChainEpoch, p4 bool) (*MsgLookup, error) { + if s.Internal.StateWaitMsg == nil { + return nil, ErrNotSupported + } + return s.Internal.StateWaitMsg(p0, p1, p2, p3, p4) +} + +func (s *GatewayStub) StateWaitMsg(p0 context.Context, p1 cid.Cid, p2 uint64, p3 abi.ChainEpoch, p4 bool) (*MsgLookup, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) Version(p0 context.Context) (APIVersion, error) { + if s.Internal.Version == nil { + return *new(APIVersion), ErrNotSupported + } + return s.Internal.Version(p0) +} + +func (s *GatewayStub) Version(p0 context.Context) (APIVersion, error) { + return *new(APIVersion), ErrNotSupported +} + +func (s *GatewayStruct) WalletBalance(p0 context.Context, p1 address.Address) (types.BigInt, error) { + if s.Internal.WalletBalance == nil { + return *new(types.BigInt), ErrNotSupported + } + return s.Internal.WalletBalance(p0, p1) +} + +func (s *GatewayStub) WalletBalance(p0 context.Context, p1 address.Address) (types.BigInt, error) { + return *new(types.BigInt), ErrNotSupported +} + +func (s *GatewayStruct) Web3ClientVersion(p0 context.Context) (string, error) { + if s.Internal.Web3ClientVersion == nil { + return "", ErrNotSupported + } + return s.Internal.Web3ClientVersion(p0) +} + +func (s *GatewayStub) Web3ClientVersion(p0 context.Context) (string, error) { + return "", ErrNotSupported +} + +func (s *NetStruct) ID(p0 context.Context) (peer.ID, error) { + if s.Internal.ID == nil { + return *new(peer.ID), ErrNotSupported + } + return s.Internal.ID(p0) +} + +func (s *NetStub) ID(p0 context.Context) (peer.ID, error) { + return *new(peer.ID), ErrNotSupported +} + +func (s *NetStruct) NetAddrsListen(p0 context.Context) (peer.AddrInfo, error) { + if s.Internal.NetAddrsListen == nil { + return *new(peer.AddrInfo), ErrNotSupported + } + return s.Internal.NetAddrsListen(p0) +} + +func (s *NetStub) NetAddrsListen(p0 context.Context) (peer.AddrInfo, error) { + return *new(peer.AddrInfo), ErrNotSupported +} + +func (s *NetStruct) NetAgentVersion(p0 context.Context, p1 peer.ID) (string, error) { + if s.Internal.NetAgentVersion == nil { + return "", ErrNotSupported + } + return s.Internal.NetAgentVersion(p0, p1) +} + +func (s *NetStub) NetAgentVersion(p0 context.Context, p1 peer.ID) (string, error) { + return "", ErrNotSupported +} + +func (s *NetStruct) NetAutoNatStatus(p0 context.Context) (NatInfo, error) { + if s.Internal.NetAutoNatStatus == nil { + return *new(NatInfo), ErrNotSupported + } + return s.Internal.NetAutoNatStatus(p0) +} + +func (s *NetStub) NetAutoNatStatus(p0 context.Context) (NatInfo, error) { + return *new(NatInfo), ErrNotSupported +} + +func (s *NetStruct) NetBandwidthStats(p0 context.Context) (metrics.Stats, error) { + if s.Internal.NetBandwidthStats == nil { + return *new(metrics.Stats), ErrNotSupported + } + return s.Internal.NetBandwidthStats(p0) +} + +func (s *NetStub) NetBandwidthStats(p0 context.Context) (metrics.Stats, error) { + return *new(metrics.Stats), ErrNotSupported +} + +func (s *NetStruct) NetBandwidthStatsByPeer(p0 context.Context) (map[string]metrics.Stats, error) { + if s.Internal.NetBandwidthStatsByPeer == nil { + return *new(map[string]metrics.Stats), ErrNotSupported + } + return s.Internal.NetBandwidthStatsByPeer(p0) +} + +func (s *NetStub) NetBandwidthStatsByPeer(p0 context.Context) (map[string]metrics.Stats, error) { + return *new(map[string]metrics.Stats), ErrNotSupported +} + +func (s *NetStruct) NetBandwidthStatsByProtocol(p0 context.Context) (map[protocol.ID]metrics.Stats, error) { + if s.Internal.NetBandwidthStatsByProtocol == nil { + return *new(map[protocol.ID]metrics.Stats), ErrNotSupported + } + return s.Internal.NetBandwidthStatsByProtocol(p0) +} + +func (s *NetStub) NetBandwidthStatsByProtocol(p0 context.Context) (map[protocol.ID]metrics.Stats, error) { + return *new(map[protocol.ID]metrics.Stats), ErrNotSupported +} + +func (s *NetStruct) NetBlockAdd(p0 context.Context, p1 NetBlockList) error { + if s.Internal.NetBlockAdd == nil { + return ErrNotSupported + } + return s.Internal.NetBlockAdd(p0, p1) +} + +func (s *NetStub) NetBlockAdd(p0 context.Context, p1 NetBlockList) error { + return ErrNotSupported +} + +func (s *NetStruct) NetBlockList(p0 context.Context) (NetBlockList, error) { + if s.Internal.NetBlockList == nil { + return *new(NetBlockList), ErrNotSupported + } + return s.Internal.NetBlockList(p0) +} + +func (s *NetStub) NetBlockList(p0 context.Context) (NetBlockList, error) { + return *new(NetBlockList), ErrNotSupported +} + +func (s *NetStruct) NetBlockRemove(p0 context.Context, p1 NetBlockList) error { + if s.Internal.NetBlockRemove == nil { + return ErrNotSupported + } + return s.Internal.NetBlockRemove(p0, p1) +} + +func (s *NetStub) NetBlockRemove(p0 context.Context, p1 NetBlockList) error { + return ErrNotSupported +} + +func (s *NetStruct) NetConnect(p0 context.Context, p1 peer.AddrInfo) error { + if s.Internal.NetConnect == nil { + return ErrNotSupported + } + return s.Internal.NetConnect(p0, p1) +} + +func (s *NetStub) NetConnect(p0 context.Context, p1 peer.AddrInfo) error { + return ErrNotSupported +} + +func (s *NetStruct) NetConnectedness(p0 context.Context, p1 peer.ID) (network.Connectedness, error) { + if s.Internal.NetConnectedness == nil { + return *new(network.Connectedness), ErrNotSupported + } + return s.Internal.NetConnectedness(p0, p1) +} + +func (s *NetStub) NetConnectedness(p0 context.Context, p1 peer.ID) (network.Connectedness, error) { + return *new(network.Connectedness), ErrNotSupported +} + +func (s *NetStruct) NetDisconnect(p0 context.Context, p1 peer.ID) error { + if s.Internal.NetDisconnect == nil { + return ErrNotSupported + } + return s.Internal.NetDisconnect(p0, p1) +} + +func (s *NetStub) NetDisconnect(p0 context.Context, p1 peer.ID) error { + return ErrNotSupported +} + +func (s *NetStruct) NetFindPeer(p0 context.Context, p1 peer.ID) (peer.AddrInfo, error) { + if s.Internal.NetFindPeer == nil { + return *new(peer.AddrInfo), ErrNotSupported + } + return s.Internal.NetFindPeer(p0, p1) +} + +func (s *NetStub) NetFindPeer(p0 context.Context, p1 peer.ID) (peer.AddrInfo, error) { + return *new(peer.AddrInfo), ErrNotSupported +} + +func (s *NetStruct) NetLimit(p0 context.Context, p1 string) (NetLimit, error) { + if s.Internal.NetLimit == nil { + return *new(NetLimit), ErrNotSupported + } + return s.Internal.NetLimit(p0, p1) +} + +func (s *NetStub) NetLimit(p0 context.Context, p1 string) (NetLimit, error) { + return *new(NetLimit), ErrNotSupported +} + +func (s *NetStruct) NetPeerInfo(p0 context.Context, p1 peer.ID) (*ExtendedPeerInfo, error) { + if s.Internal.NetPeerInfo == nil { + return nil, ErrNotSupported + } + return s.Internal.NetPeerInfo(p0, p1) +} + +func (s *NetStub) NetPeerInfo(p0 context.Context, p1 peer.ID) (*ExtendedPeerInfo, error) { + return nil, ErrNotSupported +} + +func (s *NetStruct) NetPeers(p0 context.Context) ([]peer.AddrInfo, error) { + if s.Internal.NetPeers == nil { + return *new([]peer.AddrInfo), ErrNotSupported + } + return s.Internal.NetPeers(p0) +} + +func (s *NetStub) NetPeers(p0 context.Context) ([]peer.AddrInfo, error) { + return *new([]peer.AddrInfo), ErrNotSupported +} + +func (s *NetStruct) NetPing(p0 context.Context, p1 peer.ID) (time.Duration, error) { + if s.Internal.NetPing == nil { + return *new(time.Duration), ErrNotSupported + } + return s.Internal.NetPing(p0, p1) +} + +func (s *NetStub) NetPing(p0 context.Context, p1 peer.ID) (time.Duration, error) { + return *new(time.Duration), ErrNotSupported +} + +func (s *NetStruct) NetProtectAdd(p0 context.Context, p1 []peer.ID) error { + if s.Internal.NetProtectAdd == nil { + return ErrNotSupported + } + return s.Internal.NetProtectAdd(p0, p1) +} + +func (s *NetStub) NetProtectAdd(p0 context.Context, p1 []peer.ID) error { + return ErrNotSupported +} + +func (s *NetStruct) NetProtectList(p0 context.Context) ([]peer.ID, error) { + if s.Internal.NetProtectList == nil { + return *new([]peer.ID), ErrNotSupported + } + return s.Internal.NetProtectList(p0) +} + +func (s *NetStub) NetProtectList(p0 context.Context) ([]peer.ID, error) { + return *new([]peer.ID), ErrNotSupported +} + +func (s *NetStruct) NetProtectRemove(p0 context.Context, p1 []peer.ID) error { + if s.Internal.NetProtectRemove == nil { + return ErrNotSupported + } + return s.Internal.NetProtectRemove(p0, p1) +} + +func (s *NetStub) NetProtectRemove(p0 context.Context, p1 []peer.ID) error { + return ErrNotSupported +} + +func (s *NetStruct) NetPubsubScores(p0 context.Context) ([]PubsubScore, error) { + if s.Internal.NetPubsubScores == nil { + return *new([]PubsubScore), ErrNotSupported + } + return s.Internal.NetPubsubScores(p0) +} + +func (s *NetStub) NetPubsubScores(p0 context.Context) ([]PubsubScore, error) { + return *new([]PubsubScore), ErrNotSupported +} + +func (s *NetStruct) NetSetLimit(p0 context.Context, p1 string, p2 NetLimit) error { + if s.Internal.NetSetLimit == nil { + return ErrNotSupported + } + return s.Internal.NetSetLimit(p0, p1, p2) +} + +func (s *NetStub) NetSetLimit(p0 context.Context, p1 string, p2 NetLimit) error { + return ErrNotSupported +} + +func (s *NetStruct) NetStat(p0 context.Context, p1 string) (NetStat, error) { + if s.Internal.NetStat == nil { + return *new(NetStat), ErrNotSupported + } + return s.Internal.NetStat(p0, p1) +} + +func (s *NetStub) NetStat(p0 context.Context, p1 string) (NetStat, error) { + return *new(NetStat), ErrNotSupported +} + +func (s *SignableStruct) Sign(p0 context.Context, p1 SignFunc) error { + if s.Internal.Sign == nil { + return ErrNotSupported + } + return s.Internal.Sign(p0, p1) +} + +func (s *SignableStub) Sign(p0 context.Context, p1 SignFunc) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) ActorAddress(p0 context.Context) (address.Address, error) { + if s.Internal.ActorAddress == nil { + return *new(address.Address), ErrNotSupported + } + return s.Internal.ActorAddress(p0) +} + +func (s *StorageMinerStub) ActorAddress(p0 context.Context) (address.Address, error) { + return *new(address.Address), ErrNotSupported +} + +func (s *StorageMinerStruct) ActorAddressConfig(p0 context.Context) (AddressConfig, error) { + if s.Internal.ActorAddressConfig == nil { + return *new(AddressConfig), ErrNotSupported + } + return s.Internal.ActorAddressConfig(p0) +} + +func (s *StorageMinerStub) ActorAddressConfig(p0 context.Context) (AddressConfig, error) { + return *new(AddressConfig), ErrNotSupported +} + +func (s *StorageMinerStruct) ActorSectorSize(p0 context.Context, p1 address.Address) (abi.SectorSize, error) { + if s.Internal.ActorSectorSize == nil { + return *new(abi.SectorSize), ErrNotSupported + } + return s.Internal.ActorSectorSize(p0, p1) +} + +func (s *StorageMinerStub) ActorSectorSize(p0 context.Context, p1 address.Address) (abi.SectorSize, error) { + return *new(abi.SectorSize), ErrNotSupported +} + +func (s *StorageMinerStruct) ActorWithdrawBalance(p0 context.Context, p1 abi.TokenAmount) (cid.Cid, error) { + if s.Internal.ActorWithdrawBalance == nil { + return *new(cid.Cid), ErrNotSupported + } + return s.Internal.ActorWithdrawBalance(p0, p1) +} + +func (s *StorageMinerStub) ActorWithdrawBalance(p0 context.Context, p1 abi.TokenAmount) (cid.Cid, error) { + return *new(cid.Cid), ErrNotSupported +} + +func (s *StorageMinerStruct) BeneficiaryWithdrawBalance(p0 context.Context, p1 abi.TokenAmount) (cid.Cid, error) { + if s.Internal.BeneficiaryWithdrawBalance == nil { + return *new(cid.Cid), ErrNotSupported + } + return s.Internal.BeneficiaryWithdrawBalance(p0, p1) +} + +func (s *StorageMinerStub) BeneficiaryWithdrawBalance(p0 context.Context, p1 abi.TokenAmount) (cid.Cid, error) { + return *new(cid.Cid), ErrNotSupported +} + +func (s *StorageMinerStruct) CheckProvable(p0 context.Context, p1 abi.RegisteredPoStProof, p2 []storiface.SectorRef) (map[abi.SectorNumber]string, error) { + if s.Internal.CheckProvable == nil { + return *new(map[abi.SectorNumber]string), ErrNotSupported + } + return s.Internal.CheckProvable(p0, p1, p2) +} + +func (s *StorageMinerStub) CheckProvable(p0 context.Context, p1 abi.RegisteredPoStProof, p2 []storiface.SectorRef) (map[abi.SectorNumber]string, error) { + return *new(map[abi.SectorNumber]string), ErrNotSupported +} + +func (s *StorageMinerStruct) ComputeDataCid(p0 context.Context, p1 abi.UnpaddedPieceSize, p2 storiface.Data) (abi.PieceInfo, error) { + if s.Internal.ComputeDataCid == nil { + return *new(abi.PieceInfo), ErrNotSupported + } + return s.Internal.ComputeDataCid(p0, p1, p2) +} + +func (s *StorageMinerStub) ComputeDataCid(p0 context.Context, p1 abi.UnpaddedPieceSize, p2 storiface.Data) (abi.PieceInfo, error) { + return *new(abi.PieceInfo), ErrNotSupported +} + +func (s *StorageMinerStruct) ComputeProof(p0 context.Context, p1 []builtinactors.ExtendedSectorInfo, p2 abi.PoStRandomness, p3 abi.ChainEpoch, p4 abinetwork.Version) ([]builtinactors.PoStProof, error) { + if s.Internal.ComputeProof == nil { + return *new([]builtinactors.PoStProof), ErrNotSupported + } + return s.Internal.ComputeProof(p0, p1, p2, p3, p4) +} + +func (s *StorageMinerStub) ComputeProof(p0 context.Context, p1 []builtinactors.ExtendedSectorInfo, p2 abi.PoStRandomness, p3 abi.ChainEpoch, p4 abinetwork.Version) ([]builtinactors.PoStProof, error) { + return *new([]builtinactors.PoStProof), ErrNotSupported +} + +func (s *StorageMinerStruct) ComputeWindowPoSt(p0 context.Context, p1 uint64, p2 types.TipSetKey) ([]miner.SubmitWindowedPoStParams, error) { + if s.Internal.ComputeWindowPoSt == nil { + return *new([]miner.SubmitWindowedPoStParams), ErrNotSupported + } + return s.Internal.ComputeWindowPoSt(p0, p1, p2) +} + +func (s *StorageMinerStub) ComputeWindowPoSt(p0 context.Context, p1 uint64, p2 types.TipSetKey) ([]miner.SubmitWindowedPoStParams, error) { + return *new([]miner.SubmitWindowedPoStParams), ErrNotSupported +} + +func (s *StorageMinerStruct) CreateBackup(p0 context.Context, p1 string) error { + if s.Internal.CreateBackup == nil { + return ErrNotSupported + } + return s.Internal.CreateBackup(p0, p1) +} + +func (s *StorageMinerStub) CreateBackup(p0 context.Context, p1 string) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) DagstoreGC(p0 context.Context) ([]DagstoreShardResult, error) { + if s.Internal.DagstoreGC == nil { + return *new([]DagstoreShardResult), ErrNotSupported + } + return s.Internal.DagstoreGC(p0) +} + +func (s *StorageMinerStub) DagstoreGC(p0 context.Context) ([]DagstoreShardResult, error) { + return *new([]DagstoreShardResult), ErrNotSupported +} + +func (s *StorageMinerStruct) DagstoreInitializeAll(p0 context.Context, p1 DagstoreInitializeAllParams) (<-chan DagstoreInitializeAllEvent, error) { + if s.Internal.DagstoreInitializeAll == nil { + return nil, ErrNotSupported + } + return s.Internal.DagstoreInitializeAll(p0, p1) +} + +func (s *StorageMinerStub) DagstoreInitializeAll(p0 context.Context, p1 DagstoreInitializeAllParams) (<-chan DagstoreInitializeAllEvent, error) { + return nil, ErrNotSupported +} + +func (s *StorageMinerStruct) DagstoreInitializeShard(p0 context.Context, p1 string) error { + if s.Internal.DagstoreInitializeShard == nil { + return ErrNotSupported + } + return s.Internal.DagstoreInitializeShard(p0, p1) +} + +func (s *StorageMinerStub) DagstoreInitializeShard(p0 context.Context, p1 string) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) DagstoreListShards(p0 context.Context) ([]DagstoreShardInfo, error) { + if s.Internal.DagstoreListShards == nil { + return *new([]DagstoreShardInfo), ErrNotSupported + } + return s.Internal.DagstoreListShards(p0) +} + +func (s *StorageMinerStub) DagstoreListShards(p0 context.Context) ([]DagstoreShardInfo, error) { + return *new([]DagstoreShardInfo), ErrNotSupported +} + +func (s *StorageMinerStruct) DagstoreLookupPieces(p0 context.Context, p1 cid.Cid) ([]DagstoreShardInfo, error) { + if s.Internal.DagstoreLookupPieces == nil { + return *new([]DagstoreShardInfo), ErrNotSupported + } + return s.Internal.DagstoreLookupPieces(p0, p1) +} + +func (s *StorageMinerStub) DagstoreLookupPieces(p0 context.Context, p1 cid.Cid) ([]DagstoreShardInfo, error) { + return *new([]DagstoreShardInfo), ErrNotSupported +} + +func (s *StorageMinerStruct) DagstoreRecoverShard(p0 context.Context, p1 string) error { + if s.Internal.DagstoreRecoverShard == nil { + return ErrNotSupported + } + return s.Internal.DagstoreRecoverShard(p0, p1) +} + +func (s *StorageMinerStub) DagstoreRecoverShard(p0 context.Context, p1 string) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) DagstoreRegisterShard(p0 context.Context, p1 string) error { + if s.Internal.DagstoreRegisterShard == nil { + return ErrNotSupported + } + return s.Internal.DagstoreRegisterShard(p0, p1) +} + +func (s *StorageMinerStub) DagstoreRegisterShard(p0 context.Context, p1 string) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) DealsConsiderOfflineRetrievalDeals(p0 context.Context) (bool, error) { + if s.Internal.DealsConsiderOfflineRetrievalDeals == nil { + return false, ErrNotSupported + } + return s.Internal.DealsConsiderOfflineRetrievalDeals(p0) +} + +func (s *StorageMinerStub) DealsConsiderOfflineRetrievalDeals(p0 context.Context) (bool, error) { + return false, ErrNotSupported +} + +func (s *StorageMinerStruct) DealsConsiderOfflineStorageDeals(p0 context.Context) (bool, error) { + if s.Internal.DealsConsiderOfflineStorageDeals == nil { + return false, ErrNotSupported + } + return s.Internal.DealsConsiderOfflineStorageDeals(p0) +} + +func (s *StorageMinerStub) DealsConsiderOfflineStorageDeals(p0 context.Context) (bool, error) { + return false, ErrNotSupported +} + +func (s *StorageMinerStruct) DealsConsiderOnlineRetrievalDeals(p0 context.Context) (bool, error) { + if s.Internal.DealsConsiderOnlineRetrievalDeals == nil { + return false, ErrNotSupported + } + return s.Internal.DealsConsiderOnlineRetrievalDeals(p0) +} + +func (s *StorageMinerStub) DealsConsiderOnlineRetrievalDeals(p0 context.Context) (bool, error) { + return false, ErrNotSupported +} + +func (s *StorageMinerStruct) DealsConsiderOnlineStorageDeals(p0 context.Context) (bool, error) { + if s.Internal.DealsConsiderOnlineStorageDeals == nil { + return false, ErrNotSupported + } + return s.Internal.DealsConsiderOnlineStorageDeals(p0) +} + +func (s *StorageMinerStub) DealsConsiderOnlineStorageDeals(p0 context.Context) (bool, error) { + return false, ErrNotSupported +} + +func (s *StorageMinerStruct) DealsConsiderUnverifiedStorageDeals(p0 context.Context) (bool, error) { + if s.Internal.DealsConsiderUnverifiedStorageDeals == nil { + return false, ErrNotSupported + } + return s.Internal.DealsConsiderUnverifiedStorageDeals(p0) +} + +func (s *StorageMinerStub) DealsConsiderUnverifiedStorageDeals(p0 context.Context) (bool, error) { + return false, ErrNotSupported +} + +func (s *StorageMinerStruct) DealsConsiderVerifiedStorageDeals(p0 context.Context) (bool, error) { + if s.Internal.DealsConsiderVerifiedStorageDeals == nil { + return false, ErrNotSupported + } + return s.Internal.DealsConsiderVerifiedStorageDeals(p0) +} + +func (s *StorageMinerStub) DealsConsiderVerifiedStorageDeals(p0 context.Context) (bool, error) { + return false, ErrNotSupported +} + +func (s *StorageMinerStruct) DealsImportData(p0 context.Context, p1 cid.Cid, p2 string) error { + if s.Internal.DealsImportData == nil { + return ErrNotSupported + } + return s.Internal.DealsImportData(p0, p1, p2) +} + +func (s *StorageMinerStub) DealsImportData(p0 context.Context, p1 cid.Cid, p2 string) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) DealsList(p0 context.Context) ([]*MarketDeal, error) { + if s.Internal.DealsList == nil { + return *new([]*MarketDeal), ErrNotSupported + } + return s.Internal.DealsList(p0) +} + +func (s *StorageMinerStub) DealsList(p0 context.Context) ([]*MarketDeal, error) { + return *new([]*MarketDeal), ErrNotSupported +} + +func (s *StorageMinerStruct) DealsPieceCidBlocklist(p0 context.Context) ([]cid.Cid, error) { + if s.Internal.DealsPieceCidBlocklist == nil { + return *new([]cid.Cid), ErrNotSupported + } + return s.Internal.DealsPieceCidBlocklist(p0) +} + +func (s *StorageMinerStub) DealsPieceCidBlocklist(p0 context.Context) ([]cid.Cid, error) { + return *new([]cid.Cid), ErrNotSupported +} + +func (s *StorageMinerStruct) DealsSetConsiderOfflineRetrievalDeals(p0 context.Context, p1 bool) error { + if s.Internal.DealsSetConsiderOfflineRetrievalDeals == nil { + return ErrNotSupported + } + return s.Internal.DealsSetConsiderOfflineRetrievalDeals(p0, p1) +} + +func (s *StorageMinerStub) DealsSetConsiderOfflineRetrievalDeals(p0 context.Context, p1 bool) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) DealsSetConsiderOfflineStorageDeals(p0 context.Context, p1 bool) error { + if s.Internal.DealsSetConsiderOfflineStorageDeals == nil { + return ErrNotSupported + } + return s.Internal.DealsSetConsiderOfflineStorageDeals(p0, p1) +} + +func (s *StorageMinerStub) DealsSetConsiderOfflineStorageDeals(p0 context.Context, p1 bool) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) DealsSetConsiderOnlineRetrievalDeals(p0 context.Context, p1 bool) error { + if s.Internal.DealsSetConsiderOnlineRetrievalDeals == nil { + return ErrNotSupported + } + return s.Internal.DealsSetConsiderOnlineRetrievalDeals(p0, p1) +} + +func (s *StorageMinerStub) DealsSetConsiderOnlineRetrievalDeals(p0 context.Context, p1 bool) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) DealsSetConsiderOnlineStorageDeals(p0 context.Context, p1 bool) error { + if s.Internal.DealsSetConsiderOnlineStorageDeals == nil { + return ErrNotSupported + } + return s.Internal.DealsSetConsiderOnlineStorageDeals(p0, p1) +} + +func (s *StorageMinerStub) DealsSetConsiderOnlineStorageDeals(p0 context.Context, p1 bool) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) DealsSetConsiderUnverifiedStorageDeals(p0 context.Context, p1 bool) error { + if s.Internal.DealsSetConsiderUnverifiedStorageDeals == nil { + return ErrNotSupported + } + return s.Internal.DealsSetConsiderUnverifiedStorageDeals(p0, p1) +} + +func (s *StorageMinerStub) DealsSetConsiderUnverifiedStorageDeals(p0 context.Context, p1 bool) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) DealsSetConsiderVerifiedStorageDeals(p0 context.Context, p1 bool) error { + if s.Internal.DealsSetConsiderVerifiedStorageDeals == nil { + return ErrNotSupported + } + return s.Internal.DealsSetConsiderVerifiedStorageDeals(p0, p1) +} + +func (s *StorageMinerStub) DealsSetConsiderVerifiedStorageDeals(p0 context.Context, p1 bool) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) DealsSetPieceCidBlocklist(p0 context.Context, p1 []cid.Cid) error { + if s.Internal.DealsSetPieceCidBlocklist == nil { + return ErrNotSupported + } + return s.Internal.DealsSetPieceCidBlocklist(p0, p1) +} + +func (s *StorageMinerStub) DealsSetPieceCidBlocklist(p0 context.Context, p1 []cid.Cid) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) IndexerAnnounceAllDeals(p0 context.Context) error { + if s.Internal.IndexerAnnounceAllDeals == nil { + return ErrNotSupported + } + return s.Internal.IndexerAnnounceAllDeals(p0) +} + +func (s *StorageMinerStub) IndexerAnnounceAllDeals(p0 context.Context) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) IndexerAnnounceDeal(p0 context.Context, p1 cid.Cid) error { + if s.Internal.IndexerAnnounceDeal == nil { + return ErrNotSupported + } + return s.Internal.IndexerAnnounceDeal(p0, p1) +} + +func (s *StorageMinerStub) IndexerAnnounceDeal(p0 context.Context, p1 cid.Cid) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) MarketCancelDataTransfer(p0 context.Context, p1 datatransfer.TransferID, p2 peer.ID, p3 bool) error { + if s.Internal.MarketCancelDataTransfer == nil { + return ErrNotSupported + } + return s.Internal.MarketCancelDataTransfer(p0, p1, p2, p3) +} + +func (s *StorageMinerStub) MarketCancelDataTransfer(p0 context.Context, p1 datatransfer.TransferID, p2 peer.ID, p3 bool) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) MarketDataTransferDiagnostics(p0 context.Context, p1 peer.ID) (*TransferDiagnostics, error) { + if s.Internal.MarketDataTransferDiagnostics == nil { + return nil, ErrNotSupported + } + return s.Internal.MarketDataTransferDiagnostics(p0, p1) +} + +func (s *StorageMinerStub) MarketDataTransferDiagnostics(p0 context.Context, p1 peer.ID) (*TransferDiagnostics, error) { + return nil, ErrNotSupported +} + +func (s *StorageMinerStruct) MarketDataTransferUpdates(p0 context.Context) (<-chan DataTransferChannel, error) { + if s.Internal.MarketDataTransferUpdates == nil { + return nil, ErrNotSupported + } + return s.Internal.MarketDataTransferUpdates(p0) +} + +func (s *StorageMinerStub) MarketDataTransferUpdates(p0 context.Context) (<-chan DataTransferChannel, error) { + return nil, ErrNotSupported +} + +func (s *StorageMinerStruct) MarketGetAsk(p0 context.Context) (*storagemarket.SignedStorageAsk, error) { + if s.Internal.MarketGetAsk == nil { + return nil, ErrNotSupported + } + return s.Internal.MarketGetAsk(p0) +} + +func (s *StorageMinerStub) MarketGetAsk(p0 context.Context) (*storagemarket.SignedStorageAsk, error) { + return nil, ErrNotSupported +} + +func (s *StorageMinerStruct) MarketGetDealUpdates(p0 context.Context) (<-chan storagemarket.MinerDeal, error) { + if s.Internal.MarketGetDealUpdates == nil { + return nil, ErrNotSupported + } + return s.Internal.MarketGetDealUpdates(p0) +} + +func (s *StorageMinerStub) MarketGetDealUpdates(p0 context.Context) (<-chan storagemarket.MinerDeal, error) { + return nil, ErrNotSupported +} + +func (s *StorageMinerStruct) MarketGetRetrievalAsk(p0 context.Context) (*retrievalmarket.Ask, error) { + if s.Internal.MarketGetRetrievalAsk == nil { + return nil, ErrNotSupported + } + return s.Internal.MarketGetRetrievalAsk(p0) +} + +func (s *StorageMinerStub) MarketGetRetrievalAsk(p0 context.Context) (*retrievalmarket.Ask, error) { + return nil, ErrNotSupported +} + +func (s *StorageMinerStruct) MarketImportDealData(p0 context.Context, p1 cid.Cid, p2 string) error { + if s.Internal.MarketImportDealData == nil { + return ErrNotSupported + } + return s.Internal.MarketImportDealData(p0, p1, p2) +} + +func (s *StorageMinerStub) MarketImportDealData(p0 context.Context, p1 cid.Cid, p2 string) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) MarketListDataTransfers(p0 context.Context) ([]DataTransferChannel, error) { + if s.Internal.MarketListDataTransfers == nil { + return *new([]DataTransferChannel), ErrNotSupported + } + return s.Internal.MarketListDataTransfers(p0) +} + +func (s *StorageMinerStub) MarketListDataTransfers(p0 context.Context) ([]DataTransferChannel, error) { + return *new([]DataTransferChannel), ErrNotSupported +} + +func (s *StorageMinerStruct) MarketListDeals(p0 context.Context) ([]*MarketDeal, error) { + if s.Internal.MarketListDeals == nil { + return *new([]*MarketDeal), ErrNotSupported + } + return s.Internal.MarketListDeals(p0) +} + +func (s *StorageMinerStub) MarketListDeals(p0 context.Context) ([]*MarketDeal, error) { + return *new([]*MarketDeal), ErrNotSupported +} + +func (s *StorageMinerStruct) MarketListIncompleteDeals(p0 context.Context) ([]storagemarket.MinerDeal, error) { + if s.Internal.MarketListIncompleteDeals == nil { + return *new([]storagemarket.MinerDeal), ErrNotSupported + } + return s.Internal.MarketListIncompleteDeals(p0) +} + +func (s *StorageMinerStub) MarketListIncompleteDeals(p0 context.Context) ([]storagemarket.MinerDeal, error) { + return *new([]storagemarket.MinerDeal), ErrNotSupported +} + +func (s *StorageMinerStruct) MarketListRetrievalDeals(p0 context.Context) ([]struct{}, error) { + if s.Internal.MarketListRetrievalDeals == nil { + return *new([]struct{}), ErrNotSupported + } + return s.Internal.MarketListRetrievalDeals(p0) +} + +func (s *StorageMinerStub) MarketListRetrievalDeals(p0 context.Context) ([]struct{}, error) { + return *new([]struct{}), ErrNotSupported +} + +func (s *StorageMinerStruct) MarketPendingDeals(p0 context.Context) (PendingDealInfo, error) { + if s.Internal.MarketPendingDeals == nil { + return *new(PendingDealInfo), ErrNotSupported + } + return s.Internal.MarketPendingDeals(p0) +} + +func (s *StorageMinerStub) MarketPendingDeals(p0 context.Context) (PendingDealInfo, error) { + return *new(PendingDealInfo), ErrNotSupported +} + +func (s *StorageMinerStruct) MarketPublishPendingDeals(p0 context.Context) error { + if s.Internal.MarketPublishPendingDeals == nil { + return ErrNotSupported + } + return s.Internal.MarketPublishPendingDeals(p0) +} + +func (s *StorageMinerStub) MarketPublishPendingDeals(p0 context.Context) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) MarketRestartDataTransfer(p0 context.Context, p1 datatransfer.TransferID, p2 peer.ID, p3 bool) error { + if s.Internal.MarketRestartDataTransfer == nil { + return ErrNotSupported + } + return s.Internal.MarketRestartDataTransfer(p0, p1, p2, p3) +} + +func (s *StorageMinerStub) MarketRestartDataTransfer(p0 context.Context, p1 datatransfer.TransferID, p2 peer.ID, p3 bool) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) MarketRetryPublishDeal(p0 context.Context, p1 cid.Cid) error { + if s.Internal.MarketRetryPublishDeal == nil { + return ErrNotSupported + } + return s.Internal.MarketRetryPublishDeal(p0, p1) +} + +func (s *StorageMinerStub) MarketRetryPublishDeal(p0 context.Context, p1 cid.Cid) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) MarketSetAsk(p0 context.Context, p1 types.BigInt, p2 types.BigInt, p3 abi.ChainEpoch, p4 abi.PaddedPieceSize, p5 abi.PaddedPieceSize) error { + if s.Internal.MarketSetAsk == nil { + return ErrNotSupported + } + return s.Internal.MarketSetAsk(p0, p1, p2, p3, p4, p5) +} + +func (s *StorageMinerStub) MarketSetAsk(p0 context.Context, p1 types.BigInt, p2 types.BigInt, p3 abi.ChainEpoch, p4 abi.PaddedPieceSize, p5 abi.PaddedPieceSize) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) MarketSetRetrievalAsk(p0 context.Context, p1 *retrievalmarket.Ask) error { + if s.Internal.MarketSetRetrievalAsk == nil { + return ErrNotSupported + } + return s.Internal.MarketSetRetrievalAsk(p0, p1) +} + +func (s *StorageMinerStub) MarketSetRetrievalAsk(p0 context.Context, p1 *retrievalmarket.Ask) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) MiningBase(p0 context.Context) (*types.TipSet, error) { + if s.Internal.MiningBase == nil { + return nil, ErrNotSupported + } + return s.Internal.MiningBase(p0) +} + +func (s *StorageMinerStub) MiningBase(p0 context.Context) (*types.TipSet, error) { + return nil, ErrNotSupported +} + +func (s *StorageMinerStruct) PiecesGetCIDInfo(p0 context.Context, p1 cid.Cid) (*piecestore.CIDInfo, error) { + if s.Internal.PiecesGetCIDInfo == nil { + return nil, ErrNotSupported + } + return s.Internal.PiecesGetCIDInfo(p0, p1) +} + +func (s *StorageMinerStub) PiecesGetCIDInfo(p0 context.Context, p1 cid.Cid) (*piecestore.CIDInfo, error) { + return nil, ErrNotSupported +} + +func (s *StorageMinerStruct) PiecesGetPieceInfo(p0 context.Context, p1 cid.Cid) (*piecestore.PieceInfo, error) { + if s.Internal.PiecesGetPieceInfo == nil { + return nil, ErrNotSupported + } + return s.Internal.PiecesGetPieceInfo(p0, p1) +} + +func (s *StorageMinerStub) PiecesGetPieceInfo(p0 context.Context, p1 cid.Cid) (*piecestore.PieceInfo, error) { + return nil, ErrNotSupported +} + +func (s *StorageMinerStruct) PiecesListCidInfos(p0 context.Context) ([]cid.Cid, error) { + if s.Internal.PiecesListCidInfos == nil { + return *new([]cid.Cid), ErrNotSupported + } + return s.Internal.PiecesListCidInfos(p0) +} + +func (s *StorageMinerStub) PiecesListCidInfos(p0 context.Context) ([]cid.Cid, error) { + return *new([]cid.Cid), ErrNotSupported +} + +func (s *StorageMinerStruct) PiecesListPieces(p0 context.Context) ([]cid.Cid, error) { + if s.Internal.PiecesListPieces == nil { + return *new([]cid.Cid), ErrNotSupported + } + return s.Internal.PiecesListPieces(p0) +} + +func (s *StorageMinerStub) PiecesListPieces(p0 context.Context) ([]cid.Cid, error) { + return *new([]cid.Cid), ErrNotSupported +} + +func (s *StorageMinerStruct) PledgeSector(p0 context.Context) (abi.SectorID, error) { + if s.Internal.PledgeSector == nil { + return *new(abi.SectorID), ErrNotSupported + } + return s.Internal.PledgeSector(p0) +} + +func (s *StorageMinerStub) PledgeSector(p0 context.Context) (abi.SectorID, error) { + return *new(abi.SectorID), ErrNotSupported +} + +func (s *StorageMinerStruct) RecoverFault(p0 context.Context, p1 []abi.SectorNumber) ([]cid.Cid, error) { + if s.Internal.RecoverFault == nil { + return *new([]cid.Cid), ErrNotSupported + } + return s.Internal.RecoverFault(p0, p1) +} + +func (s *StorageMinerStub) RecoverFault(p0 context.Context, p1 []abi.SectorNumber) ([]cid.Cid, error) { + return *new([]cid.Cid), ErrNotSupported +} + +func (s *StorageMinerStruct) ReturnAddPiece(p0 context.Context, p1 storiface.CallID, p2 abi.PieceInfo, p3 *storiface.CallError) error { + if s.Internal.ReturnAddPiece == nil { + return ErrNotSupported + } + return s.Internal.ReturnAddPiece(p0, p1, p2, p3) +} + +func (s *StorageMinerStub) ReturnAddPiece(p0 context.Context, p1 storiface.CallID, p2 abi.PieceInfo, p3 *storiface.CallError) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) ReturnDataCid(p0 context.Context, p1 storiface.CallID, p2 abi.PieceInfo, p3 *storiface.CallError) error { + if s.Internal.ReturnDataCid == nil { + return ErrNotSupported + } + return s.Internal.ReturnDataCid(p0, p1, p2, p3) +} + +func (s *StorageMinerStub) ReturnDataCid(p0 context.Context, p1 storiface.CallID, p2 abi.PieceInfo, p3 *storiface.CallError) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) ReturnDownloadSector(p0 context.Context, p1 storiface.CallID, p2 *storiface.CallError) error { + if s.Internal.ReturnDownloadSector == nil { + return ErrNotSupported + } + return s.Internal.ReturnDownloadSector(p0, p1, p2) +} + +func (s *StorageMinerStub) ReturnDownloadSector(p0 context.Context, p1 storiface.CallID, p2 *storiface.CallError) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) ReturnFetch(p0 context.Context, p1 storiface.CallID, p2 *storiface.CallError) error { + if s.Internal.ReturnFetch == nil { + return ErrNotSupported + } + return s.Internal.ReturnFetch(p0, p1, p2) +} + +func (s *StorageMinerStub) ReturnFetch(p0 context.Context, p1 storiface.CallID, p2 *storiface.CallError) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) ReturnFinalizeReplicaUpdate(p0 context.Context, p1 storiface.CallID, p2 *storiface.CallError) error { + if s.Internal.ReturnFinalizeReplicaUpdate == nil { + return ErrNotSupported + } + return s.Internal.ReturnFinalizeReplicaUpdate(p0, p1, p2) +} + +func (s *StorageMinerStub) ReturnFinalizeReplicaUpdate(p0 context.Context, p1 storiface.CallID, p2 *storiface.CallError) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) ReturnFinalizeSector(p0 context.Context, p1 storiface.CallID, p2 *storiface.CallError) error { + if s.Internal.ReturnFinalizeSector == nil { + return ErrNotSupported + } + return s.Internal.ReturnFinalizeSector(p0, p1, p2) +} + +func (s *StorageMinerStub) ReturnFinalizeSector(p0 context.Context, p1 storiface.CallID, p2 *storiface.CallError) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) ReturnGenerateSectorKeyFromData(p0 context.Context, p1 storiface.CallID, p2 *storiface.CallError) error { + if s.Internal.ReturnGenerateSectorKeyFromData == nil { + return ErrNotSupported + } + return s.Internal.ReturnGenerateSectorKeyFromData(p0, p1, p2) +} + +func (s *StorageMinerStub) ReturnGenerateSectorKeyFromData(p0 context.Context, p1 storiface.CallID, p2 *storiface.CallError) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) ReturnMoveStorage(p0 context.Context, p1 storiface.CallID, p2 *storiface.CallError) error { + if s.Internal.ReturnMoveStorage == nil { + return ErrNotSupported + } + return s.Internal.ReturnMoveStorage(p0, p1, p2) +} + +func (s *StorageMinerStub) ReturnMoveStorage(p0 context.Context, p1 storiface.CallID, p2 *storiface.CallError) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) ReturnProveReplicaUpdate1(p0 context.Context, p1 storiface.CallID, p2 storiface.ReplicaVanillaProofs, p3 *storiface.CallError) error { + if s.Internal.ReturnProveReplicaUpdate1 == nil { + return ErrNotSupported + } + return s.Internal.ReturnProveReplicaUpdate1(p0, p1, p2, p3) +} + +func (s *StorageMinerStub) ReturnProveReplicaUpdate1(p0 context.Context, p1 storiface.CallID, p2 storiface.ReplicaVanillaProofs, p3 *storiface.CallError) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) ReturnProveReplicaUpdate2(p0 context.Context, p1 storiface.CallID, p2 storiface.ReplicaUpdateProof, p3 *storiface.CallError) error { + if s.Internal.ReturnProveReplicaUpdate2 == nil { + return ErrNotSupported + } + return s.Internal.ReturnProveReplicaUpdate2(p0, p1, p2, p3) +} + +func (s *StorageMinerStub) ReturnProveReplicaUpdate2(p0 context.Context, p1 storiface.CallID, p2 storiface.ReplicaUpdateProof, p3 *storiface.CallError) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) ReturnReadPiece(p0 context.Context, p1 storiface.CallID, p2 bool, p3 *storiface.CallError) error { + if s.Internal.ReturnReadPiece == nil { + return ErrNotSupported + } + return s.Internal.ReturnReadPiece(p0, p1, p2, p3) +} + +func (s *StorageMinerStub) ReturnReadPiece(p0 context.Context, p1 storiface.CallID, p2 bool, p3 *storiface.CallError) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) ReturnReleaseUnsealed(p0 context.Context, p1 storiface.CallID, p2 *storiface.CallError) error { + if s.Internal.ReturnReleaseUnsealed == nil { + return ErrNotSupported + } + return s.Internal.ReturnReleaseUnsealed(p0, p1, p2) +} + +func (s *StorageMinerStub) ReturnReleaseUnsealed(p0 context.Context, p1 storiface.CallID, p2 *storiface.CallError) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) ReturnReplicaUpdate(p0 context.Context, p1 storiface.CallID, p2 storiface.ReplicaUpdateOut, p3 *storiface.CallError) error { + if s.Internal.ReturnReplicaUpdate == nil { + return ErrNotSupported + } + return s.Internal.ReturnReplicaUpdate(p0, p1, p2, p3) +} + +func (s *StorageMinerStub) ReturnReplicaUpdate(p0 context.Context, p1 storiface.CallID, p2 storiface.ReplicaUpdateOut, p3 *storiface.CallError) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) ReturnSealCommit1(p0 context.Context, p1 storiface.CallID, p2 storiface.Commit1Out, p3 *storiface.CallError) error { + if s.Internal.ReturnSealCommit1 == nil { + return ErrNotSupported + } + return s.Internal.ReturnSealCommit1(p0, p1, p2, p3) +} + +func (s *StorageMinerStub) ReturnSealCommit1(p0 context.Context, p1 storiface.CallID, p2 storiface.Commit1Out, p3 *storiface.CallError) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) ReturnSealCommit2(p0 context.Context, p1 storiface.CallID, p2 storiface.Proof, p3 *storiface.CallError) error { + if s.Internal.ReturnSealCommit2 == nil { + return ErrNotSupported + } + return s.Internal.ReturnSealCommit2(p0, p1, p2, p3) +} + +func (s *StorageMinerStub) ReturnSealCommit2(p0 context.Context, p1 storiface.CallID, p2 storiface.Proof, p3 *storiface.CallError) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) ReturnSealPreCommit1(p0 context.Context, p1 storiface.CallID, p2 storiface.PreCommit1Out, p3 *storiface.CallError) error { + if s.Internal.ReturnSealPreCommit1 == nil { + return ErrNotSupported + } + return s.Internal.ReturnSealPreCommit1(p0, p1, p2, p3) +} + +func (s *StorageMinerStub) ReturnSealPreCommit1(p0 context.Context, p1 storiface.CallID, p2 storiface.PreCommit1Out, p3 *storiface.CallError) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) ReturnSealPreCommit2(p0 context.Context, p1 storiface.CallID, p2 storiface.SectorCids, p3 *storiface.CallError) error { + if s.Internal.ReturnSealPreCommit2 == nil { + return ErrNotSupported + } + return s.Internal.ReturnSealPreCommit2(p0, p1, p2, p3) +} + +func (s *StorageMinerStub) ReturnSealPreCommit2(p0 context.Context, p1 storiface.CallID, p2 storiface.SectorCids, p3 *storiface.CallError) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) ReturnUnsealPiece(p0 context.Context, p1 storiface.CallID, p2 *storiface.CallError) error { + if s.Internal.ReturnUnsealPiece == nil { + return ErrNotSupported + } + return s.Internal.ReturnUnsealPiece(p0, p1, p2) +} + +func (s *StorageMinerStub) ReturnUnsealPiece(p0 context.Context, p1 storiface.CallID, p2 *storiface.CallError) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) RuntimeSubsystems(p0 context.Context) (MinerSubsystems, error) { + if s.Internal.RuntimeSubsystems == nil { + return *new(MinerSubsystems), ErrNotSupported + } + return s.Internal.RuntimeSubsystems(p0) +} + +func (s *StorageMinerStub) RuntimeSubsystems(p0 context.Context) (MinerSubsystems, error) { + return *new(MinerSubsystems), ErrNotSupported +} + +func (s *StorageMinerStruct) SealingAbort(p0 context.Context, p1 storiface.CallID) error { + if s.Internal.SealingAbort == nil { + return ErrNotSupported + } + return s.Internal.SealingAbort(p0, p1) +} + +func (s *StorageMinerStub) SealingAbort(p0 context.Context, p1 storiface.CallID) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) SealingRemoveRequest(p0 context.Context, p1 uuid.UUID) error { + if s.Internal.SealingRemoveRequest == nil { + return ErrNotSupported + } + return s.Internal.SealingRemoveRequest(p0, p1) +} + +func (s *StorageMinerStub) SealingRemoveRequest(p0 context.Context, p1 uuid.UUID) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) SealingSchedDiag(p0 context.Context, p1 bool) (interface{}, error) { + if s.Internal.SealingSchedDiag == nil { + return nil, ErrNotSupported + } + return s.Internal.SealingSchedDiag(p0, p1) +} + +func (s *StorageMinerStub) SealingSchedDiag(p0 context.Context, p1 bool) (interface{}, error) { + return nil, ErrNotSupported +} + +func (s *StorageMinerStruct) SectorAbortUpgrade(p0 context.Context, p1 abi.SectorNumber) error { + if s.Internal.SectorAbortUpgrade == nil { + return ErrNotSupported + } + return s.Internal.SectorAbortUpgrade(p0, p1) +} + +func (s *StorageMinerStub) SectorAbortUpgrade(p0 context.Context, p1 abi.SectorNumber) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) SectorAddPieceToAny(p0 context.Context, p1 abi.UnpaddedPieceSize, p2 storiface.Data, p3 PieceDealInfo) (SectorOffset, error) { + if s.Internal.SectorAddPieceToAny == nil { + return *new(SectorOffset), ErrNotSupported + } + return s.Internal.SectorAddPieceToAny(p0, p1, p2, p3) +} + +func (s *StorageMinerStub) SectorAddPieceToAny(p0 context.Context, p1 abi.UnpaddedPieceSize, p2 storiface.Data, p3 PieceDealInfo) (SectorOffset, error) { + return *new(SectorOffset), ErrNotSupported +} + +func (s *StorageMinerStruct) SectorCommitFlush(p0 context.Context) ([]sealiface.CommitBatchRes, error) { + if s.Internal.SectorCommitFlush == nil { + return *new([]sealiface.CommitBatchRes), ErrNotSupported + } + return s.Internal.SectorCommitFlush(p0) +} + +func (s *StorageMinerStub) SectorCommitFlush(p0 context.Context) ([]sealiface.CommitBatchRes, error) { + return *new([]sealiface.CommitBatchRes), ErrNotSupported +} + +func (s *StorageMinerStruct) SectorCommitPending(p0 context.Context) ([]abi.SectorID, error) { + if s.Internal.SectorCommitPending == nil { + return *new([]abi.SectorID), ErrNotSupported + } + return s.Internal.SectorCommitPending(p0) +} + +func (s *StorageMinerStub) SectorCommitPending(p0 context.Context) ([]abi.SectorID, error) { + return *new([]abi.SectorID), ErrNotSupported +} + +func (s *StorageMinerStruct) SectorGetExpectedSealDuration(p0 context.Context) (time.Duration, error) { + if s.Internal.SectorGetExpectedSealDuration == nil { + return *new(time.Duration), ErrNotSupported + } + return s.Internal.SectorGetExpectedSealDuration(p0) +} + +func (s *StorageMinerStub) SectorGetExpectedSealDuration(p0 context.Context) (time.Duration, error) { + return *new(time.Duration), ErrNotSupported +} + +func (s *StorageMinerStruct) SectorGetSealDelay(p0 context.Context) (time.Duration, error) { + if s.Internal.SectorGetSealDelay == nil { + return *new(time.Duration), ErrNotSupported + } + return s.Internal.SectorGetSealDelay(p0) +} + +func (s *StorageMinerStub) SectorGetSealDelay(p0 context.Context) (time.Duration, error) { + return *new(time.Duration), ErrNotSupported +} + +func (s *StorageMinerStruct) SectorMarkForUpgrade(p0 context.Context, p1 abi.SectorNumber, p2 bool) error { + if s.Internal.SectorMarkForUpgrade == nil { + return ErrNotSupported + } + return s.Internal.SectorMarkForUpgrade(p0, p1, p2) +} + +func (s *StorageMinerStub) SectorMarkForUpgrade(p0 context.Context, p1 abi.SectorNumber, p2 bool) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) SectorMatchPendingPiecesToOpenSectors(p0 context.Context) error { + if s.Internal.SectorMatchPendingPiecesToOpenSectors == nil { + return ErrNotSupported + } + return s.Internal.SectorMatchPendingPiecesToOpenSectors(p0) +} + +func (s *StorageMinerStub) SectorMatchPendingPiecesToOpenSectors(p0 context.Context) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) SectorNumAssignerMeta(p0 context.Context) (NumAssignerMeta, error) { + if s.Internal.SectorNumAssignerMeta == nil { + return *new(NumAssignerMeta), ErrNotSupported + } + return s.Internal.SectorNumAssignerMeta(p0) +} + +func (s *StorageMinerStub) SectorNumAssignerMeta(p0 context.Context) (NumAssignerMeta, error) { + return *new(NumAssignerMeta), ErrNotSupported +} + +func (s *StorageMinerStruct) SectorNumFree(p0 context.Context, p1 string) error { + if s.Internal.SectorNumFree == nil { + return ErrNotSupported + } + return s.Internal.SectorNumFree(p0, p1) +} + +func (s *StorageMinerStub) SectorNumFree(p0 context.Context, p1 string) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) SectorNumReservations(p0 context.Context) (map[string]bitfield.BitField, error) { + if s.Internal.SectorNumReservations == nil { + return *new(map[string]bitfield.BitField), ErrNotSupported + } + return s.Internal.SectorNumReservations(p0) +} + +func (s *StorageMinerStub) SectorNumReservations(p0 context.Context) (map[string]bitfield.BitField, error) { + return *new(map[string]bitfield.BitField), ErrNotSupported +} + +func (s *StorageMinerStruct) SectorNumReserve(p0 context.Context, p1 string, p2 bitfield.BitField, p3 bool) error { + if s.Internal.SectorNumReserve == nil { + return ErrNotSupported + } + return s.Internal.SectorNumReserve(p0, p1, p2, p3) +} + +func (s *StorageMinerStub) SectorNumReserve(p0 context.Context, p1 string, p2 bitfield.BitField, p3 bool) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) SectorNumReserveCount(p0 context.Context, p1 string, p2 uint64) (bitfield.BitField, error) { + if s.Internal.SectorNumReserveCount == nil { + return *new(bitfield.BitField), ErrNotSupported + } + return s.Internal.SectorNumReserveCount(p0, p1, p2) +} + +func (s *StorageMinerStub) SectorNumReserveCount(p0 context.Context, p1 string, p2 uint64) (bitfield.BitField, error) { + return *new(bitfield.BitField), ErrNotSupported +} + +func (s *StorageMinerStruct) SectorPreCommitFlush(p0 context.Context) ([]sealiface.PreCommitBatchRes, error) { + if s.Internal.SectorPreCommitFlush == nil { + return *new([]sealiface.PreCommitBatchRes), ErrNotSupported + } + return s.Internal.SectorPreCommitFlush(p0) +} + +func (s *StorageMinerStub) SectorPreCommitFlush(p0 context.Context) ([]sealiface.PreCommitBatchRes, error) { + return *new([]sealiface.PreCommitBatchRes), ErrNotSupported +} + +func (s *StorageMinerStruct) SectorPreCommitPending(p0 context.Context) ([]abi.SectorID, error) { + if s.Internal.SectorPreCommitPending == nil { + return *new([]abi.SectorID), ErrNotSupported + } + return s.Internal.SectorPreCommitPending(p0) +} + +func (s *StorageMinerStub) SectorPreCommitPending(p0 context.Context) ([]abi.SectorID, error) { + return *new([]abi.SectorID), ErrNotSupported +} + +func (s *StorageMinerStruct) SectorReceive(p0 context.Context, p1 RemoteSectorMeta) error { + if s.Internal.SectorReceive == nil { + return ErrNotSupported + } + return s.Internal.SectorReceive(p0, p1) +} + +func (s *StorageMinerStub) SectorReceive(p0 context.Context, p1 RemoteSectorMeta) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) SectorRemove(p0 context.Context, p1 abi.SectorNumber) error { + if s.Internal.SectorRemove == nil { + return ErrNotSupported + } + return s.Internal.SectorRemove(p0, p1) +} + +func (s *StorageMinerStub) SectorRemove(p0 context.Context, p1 abi.SectorNumber) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) SectorSetExpectedSealDuration(p0 context.Context, p1 time.Duration) error { + if s.Internal.SectorSetExpectedSealDuration == nil { + return ErrNotSupported + } + return s.Internal.SectorSetExpectedSealDuration(p0, p1) +} + +func (s *StorageMinerStub) SectorSetExpectedSealDuration(p0 context.Context, p1 time.Duration) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) SectorSetSealDelay(p0 context.Context, p1 time.Duration) error { + if s.Internal.SectorSetSealDelay == nil { + return ErrNotSupported + } + return s.Internal.SectorSetSealDelay(p0, p1) +} + +func (s *StorageMinerStub) SectorSetSealDelay(p0 context.Context, p1 time.Duration) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) SectorStartSealing(p0 context.Context, p1 abi.SectorNumber) error { + if s.Internal.SectorStartSealing == nil { + return ErrNotSupported + } + return s.Internal.SectorStartSealing(p0, p1) +} + +func (s *StorageMinerStub) SectorStartSealing(p0 context.Context, p1 abi.SectorNumber) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) SectorTerminate(p0 context.Context, p1 abi.SectorNumber) error { + if s.Internal.SectorTerminate == nil { + return ErrNotSupported + } + return s.Internal.SectorTerminate(p0, p1) +} + +func (s *StorageMinerStub) SectorTerminate(p0 context.Context, p1 abi.SectorNumber) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) SectorTerminateFlush(p0 context.Context) (*cid.Cid, error) { + if s.Internal.SectorTerminateFlush == nil { + return nil, ErrNotSupported + } + return s.Internal.SectorTerminateFlush(p0) +} + +func (s *StorageMinerStub) SectorTerminateFlush(p0 context.Context) (*cid.Cid, error) { + return nil, ErrNotSupported +} + +func (s *StorageMinerStruct) SectorTerminatePending(p0 context.Context) ([]abi.SectorID, error) { + if s.Internal.SectorTerminatePending == nil { + return *new([]abi.SectorID), ErrNotSupported + } + return s.Internal.SectorTerminatePending(p0) +} + +func (s *StorageMinerStub) SectorTerminatePending(p0 context.Context) ([]abi.SectorID, error) { + return *new([]abi.SectorID), ErrNotSupported +} + +func (s *StorageMinerStruct) SectorUnseal(p0 context.Context, p1 abi.SectorNumber) error { + if s.Internal.SectorUnseal == nil { + return ErrNotSupported + } + return s.Internal.SectorUnseal(p0, p1) +} + +func (s *StorageMinerStub) SectorUnseal(p0 context.Context, p1 abi.SectorNumber) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) SectorsList(p0 context.Context) ([]abi.SectorNumber, error) { + if s.Internal.SectorsList == nil { + return *new([]abi.SectorNumber), ErrNotSupported + } + return s.Internal.SectorsList(p0) +} + +func (s *StorageMinerStub) SectorsList(p0 context.Context) ([]abi.SectorNumber, error) { + return *new([]abi.SectorNumber), ErrNotSupported +} + +func (s *StorageMinerStruct) SectorsListInStates(p0 context.Context, p1 []SectorState) ([]abi.SectorNumber, error) { + if s.Internal.SectorsListInStates == nil { + return *new([]abi.SectorNumber), ErrNotSupported + } + return s.Internal.SectorsListInStates(p0, p1) +} + +func (s *StorageMinerStub) SectorsListInStates(p0 context.Context, p1 []SectorState) ([]abi.SectorNumber, error) { + return *new([]abi.SectorNumber), ErrNotSupported +} + +func (s *StorageMinerStruct) SectorsRefs(p0 context.Context) (map[string][]SealedRef, error) { + if s.Internal.SectorsRefs == nil { + return *new(map[string][]SealedRef), ErrNotSupported + } + return s.Internal.SectorsRefs(p0) +} + +func (s *StorageMinerStub) SectorsRefs(p0 context.Context) (map[string][]SealedRef, error) { + return *new(map[string][]SealedRef), ErrNotSupported +} + +func (s *StorageMinerStruct) SectorsStatus(p0 context.Context, p1 abi.SectorNumber, p2 bool) (SectorInfo, error) { + if s.Internal.SectorsStatus == nil { + return *new(SectorInfo), ErrNotSupported + } + return s.Internal.SectorsStatus(p0, p1, p2) +} + +func (s *StorageMinerStub) SectorsStatus(p0 context.Context, p1 abi.SectorNumber, p2 bool) (SectorInfo, error) { + return *new(SectorInfo), ErrNotSupported +} + +func (s *StorageMinerStruct) SectorsSummary(p0 context.Context) (map[SectorState]int, error) { + if s.Internal.SectorsSummary == nil { + return *new(map[SectorState]int), ErrNotSupported + } + return s.Internal.SectorsSummary(p0) +} + +func (s *StorageMinerStub) SectorsSummary(p0 context.Context) (map[SectorState]int, error) { + return *new(map[SectorState]int), ErrNotSupported +} + +func (s *StorageMinerStruct) SectorsUnsealPiece(p0 context.Context, p1 storiface.SectorRef, p2 storiface.UnpaddedByteIndex, p3 abi.UnpaddedPieceSize, p4 abi.SealRandomness, p5 *cid.Cid) error { + if s.Internal.SectorsUnsealPiece == nil { + return ErrNotSupported + } + return s.Internal.SectorsUnsealPiece(p0, p1, p2, p3, p4, p5) +} + +func (s *StorageMinerStub) SectorsUnsealPiece(p0 context.Context, p1 storiface.SectorRef, p2 storiface.UnpaddedByteIndex, p3 abi.UnpaddedPieceSize, p4 abi.SealRandomness, p5 *cid.Cid) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) SectorsUpdate(p0 context.Context, p1 abi.SectorNumber, p2 SectorState) error { + if s.Internal.SectorsUpdate == nil { + return ErrNotSupported + } + return s.Internal.SectorsUpdate(p0, p1, p2) +} + +func (s *StorageMinerStub) SectorsUpdate(p0 context.Context, p1 abi.SectorNumber, p2 SectorState) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) StorageAddLocal(p0 context.Context, p1 string) error { + if s.Internal.StorageAddLocal == nil { + return ErrNotSupported + } + return s.Internal.StorageAddLocal(p0, p1) +} + +func (s *StorageMinerStub) StorageAddLocal(p0 context.Context, p1 string) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) StorageAttach(p0 context.Context, p1 storiface.StorageInfo, p2 fsutil.FsStat) error { + if s.Internal.StorageAttach == nil { + return ErrNotSupported + } + return s.Internal.StorageAttach(p0, p1, p2) +} + +func (s *StorageMinerStub) StorageAttach(p0 context.Context, p1 storiface.StorageInfo, p2 fsutil.FsStat) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) StorageAuthVerify(p0 context.Context, p1 string) ([]auth.Permission, error) { + if s.Internal.StorageAuthVerify == nil { + return *new([]auth.Permission), ErrNotSupported + } + return s.Internal.StorageAuthVerify(p0, p1) +} + +func (s *StorageMinerStub) StorageAuthVerify(p0 context.Context, p1 string) ([]auth.Permission, error) { + return *new([]auth.Permission), ErrNotSupported +} + +func (s *StorageMinerStruct) StorageBestAlloc(p0 context.Context, p1 storiface.SectorFileType, p2 abi.SectorSize, p3 storiface.PathType) ([]storiface.StorageInfo, error) { + if s.Internal.StorageBestAlloc == nil { + return *new([]storiface.StorageInfo), ErrNotSupported + } + return s.Internal.StorageBestAlloc(p0, p1, p2, p3) +} + +func (s *StorageMinerStub) StorageBestAlloc(p0 context.Context, p1 storiface.SectorFileType, p2 abi.SectorSize, p3 storiface.PathType) ([]storiface.StorageInfo, error) { + return *new([]storiface.StorageInfo), ErrNotSupported +} + +func (s *StorageMinerStruct) StorageDeclareSector(p0 context.Context, p1 storiface.ID, p2 abi.SectorID, p3 storiface.SectorFileType, p4 bool) error { + if s.Internal.StorageDeclareSector == nil { + return ErrNotSupported + } + return s.Internal.StorageDeclareSector(p0, p1, p2, p3, p4) +} + +func (s *StorageMinerStub) StorageDeclareSector(p0 context.Context, p1 storiface.ID, p2 abi.SectorID, p3 storiface.SectorFileType, p4 bool) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) StorageDetach(p0 context.Context, p1 storiface.ID, p2 string) error { + if s.Internal.StorageDetach == nil { + return ErrNotSupported + } + return s.Internal.StorageDetach(p0, p1, p2) +} + +func (s *StorageMinerStub) StorageDetach(p0 context.Context, p1 storiface.ID, p2 string) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) StorageDetachLocal(p0 context.Context, p1 string) error { + if s.Internal.StorageDetachLocal == nil { + return ErrNotSupported + } + return s.Internal.StorageDetachLocal(p0, p1) +} + +func (s *StorageMinerStub) StorageDetachLocal(p0 context.Context, p1 string) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) StorageDropSector(p0 context.Context, p1 storiface.ID, p2 abi.SectorID, p3 storiface.SectorFileType) error { + if s.Internal.StorageDropSector == nil { + return ErrNotSupported + } + return s.Internal.StorageDropSector(p0, p1, p2, p3) +} + +func (s *StorageMinerStub) StorageDropSector(p0 context.Context, p1 storiface.ID, p2 abi.SectorID, p3 storiface.SectorFileType) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) StorageFindSector(p0 context.Context, p1 abi.SectorID, p2 storiface.SectorFileType, p3 abi.SectorSize, p4 bool) ([]storiface.SectorStorageInfo, error) { + if s.Internal.StorageFindSector == nil { + return *new([]storiface.SectorStorageInfo), ErrNotSupported + } + return s.Internal.StorageFindSector(p0, p1, p2, p3, p4) +} + +func (s *StorageMinerStub) StorageFindSector(p0 context.Context, p1 abi.SectorID, p2 storiface.SectorFileType, p3 abi.SectorSize, p4 bool) ([]storiface.SectorStorageInfo, error) { + return *new([]storiface.SectorStorageInfo), ErrNotSupported +} + +func (s *StorageMinerStruct) StorageGetLocks(p0 context.Context) (storiface.SectorLocks, error) { + if s.Internal.StorageGetLocks == nil { + return *new(storiface.SectorLocks), ErrNotSupported + } + return s.Internal.StorageGetLocks(p0) +} + +func (s *StorageMinerStub) StorageGetLocks(p0 context.Context) (storiface.SectorLocks, error) { + return *new(storiface.SectorLocks), ErrNotSupported +} + +func (s *StorageMinerStruct) StorageInfo(p0 context.Context, p1 storiface.ID) (storiface.StorageInfo, error) { + if s.Internal.StorageInfo == nil { + return *new(storiface.StorageInfo), ErrNotSupported + } + return s.Internal.StorageInfo(p0, p1) +} + +func (s *StorageMinerStub) StorageInfo(p0 context.Context, p1 storiface.ID) (storiface.StorageInfo, error) { + return *new(storiface.StorageInfo), ErrNotSupported +} + +func (s *StorageMinerStruct) StorageList(p0 context.Context) (map[storiface.ID][]storiface.Decl, error) { + if s.Internal.StorageList == nil { + return *new(map[storiface.ID][]storiface.Decl), ErrNotSupported + } + return s.Internal.StorageList(p0) +} + +func (s *StorageMinerStub) StorageList(p0 context.Context) (map[storiface.ID][]storiface.Decl, error) { + return *new(map[storiface.ID][]storiface.Decl), ErrNotSupported +} + +func (s *StorageMinerStruct) StorageLocal(p0 context.Context) (map[storiface.ID]string, error) { + if s.Internal.StorageLocal == nil { + return *new(map[storiface.ID]string), ErrNotSupported + } + return s.Internal.StorageLocal(p0) +} + +func (s *StorageMinerStub) StorageLocal(p0 context.Context) (map[storiface.ID]string, error) { + return *new(map[storiface.ID]string), ErrNotSupported +} + +func (s *StorageMinerStruct) StorageLock(p0 context.Context, p1 abi.SectorID, p2 storiface.SectorFileType, p3 storiface.SectorFileType) error { + if s.Internal.StorageLock == nil { + return ErrNotSupported + } + return s.Internal.StorageLock(p0, p1, p2, p3) +} + +func (s *StorageMinerStub) StorageLock(p0 context.Context, p1 abi.SectorID, p2 storiface.SectorFileType, p3 storiface.SectorFileType) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) StorageRedeclareLocal(p0 context.Context, p1 *storiface.ID, p2 bool) error { + if s.Internal.StorageRedeclareLocal == nil { + return ErrNotSupported + } + return s.Internal.StorageRedeclareLocal(p0, p1, p2) +} + +func (s *StorageMinerStub) StorageRedeclareLocal(p0 context.Context, p1 *storiface.ID, p2 bool) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) StorageReportHealth(p0 context.Context, p1 storiface.ID, p2 storiface.HealthReport) error { + if s.Internal.StorageReportHealth == nil { + return ErrNotSupported + } + return s.Internal.StorageReportHealth(p0, p1, p2) +} + +func (s *StorageMinerStub) StorageReportHealth(p0 context.Context, p1 storiface.ID, p2 storiface.HealthReport) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) StorageStat(p0 context.Context, p1 storiface.ID) (fsutil.FsStat, error) { + if s.Internal.StorageStat == nil { + return *new(fsutil.FsStat), ErrNotSupported + } + return s.Internal.StorageStat(p0, p1) +} + +func (s *StorageMinerStub) StorageStat(p0 context.Context, p1 storiface.ID) (fsutil.FsStat, error) { + return *new(fsutil.FsStat), ErrNotSupported +} + +func (s *StorageMinerStruct) StorageTryLock(p0 context.Context, p1 abi.SectorID, p2 storiface.SectorFileType, p3 storiface.SectorFileType) (bool, error) { + if s.Internal.StorageTryLock == nil { + return false, ErrNotSupported + } + return s.Internal.StorageTryLock(p0, p1, p2, p3) +} + +func (s *StorageMinerStub) StorageTryLock(p0 context.Context, p1 abi.SectorID, p2 storiface.SectorFileType, p3 storiface.SectorFileType) (bool, error) { + return false, ErrNotSupported +} + +func (s *StorageMinerStruct) WorkerConnect(p0 context.Context, p1 string) error { + if s.Internal.WorkerConnect == nil { + return ErrNotSupported + } + return s.Internal.WorkerConnect(p0, p1) +} + +func (s *StorageMinerStub) WorkerConnect(p0 context.Context, p1 string) error { + return ErrNotSupported +} + +func (s *StorageMinerStruct) WorkerJobs(p0 context.Context) (map[uuid.UUID][]storiface.WorkerJob, error) { + if s.Internal.WorkerJobs == nil { + return *new(map[uuid.UUID][]storiface.WorkerJob), ErrNotSupported + } + return s.Internal.WorkerJobs(p0) +} + +func (s *StorageMinerStub) WorkerJobs(p0 context.Context) (map[uuid.UUID][]storiface.WorkerJob, error) { + return *new(map[uuid.UUID][]storiface.WorkerJob), ErrNotSupported +} + +func (s *StorageMinerStruct) WorkerStats(p0 context.Context) (map[uuid.UUID]storiface.WorkerStats, error) { + if s.Internal.WorkerStats == nil { + return *new(map[uuid.UUID]storiface.WorkerStats), ErrNotSupported + } + return s.Internal.WorkerStats(p0) +} + +func (s *StorageMinerStub) WorkerStats(p0 context.Context) (map[uuid.UUID]storiface.WorkerStats, error) { + return *new(map[uuid.UUID]storiface.WorkerStats), ErrNotSupported +} + +func (s *WalletStruct) WalletDelete(p0 context.Context, p1 address.Address) error { + if s.Internal.WalletDelete == nil { + return ErrNotSupported + } + return s.Internal.WalletDelete(p0, p1) +} + +func (s *WalletStub) WalletDelete(p0 context.Context, p1 address.Address) error { + return ErrNotSupported +} + +func (s *WalletStruct) WalletExport(p0 context.Context, p1 address.Address) (*types.KeyInfo, error) { + if s.Internal.WalletExport == nil { + return nil, ErrNotSupported + } + return s.Internal.WalletExport(p0, p1) +} + +func (s *WalletStub) WalletExport(p0 context.Context, p1 address.Address) (*types.KeyInfo, error) { + return nil, ErrNotSupported +} + +func (s *WalletStruct) WalletHas(p0 context.Context, p1 address.Address) (bool, error) { + if s.Internal.WalletHas == nil { + return false, ErrNotSupported + } + return s.Internal.WalletHas(p0, p1) +} + +func (s *WalletStub) WalletHas(p0 context.Context, p1 address.Address) (bool, error) { + return false, ErrNotSupported +} + +func (s *WalletStruct) WalletImport(p0 context.Context, p1 *types.KeyInfo) (address.Address, error) { + if s.Internal.WalletImport == nil { + return *new(address.Address), ErrNotSupported + } + return s.Internal.WalletImport(p0, p1) +} + +func (s *WalletStub) WalletImport(p0 context.Context, p1 *types.KeyInfo) (address.Address, error) { + return *new(address.Address), ErrNotSupported +} + +func (s *WalletStruct) WalletList(p0 context.Context) ([]address.Address, error) { + if s.Internal.WalletList == nil { + return *new([]address.Address), ErrNotSupported + } + return s.Internal.WalletList(p0) +} + +func (s *WalletStub) WalletList(p0 context.Context) ([]address.Address, error) { + return *new([]address.Address), ErrNotSupported +} + +func (s *WalletStruct) WalletNew(p0 context.Context, p1 types.KeyType) (address.Address, error) { + if s.Internal.WalletNew == nil { + return *new(address.Address), ErrNotSupported + } + return s.Internal.WalletNew(p0, p1) +} + +func (s *WalletStub) WalletNew(p0 context.Context, p1 types.KeyType) (address.Address, error) { + return *new(address.Address), ErrNotSupported +} + +func (s *WalletStruct) WalletSign(p0 context.Context, p1 address.Address, p2 []byte, p3 MsgMeta) (*crypto.Signature, error) { + if s.Internal.WalletSign == nil { + return nil, ErrNotSupported + } + return s.Internal.WalletSign(p0, p1, p2, p3) +} + +func (s *WalletStub) WalletSign(p0 context.Context, p1 address.Address, p2 []byte, p3 MsgMeta) (*crypto.Signature, error) { + return nil, ErrNotSupported +} + +func (s *WorkerStruct) AddPiece(p0 context.Context, p1 storiface.SectorRef, p2 []abi.UnpaddedPieceSize, p3 abi.UnpaddedPieceSize, p4 storiface.Data) (storiface.CallID, error) { + if s.Internal.AddPiece == nil { + return *new(storiface.CallID), ErrNotSupported + } + return s.Internal.AddPiece(p0, p1, p2, p3, p4) +} + +func (s *WorkerStub) AddPiece(p0 context.Context, p1 storiface.SectorRef, p2 []abi.UnpaddedPieceSize, p3 abi.UnpaddedPieceSize, p4 storiface.Data) (storiface.CallID, error) { + return *new(storiface.CallID), ErrNotSupported +} + +func (s *WorkerStruct) DataCid(p0 context.Context, p1 abi.UnpaddedPieceSize, p2 storiface.Data) (storiface.CallID, error) { + if s.Internal.DataCid == nil { + return *new(storiface.CallID), ErrNotSupported + } + return s.Internal.DataCid(p0, p1, p2) +} + +func (s *WorkerStub) DataCid(p0 context.Context, p1 abi.UnpaddedPieceSize, p2 storiface.Data) (storiface.CallID, error) { + return *new(storiface.CallID), ErrNotSupported +} + +func (s *WorkerStruct) DownloadSectorData(p0 context.Context, p1 storiface.SectorRef, p2 bool, p3 map[storiface.SectorFileType]storiface.SectorLocation) (storiface.CallID, error) { + if s.Internal.DownloadSectorData == nil { + return *new(storiface.CallID), ErrNotSupported + } + return s.Internal.DownloadSectorData(p0, p1, p2, p3) +} + +func (s *WorkerStub) DownloadSectorData(p0 context.Context, p1 storiface.SectorRef, p2 bool, p3 map[storiface.SectorFileType]storiface.SectorLocation) (storiface.CallID, error) { + return *new(storiface.CallID), ErrNotSupported +} + +func (s *WorkerStruct) Enabled(p0 context.Context) (bool, error) { + if s.Internal.Enabled == nil { + return false, ErrNotSupported + } + return s.Internal.Enabled(p0) +} + +func (s *WorkerStub) Enabled(p0 context.Context) (bool, error) { + return false, ErrNotSupported +} + +func (s *WorkerStruct) Fetch(p0 context.Context, p1 storiface.SectorRef, p2 storiface.SectorFileType, p3 storiface.PathType, p4 storiface.AcquireMode) (storiface.CallID, error) { + if s.Internal.Fetch == nil { + return *new(storiface.CallID), ErrNotSupported + } + return s.Internal.Fetch(p0, p1, p2, p3, p4) +} + +func (s *WorkerStub) Fetch(p0 context.Context, p1 storiface.SectorRef, p2 storiface.SectorFileType, p3 storiface.PathType, p4 storiface.AcquireMode) (storiface.CallID, error) { + return *new(storiface.CallID), ErrNotSupported +} + +func (s *WorkerStruct) FinalizeReplicaUpdate(p0 context.Context, p1 storiface.SectorRef) (storiface.CallID, error) { + if s.Internal.FinalizeReplicaUpdate == nil { + return *new(storiface.CallID), ErrNotSupported + } + return s.Internal.FinalizeReplicaUpdate(p0, p1) +} + +func (s *WorkerStub) FinalizeReplicaUpdate(p0 context.Context, p1 storiface.SectorRef) (storiface.CallID, error) { + return *new(storiface.CallID), ErrNotSupported +} + +func (s *WorkerStruct) FinalizeSector(p0 context.Context, p1 storiface.SectorRef) (storiface.CallID, error) { + if s.Internal.FinalizeSector == nil { + return *new(storiface.CallID), ErrNotSupported + } + return s.Internal.FinalizeSector(p0, p1) +} + +func (s *WorkerStub) FinalizeSector(p0 context.Context, p1 storiface.SectorRef) (storiface.CallID, error) { + return *new(storiface.CallID), ErrNotSupported +} + +func (s *WorkerStruct) GenerateSectorKeyFromData(p0 context.Context, p1 storiface.SectorRef, p2 cid.Cid) (storiface.CallID, error) { + if s.Internal.GenerateSectorKeyFromData == nil { + return *new(storiface.CallID), ErrNotSupported + } + return s.Internal.GenerateSectorKeyFromData(p0, p1, p2) +} + +func (s *WorkerStub) GenerateSectorKeyFromData(p0 context.Context, p1 storiface.SectorRef, p2 cid.Cid) (storiface.CallID, error) { + return *new(storiface.CallID), ErrNotSupported +} + +func (s *WorkerStruct) GenerateWindowPoSt(p0 context.Context, p1 abi.RegisteredPoStProof, p2 abi.ActorID, p3 []storiface.PostSectorChallenge, p4 int, p5 abi.PoStRandomness) (storiface.WindowPoStResult, error) { + if s.Internal.GenerateWindowPoSt == nil { + return *new(storiface.WindowPoStResult), ErrNotSupported + } + return s.Internal.GenerateWindowPoSt(p0, p1, p2, p3, p4, p5) +} + +func (s *WorkerStub) GenerateWindowPoSt(p0 context.Context, p1 abi.RegisteredPoStProof, p2 abi.ActorID, p3 []storiface.PostSectorChallenge, p4 int, p5 abi.PoStRandomness) (storiface.WindowPoStResult, error) { + return *new(storiface.WindowPoStResult), ErrNotSupported +} + +func (s *WorkerStruct) GenerateWinningPoSt(p0 context.Context, p1 abi.RegisteredPoStProof, p2 abi.ActorID, p3 []storiface.PostSectorChallenge, p4 abi.PoStRandomness) ([]proof.PoStProof, error) { + if s.Internal.GenerateWinningPoSt == nil { + return *new([]proof.PoStProof), ErrNotSupported + } + return s.Internal.GenerateWinningPoSt(p0, p1, p2, p3, p4) +} + +func (s *WorkerStub) GenerateWinningPoSt(p0 context.Context, p1 abi.RegisteredPoStProof, p2 abi.ActorID, p3 []storiface.PostSectorChallenge, p4 abi.PoStRandomness) ([]proof.PoStProof, error) { + return *new([]proof.PoStProof), ErrNotSupported +} + +func (s *WorkerStruct) Info(p0 context.Context) (storiface.WorkerInfo, error) { + if s.Internal.Info == nil { + return *new(storiface.WorkerInfo), ErrNotSupported + } + return s.Internal.Info(p0) +} + +func (s *WorkerStub) Info(p0 context.Context) (storiface.WorkerInfo, error) { + return *new(storiface.WorkerInfo), ErrNotSupported +} + +func (s *WorkerStruct) MoveStorage(p0 context.Context, p1 storiface.SectorRef, p2 storiface.SectorFileType) (storiface.CallID, error) { + if s.Internal.MoveStorage == nil { + return *new(storiface.CallID), ErrNotSupported + } + return s.Internal.MoveStorage(p0, p1, p2) +} + +func (s *WorkerStub) MoveStorage(p0 context.Context, p1 storiface.SectorRef, p2 storiface.SectorFileType) (storiface.CallID, error) { + return *new(storiface.CallID), ErrNotSupported +} + +func (s *WorkerStruct) Paths(p0 context.Context) ([]storiface.StoragePath, error) { + if s.Internal.Paths == nil { + return *new([]storiface.StoragePath), ErrNotSupported + } + return s.Internal.Paths(p0) +} + +func (s *WorkerStub) Paths(p0 context.Context) ([]storiface.StoragePath, error) { + return *new([]storiface.StoragePath), ErrNotSupported +} + +func (s *WorkerStruct) ProcessSession(p0 context.Context) (uuid.UUID, error) { + if s.Internal.ProcessSession == nil { + return *new(uuid.UUID), ErrNotSupported + } + return s.Internal.ProcessSession(p0) +} + +func (s *WorkerStub) ProcessSession(p0 context.Context) (uuid.UUID, error) { + return *new(uuid.UUID), ErrNotSupported +} + +func (s *WorkerStruct) ProveReplicaUpdate1(p0 context.Context, p1 storiface.SectorRef, p2 cid.Cid, p3 cid.Cid, p4 cid.Cid) (storiface.CallID, error) { + if s.Internal.ProveReplicaUpdate1 == nil { + return *new(storiface.CallID), ErrNotSupported + } + return s.Internal.ProveReplicaUpdate1(p0, p1, p2, p3, p4) +} + +func (s *WorkerStub) ProveReplicaUpdate1(p0 context.Context, p1 storiface.SectorRef, p2 cid.Cid, p3 cid.Cid, p4 cid.Cid) (storiface.CallID, error) { + return *new(storiface.CallID), ErrNotSupported +} + +func (s *WorkerStruct) ProveReplicaUpdate2(p0 context.Context, p1 storiface.SectorRef, p2 cid.Cid, p3 cid.Cid, p4 cid.Cid, p5 storiface.ReplicaVanillaProofs) (storiface.CallID, error) { + if s.Internal.ProveReplicaUpdate2 == nil { + return *new(storiface.CallID), ErrNotSupported + } + return s.Internal.ProveReplicaUpdate2(p0, p1, p2, p3, p4, p5) +} + +func (s *WorkerStub) ProveReplicaUpdate2(p0 context.Context, p1 storiface.SectorRef, p2 cid.Cid, p3 cid.Cid, p4 cid.Cid, p5 storiface.ReplicaVanillaProofs) (storiface.CallID, error) { + return *new(storiface.CallID), ErrNotSupported +} + +func (s *WorkerStruct) ReleaseUnsealed(p0 context.Context, p1 storiface.SectorRef, p2 []storiface.Range) (storiface.CallID, error) { + if s.Internal.ReleaseUnsealed == nil { + return *new(storiface.CallID), ErrNotSupported + } + return s.Internal.ReleaseUnsealed(p0, p1, p2) +} + +func (s *WorkerStub) ReleaseUnsealed(p0 context.Context, p1 storiface.SectorRef, p2 []storiface.Range) (storiface.CallID, error) { + return *new(storiface.CallID), ErrNotSupported +} + +func (s *WorkerStruct) Remove(p0 context.Context, p1 abi.SectorID) error { + if s.Internal.Remove == nil { + return ErrNotSupported + } + return s.Internal.Remove(p0, p1) +} + +func (s *WorkerStub) Remove(p0 context.Context, p1 abi.SectorID) error { + return ErrNotSupported +} + +func (s *WorkerStruct) ReplicaUpdate(p0 context.Context, p1 storiface.SectorRef, p2 []abi.PieceInfo) (storiface.CallID, error) { + if s.Internal.ReplicaUpdate == nil { + return *new(storiface.CallID), ErrNotSupported + } + return s.Internal.ReplicaUpdate(p0, p1, p2) +} + +func (s *WorkerStub) ReplicaUpdate(p0 context.Context, p1 storiface.SectorRef, p2 []abi.PieceInfo) (storiface.CallID, error) { + return *new(storiface.CallID), ErrNotSupported +} + +func (s *WorkerStruct) SealCommit1(p0 context.Context, p1 storiface.SectorRef, p2 abi.SealRandomness, p3 abi.InteractiveSealRandomness, p4 []abi.PieceInfo, p5 storiface.SectorCids) (storiface.CallID, error) { + if s.Internal.SealCommit1 == nil { + return *new(storiface.CallID), ErrNotSupported + } + return s.Internal.SealCommit1(p0, p1, p2, p3, p4, p5) +} + +func (s *WorkerStub) SealCommit1(p0 context.Context, p1 storiface.SectorRef, p2 abi.SealRandomness, p3 abi.InteractiveSealRandomness, p4 []abi.PieceInfo, p5 storiface.SectorCids) (storiface.CallID, error) { + return *new(storiface.CallID), ErrNotSupported +} + +func (s *WorkerStruct) SealCommit2(p0 context.Context, p1 storiface.SectorRef, p2 storiface.Commit1Out) (storiface.CallID, error) { + if s.Internal.SealCommit2 == nil { + return *new(storiface.CallID), ErrNotSupported + } + return s.Internal.SealCommit2(p0, p1, p2) +} + +func (s *WorkerStub) SealCommit2(p0 context.Context, p1 storiface.SectorRef, p2 storiface.Commit1Out) (storiface.CallID, error) { + return *new(storiface.CallID), ErrNotSupported +} + +func (s *WorkerStruct) SealPreCommit1(p0 context.Context, p1 storiface.SectorRef, p2 abi.SealRandomness, p3 []abi.PieceInfo) (storiface.CallID, error) { + if s.Internal.SealPreCommit1 == nil { + return *new(storiface.CallID), ErrNotSupported + } + return s.Internal.SealPreCommit1(p0, p1, p2, p3) +} + +func (s *WorkerStub) SealPreCommit1(p0 context.Context, p1 storiface.SectorRef, p2 abi.SealRandomness, p3 []abi.PieceInfo) (storiface.CallID, error) { + return *new(storiface.CallID), ErrNotSupported +} + +func (s *WorkerStruct) SealPreCommit2(p0 context.Context, p1 storiface.SectorRef, p2 storiface.PreCommit1Out) (storiface.CallID, error) { + if s.Internal.SealPreCommit2 == nil { + return *new(storiface.CallID), ErrNotSupported + } + return s.Internal.SealPreCommit2(p0, p1, p2) +} + +func (s *WorkerStub) SealPreCommit2(p0 context.Context, p1 storiface.SectorRef, p2 storiface.PreCommit1Out) (storiface.CallID, error) { + return *new(storiface.CallID), ErrNotSupported +} + +func (s *WorkerStruct) Session(p0 context.Context) (uuid.UUID, error) { + if s.Internal.Session == nil { + return *new(uuid.UUID), ErrNotSupported + } + return s.Internal.Session(p0) +} + +func (s *WorkerStub) Session(p0 context.Context) (uuid.UUID, error) { + return *new(uuid.UUID), ErrNotSupported +} + +func (s *WorkerStruct) SetEnabled(p0 context.Context, p1 bool) error { + if s.Internal.SetEnabled == nil { + return ErrNotSupported + } + return s.Internal.SetEnabled(p0, p1) +} + +func (s *WorkerStub) SetEnabled(p0 context.Context, p1 bool) error { + return ErrNotSupported +} + +func (s *WorkerStruct) Shutdown(p0 context.Context) error { + if s.Internal.Shutdown == nil { + return ErrNotSupported + } + return s.Internal.Shutdown(p0) +} + +func (s *WorkerStub) Shutdown(p0 context.Context) error { + return ErrNotSupported +} + +func (s *WorkerStruct) StorageAddLocal(p0 context.Context, p1 string) error { + if s.Internal.StorageAddLocal == nil { + return ErrNotSupported + } + return s.Internal.StorageAddLocal(p0, p1) +} + +func (s *WorkerStub) StorageAddLocal(p0 context.Context, p1 string) error { + return ErrNotSupported +} + +func (s *WorkerStruct) StorageDetachAll(p0 context.Context) error { + if s.Internal.StorageDetachAll == nil { + return ErrNotSupported + } + return s.Internal.StorageDetachAll(p0) +} + +func (s *WorkerStub) StorageDetachAll(p0 context.Context) error { + return ErrNotSupported +} + +func (s *WorkerStruct) StorageDetachLocal(p0 context.Context, p1 string) error { + if s.Internal.StorageDetachLocal == nil { + return ErrNotSupported + } + return s.Internal.StorageDetachLocal(p0, p1) +} + +func (s *WorkerStub) StorageDetachLocal(p0 context.Context, p1 string) error { + return ErrNotSupported +} + +func (s *WorkerStruct) StorageLocal(p0 context.Context) (map[storiface.ID]string, error) { + if s.Internal.StorageLocal == nil { + return *new(map[storiface.ID]string), ErrNotSupported + } + return s.Internal.StorageLocal(p0) +} + +func (s *WorkerStub) StorageLocal(p0 context.Context) (map[storiface.ID]string, error) { + return *new(map[storiface.ID]string), ErrNotSupported +} + +func (s *WorkerStruct) StorageRedeclareLocal(p0 context.Context, p1 *storiface.ID, p2 bool) error { + if s.Internal.StorageRedeclareLocal == nil { + return ErrNotSupported + } + return s.Internal.StorageRedeclareLocal(p0, p1, p2) +} + +func (s *WorkerStub) StorageRedeclareLocal(p0 context.Context, p1 *storiface.ID, p2 bool) error { + return ErrNotSupported +} + +func (s *WorkerStruct) TaskDisable(p0 context.Context, p1 sealtasks.TaskType) error { + if s.Internal.TaskDisable == nil { + return ErrNotSupported + } + return s.Internal.TaskDisable(p0, p1) +} + +func (s *WorkerStub) TaskDisable(p0 context.Context, p1 sealtasks.TaskType) error { + return ErrNotSupported +} + +func (s *WorkerStruct) TaskEnable(p0 context.Context, p1 sealtasks.TaskType) error { + if s.Internal.TaskEnable == nil { + return ErrNotSupported + } + return s.Internal.TaskEnable(p0, p1) +} + +func (s *WorkerStub) TaskEnable(p0 context.Context, p1 sealtasks.TaskType) error { + return ErrNotSupported +} + +func (s *WorkerStruct) TaskTypes(p0 context.Context) (map[sealtasks.TaskType]struct{}, error) { + if s.Internal.TaskTypes == nil { + return *new(map[sealtasks.TaskType]struct{}), ErrNotSupported + } + return s.Internal.TaskTypes(p0) +} + +func (s *WorkerStub) TaskTypes(p0 context.Context) (map[sealtasks.TaskType]struct{}, error) { + return *new(map[sealtasks.TaskType]struct{}), ErrNotSupported +} + +func (s *WorkerStruct) UnsealPiece(p0 context.Context, p1 storiface.SectorRef, p2 storiface.UnpaddedByteIndex, p3 abi.UnpaddedPieceSize, p4 abi.SealRandomness, p5 cid.Cid) (storiface.CallID, error) { + if s.Internal.UnsealPiece == nil { + return *new(storiface.CallID), ErrNotSupported + } + return s.Internal.UnsealPiece(p0, p1, p2, p3, p4, p5) +} + +func (s *WorkerStub) UnsealPiece(p0 context.Context, p1 storiface.SectorRef, p2 storiface.UnpaddedByteIndex, p3 abi.UnpaddedPieceSize, p4 abi.SealRandomness, p5 cid.Cid) (storiface.CallID, error) { + return *new(storiface.CallID), ErrNotSupported +} + +func (s *WorkerStruct) Version(p0 context.Context) (Version, error) { + if s.Internal.Version == nil { + return *new(Version), ErrNotSupported + } + return s.Internal.Version(p0) +} + +func (s *WorkerStub) Version(p0 context.Context) (Version, error) { + return *new(Version), ErrNotSupported +} + +func (s *WorkerStruct) WaitQuiet(p0 context.Context) error { + if s.Internal.WaitQuiet == nil { + return ErrNotSupported + } + return s.Internal.WaitQuiet(p0) +} + +func (s *WorkerStub) WaitQuiet(p0 context.Context) error { + return ErrNotSupported +} + +var _ ChainIO = new(ChainIOStruct) +var _ Common = new(CommonStruct) +var _ CommonNet = new(CommonNetStruct) +var _ EthSubscriber = new(EthSubscriberStruct) +var _ FullNode = new(FullNodeStruct) +var _ Gateway = new(GatewayStruct) +var _ Net = new(NetStruct) +var _ Signable = new(SignableStruct) +var _ StorageMiner = new(StorageMinerStruct) +var _ Wallet = new(WalletStruct) +var _ Worker = new(WorkerStruct) diff --git a/api/proxy_util.go b/api/proxy_util.go new file mode 100644 index 000000000..ba94a9e5d --- /dev/null +++ b/api/proxy_util.go @@ -0,0 +1,30 @@ +package api + +import "reflect" + +var _internalField = "Internal" + +// GetInternalStructs extracts all pointers to 'Internal' sub-structs from the provided pointer to a proxy struct +func GetInternalStructs(in interface{}) []interface{} { + return getInternalStructs(reflect.ValueOf(in).Elem()) +} + +func getInternalStructs(rv reflect.Value) []interface{} { + var out []interface{} + + internal := rv.FieldByName(_internalField) + ii := internal.Addr().Interface() + out = append(out, ii) + + for i := 0; i < rv.NumField(); i++ { + if rv.Type().Field(i).Name == _internalField { + continue + } + + sub := getInternalStructs(rv.Field(i)) + + out = append(out, sub...) + } + + return out +} diff --git a/api/proxy_util_test.go b/api/proxy_util_test.go new file mode 100644 index 000000000..adc78a7d1 --- /dev/null +++ b/api/proxy_util_test.go @@ -0,0 +1,65 @@ +// stm: #unit +package api + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +type StrA struct { + StrB + + Internal struct { + A int + } +} + +type StrB struct { + Internal struct { + B int + } +} + +type StrC struct { + Internal struct { + Internal struct { + C int + } + } +} + +func TestGetInternalStructs(t *testing.T) { + //stm: @OTHER_IMPLEMENTATION_API_STRUCTS_001 + var proxy StrA + + sts := GetInternalStructs(&proxy) + require.Len(t, sts, 2) + + sa := sts[0].(*struct{ A int }) + sa.A = 3 + sb := sts[1].(*struct{ B int }) + sb.B = 4 + + require.Equal(t, 3, proxy.Internal.A) + require.Equal(t, 4, proxy.StrB.Internal.B) +} + +func TestNestedInternalStructs(t *testing.T) { + //stm: @OTHER_IMPLEMENTATION_API_STRUCTS_001 + var proxy StrC + + // check that only the top-level internal struct gets picked up + + sts := GetInternalStructs(&proxy) + require.Len(t, sts, 1) + + sa := sts[0].(*struct { + Internal struct { + C int + } + }) + sa.Internal.C = 5 + + require.Equal(t, 5, proxy.Internal.Internal.C) +} diff --git a/api/test/deals.go b/api/test/deals.go deleted file mode 100644 index 625be4583..000000000 --- a/api/test/deals.go +++ /dev/null @@ -1,254 +0,0 @@ -package test - -import ( - "bytes" - "context" - "fmt" - "io/ioutil" - "math/rand" - "os" - "path/filepath" - "testing" - "time" - - "github.com/ipfs/go-cid" - - files "github.com/ipfs/go-ipfs-files" - logging "github.com/ipfs/go-log/v2" - "github.com/ipld/go-car" - - "github.com/filecoin-project/go-fil-markets/storagemarket" - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/build" - dag "github.com/ipfs/go-merkledag" - dstest "github.com/ipfs/go-merkledag/test" - unixfile "github.com/ipfs/go-unixfs/file" - - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/node/impl" - ipld "github.com/ipfs/go-ipld-format" -) - -func init() { - logging.SetAllLoggers(logging.LevelInfo) - build.InsecurePoStValidation = true -} - -func TestDealFlow(t *testing.T, b APIBuilder, blocktime time.Duration, carExport bool) { - os.Setenv("BELLMAN_NO_GPU", "1") - - ctx := context.Background() - n, sn := b(t, 1, oneMiner) - client := n[0].FullNode.(*impl.FullNodeAPI) - miner := sn[0] - - addrinfo, err := client.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - - if err := miner.NetConnect(ctx, addrinfo); err != nil { - t.Fatal(err) - } - time.Sleep(time.Second) - - mine := true - done := make(chan struct{}) - go func() { - defer close(done) - for mine { - time.Sleep(blocktime) - if err := sn[0].MineOne(ctx, func(bool) {}); err != nil { - t.Error(err) - } - } - }() - - makeDeal(t, ctx, 6, client, miner, carExport) - - mine = false - fmt.Println("shutting down mining") - <-done -} - -func TestDoubleDealFlow(t *testing.T, b APIBuilder, blocktime time.Duration) { - os.Setenv("BELLMAN_NO_GPU", "1") - - ctx := context.Background() - n, sn := b(t, 1, oneMiner) - client := n[0].FullNode.(*impl.FullNodeAPI) - miner := sn[0] - - addrinfo, err := client.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - - if err := miner.NetConnect(ctx, addrinfo); err != nil { - t.Fatal(err) - } - time.Sleep(time.Second) - - mine := true - done := make(chan struct{}) - - go func() { - defer close(done) - for mine { - time.Sleep(blocktime) - if err := sn[0].MineOne(ctx, func(bool) {}); err != nil { - t.Error(err) - } - } - }() - - makeDeal(t, ctx, 6, client, miner, false) - makeDeal(t, ctx, 7, client, miner, false) - - mine = false - fmt.Println("shutting down mining") - <-done -} - -func makeDeal(t *testing.T, ctx context.Context, rseed int, client *impl.FullNodeAPI, miner TestStorageNode, carExport bool) { - data := make([]byte, 1600) - rand.New(rand.NewSource(int64(rseed))).Read(data) - - r := bytes.NewReader(data) - fcid, err := client.ClientImportLocal(ctx, r) - if err != nil { - t.Fatal(err) - } - - fmt.Println("FILE CID: ", fcid) - - deal := startDeal(t, ctx, miner, client, fcid) - - // TODO: this sleep is only necessary because deals don't immediately get logged in the dealstore, we should fix this - time.Sleep(time.Second) - waitDealSealed(t, ctx, client, deal) - - // Retrieval - - testRetrieval(t, ctx, err, client, fcid, carExport, data) -} - -func startDeal(t *testing.T, ctx context.Context, miner TestStorageNode, client *impl.FullNodeAPI, fcid cid.Cid) *cid.Cid { - maddr, err := miner.ActorAddress(ctx) - if err != nil { - t.Fatal(err) - } - - addr, err := client.WalletDefaultAddress(ctx) - if err != nil { - t.Fatal(err) - } - deal, err := client.ClientStartDeal(ctx, &api.StartDealParams{ - Data: &storagemarket.DataRef{Root: fcid}, - Wallet: addr, - Miner: maddr, - EpochPrice: types.NewInt(1000000), - MinBlocksDuration: 100, - }) - if err != nil { - t.Fatalf("%+v", err) - } - return deal -} - -func waitDealSealed(t *testing.T, ctx context.Context, client *impl.FullNodeAPI, deal *cid.Cid) { -loop: - for { - di, err := client.ClientGetDealInfo(ctx, *deal) - if err != nil { - t.Fatal(err) - } - switch di.State { - case storagemarket.StorageDealProposalRejected: - t.Fatal("deal rejected") - case storagemarket.StorageDealFailing: - t.Fatal("deal failed") - case storagemarket.StorageDealError: - t.Fatal("deal errored", di.Message) - case storagemarket.StorageDealActive: - fmt.Println("COMPLETE", di) - break loop - } - fmt.Println("Deal state: ", storagemarket.DealStates[di.State]) - time.Sleep(time.Second / 2) - } -} - -func testRetrieval(t *testing.T, ctx context.Context, err error, client *impl.FullNodeAPI, fcid cid.Cid, carExport bool, data []byte) { - offers, err := client.ClientFindData(ctx, fcid) - if err != nil { - t.Fatal(err) - } - - if len(offers) < 1 { - t.Fatal("no offers") - } - - rpath, err := ioutil.TempDir("", "lotus-retrieve-test-") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(rpath) - - caddr, err := client.WalletDefaultAddress(ctx) - if err != nil { - t.Fatal(err) - } - - ref := &api.FileRef{ - Path: filepath.Join(rpath, "ret"), - IsCAR: carExport, - } - err = client.ClientRetrieve(ctx, offers[0].Order(caddr), ref) - if err != nil { - t.Fatalf("%+v", err) - } - - rdata, err := ioutil.ReadFile(filepath.Join(rpath, "ret")) - if err != nil { - t.Fatal(err) - } - - if carExport { - rdata = extractCarData(t, ctx, rdata, rpath) - } - - if !bytes.Equal(rdata, data) { - t.Fatal("wrong data retrieved") - } -} - -func extractCarData(t *testing.T, ctx context.Context, rdata []byte, rpath string) []byte { - bserv := dstest.Bserv() - ch, err := car.LoadCar(bserv.Blockstore(), bytes.NewReader(rdata)) - if err != nil { - t.Fatal(err) - } - b, err := bserv.GetBlock(ctx, ch.Roots[0]) - if err != nil { - t.Fatal(err) - } - nd, err := ipld.Decode(b) - if err != nil { - t.Fatal(err) - } - dserv := dag.NewDAGService(bserv) - fil, err := unixfile.NewUnixfsFile(ctx, dserv, nd) - if err != nil { - t.Fatal(err) - } - outPath := filepath.Join(rpath, "retLoadedCAR") - if err := files.WriteTo(fil, outPath); err != nil { - t.Fatal(err) - } - rdata, err = ioutil.ReadFile(outPath) - if err != nil { - t.Fatal(err) - } - return rdata -} diff --git a/api/test/mining.go b/api/test/mining.go deleted file mode 100644 index 374999df1..000000000 --- a/api/test/mining.go +++ /dev/null @@ -1,199 +0,0 @@ -package test - -import ( - "bytes" - "context" - "fmt" - "math/rand" - "os" - "sync/atomic" - "testing" - "time" - - logging "github.com/ipfs/go-log/v2" - - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/stretchr/testify/require" - - "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/node/impl" -) - -var log = logging.Logger("apitest") - -func (ts *testSuite) testMining(t *testing.T) { - ctx := context.Background() - apis, sn := ts.makeNodes(t, 1, oneMiner) - api := apis[0] - - h1, err := api.ChainHead(ctx) - require.NoError(t, err) - require.Equal(t, abi.ChainEpoch(0), h1.Height()) - - newHeads, err := api.ChainNotify(ctx) - require.NoError(t, err) - <-newHeads - - err = sn[0].MineOne(ctx, func(bool) {}) - require.NoError(t, err) - - <-newHeads - - h2, err := api.ChainHead(ctx) - require.NoError(t, err) - require.Equal(t, abi.ChainEpoch(1), h2.Height()) -} - -func (ts *testSuite) testMiningReal(t *testing.T) { - build.InsecurePoStValidation = false - defer func() { - build.InsecurePoStValidation = true - }() - - ctx := context.Background() - apis, sn := ts.makeNodes(t, 1, oneMiner) - api := apis[0] - - h1, err := api.ChainHead(ctx) - require.NoError(t, err) - require.Equal(t, abi.ChainEpoch(0), h1.Height()) - - newHeads, err := api.ChainNotify(ctx) - require.NoError(t, err) - <-newHeads - - err = sn[0].MineOne(ctx, func(bool) {}) - require.NoError(t, err) - - <-newHeads - - h2, err := api.ChainHead(ctx) - require.NoError(t, err) - require.Equal(t, abi.ChainEpoch(1), h2.Height()) - - err = sn[0].MineOne(ctx, func(bool) {}) - require.NoError(t, err) - - <-newHeads - - h2, err = api.ChainHead(ctx) - require.NoError(t, err) - require.Equal(t, abi.ChainEpoch(2), h2.Height()) -} - -func TestDealMining(t *testing.T, b APIBuilder, blocktime time.Duration, carExport bool) { - os.Setenv("BELLMAN_NO_GPU", "1") - - // test making a deal with a fresh miner, and see if it starts to mine - - ctx := context.Background() - n, sn := b(t, 1, []StorageMiner{ - {Full: 0, Preseal: PresealGenesis}, - {Full: 0, Preseal: 0}, // TODO: Add support for storage miners on non-first full node - }) - client := n[0].FullNode.(*impl.FullNodeAPI) - provider := sn[1] - genesisMiner := sn[0] - - addrinfo, err := client.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - - if err := provider.NetConnect(ctx, addrinfo); err != nil { - t.Fatal(err) - } - - if err := genesisMiner.NetConnect(ctx, addrinfo); err != nil { - t.Fatal(err) - } - - time.Sleep(time.Second) - - data := make([]byte, 600) - rand.New(rand.NewSource(5)).Read(data) - - r := bytes.NewReader(data) - fcid, err := client.ClientImportLocal(ctx, r) - if err != nil { - t.Fatal(err) - } - - fmt.Println("FILE CID: ", fcid) - - var mine int32 = 1 - done := make(chan struct{}) - minedTwo := make(chan struct{}) - - go func() { - defer close(done) - - prevExpect := 0 - for atomic.LoadInt32(&mine) != 0 { - wait := make(chan int, 2) - mdone := func(mined bool) { - go func() { - n := 0 - if mined { - n = 1 - } - wait <- n - }() - } - - if err := sn[0].MineOne(ctx, mdone); err != nil { - t.Error(err) - } - - if err := sn[1].MineOne(ctx, mdone); err != nil { - t.Error(err) - } - - expect := <-wait - expect += <-wait - - time.Sleep(blocktime) - - for { - n := 0 - for i, node := range sn { - mb, err := node.MiningBase(ctx) - if err != nil { - t.Error(err) - return - } - - if len(mb.Cids()) != expect { - log.Warnf("node %d mining base not complete (%d, want %d)", i, len(mb.Cids()), expect) - continue - } - n++ - } - if n == len(sn) { - break - } - time.Sleep(blocktime) - } - - if prevExpect == 2 && expect == 2 && minedTwo != nil { - close(minedTwo) - minedTwo = nil - } - - prevExpect = expect - } - }() - - deal := startDeal(t, ctx, provider, client, fcid) - - // TODO: this sleep is only necessary because deals don't immediately get logged in the dealstore, we should fix this - time.Sleep(time.Second) - - waitDealSealed(t, ctx, client, deal) - - <-minedTwo - - atomic.StoreInt32(&mine, 0) - fmt.Println("shutting down mining") - <-done -} diff --git a/api/test/test.go b/api/test/test.go deleted file mode 100644 index 43dc7a25e..000000000 --- a/api/test/test.go +++ /dev/null @@ -1,124 +0,0 @@ -package test - -import ( - "context" - "testing" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/build" - "github.com/stretchr/testify/assert" -) - -type TestNode struct { - api.FullNode -} - -type TestStorageNode struct { - api.StorageMiner - - MineOne func(context.Context, func(bool)) error -} - -var PresealGenesis = -1 - -type StorageMiner struct { - Full int - Preseal int -} - -// APIBuilder is a function which is invoked in test suite to provide -// test nodes and networks -// -// storage array defines storage nodes, numbers in the array specify full node -// index the storage node 'belongs' to -type APIBuilder func(t *testing.T, nFull int, storage []StorageMiner) ([]TestNode, []TestStorageNode) -type testSuite struct { - makeNodes APIBuilder -} - -// TestApis is the entry point to API test suite -func TestApis(t *testing.T, b APIBuilder) { - ts := testSuite{ - makeNodes: b, - } - - t.Run("version", ts.testVersion) - t.Run("id", ts.testID) - t.Run("testConnectTwo", ts.testConnectTwo) - t.Run("testMining", ts.testMining) - t.Run("testMiningReal", ts.testMiningReal) -} - -var oneMiner = []StorageMiner{{Full: 0, Preseal: PresealGenesis}} - -func (ts *testSuite) testVersion(t *testing.T) { - ctx := context.Background() - apis, _ := ts.makeNodes(t, 1, oneMiner) - api := apis[0] - - v, err := api.Version(ctx) - if err != nil { - t.Fatal(err) - } - if v.Version != build.BuildVersion { - t.Error("Version didn't work properly") - } -} - -func (ts *testSuite) testID(t *testing.T) { - ctx := context.Background() - apis, _ := ts.makeNodes(t, 1, oneMiner) - api := apis[0] - - id, err := api.ID(ctx) - if err != nil { - t.Fatal(err) - } - assert.Regexp(t, "^12", id.Pretty()) -} - -func (ts *testSuite) testConnectTwo(t *testing.T) { - ctx := context.Background() - apis, _ := ts.makeNodes(t, 2, 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") - } -} diff --git a/api/types.go b/api/types.go index 732f1f328..625b770c5 100644 --- a/api/types.go +++ b/api/types.go @@ -2,11 +2,29 @@ package api import ( "encoding/json" + "fmt" + "time" + "github.com/google/uuid" + "github.com/ipfs/go-cid" + "github.com/ipfs/go-graphsync" + "github.com/ipld/go-ipld-prime" + "github.com/ipld/go-ipld-prime/codec/dagjson" + pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/libp2p/go-libp2p/core/network" + "github.com/libp2p/go-libp2p/core/peer" ma "github.com/multiformats/go-multiaddr" + + "github.com/filecoin-project/go-address" + datatransfer "github.com/filecoin-project/go-data-transfer/v2" + "github.com/filecoin-project/go-fil-markets/retrievalmarket" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/builtin/v9/miner" + + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/node/modules/dtypes" ) -// TODO: check if this exists anywhere else type MultiaddrSlice []ma.Multiaddr func (m *MultiaddrSlice) UnmarshalJSON(raw []byte) (err error) { @@ -32,3 +50,359 @@ type ObjStat struct { Size uint64 Links uint64 } + +type PubsubScore struct { + ID peer.ID + Score *pubsub.PeerScoreSnapshot +} + +type MessageSendSpec struct { + MaxFee abi.TokenAmount + MsgUuid uuid.UUID +} + +type MpoolMessageWhole struct { + Msg *types.Message + Spec *MessageSendSpec +} + +// GraphSyncDataTransfer provides diagnostics on a data transfer happening over graphsync +type GraphSyncDataTransfer struct { + // GraphSync request id for this transfer + RequestID *graphsync.RequestID + // Graphsync state for this transfer + RequestState string + // If a channel ID is present, indicates whether this is the current graphsync request for this channel + // (could have changed in a restart) + IsCurrentChannelRequest bool + // Data transfer channel ID for this transfer + ChannelID *datatransfer.ChannelID + // Data transfer state for this transfer + ChannelState *DataTransferChannel + // Diagnostic information about this request -- and unexpected inconsistencies in + // request state + Diagnostics []string +} + +// TransferDiagnostics give current information about transfers going over graphsync that may be helpful for debugging +type TransferDiagnostics struct { + ReceivingTransfers []*GraphSyncDataTransfer + SendingTransfers []*GraphSyncDataTransfer +} + +type DataTransferChannel struct { + TransferID datatransfer.TransferID + Status datatransfer.Status + BaseCID cid.Cid + IsInitiator bool + IsSender bool + Voucher string + Message string + OtherPeer peer.ID + Transferred uint64 + Stages *datatransfer.ChannelStages +} + +// NewDataTransferChannel constructs an API DataTransferChannel type from full channel state snapshot and a host id +func NewDataTransferChannel(hostID peer.ID, channelState datatransfer.ChannelState) DataTransferChannel { + channel := DataTransferChannel{ + TransferID: channelState.TransferID(), + Status: channelState.Status(), + BaseCID: channelState.BaseCID(), + IsSender: channelState.Sender() == hostID, + Message: channelState.Message(), + } + voucher := channelState.Voucher() + voucherJSON, err := ipld.Encode(voucher.Voucher, dagjson.Encode) + if err != nil { + channel.Voucher = fmt.Errorf("Voucher Serialization: %w", err).Error() + } else { + channel.Voucher = string(voucherJSON) + } + if channel.IsSender { + channel.IsInitiator = !channelState.IsPull() + channel.Transferred = channelState.Sent() + channel.OtherPeer = channelState.Recipient() + } else { + channel.IsInitiator = channelState.IsPull() + channel.Transferred = channelState.Received() + channel.OtherPeer = channelState.Sender() + } + return channel +} + +type NetStat struct { + System *network.ScopeStat `json:",omitempty"` + Transient *network.ScopeStat `json:",omitempty"` + Services map[string]network.ScopeStat `json:",omitempty"` + Protocols map[string]network.ScopeStat `json:",omitempty"` + Peers map[string]network.ScopeStat `json:",omitempty"` +} + +type NetLimit struct { + Memory int64 `json:",omitempty"` + + Streams, StreamsInbound, StreamsOutbound int + Conns, ConnsInbound, ConnsOutbound int + FD int +} + +type NetBlockList struct { + Peers []peer.ID + IPAddrs []string + IPSubnets []string +} + +type ExtendedPeerInfo struct { + ID peer.ID + Agent string + Addrs []string + Protocols []string + ConnMgrMeta *ConnMgrInfo +} + +type ConnMgrInfo struct { + FirstSeen time.Time + Value int + Tags map[string]int + Conns map[string]time.Time +} + +type NodeStatus struct { + SyncStatus NodeSyncStatus + PeerStatus NodePeerStatus + ChainStatus NodeChainStatus +} + +type NodeSyncStatus struct { + Epoch uint64 + Behind uint64 +} + +type NodePeerStatus struct { + PeersToPublishMsgs int + PeersToPublishBlocks int +} + +type NodeChainStatus struct { + BlocksPerTipsetLast100 float64 + BlocksPerTipsetLastFinality float64 +} + +type CheckStatusCode int + +//go:generate go run golang.org/x/tools/cmd/stringer -type=CheckStatusCode -trimprefix=CheckStatus +const ( + _ CheckStatusCode = iota + // Message Checks + CheckStatusMessageSerialize + CheckStatusMessageSize + CheckStatusMessageValidity + CheckStatusMessageMinGas + CheckStatusMessageMinBaseFee + CheckStatusMessageBaseFee + CheckStatusMessageBaseFeeLowerBound + CheckStatusMessageBaseFeeUpperBound + CheckStatusMessageGetStateNonce + CheckStatusMessageNonce + CheckStatusMessageGetStateBalance + CheckStatusMessageBalance +) + +type CheckStatus struct { + Code CheckStatusCode + OK bool + Err string + Hint map[string]interface{} +} + +type MessageCheckStatus struct { + Cid cid.Cid + CheckStatus +} + +type MessagePrototype struct { + Message types.Message + ValidNonce bool +} + +type RetrievalInfo struct { + PayloadCID cid.Cid + ID retrievalmarket.DealID + PieceCID *cid.Cid + PricePerByte abi.TokenAmount + UnsealPrice abi.TokenAmount + + Status retrievalmarket.DealStatus + Message string // more information about deal state, particularly errors + Provider peer.ID + BytesReceived uint64 + BytesPaidFor uint64 + TotalPaid abi.TokenAmount + + TransferChannelID *datatransfer.ChannelID + DataTransfer *DataTransferChannel + + // optional event if part of ClientGetRetrievalUpdates + Event *retrievalmarket.ClientEvent +} + +type RestrievalRes struct { + DealID retrievalmarket.DealID +} + +// Selector specifies ipld selector string +// - if the string starts with '{', it's interpreted as json selector string +// see https://ipld.io/specs/selectors/ and https://ipld.io/specs/selectors/fixtures/selector-fixtures-1/ +// - otherwise the string is interpreted as ipld-selector-text-lite (simple ipld path) +// see https://github.com/ipld/go-ipld-selector-text-lite +type Selector string + +type DagSpec struct { + // DataSelector matches data to be retrieved + // - when using textselector, the path specifies subtree + // - the matched graph must have a single root + DataSelector *Selector + + // ExportMerkleProof is applicable only when exporting to a CAR file via a path textselector + // When true, in addition to the selection target, the resulting CAR will contain every block along the + // path back to, and including the original root + // When false the resulting CAR contains only the blocks of the target subdag + ExportMerkleProof bool +} + +type ExportRef struct { + Root cid.Cid + + // DAGs array specifies a list of DAGs to export + // - If exporting into unixfs files, only one DAG is supported, DataSelector is only used to find the targeted root node + // - If exporting into a car file + // - When exactly one text-path DataSelector is specified exports the subgraph and its full merkle-path from the original root + // - Otherwise ( multiple paths and/or JSON selector specs) determines each individual subroot and exports the subtrees as a multi-root car + // - When not specified defaults to a single DAG: + // - Data - the entire DAG: `{"R":{"l":{"none":{}},":>":{"a":{">":{"@":{}}}}}}` + DAGs []DagSpec + + FromLocalCAR string // if specified, get data from a local CARv2 file. + DealID retrievalmarket.DealID +} + +type MinerInfo struct { + Owner address.Address // Must be an ID-address. + Worker address.Address // Must be an ID-address. + NewWorker address.Address // Must be an ID-address. + ControlAddresses []address.Address // Must be an ID-addresses. + WorkerChangeEpoch abi.ChainEpoch + PeerId *peer.ID + Multiaddrs []abi.Multiaddrs + WindowPoStProofType abi.RegisteredPoStProof + SectorSize abi.SectorSize + WindowPoStPartitionSectors uint64 + ConsensusFaultElapsed abi.ChainEpoch + Beneficiary address.Address + BeneficiaryTerm *miner.BeneficiaryTerm + PendingBeneficiaryTerm *miner.PendingBeneficiaryChange +} + +type NetworkParams struct { + NetworkName dtypes.NetworkName + BlockDelaySecs uint64 + ConsensusMinerMinPower abi.StoragePower + SupportedProofTypes []abi.RegisteredSealProof + PreCommitChallengeDelay abi.ChainEpoch + ForkUpgradeParams ForkUpgradeParams +} + +type ForkUpgradeParams struct { + UpgradeSmokeHeight abi.ChainEpoch + UpgradeBreezeHeight abi.ChainEpoch + UpgradeIgnitionHeight abi.ChainEpoch + UpgradeLiftoffHeight abi.ChainEpoch + UpgradeAssemblyHeight abi.ChainEpoch + UpgradeRefuelHeight abi.ChainEpoch + UpgradeTapeHeight abi.ChainEpoch + UpgradeKumquatHeight abi.ChainEpoch + BreezeGasTampingDuration abi.ChainEpoch + UpgradeCalicoHeight abi.ChainEpoch + UpgradePersianHeight abi.ChainEpoch + UpgradeOrangeHeight abi.ChainEpoch + UpgradeClausHeight abi.ChainEpoch + UpgradeTrustHeight abi.ChainEpoch + UpgradeNorwegianHeight abi.ChainEpoch + UpgradeTurboHeight abi.ChainEpoch + UpgradeHyperdriveHeight abi.ChainEpoch + UpgradeChocolateHeight abi.ChainEpoch + UpgradeOhSnapHeight abi.ChainEpoch + UpgradeSkyrHeight abi.ChainEpoch + UpgradeSharkHeight abi.ChainEpoch + UpgradeHyggeHeight abi.ChainEpoch + UpgradeLightningHeight abi.ChainEpoch + UpgradeThunderHeight abi.ChainEpoch +} + +type NonceMapType map[address.Address]uint64 +type MsgUuidMapType map[uuid.UUID]*types.SignedMessage + +type RaftStateData struct { + NonceMap NonceMapType + MsgUuids MsgUuidMapType +} + +func (n *NonceMapType) MarshalJSON() ([]byte, error) { + marshalled := make(map[string]uint64) + for a, n := range *n { + marshalled[a.String()] = n + } + return json.Marshal(marshalled) +} + +func (n *NonceMapType) UnmarshalJSON(b []byte) error { + unmarshalled := make(map[string]uint64) + err := json.Unmarshal(b, &unmarshalled) + if err != nil { + return err + } + *n = make(map[address.Address]uint64) + for saddr, nonce := range unmarshalled { + a, err := address.NewFromString(saddr) + if err != nil { + return err + } + (*n)[a] = nonce + } + return nil +} + +func (m *MsgUuidMapType) MarshalJSON() ([]byte, error) { + marshalled := make(map[string]*types.SignedMessage) + for u, msg := range *m { + marshalled[u.String()] = msg + } + return json.Marshal(marshalled) +} + +func (m *MsgUuidMapType) UnmarshalJSON(b []byte) error { + unmarshalled := make(map[string]*types.SignedMessage) + err := json.Unmarshal(b, &unmarshalled) + if err != nil { + return err + } + *m = make(map[uuid.UUID]*types.SignedMessage) + for suid, msg := range unmarshalled { + u, err := uuid.Parse(suid) + if err != nil { + return err + } + (*m)[u] = msg + } + return nil +} + +// ChainExportConfig holds configuration for chain ranged exports. +type ChainExportConfig struct { + WriteBufferSize int + NumWorkers int + IncludeMessages bool + IncludeReceipts bool + IncludeStateRoots bool +} diff --git a/api/types/actors.go b/api/types/actors.go new file mode 100644 index 000000000..d55ef3e10 --- /dev/null +++ b/api/types/actors.go @@ -0,0 +1,5 @@ +package apitypes + +import "github.com/filecoin-project/go-state-types/network" + +type NetworkVersion = network.Version diff --git a/api/types/openrpc.go b/api/types/openrpc.go new file mode 100644 index 000000000..7d65cbde6 --- /dev/null +++ b/api/types/openrpc.go @@ -0,0 +1,3 @@ +package apitypes + +type OpenRPCDocument map[string]interface{} diff --git a/api/types/rpc.go b/api/types/rpc.go new file mode 100644 index 000000000..4bde6dfbc --- /dev/null +++ b/api/types/rpc.go @@ -0,0 +1,5 @@ +package apitypes + +type Aliaser interface { + AliasMethod(alias, original string) +} diff --git a/api/utils.go b/api/utils.go index 13d5c92cb..a9d02c31b 100644 --- a/api/utils.go +++ b/api/utils.go @@ -4,7 +4,7 @@ import ( "context" "github.com/filecoin-project/go-address" - "github.com/filecoin-project/specs-actors/actors/crypto" + "github.com/filecoin-project/go-state-types/crypto" ) type SignFunc = func(context.Context, []byte) (*crypto.Signature, error) diff --git a/api/v0api/full.go b/api/v0api/full.go new file mode 100644 index 000000000..322f72449 --- /dev/null +++ b/api/v0api/full.go @@ -0,0 +1,776 @@ +package v0api + +import ( + "context" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + textselector "github.com/ipld/go-ipld-selector-text-lite" + "github.com/libp2p/go-libp2p/core/peer" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-bitfield" + datatransfer "github.com/filecoin-project/go-data-transfer/v2" + "github.com/filecoin-project/go-fil-markets/retrievalmarket" + "github.com/filecoin-project/go-fil-markets/storagemarket" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/builtin/v8/paych" + "github.com/filecoin-project/go-state-types/builtin/v9/miner" + verifregtypes "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/go-state-types/dline" + abinetwork "github.com/filecoin-project/go-state-types/network" + + "github.com/filecoin-project/lotus/api" + apitypes "github.com/filecoin-project/lotus/api/types" + lminer "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/types" + marketevents "github.com/filecoin-project/lotus/markets/loggers" + "github.com/filecoin-project/lotus/node/modules/dtypes" + "github.com/filecoin-project/lotus/node/repo/imports" +) + +//go:generate go run github.com/golang/mock/mockgen -destination=v0mocks/mock_full.go -package=v0mocks . FullNode + +// MODIFYING THE API INTERFACE +// +// NOTE: This is the V0 (Stable) API - when adding methods to this interface, +// you'll need to make sure they are also present on the V1 (Unstable) API +// +// This API is implemented in `v1_wrapper.go` as a compatibility layer backed +// by the V1 api +// +// When adding / changing methods in this file: +// * Do the change here +// * Adjust implementation in `node/impl/` +// * Run `make gen` - this will: +// * Generate proxy structs +// * Generate mocks +// * Generate markdown docs +// * Generate openrpc blobs + +// FullNode API is a low-level interface to the Filecoin network full node +type FullNode interface { + Common + Net + + // MethodGroup: Chain + // The Chain method group contains methods for interacting with the + // blockchain, but that do not require any form of state computation. + + // ChainNotify returns channel with chain head updates. + // First message is guaranteed to be of len == 1, and type == 'current'. + ChainNotify(context.Context) (<-chan []*api.HeadChange, error) //perm:read + + // ChainHead returns the current head of the chain. + ChainHead(context.Context) (*types.TipSet, error) //perm:read + + // ChainGetRandomnessFromTickets is used to sample the chain for randomness. + ChainGetRandomnessFromTickets(ctx context.Context, tsk types.TipSetKey, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) (abi.Randomness, error) //perm:read + + // ChainGetRandomnessFromBeacon is used to sample the beacon for randomness. + ChainGetRandomnessFromBeacon(ctx context.Context, tsk types.TipSetKey, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) (abi.Randomness, error) //perm:read + + // ChainGetBlock returns the block specified by the given CID. + ChainGetBlock(context.Context, cid.Cid) (*types.BlockHeader, error) //perm:read + // ChainGetTipSet returns the tipset specified by the given TipSetKey. + ChainGetTipSet(context.Context, types.TipSetKey) (*types.TipSet, error) //perm:read + + // ChainGetBlockMessages returns messages stored in the specified block. + // + // Note: If there are multiple blocks in a tipset, it's likely that some + // messages will be duplicated. It's also possible for blocks in a tipset to have + // different messages from the same sender at the same nonce. When that happens, + // only the first message (in a block with lowest ticket) will be considered + // for execution + // + // NOTE: THIS METHOD SHOULD ONLY BE USED FOR GETTING MESSAGES IN A SPECIFIC BLOCK + // + // DO NOT USE THIS METHOD TO GET MESSAGES INCLUDED IN A TIPSET + // Use ChainGetParentMessages, which will perform correct message deduplication + ChainGetBlockMessages(ctx context.Context, blockCid cid.Cid) (*api.BlockMessages, error) //perm:read + + // ChainGetParentReceipts returns receipts for messages in parent tipset of + // the specified block. The receipts in the list returned is one-to-one with the + // messages returned by a call to ChainGetParentMessages with the same blockCid. + ChainGetParentReceipts(ctx context.Context, blockCid cid.Cid) ([]*types.MessageReceipt, error) //perm:read + + // ChainGetParentMessages returns messages stored in parent tipset of the + // specified block. + ChainGetParentMessages(ctx context.Context, blockCid cid.Cid) ([]api.Message, error) //perm:read + + // ChainGetMessagesInTipset returns message stores in current tipset + ChainGetMessagesInTipset(ctx context.Context, tsk types.TipSetKey) ([]api.Message, error) //perm:read + + // ChainGetTipSetByHeight looks back for a tipset at the specified epoch. + // If there are no blocks at the specified epoch, a tipset at an earlier epoch + // will be returned. + ChainGetTipSetByHeight(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error) //perm:read + + // ChainReadObj reads ipld nodes referenced by the specified CID from chain + // blockstore and returns raw bytes. + ChainReadObj(context.Context, cid.Cid) ([]byte, error) //perm:read + + // ChainDeleteObj deletes node referenced by the given CID + ChainDeleteObj(context.Context, cid.Cid) error //perm:admin + + // ChainPutObj puts and object into the blockstore + ChainPutObj(context.Context, blocks.Block) error + + // ChainHasObj checks if a given CID exists in the chain blockstore. + ChainHasObj(context.Context, cid.Cid) (bool, error) //perm:read + + // ChainStatObj returns statistics about the graph referenced by 'obj'. + // If 'base' is also specified, then the returned stat will be a diff + // between the two objects. + ChainStatObj(ctx context.Context, obj cid.Cid, base cid.Cid) (api.ObjStat, error) //perm:read + + // ChainSetHead forcefully sets current chain head. Use with caution. + ChainSetHead(context.Context, types.TipSetKey) error //perm:admin + + // ChainGetGenesis returns the genesis tipset. + ChainGetGenesis(context.Context) (*types.TipSet, error) //perm:read + + // ChainTipSetWeight computes weight for the specified tipset. + ChainTipSetWeight(context.Context, types.TipSetKey) (types.BigInt, error) //perm:read + ChainGetNode(ctx context.Context, p string) (*api.IpldObject, error) //perm:read + + // ChainGetMessage reads a message referenced by the specified CID from the + // chain blockstore. + ChainGetMessage(context.Context, cid.Cid) (*types.Message, error) //perm:read + + // ChainGetPath returns a set of revert/apply operations needed to get from + // one tipset to another, for example: + // ``` + // to + // ^ + // from tAA + // ^ ^ + // tBA tAB + // ^---*--^ + // ^ + // tRR + // ``` + // Would return `[revert(tBA), apply(tAB), apply(tAA)]` + ChainGetPath(ctx context.Context, from types.TipSetKey, to types.TipSetKey) ([]*api.HeadChange, error) //perm:read + + // ChainExport returns a stream of bytes with CAR dump of chain data. + // The exported chain data includes the header chain from the given tipset + // back to genesis, the entire genesis state, and the most recent 'nroots' + // state trees. + // If oldmsgskip is set, messages from before the requested roots are also not included. + ChainExport(ctx context.Context, nroots abi.ChainEpoch, oldmsgskip bool, tsk types.TipSetKey) (<-chan []byte, error) //perm:read + + // MethodGroup: Beacon + // The Beacon method group contains methods for interacting with the random beacon (DRAND) + + // BeaconGetEntry returns the beacon entry for the given filecoin epoch. If + // the entry has not yet been produced, the call will block until the entry + // becomes available + BeaconGetEntry(ctx context.Context, epoch abi.ChainEpoch) (*types.BeaconEntry, error) //perm:read + + // GasEstimateFeeCap estimates gas fee cap + GasEstimateFeeCap(context.Context, *types.Message, int64, types.TipSetKey) (types.BigInt, error) //perm:read + + // GasEstimateGasLimit estimates gas used by the message and returns it. + // It fails if message fails to execute. + GasEstimateGasLimit(context.Context, *types.Message, types.TipSetKey) (int64, error) //perm:read + + // GasEstimateGasPremium estimates what gas price should be used for a + // message to have high likelihood of inclusion in `nblocksincl` epochs. + + GasEstimateGasPremium(_ context.Context, nblocksincl uint64, + sender address.Address, gaslimit int64, tsk types.TipSetKey) (types.BigInt, error) //perm:read + + // GasEstimateMessageGas estimates gas values for unset message gas fields + GasEstimateMessageGas(context.Context, *types.Message, *api.MessageSendSpec, types.TipSetKey) (*types.Message, error) //perm:read + + // MethodGroup: Sync + // The Sync method group contains methods for interacting with and + // observing the lotus sync service. + + // SyncState returns the current status of the lotus sync system. + SyncState(context.Context) (*api.SyncState, error) //perm:read + + // SyncSubmitBlock can be used to submit a newly created block to the. + // network through this node + SyncSubmitBlock(ctx context.Context, blk *types.BlockMsg) error //perm:write + + // SyncIncomingBlocks returns a channel streaming incoming, potentially not + // yet synced block headers. + SyncIncomingBlocks(ctx context.Context) (<-chan *types.BlockHeader, error) //perm:read + + // SyncCheckpoint marks a blocks as checkpointed, meaning that it won't ever fork away from it. + SyncCheckpoint(ctx context.Context, tsk types.TipSetKey) error //perm:admin + + // SyncMarkBad marks a blocks as bad, meaning that it won't ever by synced. + // Use with extreme caution. + SyncMarkBad(ctx context.Context, bcid cid.Cid) error //perm:admin + + // SyncUnmarkBad unmarks a blocks as bad, making it possible to be validated and synced again. + SyncUnmarkBad(ctx context.Context, bcid cid.Cid) error //perm:admin + + // SyncUnmarkAllBad purges bad block cache, making it possible to sync to chains previously marked as bad + SyncUnmarkAllBad(ctx context.Context) error //perm:admin + + // SyncCheckBad checks if a block was marked as bad, and if it was, returns + // the reason. + SyncCheckBad(ctx context.Context, bcid cid.Cid) (string, error) //perm:read + + // SyncValidateTipset indicates whether the provided tipset is valid or not + SyncValidateTipset(ctx context.Context, tsk types.TipSetKey) (bool, error) //perm:read + + // MethodGroup: Mpool + // The Mpool methods are for interacting with the message pool. The message pool + // manages all incoming and outgoing 'messages' going over the network. + + // MpoolPending returns pending mempool messages. + MpoolPending(context.Context, types.TipSetKey) ([]*types.SignedMessage, error) //perm:read + + // MpoolSelect returns a list of pending messages for inclusion in the next block + MpoolSelect(context.Context, types.TipSetKey, float64) ([]*types.SignedMessage, error) //perm:read + + // MpoolPush pushes a signed message to mempool. + MpoolPush(context.Context, *types.SignedMessage) (cid.Cid, error) //perm:write + + // MpoolPushUntrusted pushes a signed message to mempool from untrusted sources. + MpoolPushUntrusted(context.Context, *types.SignedMessage) (cid.Cid, error) //perm:write + + // MpoolPushMessage atomically assigns a nonce, signs, and pushes a message + // to mempool. + // maxFee is only used when GasFeeCap/GasPremium fields aren't specified + // + // When maxFee is set to 0, MpoolPushMessage will guess appropriate fee + // based on current chain conditions + MpoolPushMessage(ctx context.Context, msg *types.Message, spec *api.MessageSendSpec) (*types.SignedMessage, error) //perm:sign + + // MpoolBatchPush batch pushes a signed message to mempool. + MpoolBatchPush(context.Context, []*types.SignedMessage) ([]cid.Cid, error) //perm:write + + // MpoolBatchPushUntrusted batch pushes a signed message to mempool from untrusted sources. + MpoolBatchPushUntrusted(context.Context, []*types.SignedMessage) ([]cid.Cid, error) //perm:write + + // MpoolBatchPushMessage batch pushes a unsigned message to mempool. + MpoolBatchPushMessage(context.Context, []*types.Message, *api.MessageSendSpec) ([]*types.SignedMessage, error) //perm:sign + + // MpoolGetNonce gets next nonce for the specified sender. + // Note that this method may not be atomic. Use MpoolPushMessage instead. + MpoolGetNonce(context.Context, address.Address) (uint64, error) //perm:read + MpoolSub(context.Context) (<-chan api.MpoolUpdate, error) //perm:read + + // MpoolClear clears pending messages from the mpool + MpoolClear(context.Context, bool) error //perm:write + + // MpoolGetConfig returns (a copy of) the current mpool config + MpoolGetConfig(context.Context) (*types.MpoolConfig, error) //perm:read + // MpoolSetConfig sets the mpool config to (a copy of) the supplied config + MpoolSetConfig(context.Context, *types.MpoolConfig) error //perm:admin + + // MethodGroup: Miner + + MinerGetBaseInfo(context.Context, address.Address, abi.ChainEpoch, types.TipSetKey) (*api.MiningBaseInfo, error) //perm:read + MinerCreateBlock(context.Context, *api.BlockTemplate) (*types.BlockMsg, error) //perm:write + + // // UX ? + + // MethodGroup: Wallet + + // WalletNew creates a new address in the wallet with the given sigType. + // Available key types: bls, secp256k1, secp256k1-ledger + // Support for numerical types: 1 - secp256k1, 2 - BLS is deprecated + WalletNew(context.Context, types.KeyType) (address.Address, error) //perm:write + // WalletHas indicates whether the given address is in the wallet. + WalletHas(context.Context, address.Address) (bool, error) //perm:write + // WalletList lists all the addresses in the wallet. + WalletList(context.Context) ([]address.Address, error) //perm:write + // WalletBalance returns the balance of the given address at the current head of the chain. + WalletBalance(context.Context, address.Address) (types.BigInt, error) //perm:read + // WalletSign signs the given bytes using the given address. + WalletSign(context.Context, address.Address, []byte) (*crypto.Signature, error) //perm:sign + // WalletSignMessage signs the given message using the given address. + WalletSignMessage(context.Context, address.Address, *types.Message) (*types.SignedMessage, error) //perm:sign + // WalletVerify takes an address, a signature, and some bytes, and indicates whether the signature is valid. + // The address does not have to be in the wallet. + WalletVerify(context.Context, address.Address, []byte, *crypto.Signature) (bool, error) //perm:read + // WalletDefaultAddress returns the address marked as default in the wallet. + WalletDefaultAddress(context.Context) (address.Address, error) //perm:write + // WalletSetDefault marks the given address as as the default one. + WalletSetDefault(context.Context, address.Address) error //perm:write + // WalletExport returns the private key of an address in the wallet. + WalletExport(context.Context, address.Address) (*types.KeyInfo, error) //perm:admin + // WalletImport receives a KeyInfo, which includes a private key, and imports it into the wallet. + WalletImport(context.Context, *types.KeyInfo) (address.Address, error) //perm:admin + // WalletDelete deletes an address from the wallet. + WalletDelete(context.Context, address.Address) error //perm:admin + // WalletValidateAddress validates whether a given string can be decoded as a well-formed address + WalletValidateAddress(context.Context, string) (address.Address, error) //perm:read + + // Other + + // MethodGroup: Client + // The Client methods all have to do with interacting with the storage and + // retrieval markets as a client + + // ClientImport imports file under the specified path into filestore. + ClientImport(ctx context.Context, ref api.FileRef) (*api.ImportRes, error) //perm:admin + // ClientRemoveImport removes file import + ClientRemoveImport(ctx context.Context, importID imports.ID) error //perm:admin + // ClientStartDeal proposes a deal with a miner. + ClientStartDeal(ctx context.Context, params *api.StartDealParams) (*cid.Cid, error) //perm:admin + // ClientStatelessDeal fire-and-forget-proposes an offline deal to a miner without subsequent tracking. + ClientStatelessDeal(ctx context.Context, params *api.StartDealParams) (*cid.Cid, error) //perm:write + // ClientGetDealInfo returns the latest information about a given deal. + ClientGetDealInfo(context.Context, cid.Cid) (*api.DealInfo, error) //perm:read + // ClientListDeals returns information about the deals made by the local client. + ClientListDeals(ctx context.Context) ([]api.DealInfo, error) //perm:write + // ClientGetDealUpdates returns the status of updated deals + ClientGetDealUpdates(ctx context.Context) (<-chan api.DealInfo, error) //perm:write + // ClientGetDealStatus returns status given a code + ClientGetDealStatus(ctx context.Context, statusCode uint64) (string, error) //perm:read + // ClientHasLocal indicates whether a certain CID is locally stored. + ClientHasLocal(ctx context.Context, root cid.Cid) (bool, error) //perm:write + // ClientFindData identifies peers that have a certain file, and returns QueryOffers (one per peer). + ClientFindData(ctx context.Context, root cid.Cid, piece *cid.Cid) ([]api.QueryOffer, error) //perm:read + // ClientMinerQueryOffer returns a QueryOffer for the specific miner and file. + ClientMinerQueryOffer(ctx context.Context, miner address.Address, root cid.Cid, piece *cid.Cid) (api.QueryOffer, error) //perm:read + // ClientRetrieve initiates the retrieval of a file, as specified in the order. + ClientRetrieve(ctx context.Context, order RetrievalOrder, ref *api.FileRef) error //perm:admin + // ClientRetrieveWithEvents initiates the retrieval of a file, as specified in the order, and provides a channel + // of status updates. + ClientRetrieveWithEvents(ctx context.Context, order RetrievalOrder, ref *api.FileRef) (<-chan marketevents.RetrievalEvent, error) //perm:admin + // ClientQueryAsk returns a signed StorageAsk from the specified miner. + // ClientListRetrievals returns information about retrievals made by the local client + ClientListRetrievals(ctx context.Context) ([]api.RetrievalInfo, error) //perm:write + // ClientGetRetrievalUpdates returns status of updated retrieval deals + ClientGetRetrievalUpdates(ctx context.Context) (<-chan api.RetrievalInfo, error) //perm:write + ClientQueryAsk(ctx context.Context, p peer.ID, miner address.Address) (*storagemarket.StorageAsk, error) //perm:read + // ClientCalcCommP calculates the CommP and data size of the specified CID + ClientDealPieceCID(ctx context.Context, root cid.Cid) (api.DataCIDSize, error) //perm:read + // ClientCalcCommP calculates the CommP for a specified file + ClientCalcCommP(ctx context.Context, inpath string) (*api.CommPRet, error) //perm:write + // ClientGenCar generates a CAR file for the specified file. + ClientGenCar(ctx context.Context, ref api.FileRef, outpath string) error //perm:write + // ClientDealSize calculates real deal data size + ClientDealSize(ctx context.Context, root cid.Cid) (api.DataSize, error) //perm:read + // ClientListTransfers returns the status of all ongoing transfers of data + ClientListDataTransfers(ctx context.Context) ([]api.DataTransferChannel, error) //perm:write + ClientDataTransferUpdates(ctx context.Context) (<-chan api.DataTransferChannel, error) //perm:write + // ClientRestartDataTransfer attempts to restart a data transfer with the given transfer ID and other peer + ClientRestartDataTransfer(ctx context.Context, transferID datatransfer.TransferID, otherPeer peer.ID, isInitiator bool) error //perm:write + // ClientCancelDataTransfer cancels a data transfer with the given transfer ID and other peer + ClientCancelDataTransfer(ctx context.Context, transferID datatransfer.TransferID, otherPeer peer.ID, isInitiator bool) error //perm:write + // ClientRetrieveTryRestartInsufficientFunds attempts to restart stalled retrievals on a given payment channel + // which are stuck due to insufficient funds + ClientRetrieveTryRestartInsufficientFunds(ctx context.Context, paymentChannel address.Address) error //perm:write + + // ClientCancelRetrievalDeal cancels an ongoing retrieval deal based on DealID + ClientCancelRetrievalDeal(ctx context.Context, dealid retrievalmarket.DealID) error //perm:write + + // ClientUnimport removes references to the specified file from filestore + // ClientUnimport(path string) + + // ClientListImports lists imported files and their root CIDs + ClientListImports(ctx context.Context) ([]api.Import, error) //perm:write + + // ClientListAsks() []Ask + + // MethodGroup: State + // The State methods are used to query, inspect, and interact with chain state. + // Most methods take a TipSetKey as a parameter. The state looked up is the parent state of the tipset. + // A nil TipSetKey can be provided as a param, this will cause the heaviest tipset in the chain to be used. + + // StateCall runs the given message and returns its result without any persisted changes. + // + // StateCall applies the message to the tipset's parent state. The + // message is not applied on-top-of the messages in the passed-in + // tipset. + StateCall(context.Context, *types.Message, types.TipSetKey) (*api.InvocResult, error) //perm:read + // StateReplay replays a given message, assuming it was included in a block in the specified tipset. + // + // If a tipset key is provided, and a replacing message is not found on chain, + // the method will return an error saying that the message wasn't found + // + // If no tipset key is provided, the appropriate tipset is looked up, and if + // the message was gas-repriced, the on-chain message will be replayed - in + // that case the returned InvocResult.MsgCid will not match the Cid param + // + // If the caller wants to ensure that exactly the requested message was executed, + // they MUST check that InvocResult.MsgCid is equal to the provided Cid. + // Without this check both the requested and original message may appear as + // successfully executed on-chain, which may look like a double-spend. + // + // A replacing message is a message with a different CID, any of Gas values, and + // different signature, but with all other parameters matching (source/destination, + // nonce, params, etc.) + StateReplay(context.Context, types.TipSetKey, cid.Cid) (*api.InvocResult, error) //perm:read + // StateGetActor returns the indicated actor's nonce and balance. + StateGetActor(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*types.Actor, error) //perm:read + // StateReadState returns the indicated actor's state. + StateReadState(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*api.ActorState, error) //perm:read + // StateListMessages looks back and returns all messages with a matching to or from address, stopping at the given height. + StateListMessages(ctx context.Context, match *api.MessageMatch, tsk types.TipSetKey, toht abi.ChainEpoch) ([]cid.Cid, error) //perm:read + // StateDecodeParams attempts to decode the provided params, based on the recipient actor address and method number. + StateDecodeParams(ctx context.Context, toAddr address.Address, method abi.MethodNum, params []byte, tsk types.TipSetKey) (interface{}, error) //perm:read + + // StateNetworkName returns the name of the network the node is synced to + StateNetworkName(context.Context) (dtypes.NetworkName, error) //perm:read + // StateMinerSectors returns info about the given miner's sectors. If the filter bitfield is nil, all sectors are included. + StateMinerSectors(context.Context, address.Address, *bitfield.BitField, types.TipSetKey) ([]*miner.SectorOnChainInfo, error) //perm:read + // StateMinerActiveSectors returns info about sectors that a given miner is actively proving. + StateMinerActiveSectors(context.Context, address.Address, types.TipSetKey) ([]*miner.SectorOnChainInfo, error) //perm:read + // StateMinerProvingDeadline calculates the deadline at some epoch for a proving period + // and returns the deadline-related calculations. + StateMinerProvingDeadline(context.Context, address.Address, types.TipSetKey) (*dline.Info, error) //perm:read + // StateMinerPower returns the power of the indicated miner + StateMinerPower(context.Context, address.Address, types.TipSetKey) (*api.MinerPower, error) //perm:read + // StateMinerInfo returns info about the indicated miner + StateMinerInfo(context.Context, address.Address, types.TipSetKey) (api.MinerInfo, error) //perm:read + // StateMinerDeadlines returns all the proving deadlines for the given miner + StateMinerDeadlines(context.Context, address.Address, types.TipSetKey) ([]api.Deadline, error) //perm:read + // StateMinerPartitions returns all partitions in the specified deadline + StateMinerPartitions(ctx context.Context, m address.Address, dlIdx uint64, tsk types.TipSetKey) ([]api.Partition, error) //perm:read + // StateMinerFaults returns a bitfield indicating the faulty sectors of the given miner + StateMinerFaults(context.Context, address.Address, types.TipSetKey) (bitfield.BitField, error) //perm:read + // StateAllMinerFaults returns all non-expired Faults that occur within lookback epochs of the given tipset + StateAllMinerFaults(ctx context.Context, lookback abi.ChainEpoch, ts types.TipSetKey) ([]*api.Fault, error) //perm:read + // StateMinerRecoveries returns a bitfield indicating the recovering sectors of the given miner + StateMinerRecoveries(context.Context, address.Address, types.TipSetKey) (bitfield.BitField, error) //perm:read + // StateMinerInitialPledgeCollateral returns the precommit deposit for the specified miner's sector + StateMinerPreCommitDepositForPower(context.Context, address.Address, miner.SectorPreCommitInfo, types.TipSetKey) (types.BigInt, error) //perm:read + // StateMinerInitialPledgeCollateral returns the initial pledge collateral for the specified miner's sector + StateMinerInitialPledgeCollateral(context.Context, address.Address, miner.SectorPreCommitInfo, types.TipSetKey) (types.BigInt, error) //perm:read + // StateMinerAvailableBalance returns the portion of a miner's balance that can be withdrawn or spent + StateMinerAvailableBalance(context.Context, address.Address, types.TipSetKey) (types.BigInt, error) //perm:read + // StateMinerSectorAllocated checks if a sector is allocated + StateMinerSectorAllocated(context.Context, address.Address, abi.SectorNumber, types.TipSetKey) (bool, error) //perm:read + // StateSectorPreCommitInfo returns the PreCommit info for the specified miner's sector + StateSectorPreCommitInfo(context.Context, address.Address, abi.SectorNumber, types.TipSetKey) (miner.SectorPreCommitOnChainInfo, error) //perm:read + // StateSectorGetInfo returns the on-chain info for the specified miner's sector. Returns null in case the sector info isn't found + // NOTE: returned info.Expiration may not be accurate in some cases, use StateSectorExpiration to get accurate + // expiration epoch + StateSectorGetInfo(context.Context, address.Address, abi.SectorNumber, types.TipSetKey) (*miner.SectorOnChainInfo, error) //perm:read + // StateSectorExpiration returns epoch at which given sector will expire + StateSectorExpiration(context.Context, address.Address, abi.SectorNumber, types.TipSetKey) (*lminer.SectorExpiration, error) //perm:read + // StateSectorPartition finds deadline/partition with the specified sector + StateSectorPartition(ctx context.Context, maddr address.Address, sectorNumber abi.SectorNumber, tok types.TipSetKey) (*lminer.SectorLocation, error) //perm:read + // StateSearchMsg searches for a message in the chain, and returns its receipt and the tipset where it was executed + // + // NOTE: If a replacing message is found on chain, this method will return + // a MsgLookup for the replacing message - the MsgLookup.Message will be a different + // CID than the one provided in the 'cid' param, MsgLookup.Receipt will contain the + // result of the execution of the replacing message. + // + // If the caller wants to ensure that exactly the requested message was executed, + // they MUST check that MsgLookup.Message is equal to the provided 'cid'. + // Without this check both the requested and original message may appear as + // successfully executed on-chain, which may look like a double-spend. + // + // A replacing message is a message with a different CID, any of Gas values, and + // different signature, but with all other parameters matching (source/destination, + // nonce, params, etc.) + StateSearchMsg(context.Context, cid.Cid) (*api.MsgLookup, error) //perm:read + // StateSearchMsgLimited looks back up to limit epochs in the chain for a message, and returns its receipt and the tipset where it was executed + // + // NOTE: If a replacing message is found on chain, this method will return + // a MsgLookup for the replacing message - the MsgLookup.Message will be a different + // CID than the one provided in the 'cid' param, MsgLookup.Receipt will contain the + // result of the execution of the replacing message. + // + // If the caller wants to ensure that exactly the requested message was executed, + // they MUST check that MsgLookup.Message is equal to the provided 'cid'. + // Without this check both the requested and original message may appear as + // successfully executed on-chain, which may look like a double-spend. + // + // A replacing message is a message with a different CID, any of Gas values, and + // different signature, but with all other parameters matching (source/destination, + // nonce, params, etc.) + StateSearchMsgLimited(ctx context.Context, msg cid.Cid, limit abi.ChainEpoch) (*api.MsgLookup, error) //perm:read + // StateWaitMsg looks back in the chain for a message. If not found, it blocks until the + // message arrives on chain, and gets to the indicated confidence depth. + // + // NOTE: If a replacing message is found on chain, this method will return + // a MsgLookup for the replacing message - the MsgLookup.Message will be a different + // CID than the one provided in the 'cid' param, MsgLookup.Receipt will contain the + // result of the execution of the replacing message. + // + // If the caller wants to ensure that exactly the requested message was executed, + // they MUST check that MsgLookup.Message is equal to the provided 'cid'. + // Without this check both the requested and original message may appear as + // successfully executed on-chain, which may look like a double-spend. + // + // A replacing message is a message with a different CID, any of Gas values, and + // different signature, but with all other parameters matching (source/destination, + // nonce, params, etc.) + StateWaitMsg(ctx context.Context, cid cid.Cid, confidence uint64) (*api.MsgLookup, error) //perm:read + // StateWaitMsgLimited looks back up to limit epochs in the chain for a message. + // If not found, it blocks until the message arrives on chain, and gets to the + // indicated confidence depth. + // + // NOTE: If a replacing message is found on chain, this method will return + // a MsgLookup for the replacing message - the MsgLookup.Message will be a different + // CID than the one provided in the 'cid' param, MsgLookup.Receipt will contain the + // result of the execution of the replacing message. + // + // If the caller wants to ensure that exactly the requested message was executed, + // they MUST check that MsgLookup.Message is equal to the provided 'cid'. + // Without this check both the requested and original message may appear as + // successfully executed on-chain, which may look like a double-spend. + // + // A replacing message is a message with a different CID, any of Gas values, and + // different signature, but with all other parameters matching (source/destination, + // nonce, params, etc.) + StateWaitMsgLimited(ctx context.Context, cid cid.Cid, confidence uint64, limit abi.ChainEpoch) (*api.MsgLookup, error) //perm:read + // StateListMiners returns the addresses of every miner that has claimed power in the Power Actor + StateListMiners(context.Context, types.TipSetKey) ([]address.Address, error) //perm:read + // StateListActors returns the addresses of every actor in the state + StateListActors(context.Context, types.TipSetKey) ([]address.Address, error) //perm:read + // StateMarketBalance looks up the Escrow and Locked balances of the given address in the Storage Market + StateMarketBalance(context.Context, address.Address, types.TipSetKey) (api.MarketBalance, error) //perm:read + // StateMarketParticipants returns the Escrow and Locked balances of every participant in the Storage Market + StateMarketParticipants(context.Context, types.TipSetKey) (map[string]api.MarketBalance, error) //perm:read + // StateMarketDeals returns information about every deal in the Storage Market + StateMarketDeals(context.Context, types.TipSetKey) (map[string]*api.MarketDeal, error) //perm:read + // StateMarketStorageDeal returns information about the indicated deal + StateMarketStorageDeal(context.Context, abi.DealID, types.TipSetKey) (*api.MarketDeal, error) //perm:read + // StateGetAllocationForPendingDeal returns the allocation for a given deal ID of a pending deal. + StateGetAllocationForPendingDeal(ctx context.Context, dealId abi.DealID, tsk types.TipSetKey) (*verifregtypes.Allocation, error) //perm:read + // StateGetAllocation returns the allocation for a given address and allocation ID. + StateGetAllocation(ctx context.Context, clientAddr address.Address, allocationId verifregtypes.AllocationId, tsk types.TipSetKey) (*verifregtypes.Allocation, error) //perm:read + // StateGetAllocations returns the all the allocations for a given client. + StateGetAllocations(ctx context.Context, clientAddr address.Address, tsk types.TipSetKey) (map[verifregtypes.AllocationId]verifregtypes.Allocation, error) //perm:read + // StateGetClaim returns the claim for a given address and claim ID. + StateGetClaim(ctx context.Context, providerAddr address.Address, claimId verifregtypes.ClaimId, tsk types.TipSetKey) (*verifregtypes.Claim, error) //perm:read + // StateGetClaims returns the all the claims for a given provider. + StateGetClaims(ctx context.Context, providerAddr address.Address, tsk types.TipSetKey) (map[verifregtypes.ClaimId]verifregtypes.Claim, error) //perm:read + // StateLookupID retrieves the ID address of the given address + StateLookupID(context.Context, address.Address, types.TipSetKey) (address.Address, error) //perm:read + // StateAccountKey returns the public key address of the given ID address + StateAccountKey(context.Context, address.Address, types.TipSetKey) (address.Address, error) //perm:read + // StateChangedActors returns all the actors whose states change between the two given state CIDs + // TODO: Should this take tipset keys instead? + StateChangedActors(context.Context, cid.Cid, cid.Cid) (map[string]types.Actor, error) //perm:read + // StateGetReceipt returns the message receipt for the given message or for a + // matching gas-repriced replacing message + // + // NOTE: If the requested message was replaced, this method will return the receipt + // for the replacing message - if the caller needs the receipt for exactly the + // requested message, use StateSearchMsg().Receipt, and check that MsgLookup.Message + // is matching the requested CID + // + // DEPRECATED: Use StateSearchMsg, this method won't be supported in v1 API + StateGetReceipt(context.Context, cid.Cid, types.TipSetKey) (*types.MessageReceipt, error) //perm:read + // StateMinerSectorCount returns the number of sectors in a miner's sector set and proving set + StateMinerSectorCount(context.Context, address.Address, types.TipSetKey) (api.MinerSectors, error) //perm:read + // StateCompute is a flexible command that applies the given messages on the given tipset. + // The messages are run as though the VM were at the provided height. + // + // When called, StateCompute will: + // - Load the provided tipset, or use the current chain head if not provided + // - Compute the tipset state of the provided tipset on top of the parent state + // - (note that this step runs before vmheight is applied to the execution) + // - Execute state upgrade if any were scheduled at the epoch, or in null + // blocks preceding the tipset + // - Call the cron actor on null blocks preceding the tipset + // - For each block in the tipset + // - Apply messages in blocks in the specified + // - Award block reward by calling the reward actor + // - Call the cron actor for the current epoch + // - If the specified vmheight is higher than the current epoch, apply any + // needed state upgrades to the state + // - Apply the specified messages to the state + // + // The vmheight parameter sets VM execution epoch, and can be used to simulate + // message execution in different network versions. If the specified vmheight + // epoch is higher than the epoch of the specified tipset, any state upgrades + // until the vmheight will be executed on the state before applying messages + // specified by the user. + // + // Note that the initial tipset state computation is not affected by the + // vmheight parameter - only the messages in the `apply` set are + // + // If the caller wants to simply compute the state, vmheight should be set to + // the epoch of the specified tipset. + // + // Messages in the `apply` parameter must have the correct nonces, and gas + // values set. + StateCompute(context.Context, abi.ChainEpoch, []*types.Message, types.TipSetKey) (*api.ComputeStateOutput, error) //perm:read + // StateVerifierStatus returns the data cap for the given address. + // Returns nil if there is no entry in the data cap table for the + // address. + StateVerifierStatus(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*abi.StoragePower, error) //perm:read + // StateVerifiedClientStatus returns the data cap for the given address. + // Returns nil if there is no entry in the data cap table for the + // address. + StateVerifiedClientStatus(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*abi.StoragePower, error) //perm:read + // StateVerifiedRegistryRootKey returns the address of the Verified Registry's root key + StateVerifiedRegistryRootKey(ctx context.Context, tsk types.TipSetKey) (address.Address, error) //perm:read + // StateDealProviderCollateralBounds returns the min and max collateral a storage provider + // can issue. It takes the deal size and verified status as parameters. + StateDealProviderCollateralBounds(context.Context, abi.PaddedPieceSize, bool, types.TipSetKey) (api.DealCollateralBounds, error) //perm:read + + // StateCirculatingSupply returns the exact circulating supply of Filecoin at the given tipset. + // This is not used anywhere in the protocol itself, and is only for external consumption. + StateCirculatingSupply(context.Context, types.TipSetKey) (abi.TokenAmount, error) //perm:read + // StateVMCirculatingSupplyInternal returns an approximation of the circulating supply of Filecoin at the given tipset. + // This is the value reported by the runtime interface to actors code. + StateVMCirculatingSupplyInternal(context.Context, types.TipSetKey) (api.CirculatingSupply, error) //perm:read + // StateNetworkVersion returns the network version at the given tipset + StateNetworkVersion(context.Context, types.TipSetKey) (apitypes.NetworkVersion, error) //perm:read + // StateActorCodeCIDs returns the CIDs of all the builtin actors for the given network version + StateActorCodeCIDs(context.Context, abinetwork.Version) (map[string]cid.Cid, error) //perm:read + // StateActorManifestCID returns the CID of the builtin actors manifest for the given network version + StateActorManifestCID(context.Context, abinetwork.Version) (cid.Cid, error) //perm:read + + // StateGetRandomnessFromTickets is used to sample the chain for randomness. + StateGetRandomnessFromTickets(ctx context.Context, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte, tsk types.TipSetKey) (abi.Randomness, error) //perm:read + // StateGetRandomnessFromBeacon is used to sample the beacon for randomness. + StateGetRandomnessFromBeacon(ctx context.Context, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte, tsk types.TipSetKey) (abi.Randomness, error) //perm:read + + // StateGetNetworkParams return current network params + StateGetNetworkParams(ctx context.Context) (*api.NetworkParams, error) //perm:read + + // MethodGroup: Msig + // The Msig methods are used to interact with multisig wallets on the + // filecoin network + + // MsigGetAvailableBalance returns the portion of a multisig's balance that can be withdrawn or spent + MsigGetAvailableBalance(context.Context, address.Address, types.TipSetKey) (types.BigInt, error) //perm:read + // MsigGetVestingSchedule returns the vesting details of a given multisig. + MsigGetVestingSchedule(context.Context, address.Address, types.TipSetKey) (api.MsigVesting, error) //perm:read + // MsigGetVested returns the amount of FIL that vested in a multisig in a certain period. + // It takes the following params: , , + MsigGetVested(context.Context, address.Address, types.TipSetKey, types.TipSetKey) (types.BigInt, error) //perm:read + + // MsigGetPending returns pending transactions for the given multisig + // wallet. Once pending transactions are fully approved, they will no longer + // appear here. + MsigGetPending(context.Context, address.Address, types.TipSetKey) ([]*api.MsigTransaction, error) //perm:read + + // MsigCreate creates a multisig wallet + // It takes the following params: , , + // , , + MsigCreate(context.Context, uint64, []address.Address, abi.ChainEpoch, types.BigInt, address.Address, types.BigInt) (cid.Cid, error) //perm:sign + // MsigPropose proposes a multisig message + // It takes the following params: , , , + // , , + MsigPropose(context.Context, address.Address, address.Address, types.BigInt, address.Address, uint64, []byte) (cid.Cid, error) //perm:sign + + // MsigApprove approves a previously-proposed multisig message by transaction ID + // It takes the following params: , + MsigApprove(context.Context, address.Address, uint64, address.Address) (cid.Cid, error) //perm:sign + + // MsigApproveTxnHash approves a previously-proposed multisig message, specified + // using both transaction ID and a hash of the parameters used in the + // proposal. This method of approval can be used to ensure you only approve + // exactly the transaction you think you are. + // It takes the following params: , , , , , + // , , + MsigApproveTxnHash(context.Context, address.Address, uint64, address.Address, address.Address, types.BigInt, address.Address, uint64, []byte) (cid.Cid, error) //perm:sign + + // MsigCancel cancels a previously-proposed multisig message + // It takes the following params: , , , , + // , , + MsigCancel(context.Context, address.Address, uint64, address.Address, types.BigInt, address.Address, uint64, []byte) (cid.Cid, error) //perm:sign + // MsigAddPropose proposes adding a signer in the multisig + // It takes the following params: , , + // , + MsigAddPropose(context.Context, address.Address, address.Address, address.Address, bool) (cid.Cid, error) //perm:sign + // MsigAddApprove approves a previously proposed AddSigner message + // It takes the following params: , , , + // , , + MsigAddApprove(context.Context, address.Address, address.Address, uint64, address.Address, address.Address, bool) (cid.Cid, error) //perm:sign + // MsigAddCancel cancels a previously proposed AddSigner message + // It takes the following params: , , , + // , + MsigAddCancel(context.Context, address.Address, address.Address, uint64, address.Address, bool) (cid.Cid, error) //perm:sign + // MsigSwapPropose proposes swapping 2 signers in the multisig + // It takes the following params: , , + // , + MsigSwapPropose(context.Context, address.Address, address.Address, address.Address, address.Address) (cid.Cid, error) //perm:sign + // MsigSwapApprove approves a previously proposed SwapSigner + // It takes the following params: , , , + // , , + MsigSwapApprove(context.Context, address.Address, address.Address, uint64, address.Address, address.Address, address.Address) (cid.Cid, error) //perm:sign + // MsigSwapCancel cancels a previously proposed SwapSigner message + // It takes the following params: , , , + // , + MsigSwapCancel(context.Context, address.Address, address.Address, uint64, address.Address, address.Address) (cid.Cid, error) //perm:sign + + // MsigRemoveSigner proposes the removal of a signer from the multisig. + // It accepts the multisig to make the change on, the proposer address to + // send the message from, the address to be removed, and a boolean + // indicating whether or not the signing threshold should be lowered by one + // along with the address removal. + MsigRemoveSigner(ctx context.Context, msig address.Address, proposer address.Address, toRemove address.Address, decrease bool) (cid.Cid, error) //perm:sign + + // MarketAddBalance adds funds to the market actor + MarketAddBalance(ctx context.Context, wallet, addr address.Address, amt types.BigInt) (cid.Cid, error) //perm:sign + // MarketGetReserved gets the amount of funds that are currently reserved for the address + MarketGetReserved(ctx context.Context, addr address.Address) (types.BigInt, error) //perm:sign + // MarketReserveFunds reserves funds for a deal + MarketReserveFunds(ctx context.Context, wallet address.Address, addr address.Address, amt types.BigInt) (cid.Cid, error) //perm:sign + // MarketReleaseFunds releases funds reserved by MarketReserveFunds + MarketReleaseFunds(ctx context.Context, addr address.Address, amt types.BigInt) error //perm:sign + // MarketWithdraw withdraws unlocked funds from the market actor + MarketWithdraw(ctx context.Context, wallet, addr address.Address, amt types.BigInt) (cid.Cid, error) //perm:sign + + // MethodGroup: Paych + // The Paych methods are for interacting with and managing payment channels + + PaychGet(ctx context.Context, from, to address.Address, amt types.BigInt) (*api.ChannelInfo, error) //perm:sign + PaychGetWaitReady(context.Context, cid.Cid) (address.Address, error) //perm:sign + PaychAvailableFunds(ctx context.Context, ch address.Address) (*api.ChannelAvailableFunds, error) //perm:sign + PaychAvailableFundsByFromTo(ctx context.Context, from, to address.Address) (*api.ChannelAvailableFunds, error) //perm:sign + PaychList(context.Context) ([]address.Address, error) //perm:read + PaychStatus(context.Context, address.Address) (*api.PaychStatus, error) //perm:read + PaychSettle(context.Context, address.Address) (cid.Cid, error) //perm:sign + PaychCollect(context.Context, address.Address) (cid.Cid, error) //perm:sign + PaychAllocateLane(ctx context.Context, ch address.Address) (uint64, error) //perm:sign + PaychNewPayment(ctx context.Context, from, to address.Address, vouchers []api.VoucherSpec) (*api.PaymentInfo, error) //perm:sign + PaychVoucherCheckValid(context.Context, address.Address, *paych.SignedVoucher) error //perm:read + PaychVoucherCheckSpendable(context.Context, address.Address, *paych.SignedVoucher, []byte, []byte) (bool, error) //perm:read + PaychVoucherCreate(context.Context, address.Address, types.BigInt, uint64) (*api.VoucherCreateResult, error) //perm:sign + PaychVoucherAdd(context.Context, address.Address, *paych.SignedVoucher, []byte, types.BigInt) (types.BigInt, error) //perm:write + PaychVoucherList(context.Context, address.Address) ([]*paych.SignedVoucher, error) //perm:write + PaychVoucherSubmit(context.Context, address.Address, *paych.SignedVoucher, []byte, []byte) (cid.Cid, error) //perm:sign + + // CreateBackup creates node backup onder the specified file name. The + // method requires that the lotus daemon is running with the + // LOTUS_BACKUP_BASE_PATH environment variable set to some path, and that + // the path specified when calling CreateBackup is within the base path + CreateBackup(ctx context.Context, fpath string) error //perm:admin +} + +func OfferOrder(o api.QueryOffer, client address.Address) RetrievalOrder { + return RetrievalOrder{ + Root: o.Root, + Piece: o.Piece, + Size: o.Size, + Total: o.MinPrice, + UnsealPrice: o.UnsealPrice, + PaymentInterval: o.PaymentInterval, + PaymentIntervalIncrease: o.PaymentIntervalIncrease, + Client: client, + + Miner: o.Miner, + MinerPeer: &o.MinerPeer, + } +} + +type RetrievalOrder struct { + // TODO: make this less unixfs specific + Root cid.Cid + Piece *cid.Cid + DatamodelPathSelector *textselector.Expression + Size uint64 + + FromLocalCAR string // if specified, get data from a local CARv2 file. + // TODO: support offset + Total types.BigInt + UnsealPrice types.BigInt + PaymentInterval uint64 + PaymentIntervalIncrease uint64 + Client address.Address + Miner address.Address + MinerPeer *retrievalmarket.RetrievalPeer +} diff --git a/api/v0api/gateway.go b/api/v0api/gateway.go new file mode 100644 index 000000000..30eb0d1c4 --- /dev/null +++ b/api/v0api/gateway.go @@ -0,0 +1,79 @@ +package v0api + +import ( + "context" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/builtin/v9/miner" + "github.com/filecoin-project/go-state-types/dline" + abinetwork "github.com/filecoin-project/go-state-types/network" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/node/modules/dtypes" +) + +// MODIFYING THE API INTERFACE +// +// NOTE: This is the V0 (Stable) API - when adding methods to this interface, +// you'll need to make sure they are also present on the V1 (Unstable) API +// +// This API is implemented in `v1_wrapper.go` as a compatibility layer backed +// by the V1 api +// +// When adding / changing methods in this file: +// * Do the change here +// * Adjust implementation in `node/impl/` +// * Run `make gen` - this will: +// * Generate proxy structs +// * Generate mocks +// * Generate markdown docs +// * Generate openrpc blobs + +type Gateway interface { + StateMinerSectorCount(context.Context, address.Address, types.TipSetKey) (api.MinerSectors, error) + GasEstimateGasPremium(context.Context, uint64, address.Address, int64, types.TipSetKey) (types.BigInt, error) + StateReplay(context.Context, types.TipSetKey, cid.Cid) (*api.InvocResult, error) + ChainHasObj(context.Context, cid.Cid) (bool, error) + ChainPutObj(context.Context, blocks.Block) error + ChainHead(ctx context.Context) (*types.TipSet, error) + ChainGetBlockMessages(context.Context, cid.Cid) (*api.BlockMessages, error) + ChainGetMessage(ctx context.Context, mc cid.Cid) (*types.Message, error) + ChainGetTipSet(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error) + ChainGetTipSetByHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error) + ChainNotify(context.Context) (<-chan []*api.HeadChange, error) + ChainReadObj(context.Context, cid.Cid) ([]byte, error) + GasEstimateMessageGas(ctx context.Context, msg *types.Message, spec *api.MessageSendSpec, tsk types.TipSetKey) (*types.Message, error) + MpoolGetNonce(ctx context.Context, addr address.Address) (uint64, error) + MpoolPush(ctx context.Context, sm *types.SignedMessage) (cid.Cid, error) + MsigGetAvailableBalance(ctx context.Context, addr address.Address, tsk types.TipSetKey) (types.BigInt, error) + MsigGetVested(ctx context.Context, addr address.Address, start types.TipSetKey, end types.TipSetKey) (types.BigInt, error) + MsigGetPending(context.Context, address.Address, types.TipSetKey) ([]*api.MsigTransaction, error) + StateAccountKey(ctx context.Context, addr address.Address, tsk types.TipSetKey) (address.Address, error) + StateCall(ctx context.Context, msg *types.Message, tsk types.TipSetKey) (*api.InvocResult, error) + StateDealProviderCollateralBounds(ctx context.Context, size abi.PaddedPieceSize, verified bool, tsk types.TipSetKey) (api.DealCollateralBounds, error) + StateDecodeParams(ctx context.Context, toAddr address.Address, method abi.MethodNum, params []byte, tsk types.TipSetKey) (interface{}, error) + StateGetActor(ctx context.Context, actor address.Address, ts types.TipSetKey) (*types.Actor, error) + StateGetReceipt(context.Context, cid.Cid, types.TipSetKey) (*types.MessageReceipt, error) + StateListMiners(ctx context.Context, tsk types.TipSetKey) ([]address.Address, error) + StateLookupID(ctx context.Context, addr address.Address, tsk types.TipSetKey) (address.Address, error) + StateMarketBalance(ctx context.Context, addr address.Address, tsk types.TipSetKey) (api.MarketBalance, error) + StateMarketStorageDeal(ctx context.Context, dealId abi.DealID, tsk types.TipSetKey) (*api.MarketDeal, error) + StateMinerInfo(ctx context.Context, actor address.Address, tsk types.TipSetKey) (api.MinerInfo, error) + StateMinerProvingDeadline(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*dline.Info, error) + StateMinerPower(context.Context, address.Address, types.TipSetKey) (*api.MinerPower, error) + StateNetworkName(context.Context) (dtypes.NetworkName, error) + StateNetworkVersion(context.Context, types.TipSetKey) (abinetwork.Version, error) + StateSearchMsg(ctx context.Context, msg cid.Cid) (*api.MsgLookup, error) + StateSectorGetInfo(ctx context.Context, maddr address.Address, n abi.SectorNumber, tsk types.TipSetKey) (*miner.SectorOnChainInfo, error) + 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/latest.go b/api/v0api/latest.go new file mode 100644 index 000000000..d423f57bc --- /dev/null +++ b/api/v0api/latest.go @@ -0,0 +1,32 @@ +package v0api + +import ( + "github.com/filecoin-project/lotus/api" +) + +type Common = api.Common +type Net = api.Net +type CommonNet = api.CommonNet + +type CommonStruct = api.CommonStruct +type CommonStub = api.CommonStub +type NetStruct = api.NetStruct +type NetStub = api.NetStub +type CommonNetStruct = api.CommonNetStruct +type CommonNetStub = api.CommonNetStub + +type StorageMiner = api.StorageMiner +type StorageMinerStruct = api.StorageMinerStruct + +type Worker = api.Worker +type WorkerStruct = api.WorkerStruct + +type Wallet = api.Wallet + +func PermissionedStorMinerAPI(a StorageMiner) StorageMiner { + return api.PermissionedStorMinerAPI(a) +} + +func PermissionedWorkerAPI(a Worker) Worker { + return api.PermissionedWorkerAPI(a) +} diff --git a/api/v0api/permissioned.go b/api/v0api/permissioned.go new file mode 100644 index 000000000..95fccdfbb --- /dev/null +++ b/api/v0api/permissioned.go @@ -0,0 +1,14 @@ +package v0api + +import ( + "github.com/filecoin-project/go-jsonrpc/auth" + + "github.com/filecoin-project/lotus/api" +) + +func PermissionedFullAPI(a FullNode) FullNode { + var out FullNodeStruct + auth.PermissionedProxy(api.AllPermissions, api.DefaultPerms, a, &out.Internal) + auth.PermissionedProxy(api.AllPermissions, api.DefaultPerms, a, &out.CommonStruct.Internal) + return &out +} diff --git a/api/v0api/proxy_gen.go b/api/v0api/proxy_gen.go new file mode 100644 index 000000000..069527ae8 --- /dev/null +++ b/api/v0api/proxy_gen.go @@ -0,0 +1,3014 @@ +// Code generated by github.com/filecoin-project/lotus/gen/api. DO NOT EDIT. + +package v0api + +import ( + "context" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + "github.com/libp2p/go-libp2p/core/peer" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-bitfield" + datatransfer "github.com/filecoin-project/go-data-transfer/v2" + "github.com/filecoin-project/go-fil-markets/retrievalmarket" + "github.com/filecoin-project/go-fil-markets/storagemarket" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/builtin/v8/paych" + "github.com/filecoin-project/go-state-types/builtin/v9/miner" + verifregtypes "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/go-state-types/dline" + abinetwork "github.com/filecoin-project/go-state-types/network" + + "github.com/filecoin-project/lotus/api" + apitypes "github.com/filecoin-project/lotus/api/types" + lminer "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/types" + marketevents "github.com/filecoin-project/lotus/markets/loggers" + "github.com/filecoin-project/lotus/node/modules/dtypes" + "github.com/filecoin-project/lotus/node/repo/imports" +) + +var ErrNotSupported = xerrors.New("method not supported") + +type FullNodeStruct struct { + CommonStruct + + NetStruct + + Internal FullNodeMethods +} + +type FullNodeMethods struct { + BeaconGetEntry func(p0 context.Context, p1 abi.ChainEpoch) (*types.BeaconEntry, error) `perm:"read"` + + ChainDeleteObj func(p0 context.Context, p1 cid.Cid) error `perm:"admin"` + + ChainExport func(p0 context.Context, p1 abi.ChainEpoch, p2 bool, p3 types.TipSetKey) (<-chan []byte, error) `perm:"read"` + + ChainGetBlock func(p0 context.Context, p1 cid.Cid) (*types.BlockHeader, error) `perm:"read"` + + ChainGetBlockMessages func(p0 context.Context, p1 cid.Cid) (*api.BlockMessages, error) `perm:"read"` + + ChainGetGenesis func(p0 context.Context) (*types.TipSet, error) `perm:"read"` + + ChainGetMessage func(p0 context.Context, p1 cid.Cid) (*types.Message, error) `perm:"read"` + + ChainGetMessagesInTipset func(p0 context.Context, p1 types.TipSetKey) ([]api.Message, error) `perm:"read"` + + ChainGetNode func(p0 context.Context, p1 string) (*api.IpldObject, error) `perm:"read"` + + ChainGetParentMessages func(p0 context.Context, p1 cid.Cid) ([]api.Message, error) `perm:"read"` + + ChainGetParentReceipts func(p0 context.Context, p1 cid.Cid) ([]*types.MessageReceipt, error) `perm:"read"` + + ChainGetPath func(p0 context.Context, p1 types.TipSetKey, p2 types.TipSetKey) ([]*api.HeadChange, error) `perm:"read"` + + ChainGetRandomnessFromBeacon func(p0 context.Context, p1 types.TipSetKey, p2 crypto.DomainSeparationTag, p3 abi.ChainEpoch, p4 []byte) (abi.Randomness, error) `perm:"read"` + + ChainGetRandomnessFromTickets func(p0 context.Context, p1 types.TipSetKey, p2 crypto.DomainSeparationTag, p3 abi.ChainEpoch, p4 []byte) (abi.Randomness, error) `perm:"read"` + + ChainGetTipSet func(p0 context.Context, p1 types.TipSetKey) (*types.TipSet, error) `perm:"read"` + + ChainGetTipSetByHeight func(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) `perm:"read"` + + ChainHasObj func(p0 context.Context, p1 cid.Cid) (bool, error) `perm:"read"` + + ChainHead func(p0 context.Context) (*types.TipSet, error) `perm:"read"` + + ChainNotify func(p0 context.Context) (<-chan []*api.HeadChange, error) `perm:"read"` + + ChainPutObj func(p0 context.Context, p1 blocks.Block) error `` + + ChainReadObj func(p0 context.Context, p1 cid.Cid) ([]byte, error) `perm:"read"` + + ChainSetHead func(p0 context.Context, p1 types.TipSetKey) error `perm:"admin"` + + ChainStatObj func(p0 context.Context, p1 cid.Cid, p2 cid.Cid) (api.ObjStat, error) `perm:"read"` + + ChainTipSetWeight func(p0 context.Context, p1 types.TipSetKey) (types.BigInt, error) `perm:"read"` + + ClientCalcCommP func(p0 context.Context, p1 string) (*api.CommPRet, error) `perm:"write"` + + ClientCancelDataTransfer func(p0 context.Context, p1 datatransfer.TransferID, p2 peer.ID, p3 bool) error `perm:"write"` + + ClientCancelRetrievalDeal func(p0 context.Context, p1 retrievalmarket.DealID) error `perm:"write"` + + ClientDataTransferUpdates func(p0 context.Context) (<-chan api.DataTransferChannel, error) `perm:"write"` + + ClientDealPieceCID func(p0 context.Context, p1 cid.Cid) (api.DataCIDSize, error) `perm:"read"` + + ClientDealSize func(p0 context.Context, p1 cid.Cid) (api.DataSize, error) `perm:"read"` + + ClientFindData func(p0 context.Context, p1 cid.Cid, p2 *cid.Cid) ([]api.QueryOffer, error) `perm:"read"` + + ClientGenCar func(p0 context.Context, p1 api.FileRef, p2 string) error `perm:"write"` + + ClientGetDealInfo func(p0 context.Context, p1 cid.Cid) (*api.DealInfo, error) `perm:"read"` + + ClientGetDealStatus func(p0 context.Context, p1 uint64) (string, error) `perm:"read"` + + ClientGetDealUpdates func(p0 context.Context) (<-chan api.DealInfo, error) `perm:"write"` + + ClientGetRetrievalUpdates func(p0 context.Context) (<-chan api.RetrievalInfo, error) `perm:"write"` + + ClientHasLocal func(p0 context.Context, p1 cid.Cid) (bool, error) `perm:"write"` + + ClientImport func(p0 context.Context, p1 api.FileRef) (*api.ImportRes, error) `perm:"admin"` + + ClientListDataTransfers func(p0 context.Context) ([]api.DataTransferChannel, error) `perm:"write"` + + ClientListDeals func(p0 context.Context) ([]api.DealInfo, error) `perm:"write"` + + ClientListImports func(p0 context.Context) ([]api.Import, error) `perm:"write"` + + ClientListRetrievals func(p0 context.Context) ([]api.RetrievalInfo, error) `perm:"write"` + + ClientMinerQueryOffer func(p0 context.Context, p1 address.Address, p2 cid.Cid, p3 *cid.Cid) (api.QueryOffer, error) `perm:"read"` + + ClientQueryAsk func(p0 context.Context, p1 peer.ID, p2 address.Address) (*storagemarket.StorageAsk, error) `perm:"read"` + + ClientRemoveImport func(p0 context.Context, p1 imports.ID) error `perm:"admin"` + + ClientRestartDataTransfer func(p0 context.Context, p1 datatransfer.TransferID, p2 peer.ID, p3 bool) error `perm:"write"` + + ClientRetrieve func(p0 context.Context, p1 RetrievalOrder, p2 *api.FileRef) error `perm:"admin"` + + ClientRetrieveTryRestartInsufficientFunds func(p0 context.Context, p1 address.Address) error `perm:"write"` + + ClientRetrieveWithEvents func(p0 context.Context, p1 RetrievalOrder, p2 *api.FileRef) (<-chan marketevents.RetrievalEvent, error) `perm:"admin"` + + ClientStartDeal func(p0 context.Context, p1 *api.StartDealParams) (*cid.Cid, error) `perm:"admin"` + + ClientStatelessDeal func(p0 context.Context, p1 *api.StartDealParams) (*cid.Cid, error) `perm:"write"` + + CreateBackup func(p0 context.Context, p1 string) error `perm:"admin"` + + GasEstimateFeeCap func(p0 context.Context, p1 *types.Message, p2 int64, p3 types.TipSetKey) (types.BigInt, error) `perm:"read"` + + GasEstimateGasLimit func(p0 context.Context, p1 *types.Message, p2 types.TipSetKey) (int64, error) `perm:"read"` + + GasEstimateGasPremium func(p0 context.Context, p1 uint64, p2 address.Address, p3 int64, p4 types.TipSetKey) (types.BigInt, error) `perm:"read"` + + GasEstimateMessageGas func(p0 context.Context, p1 *types.Message, p2 *api.MessageSendSpec, p3 types.TipSetKey) (*types.Message, error) `perm:"read"` + + MarketAddBalance func(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt) (cid.Cid, error) `perm:"sign"` + + MarketGetReserved func(p0 context.Context, p1 address.Address) (types.BigInt, error) `perm:"sign"` + + MarketReleaseFunds func(p0 context.Context, p1 address.Address, p2 types.BigInt) error `perm:"sign"` + + MarketReserveFunds func(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt) (cid.Cid, error) `perm:"sign"` + + MarketWithdraw func(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt) (cid.Cid, error) `perm:"sign"` + + MinerCreateBlock func(p0 context.Context, p1 *api.BlockTemplate) (*types.BlockMsg, error) `perm:"write"` + + MinerGetBaseInfo func(p0 context.Context, p1 address.Address, p2 abi.ChainEpoch, p3 types.TipSetKey) (*api.MiningBaseInfo, error) `perm:"read"` + + MpoolBatchPush func(p0 context.Context, p1 []*types.SignedMessage) ([]cid.Cid, error) `perm:"write"` + + MpoolBatchPushMessage func(p0 context.Context, p1 []*types.Message, p2 *api.MessageSendSpec) ([]*types.SignedMessage, error) `perm:"sign"` + + MpoolBatchPushUntrusted func(p0 context.Context, p1 []*types.SignedMessage) ([]cid.Cid, error) `perm:"write"` + + MpoolClear func(p0 context.Context, p1 bool) error `perm:"write"` + + MpoolGetConfig func(p0 context.Context) (*types.MpoolConfig, error) `perm:"read"` + + MpoolGetNonce func(p0 context.Context, p1 address.Address) (uint64, error) `perm:"read"` + + MpoolPending func(p0 context.Context, p1 types.TipSetKey) ([]*types.SignedMessage, error) `perm:"read"` + + MpoolPush func(p0 context.Context, p1 *types.SignedMessage) (cid.Cid, error) `perm:"write"` + + MpoolPushMessage func(p0 context.Context, p1 *types.Message, p2 *api.MessageSendSpec) (*types.SignedMessage, error) `perm:"sign"` + + MpoolPushUntrusted func(p0 context.Context, p1 *types.SignedMessage) (cid.Cid, error) `perm:"write"` + + MpoolSelect func(p0 context.Context, p1 types.TipSetKey, p2 float64) ([]*types.SignedMessage, error) `perm:"read"` + + MpoolSetConfig func(p0 context.Context, p1 *types.MpoolConfig) error `perm:"admin"` + + MpoolSub func(p0 context.Context) (<-chan api.MpoolUpdate, error) `perm:"read"` + + MsigAddApprove func(p0 context.Context, p1 address.Address, p2 address.Address, p3 uint64, p4 address.Address, p5 address.Address, p6 bool) (cid.Cid, error) `perm:"sign"` + + MsigAddCancel func(p0 context.Context, p1 address.Address, p2 address.Address, p3 uint64, p4 address.Address, p5 bool) (cid.Cid, error) `perm:"sign"` + + MsigAddPropose func(p0 context.Context, p1 address.Address, p2 address.Address, p3 address.Address, p4 bool) (cid.Cid, error) `perm:"sign"` + + MsigApprove func(p0 context.Context, p1 address.Address, p2 uint64, p3 address.Address) (cid.Cid, error) `perm:"sign"` + + MsigApproveTxnHash func(p0 context.Context, p1 address.Address, p2 uint64, p3 address.Address, p4 address.Address, p5 types.BigInt, p6 address.Address, p7 uint64, p8 []byte) (cid.Cid, error) `perm:"sign"` + + MsigCancel func(p0 context.Context, p1 address.Address, p2 uint64, p3 address.Address, p4 types.BigInt, p5 address.Address, p6 uint64, p7 []byte) (cid.Cid, error) `perm:"sign"` + + MsigCreate func(p0 context.Context, p1 uint64, p2 []address.Address, p3 abi.ChainEpoch, p4 types.BigInt, p5 address.Address, p6 types.BigInt) (cid.Cid, error) `perm:"sign"` + + MsigGetAvailableBalance func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (types.BigInt, error) `perm:"read"` + + MsigGetPending func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) ([]*api.MsigTransaction, error) `perm:"read"` + + MsigGetVested func(p0 context.Context, p1 address.Address, p2 types.TipSetKey, p3 types.TipSetKey) (types.BigInt, error) `perm:"read"` + + MsigGetVestingSchedule func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (api.MsigVesting, error) `perm:"read"` + + MsigPropose func(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt, p4 address.Address, p5 uint64, p6 []byte) (cid.Cid, error) `perm:"sign"` + + MsigRemoveSigner func(p0 context.Context, p1 address.Address, p2 address.Address, p3 address.Address, p4 bool) (cid.Cid, error) `perm:"sign"` + + MsigSwapApprove func(p0 context.Context, p1 address.Address, p2 address.Address, p3 uint64, p4 address.Address, p5 address.Address, p6 address.Address) (cid.Cid, error) `perm:"sign"` + + MsigSwapCancel func(p0 context.Context, p1 address.Address, p2 address.Address, p3 uint64, p4 address.Address, p5 address.Address) (cid.Cid, error) `perm:"sign"` + + MsigSwapPropose func(p0 context.Context, p1 address.Address, p2 address.Address, p3 address.Address, p4 address.Address) (cid.Cid, error) `perm:"sign"` + + PaychAllocateLane func(p0 context.Context, p1 address.Address) (uint64, error) `perm:"sign"` + + PaychAvailableFunds func(p0 context.Context, p1 address.Address) (*api.ChannelAvailableFunds, error) `perm:"sign"` + + PaychAvailableFundsByFromTo func(p0 context.Context, p1 address.Address, p2 address.Address) (*api.ChannelAvailableFunds, error) `perm:"sign"` + + PaychCollect func(p0 context.Context, p1 address.Address) (cid.Cid, error) `perm:"sign"` + + PaychGet func(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt) (*api.ChannelInfo, error) `perm:"sign"` + + PaychGetWaitReady func(p0 context.Context, p1 cid.Cid) (address.Address, error) `perm:"sign"` + + PaychList func(p0 context.Context) ([]address.Address, error) `perm:"read"` + + PaychNewPayment func(p0 context.Context, p1 address.Address, p2 address.Address, p3 []api.VoucherSpec) (*api.PaymentInfo, error) `perm:"sign"` + + PaychSettle func(p0 context.Context, p1 address.Address) (cid.Cid, error) `perm:"sign"` + + PaychStatus func(p0 context.Context, p1 address.Address) (*api.PaychStatus, error) `perm:"read"` + + PaychVoucherAdd func(p0 context.Context, p1 address.Address, p2 *paych.SignedVoucher, p3 []byte, p4 types.BigInt) (types.BigInt, error) `perm:"write"` + + PaychVoucherCheckSpendable func(p0 context.Context, p1 address.Address, p2 *paych.SignedVoucher, p3 []byte, p4 []byte) (bool, error) `perm:"read"` + + PaychVoucherCheckValid func(p0 context.Context, p1 address.Address, p2 *paych.SignedVoucher) error `perm:"read"` + + PaychVoucherCreate func(p0 context.Context, p1 address.Address, p2 types.BigInt, p3 uint64) (*api.VoucherCreateResult, error) `perm:"sign"` + + PaychVoucherList func(p0 context.Context, p1 address.Address) ([]*paych.SignedVoucher, error) `perm:"write"` + + PaychVoucherSubmit func(p0 context.Context, p1 address.Address, p2 *paych.SignedVoucher, p3 []byte, p4 []byte) (cid.Cid, error) `perm:"sign"` + + StateAccountKey func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) `perm:"read"` + + StateActorCodeCIDs func(p0 context.Context, p1 abinetwork.Version) (map[string]cid.Cid, error) `perm:"read"` + + StateActorManifestCID func(p0 context.Context, p1 abinetwork.Version) (cid.Cid, error) `perm:"read"` + + StateAllMinerFaults func(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) ([]*api.Fault, error) `perm:"read"` + + StateCall func(p0 context.Context, p1 *types.Message, p2 types.TipSetKey) (*api.InvocResult, error) `perm:"read"` + + StateChangedActors func(p0 context.Context, p1 cid.Cid, p2 cid.Cid) (map[string]types.Actor, error) `perm:"read"` + + StateCirculatingSupply func(p0 context.Context, p1 types.TipSetKey) (abi.TokenAmount, error) `perm:"read"` + + StateCompute func(p0 context.Context, p1 abi.ChainEpoch, p2 []*types.Message, p3 types.TipSetKey) (*api.ComputeStateOutput, error) `perm:"read"` + + StateDealProviderCollateralBounds func(p0 context.Context, p1 abi.PaddedPieceSize, p2 bool, p3 types.TipSetKey) (api.DealCollateralBounds, error) `perm:"read"` + + StateDecodeParams func(p0 context.Context, p1 address.Address, p2 abi.MethodNum, p3 []byte, p4 types.TipSetKey) (interface{}, error) `perm:"read"` + + StateGetActor func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*types.Actor, error) `perm:"read"` + + StateGetAllocation func(p0 context.Context, p1 address.Address, p2 verifregtypes.AllocationId, p3 types.TipSetKey) (*verifregtypes.Allocation, error) `perm:"read"` + + StateGetAllocationForPendingDeal func(p0 context.Context, p1 abi.DealID, p2 types.TipSetKey) (*verifregtypes.Allocation, error) `perm:"read"` + + StateGetAllocations func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (map[verifregtypes.AllocationId]verifregtypes.Allocation, error) `perm:"read"` + + StateGetClaim func(p0 context.Context, p1 address.Address, p2 verifregtypes.ClaimId, p3 types.TipSetKey) (*verifregtypes.Claim, error) `perm:"read"` + + StateGetClaims func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (map[verifregtypes.ClaimId]verifregtypes.Claim, error) `perm:"read"` + + StateGetNetworkParams func(p0 context.Context) (*api.NetworkParams, error) `perm:"read"` + + StateGetRandomnessFromBeacon func(p0 context.Context, p1 crypto.DomainSeparationTag, p2 abi.ChainEpoch, p3 []byte, p4 types.TipSetKey) (abi.Randomness, error) `perm:"read"` + + StateGetRandomnessFromTickets func(p0 context.Context, p1 crypto.DomainSeparationTag, p2 abi.ChainEpoch, p3 []byte, p4 types.TipSetKey) (abi.Randomness, error) `perm:"read"` + + StateGetReceipt func(p0 context.Context, p1 cid.Cid, p2 types.TipSetKey) (*types.MessageReceipt, error) `perm:"read"` + + StateListActors func(p0 context.Context, p1 types.TipSetKey) ([]address.Address, error) `perm:"read"` + + StateListMessages func(p0 context.Context, p1 *api.MessageMatch, p2 types.TipSetKey, p3 abi.ChainEpoch) ([]cid.Cid, error) `perm:"read"` + + StateListMiners func(p0 context.Context, p1 types.TipSetKey) ([]address.Address, error) `perm:"read"` + + StateLookupID func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) `perm:"read"` + + StateMarketBalance func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (api.MarketBalance, error) `perm:"read"` + + StateMarketDeals func(p0 context.Context, p1 types.TipSetKey) (map[string]*api.MarketDeal, error) `perm:"read"` + + StateMarketParticipants func(p0 context.Context, p1 types.TipSetKey) (map[string]api.MarketBalance, error) `perm:"read"` + + StateMarketStorageDeal func(p0 context.Context, p1 abi.DealID, p2 types.TipSetKey) (*api.MarketDeal, error) `perm:"read"` + + StateMinerActiveSectors func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) ([]*miner.SectorOnChainInfo, error) `perm:"read"` + + StateMinerAvailableBalance func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (types.BigInt, error) `perm:"read"` + + StateMinerDeadlines func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) ([]api.Deadline, error) `perm:"read"` + + StateMinerFaults func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (bitfield.BitField, error) `perm:"read"` + + StateMinerInfo func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (api.MinerInfo, error) `perm:"read"` + + StateMinerInitialPledgeCollateral func(p0 context.Context, p1 address.Address, p2 miner.SectorPreCommitInfo, p3 types.TipSetKey) (types.BigInt, error) `perm:"read"` + + StateMinerPartitions func(p0 context.Context, p1 address.Address, p2 uint64, p3 types.TipSetKey) ([]api.Partition, error) `perm:"read"` + + StateMinerPower func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*api.MinerPower, error) `perm:"read"` + + StateMinerPreCommitDepositForPower func(p0 context.Context, p1 address.Address, p2 miner.SectorPreCommitInfo, p3 types.TipSetKey) (types.BigInt, error) `perm:"read"` + + StateMinerProvingDeadline func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*dline.Info, error) `perm:"read"` + + StateMinerRecoveries func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (bitfield.BitField, error) `perm:"read"` + + StateMinerSectorAllocated func(p0 context.Context, p1 address.Address, p2 abi.SectorNumber, p3 types.TipSetKey) (bool, error) `perm:"read"` + + StateMinerSectorCount func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (api.MinerSectors, error) `perm:"read"` + + StateMinerSectors func(p0 context.Context, p1 address.Address, p2 *bitfield.BitField, p3 types.TipSetKey) ([]*miner.SectorOnChainInfo, error) `perm:"read"` + + StateNetworkName func(p0 context.Context) (dtypes.NetworkName, error) `perm:"read"` + + StateNetworkVersion func(p0 context.Context, p1 types.TipSetKey) (apitypes.NetworkVersion, error) `perm:"read"` + + StateReadState func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*api.ActorState, error) `perm:"read"` + + StateReplay func(p0 context.Context, p1 types.TipSetKey, p2 cid.Cid) (*api.InvocResult, error) `perm:"read"` + + StateSearchMsg func(p0 context.Context, p1 cid.Cid) (*api.MsgLookup, error) `perm:"read"` + + StateSearchMsgLimited func(p0 context.Context, p1 cid.Cid, p2 abi.ChainEpoch) (*api.MsgLookup, error) `perm:"read"` + + StateSectorExpiration func(p0 context.Context, p1 address.Address, p2 abi.SectorNumber, p3 types.TipSetKey) (*lminer.SectorExpiration, error) `perm:"read"` + + StateSectorGetInfo func(p0 context.Context, p1 address.Address, p2 abi.SectorNumber, p3 types.TipSetKey) (*miner.SectorOnChainInfo, error) `perm:"read"` + + StateSectorPartition func(p0 context.Context, p1 address.Address, p2 abi.SectorNumber, p3 types.TipSetKey) (*lminer.SectorLocation, error) `perm:"read"` + + StateSectorPreCommitInfo func(p0 context.Context, p1 address.Address, p2 abi.SectorNumber, p3 types.TipSetKey) (miner.SectorPreCommitOnChainInfo, error) `perm:"read"` + + StateVMCirculatingSupplyInternal func(p0 context.Context, p1 types.TipSetKey) (api.CirculatingSupply, error) `perm:"read"` + + StateVerifiedClientStatus func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*abi.StoragePower, error) `perm:"read"` + + StateVerifiedRegistryRootKey func(p0 context.Context, p1 types.TipSetKey) (address.Address, error) `perm:"read"` + + StateVerifierStatus func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*abi.StoragePower, error) `perm:"read"` + + StateWaitMsg func(p0 context.Context, p1 cid.Cid, p2 uint64) (*api.MsgLookup, error) `perm:"read"` + + StateWaitMsgLimited func(p0 context.Context, p1 cid.Cid, p2 uint64, p3 abi.ChainEpoch) (*api.MsgLookup, error) `perm:"read"` + + SyncCheckBad func(p0 context.Context, p1 cid.Cid) (string, error) `perm:"read"` + + SyncCheckpoint func(p0 context.Context, p1 types.TipSetKey) error `perm:"admin"` + + SyncIncomingBlocks func(p0 context.Context) (<-chan *types.BlockHeader, error) `perm:"read"` + + SyncMarkBad func(p0 context.Context, p1 cid.Cid) error `perm:"admin"` + + SyncState func(p0 context.Context) (*api.SyncState, error) `perm:"read"` + + SyncSubmitBlock func(p0 context.Context, p1 *types.BlockMsg) error `perm:"write"` + + SyncUnmarkAllBad func(p0 context.Context) error `perm:"admin"` + + SyncUnmarkBad func(p0 context.Context, p1 cid.Cid) error `perm:"admin"` + + SyncValidateTipset func(p0 context.Context, p1 types.TipSetKey) (bool, error) `perm:"read"` + + WalletBalance func(p0 context.Context, p1 address.Address) (types.BigInt, error) `perm:"read"` + + WalletDefaultAddress func(p0 context.Context) (address.Address, error) `perm:"write"` + + WalletDelete func(p0 context.Context, p1 address.Address) error `perm:"admin"` + + WalletExport func(p0 context.Context, p1 address.Address) (*types.KeyInfo, error) `perm:"admin"` + + WalletHas func(p0 context.Context, p1 address.Address) (bool, error) `perm:"write"` + + WalletImport func(p0 context.Context, p1 *types.KeyInfo) (address.Address, error) `perm:"admin"` + + WalletList func(p0 context.Context) ([]address.Address, error) `perm:"write"` + + WalletNew func(p0 context.Context, p1 types.KeyType) (address.Address, error) `perm:"write"` + + WalletSetDefault func(p0 context.Context, p1 address.Address) error `perm:"write"` + + WalletSign func(p0 context.Context, p1 address.Address, p2 []byte) (*crypto.Signature, error) `perm:"sign"` + + WalletSignMessage func(p0 context.Context, p1 address.Address, p2 *types.Message) (*types.SignedMessage, error) `perm:"sign"` + + WalletValidateAddress func(p0 context.Context, p1 string) (address.Address, error) `perm:"read"` + + WalletVerify func(p0 context.Context, p1 address.Address, p2 []byte, p3 *crypto.Signature) (bool, error) `perm:"read"` +} + +type FullNodeStub struct { + CommonStub + + NetStub +} + +type GatewayStruct struct { + Internal GatewayMethods +} + +type GatewayMethods struct { + ChainGetBlockMessages func(p0 context.Context, p1 cid.Cid) (*api.BlockMessages, error) `` + + ChainGetMessage func(p0 context.Context, p1 cid.Cid) (*types.Message, error) `` + + ChainGetTipSet func(p0 context.Context, p1 types.TipSetKey) (*types.TipSet, error) `` + + ChainGetTipSetByHeight func(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) `` + + ChainHasObj func(p0 context.Context, p1 cid.Cid) (bool, error) `` + + ChainHead func(p0 context.Context) (*types.TipSet, error) `` + + ChainNotify func(p0 context.Context) (<-chan []*api.HeadChange, error) `` + + ChainPutObj func(p0 context.Context, p1 blocks.Block) error `` + + ChainReadObj func(p0 context.Context, p1 cid.Cid) ([]byte, error) `` + + GasEstimateGasPremium func(p0 context.Context, p1 uint64, p2 address.Address, p3 int64, p4 types.TipSetKey) (types.BigInt, error) `` + + GasEstimateMessageGas func(p0 context.Context, p1 *types.Message, p2 *api.MessageSendSpec, p3 types.TipSetKey) (*types.Message, error) `` + + MpoolGetNonce func(p0 context.Context, p1 address.Address) (uint64, error) `` + + MpoolPush func(p0 context.Context, p1 *types.SignedMessage) (cid.Cid, error) `` + + MsigGetAvailableBalance func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (types.BigInt, error) `` + + MsigGetPending func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) ([]*api.MsigTransaction, error) `` + + MsigGetVested func(p0 context.Context, p1 address.Address, p2 types.TipSetKey, p3 types.TipSetKey) (types.BigInt, error) `` + + StateAccountKey func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) `` + + StateCall func(p0 context.Context, p1 *types.Message, p2 types.TipSetKey) (*api.InvocResult, error) `` + + StateDealProviderCollateralBounds func(p0 context.Context, p1 abi.PaddedPieceSize, p2 bool, p3 types.TipSetKey) (api.DealCollateralBounds, error) `` + + StateDecodeParams func(p0 context.Context, p1 address.Address, p2 abi.MethodNum, p3 []byte, p4 types.TipSetKey) (interface{}, error) `` + + StateGetActor func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*types.Actor, error) `` + + StateGetReceipt func(p0 context.Context, p1 cid.Cid, p2 types.TipSetKey) (*types.MessageReceipt, error) `` + + StateListMiners func(p0 context.Context, p1 types.TipSetKey) ([]address.Address, error) `` + + StateLookupID func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) `` + + StateMarketBalance func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (api.MarketBalance, error) `` + + StateMarketStorageDeal func(p0 context.Context, p1 abi.DealID, p2 types.TipSetKey) (*api.MarketDeal, error) `` + + StateMinerInfo func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (api.MinerInfo, error) `` + + StateMinerPower func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*api.MinerPower, error) `` + + StateMinerProvingDeadline func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*dline.Info, error) `` + + StateMinerSectorCount func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (api.MinerSectors, error) `` + + StateNetworkName func(p0 context.Context) (dtypes.NetworkName, error) `` + + StateNetworkVersion func(p0 context.Context, p1 types.TipSetKey) (abinetwork.Version, error) `` + + StateReplay func(p0 context.Context, p1 types.TipSetKey, p2 cid.Cid) (*api.InvocResult, error) `` + + StateSearchMsg func(p0 context.Context, p1 cid.Cid) (*api.MsgLookup, error) `` + + StateSectorGetInfo func(p0 context.Context, p1 address.Address, p2 abi.SectorNumber, p3 types.TipSetKey) (*miner.SectorOnChainInfo, error) `` + + StateVerifiedClientStatus func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*abi.StoragePower, error) `` + + 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) `` +} + +type GatewayStub struct { +} + +func (s *FullNodeStruct) BeaconGetEntry(p0 context.Context, p1 abi.ChainEpoch) (*types.BeaconEntry, error) { + if s.Internal.BeaconGetEntry == nil { + return nil, ErrNotSupported + } + return s.Internal.BeaconGetEntry(p0, p1) +} + +func (s *FullNodeStub) BeaconGetEntry(p0 context.Context, p1 abi.ChainEpoch) (*types.BeaconEntry, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ChainDeleteObj(p0 context.Context, p1 cid.Cid) error { + if s.Internal.ChainDeleteObj == nil { + return ErrNotSupported + } + return s.Internal.ChainDeleteObj(p0, p1) +} + +func (s *FullNodeStub) ChainDeleteObj(p0 context.Context, p1 cid.Cid) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) ChainExport(p0 context.Context, p1 abi.ChainEpoch, p2 bool, p3 types.TipSetKey) (<-chan []byte, error) { + if s.Internal.ChainExport == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainExport(p0, p1, p2, p3) +} + +func (s *FullNodeStub) ChainExport(p0 context.Context, p1 abi.ChainEpoch, p2 bool, p3 types.TipSetKey) (<-chan []byte, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ChainGetBlock(p0 context.Context, p1 cid.Cid) (*types.BlockHeader, error) { + if s.Internal.ChainGetBlock == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainGetBlock(p0, p1) +} + +func (s *FullNodeStub) ChainGetBlock(p0 context.Context, p1 cid.Cid) (*types.BlockHeader, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ChainGetBlockMessages(p0 context.Context, p1 cid.Cid) (*api.BlockMessages, error) { + if s.Internal.ChainGetBlockMessages == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainGetBlockMessages(p0, p1) +} + +func (s *FullNodeStub) ChainGetBlockMessages(p0 context.Context, p1 cid.Cid) (*api.BlockMessages, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ChainGetGenesis(p0 context.Context) (*types.TipSet, error) { + if s.Internal.ChainGetGenesis == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainGetGenesis(p0) +} + +func (s *FullNodeStub) ChainGetGenesis(p0 context.Context) (*types.TipSet, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ChainGetMessage(p0 context.Context, p1 cid.Cid) (*types.Message, error) { + if s.Internal.ChainGetMessage == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainGetMessage(p0, p1) +} + +func (s *FullNodeStub) ChainGetMessage(p0 context.Context, p1 cid.Cid) (*types.Message, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ChainGetMessagesInTipset(p0 context.Context, p1 types.TipSetKey) ([]api.Message, error) { + if s.Internal.ChainGetMessagesInTipset == nil { + return *new([]api.Message), ErrNotSupported + } + return s.Internal.ChainGetMessagesInTipset(p0, p1) +} + +func (s *FullNodeStub) ChainGetMessagesInTipset(p0 context.Context, p1 types.TipSetKey) ([]api.Message, error) { + return *new([]api.Message), ErrNotSupported +} + +func (s *FullNodeStruct) ChainGetNode(p0 context.Context, p1 string) (*api.IpldObject, error) { + if s.Internal.ChainGetNode == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainGetNode(p0, p1) +} + +func (s *FullNodeStub) ChainGetNode(p0 context.Context, p1 string) (*api.IpldObject, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ChainGetParentMessages(p0 context.Context, p1 cid.Cid) ([]api.Message, error) { + if s.Internal.ChainGetParentMessages == nil { + return *new([]api.Message), ErrNotSupported + } + return s.Internal.ChainGetParentMessages(p0, p1) +} + +func (s *FullNodeStub) ChainGetParentMessages(p0 context.Context, p1 cid.Cid) ([]api.Message, error) { + return *new([]api.Message), ErrNotSupported +} + +func (s *FullNodeStruct) ChainGetParentReceipts(p0 context.Context, p1 cid.Cid) ([]*types.MessageReceipt, error) { + if s.Internal.ChainGetParentReceipts == nil { + return *new([]*types.MessageReceipt), ErrNotSupported + } + return s.Internal.ChainGetParentReceipts(p0, p1) +} + +func (s *FullNodeStub) ChainGetParentReceipts(p0 context.Context, p1 cid.Cid) ([]*types.MessageReceipt, error) { + return *new([]*types.MessageReceipt), ErrNotSupported +} + +func (s *FullNodeStruct) ChainGetPath(p0 context.Context, p1 types.TipSetKey, p2 types.TipSetKey) ([]*api.HeadChange, error) { + if s.Internal.ChainGetPath == nil { + return *new([]*api.HeadChange), ErrNotSupported + } + return s.Internal.ChainGetPath(p0, p1, p2) +} + +func (s *FullNodeStub) ChainGetPath(p0 context.Context, p1 types.TipSetKey, p2 types.TipSetKey) ([]*api.HeadChange, error) { + return *new([]*api.HeadChange), ErrNotSupported +} + +func (s *FullNodeStruct) ChainGetRandomnessFromBeacon(p0 context.Context, p1 types.TipSetKey, p2 crypto.DomainSeparationTag, p3 abi.ChainEpoch, p4 []byte) (abi.Randomness, error) { + if s.Internal.ChainGetRandomnessFromBeacon == nil { + return *new(abi.Randomness), ErrNotSupported + } + return s.Internal.ChainGetRandomnessFromBeacon(p0, p1, p2, p3, p4) +} + +func (s *FullNodeStub) ChainGetRandomnessFromBeacon(p0 context.Context, p1 types.TipSetKey, p2 crypto.DomainSeparationTag, p3 abi.ChainEpoch, p4 []byte) (abi.Randomness, error) { + return *new(abi.Randomness), ErrNotSupported +} + +func (s *FullNodeStruct) ChainGetRandomnessFromTickets(p0 context.Context, p1 types.TipSetKey, p2 crypto.DomainSeparationTag, p3 abi.ChainEpoch, p4 []byte) (abi.Randomness, error) { + if s.Internal.ChainGetRandomnessFromTickets == nil { + return *new(abi.Randomness), ErrNotSupported + } + return s.Internal.ChainGetRandomnessFromTickets(p0, p1, p2, p3, p4) +} + +func (s *FullNodeStub) ChainGetRandomnessFromTickets(p0 context.Context, p1 types.TipSetKey, p2 crypto.DomainSeparationTag, p3 abi.ChainEpoch, p4 []byte) (abi.Randomness, error) { + return *new(abi.Randomness), ErrNotSupported +} + +func (s *FullNodeStruct) ChainGetTipSet(p0 context.Context, p1 types.TipSetKey) (*types.TipSet, error) { + if s.Internal.ChainGetTipSet == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainGetTipSet(p0, p1) +} + +func (s *FullNodeStub) ChainGetTipSet(p0 context.Context, p1 types.TipSetKey) (*types.TipSet, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ChainGetTipSetByHeight(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) { + if s.Internal.ChainGetTipSetByHeight == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainGetTipSetByHeight(p0, p1, p2) +} + +func (s *FullNodeStub) ChainGetTipSetByHeight(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ChainHasObj(p0 context.Context, p1 cid.Cid) (bool, error) { + if s.Internal.ChainHasObj == nil { + return false, ErrNotSupported + } + return s.Internal.ChainHasObj(p0, p1) +} + +func (s *FullNodeStub) ChainHasObj(p0 context.Context, p1 cid.Cid) (bool, error) { + return false, ErrNotSupported +} + +func (s *FullNodeStruct) ChainHead(p0 context.Context) (*types.TipSet, error) { + if s.Internal.ChainHead == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainHead(p0) +} + +func (s *FullNodeStub) ChainHead(p0 context.Context) (*types.TipSet, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ChainNotify(p0 context.Context) (<-chan []*api.HeadChange, error) { + if s.Internal.ChainNotify == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainNotify(p0) +} + +func (s *FullNodeStub) ChainNotify(p0 context.Context) (<-chan []*api.HeadChange, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ChainPutObj(p0 context.Context, p1 blocks.Block) error { + if s.Internal.ChainPutObj == nil { + return ErrNotSupported + } + return s.Internal.ChainPutObj(p0, p1) +} + +func (s *FullNodeStub) ChainPutObj(p0 context.Context, p1 blocks.Block) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) ChainReadObj(p0 context.Context, p1 cid.Cid) ([]byte, error) { + if s.Internal.ChainReadObj == nil { + return *new([]byte), ErrNotSupported + } + return s.Internal.ChainReadObj(p0, p1) +} + +func (s *FullNodeStub) ChainReadObj(p0 context.Context, p1 cid.Cid) ([]byte, error) { + return *new([]byte), ErrNotSupported +} + +func (s *FullNodeStruct) ChainSetHead(p0 context.Context, p1 types.TipSetKey) error { + if s.Internal.ChainSetHead == nil { + return ErrNotSupported + } + return s.Internal.ChainSetHead(p0, p1) +} + +func (s *FullNodeStub) ChainSetHead(p0 context.Context, p1 types.TipSetKey) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) ChainStatObj(p0 context.Context, p1 cid.Cid, p2 cid.Cid) (api.ObjStat, error) { + if s.Internal.ChainStatObj == nil { + return *new(api.ObjStat), ErrNotSupported + } + return s.Internal.ChainStatObj(p0, p1, p2) +} + +func (s *FullNodeStub) ChainStatObj(p0 context.Context, p1 cid.Cid, p2 cid.Cid) (api.ObjStat, error) { + return *new(api.ObjStat), ErrNotSupported +} + +func (s *FullNodeStruct) ChainTipSetWeight(p0 context.Context, p1 types.TipSetKey) (types.BigInt, error) { + if s.Internal.ChainTipSetWeight == nil { + return *new(types.BigInt), ErrNotSupported + } + return s.Internal.ChainTipSetWeight(p0, p1) +} + +func (s *FullNodeStub) ChainTipSetWeight(p0 context.Context, p1 types.TipSetKey) (types.BigInt, error) { + return *new(types.BigInt), ErrNotSupported +} + +func (s *FullNodeStruct) ClientCalcCommP(p0 context.Context, p1 string) (*api.CommPRet, error) { + if s.Internal.ClientCalcCommP == nil { + return nil, ErrNotSupported + } + return s.Internal.ClientCalcCommP(p0, p1) +} + +func (s *FullNodeStub) ClientCalcCommP(p0 context.Context, p1 string) (*api.CommPRet, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ClientCancelDataTransfer(p0 context.Context, p1 datatransfer.TransferID, p2 peer.ID, p3 bool) error { + if s.Internal.ClientCancelDataTransfer == nil { + return ErrNotSupported + } + return s.Internal.ClientCancelDataTransfer(p0, p1, p2, p3) +} + +func (s *FullNodeStub) ClientCancelDataTransfer(p0 context.Context, p1 datatransfer.TransferID, p2 peer.ID, p3 bool) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) ClientCancelRetrievalDeal(p0 context.Context, p1 retrievalmarket.DealID) error { + if s.Internal.ClientCancelRetrievalDeal == nil { + return ErrNotSupported + } + return s.Internal.ClientCancelRetrievalDeal(p0, p1) +} + +func (s *FullNodeStub) ClientCancelRetrievalDeal(p0 context.Context, p1 retrievalmarket.DealID) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) ClientDataTransferUpdates(p0 context.Context) (<-chan api.DataTransferChannel, error) { + if s.Internal.ClientDataTransferUpdates == nil { + return nil, ErrNotSupported + } + return s.Internal.ClientDataTransferUpdates(p0) +} + +func (s *FullNodeStub) ClientDataTransferUpdates(p0 context.Context) (<-chan api.DataTransferChannel, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ClientDealPieceCID(p0 context.Context, p1 cid.Cid) (api.DataCIDSize, error) { + if s.Internal.ClientDealPieceCID == nil { + return *new(api.DataCIDSize), ErrNotSupported + } + return s.Internal.ClientDealPieceCID(p0, p1) +} + +func (s *FullNodeStub) ClientDealPieceCID(p0 context.Context, p1 cid.Cid) (api.DataCIDSize, error) { + return *new(api.DataCIDSize), ErrNotSupported +} + +func (s *FullNodeStruct) ClientDealSize(p0 context.Context, p1 cid.Cid) (api.DataSize, error) { + if s.Internal.ClientDealSize == nil { + return *new(api.DataSize), ErrNotSupported + } + return s.Internal.ClientDealSize(p0, p1) +} + +func (s *FullNodeStub) ClientDealSize(p0 context.Context, p1 cid.Cid) (api.DataSize, error) { + return *new(api.DataSize), ErrNotSupported +} + +func (s *FullNodeStruct) ClientFindData(p0 context.Context, p1 cid.Cid, p2 *cid.Cid) ([]api.QueryOffer, error) { + if s.Internal.ClientFindData == nil { + return *new([]api.QueryOffer), ErrNotSupported + } + return s.Internal.ClientFindData(p0, p1, p2) +} + +func (s *FullNodeStub) ClientFindData(p0 context.Context, p1 cid.Cid, p2 *cid.Cid) ([]api.QueryOffer, error) { + return *new([]api.QueryOffer), ErrNotSupported +} + +func (s *FullNodeStruct) ClientGenCar(p0 context.Context, p1 api.FileRef, p2 string) error { + if s.Internal.ClientGenCar == nil { + return ErrNotSupported + } + return s.Internal.ClientGenCar(p0, p1, p2) +} + +func (s *FullNodeStub) ClientGenCar(p0 context.Context, p1 api.FileRef, p2 string) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) ClientGetDealInfo(p0 context.Context, p1 cid.Cid) (*api.DealInfo, error) { + if s.Internal.ClientGetDealInfo == nil { + return nil, ErrNotSupported + } + return s.Internal.ClientGetDealInfo(p0, p1) +} + +func (s *FullNodeStub) ClientGetDealInfo(p0 context.Context, p1 cid.Cid) (*api.DealInfo, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ClientGetDealStatus(p0 context.Context, p1 uint64) (string, error) { + if s.Internal.ClientGetDealStatus == nil { + return "", ErrNotSupported + } + return s.Internal.ClientGetDealStatus(p0, p1) +} + +func (s *FullNodeStub) ClientGetDealStatus(p0 context.Context, p1 uint64) (string, error) { + return "", ErrNotSupported +} + +func (s *FullNodeStruct) ClientGetDealUpdates(p0 context.Context) (<-chan api.DealInfo, error) { + if s.Internal.ClientGetDealUpdates == nil { + return nil, ErrNotSupported + } + return s.Internal.ClientGetDealUpdates(p0) +} + +func (s *FullNodeStub) ClientGetDealUpdates(p0 context.Context) (<-chan api.DealInfo, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ClientGetRetrievalUpdates(p0 context.Context) (<-chan api.RetrievalInfo, error) { + if s.Internal.ClientGetRetrievalUpdates == nil { + return nil, ErrNotSupported + } + return s.Internal.ClientGetRetrievalUpdates(p0) +} + +func (s *FullNodeStub) ClientGetRetrievalUpdates(p0 context.Context) (<-chan api.RetrievalInfo, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ClientHasLocal(p0 context.Context, p1 cid.Cid) (bool, error) { + if s.Internal.ClientHasLocal == nil { + return false, ErrNotSupported + } + return s.Internal.ClientHasLocal(p0, p1) +} + +func (s *FullNodeStub) ClientHasLocal(p0 context.Context, p1 cid.Cid) (bool, error) { + return false, ErrNotSupported +} + +func (s *FullNodeStruct) ClientImport(p0 context.Context, p1 api.FileRef) (*api.ImportRes, error) { + if s.Internal.ClientImport == nil { + return nil, ErrNotSupported + } + return s.Internal.ClientImport(p0, p1) +} + +func (s *FullNodeStub) ClientImport(p0 context.Context, p1 api.FileRef) (*api.ImportRes, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ClientListDataTransfers(p0 context.Context) ([]api.DataTransferChannel, error) { + if s.Internal.ClientListDataTransfers == nil { + return *new([]api.DataTransferChannel), ErrNotSupported + } + return s.Internal.ClientListDataTransfers(p0) +} + +func (s *FullNodeStub) ClientListDataTransfers(p0 context.Context) ([]api.DataTransferChannel, error) { + return *new([]api.DataTransferChannel), ErrNotSupported +} + +func (s *FullNodeStruct) ClientListDeals(p0 context.Context) ([]api.DealInfo, error) { + if s.Internal.ClientListDeals == nil { + return *new([]api.DealInfo), ErrNotSupported + } + return s.Internal.ClientListDeals(p0) +} + +func (s *FullNodeStub) ClientListDeals(p0 context.Context) ([]api.DealInfo, error) { + return *new([]api.DealInfo), ErrNotSupported +} + +func (s *FullNodeStruct) ClientListImports(p0 context.Context) ([]api.Import, error) { + if s.Internal.ClientListImports == nil { + return *new([]api.Import), ErrNotSupported + } + return s.Internal.ClientListImports(p0) +} + +func (s *FullNodeStub) ClientListImports(p0 context.Context) ([]api.Import, error) { + return *new([]api.Import), ErrNotSupported +} + +func (s *FullNodeStruct) ClientListRetrievals(p0 context.Context) ([]api.RetrievalInfo, error) { + if s.Internal.ClientListRetrievals == nil { + return *new([]api.RetrievalInfo), ErrNotSupported + } + return s.Internal.ClientListRetrievals(p0) +} + +func (s *FullNodeStub) ClientListRetrievals(p0 context.Context) ([]api.RetrievalInfo, error) { + return *new([]api.RetrievalInfo), ErrNotSupported +} + +func (s *FullNodeStruct) ClientMinerQueryOffer(p0 context.Context, p1 address.Address, p2 cid.Cid, p3 *cid.Cid) (api.QueryOffer, error) { + if s.Internal.ClientMinerQueryOffer == nil { + return *new(api.QueryOffer), ErrNotSupported + } + return s.Internal.ClientMinerQueryOffer(p0, p1, p2, p3) +} + +func (s *FullNodeStub) ClientMinerQueryOffer(p0 context.Context, p1 address.Address, p2 cid.Cid, p3 *cid.Cid) (api.QueryOffer, error) { + return *new(api.QueryOffer), ErrNotSupported +} + +func (s *FullNodeStruct) ClientQueryAsk(p0 context.Context, p1 peer.ID, p2 address.Address) (*storagemarket.StorageAsk, error) { + if s.Internal.ClientQueryAsk == nil { + return nil, ErrNotSupported + } + return s.Internal.ClientQueryAsk(p0, p1, p2) +} + +func (s *FullNodeStub) ClientQueryAsk(p0 context.Context, p1 peer.ID, p2 address.Address) (*storagemarket.StorageAsk, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ClientRemoveImport(p0 context.Context, p1 imports.ID) error { + if s.Internal.ClientRemoveImport == nil { + return ErrNotSupported + } + return s.Internal.ClientRemoveImport(p0, p1) +} + +func (s *FullNodeStub) ClientRemoveImport(p0 context.Context, p1 imports.ID) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) ClientRestartDataTransfer(p0 context.Context, p1 datatransfer.TransferID, p2 peer.ID, p3 bool) error { + if s.Internal.ClientRestartDataTransfer == nil { + return ErrNotSupported + } + return s.Internal.ClientRestartDataTransfer(p0, p1, p2, p3) +} + +func (s *FullNodeStub) ClientRestartDataTransfer(p0 context.Context, p1 datatransfer.TransferID, p2 peer.ID, p3 bool) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) ClientRetrieve(p0 context.Context, p1 RetrievalOrder, p2 *api.FileRef) error { + if s.Internal.ClientRetrieve == nil { + return ErrNotSupported + } + return s.Internal.ClientRetrieve(p0, p1, p2) +} + +func (s *FullNodeStub) ClientRetrieve(p0 context.Context, p1 RetrievalOrder, p2 *api.FileRef) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) ClientRetrieveTryRestartInsufficientFunds(p0 context.Context, p1 address.Address) error { + if s.Internal.ClientRetrieveTryRestartInsufficientFunds == nil { + return ErrNotSupported + } + return s.Internal.ClientRetrieveTryRestartInsufficientFunds(p0, p1) +} + +func (s *FullNodeStub) ClientRetrieveTryRestartInsufficientFunds(p0 context.Context, p1 address.Address) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) ClientRetrieveWithEvents(p0 context.Context, p1 RetrievalOrder, p2 *api.FileRef) (<-chan marketevents.RetrievalEvent, error) { + if s.Internal.ClientRetrieveWithEvents == nil { + return nil, ErrNotSupported + } + return s.Internal.ClientRetrieveWithEvents(p0, p1, p2) +} + +func (s *FullNodeStub) ClientRetrieveWithEvents(p0 context.Context, p1 RetrievalOrder, p2 *api.FileRef) (<-chan marketevents.RetrievalEvent, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ClientStartDeal(p0 context.Context, p1 *api.StartDealParams) (*cid.Cid, error) { + if s.Internal.ClientStartDeal == nil { + return nil, ErrNotSupported + } + return s.Internal.ClientStartDeal(p0, p1) +} + +func (s *FullNodeStub) ClientStartDeal(p0 context.Context, p1 *api.StartDealParams) (*cid.Cid, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) ClientStatelessDeal(p0 context.Context, p1 *api.StartDealParams) (*cid.Cid, error) { + if s.Internal.ClientStatelessDeal == nil { + return nil, ErrNotSupported + } + return s.Internal.ClientStatelessDeal(p0, p1) +} + +func (s *FullNodeStub) ClientStatelessDeal(p0 context.Context, p1 *api.StartDealParams) (*cid.Cid, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) CreateBackup(p0 context.Context, p1 string) error { + if s.Internal.CreateBackup == nil { + return ErrNotSupported + } + return s.Internal.CreateBackup(p0, p1) +} + +func (s *FullNodeStub) CreateBackup(p0 context.Context, p1 string) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) GasEstimateFeeCap(p0 context.Context, p1 *types.Message, p2 int64, p3 types.TipSetKey) (types.BigInt, error) { + if s.Internal.GasEstimateFeeCap == nil { + return *new(types.BigInt), ErrNotSupported + } + return s.Internal.GasEstimateFeeCap(p0, p1, p2, p3) +} + +func (s *FullNodeStub) GasEstimateFeeCap(p0 context.Context, p1 *types.Message, p2 int64, p3 types.TipSetKey) (types.BigInt, error) { + return *new(types.BigInt), ErrNotSupported +} + +func (s *FullNodeStruct) GasEstimateGasLimit(p0 context.Context, p1 *types.Message, p2 types.TipSetKey) (int64, error) { + if s.Internal.GasEstimateGasLimit == nil { + return 0, ErrNotSupported + } + return s.Internal.GasEstimateGasLimit(p0, p1, p2) +} + +func (s *FullNodeStub) GasEstimateGasLimit(p0 context.Context, p1 *types.Message, p2 types.TipSetKey) (int64, error) { + return 0, ErrNotSupported +} + +func (s *FullNodeStruct) GasEstimateGasPremium(p0 context.Context, p1 uint64, p2 address.Address, p3 int64, p4 types.TipSetKey) (types.BigInt, error) { + if s.Internal.GasEstimateGasPremium == nil { + return *new(types.BigInt), ErrNotSupported + } + return s.Internal.GasEstimateGasPremium(p0, p1, p2, p3, p4) +} + +func (s *FullNodeStub) GasEstimateGasPremium(p0 context.Context, p1 uint64, p2 address.Address, p3 int64, p4 types.TipSetKey) (types.BigInt, error) { + return *new(types.BigInt), ErrNotSupported +} + +func (s *FullNodeStruct) GasEstimateMessageGas(p0 context.Context, p1 *types.Message, p2 *api.MessageSendSpec, p3 types.TipSetKey) (*types.Message, error) { + if s.Internal.GasEstimateMessageGas == nil { + return nil, ErrNotSupported + } + return s.Internal.GasEstimateMessageGas(p0, p1, p2, p3) +} + +func (s *FullNodeStub) GasEstimateMessageGas(p0 context.Context, p1 *types.Message, p2 *api.MessageSendSpec, p3 types.TipSetKey) (*types.Message, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) MarketAddBalance(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt) (cid.Cid, error) { + if s.Internal.MarketAddBalance == nil { + return *new(cid.Cid), ErrNotSupported + } + return s.Internal.MarketAddBalance(p0, p1, p2, p3) +} + +func (s *FullNodeStub) MarketAddBalance(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt) (cid.Cid, error) { + return *new(cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) MarketGetReserved(p0 context.Context, p1 address.Address) (types.BigInt, error) { + if s.Internal.MarketGetReserved == nil { + return *new(types.BigInt), ErrNotSupported + } + return s.Internal.MarketGetReserved(p0, p1) +} + +func (s *FullNodeStub) MarketGetReserved(p0 context.Context, p1 address.Address) (types.BigInt, error) { + return *new(types.BigInt), ErrNotSupported +} + +func (s *FullNodeStruct) MarketReleaseFunds(p0 context.Context, p1 address.Address, p2 types.BigInt) error { + if s.Internal.MarketReleaseFunds == nil { + return ErrNotSupported + } + return s.Internal.MarketReleaseFunds(p0, p1, p2) +} + +func (s *FullNodeStub) MarketReleaseFunds(p0 context.Context, p1 address.Address, p2 types.BigInt) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) MarketReserveFunds(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt) (cid.Cid, error) { + if s.Internal.MarketReserveFunds == nil { + return *new(cid.Cid), ErrNotSupported + } + return s.Internal.MarketReserveFunds(p0, p1, p2, p3) +} + +func (s *FullNodeStub) MarketReserveFunds(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt) (cid.Cid, error) { + return *new(cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) MarketWithdraw(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt) (cid.Cid, error) { + if s.Internal.MarketWithdraw == nil { + return *new(cid.Cid), ErrNotSupported + } + return s.Internal.MarketWithdraw(p0, p1, p2, p3) +} + +func (s *FullNodeStub) MarketWithdraw(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt) (cid.Cid, error) { + return *new(cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) MinerCreateBlock(p0 context.Context, p1 *api.BlockTemplate) (*types.BlockMsg, error) { + if s.Internal.MinerCreateBlock == nil { + return nil, ErrNotSupported + } + return s.Internal.MinerCreateBlock(p0, p1) +} + +func (s *FullNodeStub) MinerCreateBlock(p0 context.Context, p1 *api.BlockTemplate) (*types.BlockMsg, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) MinerGetBaseInfo(p0 context.Context, p1 address.Address, p2 abi.ChainEpoch, p3 types.TipSetKey) (*api.MiningBaseInfo, error) { + if s.Internal.MinerGetBaseInfo == nil { + return nil, ErrNotSupported + } + return s.Internal.MinerGetBaseInfo(p0, p1, p2, p3) +} + +func (s *FullNodeStub) MinerGetBaseInfo(p0 context.Context, p1 address.Address, p2 abi.ChainEpoch, p3 types.TipSetKey) (*api.MiningBaseInfo, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) MpoolBatchPush(p0 context.Context, p1 []*types.SignedMessage) ([]cid.Cid, error) { + if s.Internal.MpoolBatchPush == nil { + return *new([]cid.Cid), ErrNotSupported + } + return s.Internal.MpoolBatchPush(p0, p1) +} + +func (s *FullNodeStub) MpoolBatchPush(p0 context.Context, p1 []*types.SignedMessage) ([]cid.Cid, error) { + return *new([]cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) MpoolBatchPushMessage(p0 context.Context, p1 []*types.Message, p2 *api.MessageSendSpec) ([]*types.SignedMessage, error) { + if s.Internal.MpoolBatchPushMessage == nil { + return *new([]*types.SignedMessage), ErrNotSupported + } + return s.Internal.MpoolBatchPushMessage(p0, p1, p2) +} + +func (s *FullNodeStub) MpoolBatchPushMessage(p0 context.Context, p1 []*types.Message, p2 *api.MessageSendSpec) ([]*types.SignedMessage, error) { + return *new([]*types.SignedMessage), ErrNotSupported +} + +func (s *FullNodeStruct) MpoolBatchPushUntrusted(p0 context.Context, p1 []*types.SignedMessage) ([]cid.Cid, error) { + if s.Internal.MpoolBatchPushUntrusted == nil { + return *new([]cid.Cid), ErrNotSupported + } + return s.Internal.MpoolBatchPushUntrusted(p0, p1) +} + +func (s *FullNodeStub) MpoolBatchPushUntrusted(p0 context.Context, p1 []*types.SignedMessage) ([]cid.Cid, error) { + return *new([]cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) MpoolClear(p0 context.Context, p1 bool) error { + if s.Internal.MpoolClear == nil { + return ErrNotSupported + } + return s.Internal.MpoolClear(p0, p1) +} + +func (s *FullNodeStub) MpoolClear(p0 context.Context, p1 bool) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) MpoolGetConfig(p0 context.Context) (*types.MpoolConfig, error) { + if s.Internal.MpoolGetConfig == nil { + return nil, ErrNotSupported + } + return s.Internal.MpoolGetConfig(p0) +} + +func (s *FullNodeStub) MpoolGetConfig(p0 context.Context) (*types.MpoolConfig, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) MpoolGetNonce(p0 context.Context, p1 address.Address) (uint64, error) { + if s.Internal.MpoolGetNonce == nil { + return 0, ErrNotSupported + } + return s.Internal.MpoolGetNonce(p0, p1) +} + +func (s *FullNodeStub) MpoolGetNonce(p0 context.Context, p1 address.Address) (uint64, error) { + return 0, ErrNotSupported +} + +func (s *FullNodeStruct) MpoolPending(p0 context.Context, p1 types.TipSetKey) ([]*types.SignedMessage, error) { + if s.Internal.MpoolPending == nil { + return *new([]*types.SignedMessage), ErrNotSupported + } + return s.Internal.MpoolPending(p0, p1) +} + +func (s *FullNodeStub) MpoolPending(p0 context.Context, p1 types.TipSetKey) ([]*types.SignedMessage, error) { + return *new([]*types.SignedMessage), ErrNotSupported +} + +func (s *FullNodeStruct) MpoolPush(p0 context.Context, p1 *types.SignedMessage) (cid.Cid, error) { + if s.Internal.MpoolPush == nil { + return *new(cid.Cid), ErrNotSupported + } + return s.Internal.MpoolPush(p0, p1) +} + +func (s *FullNodeStub) MpoolPush(p0 context.Context, p1 *types.SignedMessage) (cid.Cid, error) { + return *new(cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) MpoolPushMessage(p0 context.Context, p1 *types.Message, p2 *api.MessageSendSpec) (*types.SignedMessage, error) { + if s.Internal.MpoolPushMessage == nil { + return nil, ErrNotSupported + } + return s.Internal.MpoolPushMessage(p0, p1, p2) +} + +func (s *FullNodeStub) MpoolPushMessage(p0 context.Context, p1 *types.Message, p2 *api.MessageSendSpec) (*types.SignedMessage, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) MpoolPushUntrusted(p0 context.Context, p1 *types.SignedMessage) (cid.Cid, error) { + if s.Internal.MpoolPushUntrusted == nil { + return *new(cid.Cid), ErrNotSupported + } + return s.Internal.MpoolPushUntrusted(p0, p1) +} + +func (s *FullNodeStub) MpoolPushUntrusted(p0 context.Context, p1 *types.SignedMessage) (cid.Cid, error) { + return *new(cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) MpoolSelect(p0 context.Context, p1 types.TipSetKey, p2 float64) ([]*types.SignedMessage, error) { + if s.Internal.MpoolSelect == nil { + return *new([]*types.SignedMessage), ErrNotSupported + } + return s.Internal.MpoolSelect(p0, p1, p2) +} + +func (s *FullNodeStub) MpoolSelect(p0 context.Context, p1 types.TipSetKey, p2 float64) ([]*types.SignedMessage, error) { + return *new([]*types.SignedMessage), ErrNotSupported +} + +func (s *FullNodeStruct) MpoolSetConfig(p0 context.Context, p1 *types.MpoolConfig) error { + if s.Internal.MpoolSetConfig == nil { + return ErrNotSupported + } + return s.Internal.MpoolSetConfig(p0, p1) +} + +func (s *FullNodeStub) MpoolSetConfig(p0 context.Context, p1 *types.MpoolConfig) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) MpoolSub(p0 context.Context) (<-chan api.MpoolUpdate, error) { + if s.Internal.MpoolSub == nil { + return nil, ErrNotSupported + } + return s.Internal.MpoolSub(p0) +} + +func (s *FullNodeStub) MpoolSub(p0 context.Context) (<-chan api.MpoolUpdate, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) MsigAddApprove(p0 context.Context, p1 address.Address, p2 address.Address, p3 uint64, p4 address.Address, p5 address.Address, p6 bool) (cid.Cid, error) { + if s.Internal.MsigAddApprove == nil { + return *new(cid.Cid), ErrNotSupported + } + return s.Internal.MsigAddApprove(p0, p1, p2, p3, p4, p5, p6) +} + +func (s *FullNodeStub) MsigAddApprove(p0 context.Context, p1 address.Address, p2 address.Address, p3 uint64, p4 address.Address, p5 address.Address, p6 bool) (cid.Cid, error) { + return *new(cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) MsigAddCancel(p0 context.Context, p1 address.Address, p2 address.Address, p3 uint64, p4 address.Address, p5 bool) (cid.Cid, error) { + if s.Internal.MsigAddCancel == nil { + return *new(cid.Cid), ErrNotSupported + } + return s.Internal.MsigAddCancel(p0, p1, p2, p3, p4, p5) +} + +func (s *FullNodeStub) MsigAddCancel(p0 context.Context, p1 address.Address, p2 address.Address, p3 uint64, p4 address.Address, p5 bool) (cid.Cid, error) { + return *new(cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) MsigAddPropose(p0 context.Context, p1 address.Address, p2 address.Address, p3 address.Address, p4 bool) (cid.Cid, error) { + if s.Internal.MsigAddPropose == nil { + return *new(cid.Cid), ErrNotSupported + } + return s.Internal.MsigAddPropose(p0, p1, p2, p3, p4) +} + +func (s *FullNodeStub) MsigAddPropose(p0 context.Context, p1 address.Address, p2 address.Address, p3 address.Address, p4 bool) (cid.Cid, error) { + return *new(cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) MsigApprove(p0 context.Context, p1 address.Address, p2 uint64, p3 address.Address) (cid.Cid, error) { + if s.Internal.MsigApprove == nil { + return *new(cid.Cid), ErrNotSupported + } + return s.Internal.MsigApprove(p0, p1, p2, p3) +} + +func (s *FullNodeStub) MsigApprove(p0 context.Context, p1 address.Address, p2 uint64, p3 address.Address) (cid.Cid, error) { + return *new(cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) MsigApproveTxnHash(p0 context.Context, p1 address.Address, p2 uint64, p3 address.Address, p4 address.Address, p5 types.BigInt, p6 address.Address, p7 uint64, p8 []byte) (cid.Cid, error) { + if s.Internal.MsigApproveTxnHash == nil { + return *new(cid.Cid), ErrNotSupported + } + return s.Internal.MsigApproveTxnHash(p0, p1, p2, p3, p4, p5, p6, p7, p8) +} + +func (s *FullNodeStub) MsigApproveTxnHash(p0 context.Context, p1 address.Address, p2 uint64, p3 address.Address, p4 address.Address, p5 types.BigInt, p6 address.Address, p7 uint64, p8 []byte) (cid.Cid, error) { + return *new(cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) MsigCancel(p0 context.Context, p1 address.Address, p2 uint64, p3 address.Address, p4 types.BigInt, p5 address.Address, p6 uint64, p7 []byte) (cid.Cid, error) { + if s.Internal.MsigCancel == nil { + return *new(cid.Cid), ErrNotSupported + } + return s.Internal.MsigCancel(p0, p1, p2, p3, p4, p5, p6, p7) +} + +func (s *FullNodeStub) MsigCancel(p0 context.Context, p1 address.Address, p2 uint64, p3 address.Address, p4 types.BigInt, p5 address.Address, p6 uint64, p7 []byte) (cid.Cid, error) { + return *new(cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) MsigCreate(p0 context.Context, p1 uint64, p2 []address.Address, p3 abi.ChainEpoch, p4 types.BigInt, p5 address.Address, p6 types.BigInt) (cid.Cid, error) { + if s.Internal.MsigCreate == nil { + return *new(cid.Cid), ErrNotSupported + } + return s.Internal.MsigCreate(p0, p1, p2, p3, p4, p5, p6) +} + +func (s *FullNodeStub) MsigCreate(p0 context.Context, p1 uint64, p2 []address.Address, p3 abi.ChainEpoch, p4 types.BigInt, p5 address.Address, p6 types.BigInt) (cid.Cid, error) { + return *new(cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) MsigGetAvailableBalance(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (types.BigInt, error) { + if s.Internal.MsigGetAvailableBalance == nil { + return *new(types.BigInt), ErrNotSupported + } + return s.Internal.MsigGetAvailableBalance(p0, p1, p2) +} + +func (s *FullNodeStub) MsigGetAvailableBalance(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (types.BigInt, error) { + return *new(types.BigInt), ErrNotSupported +} + +func (s *FullNodeStruct) MsigGetPending(p0 context.Context, p1 address.Address, p2 types.TipSetKey) ([]*api.MsigTransaction, error) { + if s.Internal.MsigGetPending == nil { + return *new([]*api.MsigTransaction), ErrNotSupported + } + return s.Internal.MsigGetPending(p0, p1, p2) +} + +func (s *FullNodeStub) MsigGetPending(p0 context.Context, p1 address.Address, p2 types.TipSetKey) ([]*api.MsigTransaction, error) { + return *new([]*api.MsigTransaction), ErrNotSupported +} + +func (s *FullNodeStruct) MsigGetVested(p0 context.Context, p1 address.Address, p2 types.TipSetKey, p3 types.TipSetKey) (types.BigInt, error) { + if s.Internal.MsigGetVested == nil { + return *new(types.BigInt), ErrNotSupported + } + return s.Internal.MsigGetVested(p0, p1, p2, p3) +} + +func (s *FullNodeStub) MsigGetVested(p0 context.Context, p1 address.Address, p2 types.TipSetKey, p3 types.TipSetKey) (types.BigInt, error) { + return *new(types.BigInt), ErrNotSupported +} + +func (s *FullNodeStruct) MsigGetVestingSchedule(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (api.MsigVesting, error) { + if s.Internal.MsigGetVestingSchedule == nil { + return *new(api.MsigVesting), ErrNotSupported + } + return s.Internal.MsigGetVestingSchedule(p0, p1, p2) +} + +func (s *FullNodeStub) MsigGetVestingSchedule(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (api.MsigVesting, error) { + return *new(api.MsigVesting), ErrNotSupported +} + +func (s *FullNodeStruct) MsigPropose(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt, p4 address.Address, p5 uint64, p6 []byte) (cid.Cid, error) { + if s.Internal.MsigPropose == nil { + return *new(cid.Cid), ErrNotSupported + } + return s.Internal.MsigPropose(p0, p1, p2, p3, p4, p5, p6) +} + +func (s *FullNodeStub) MsigPropose(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt, p4 address.Address, p5 uint64, p6 []byte) (cid.Cid, error) { + return *new(cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) MsigRemoveSigner(p0 context.Context, p1 address.Address, p2 address.Address, p3 address.Address, p4 bool) (cid.Cid, error) { + if s.Internal.MsigRemoveSigner == nil { + return *new(cid.Cid), ErrNotSupported + } + return s.Internal.MsigRemoveSigner(p0, p1, p2, p3, p4) +} + +func (s *FullNodeStub) MsigRemoveSigner(p0 context.Context, p1 address.Address, p2 address.Address, p3 address.Address, p4 bool) (cid.Cid, error) { + return *new(cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) MsigSwapApprove(p0 context.Context, p1 address.Address, p2 address.Address, p3 uint64, p4 address.Address, p5 address.Address, p6 address.Address) (cid.Cid, error) { + if s.Internal.MsigSwapApprove == nil { + return *new(cid.Cid), ErrNotSupported + } + return s.Internal.MsigSwapApprove(p0, p1, p2, p3, p4, p5, p6) +} + +func (s *FullNodeStub) MsigSwapApprove(p0 context.Context, p1 address.Address, p2 address.Address, p3 uint64, p4 address.Address, p5 address.Address, p6 address.Address) (cid.Cid, error) { + return *new(cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) MsigSwapCancel(p0 context.Context, p1 address.Address, p2 address.Address, p3 uint64, p4 address.Address, p5 address.Address) (cid.Cid, error) { + if s.Internal.MsigSwapCancel == nil { + return *new(cid.Cid), ErrNotSupported + } + return s.Internal.MsigSwapCancel(p0, p1, p2, p3, p4, p5) +} + +func (s *FullNodeStub) MsigSwapCancel(p0 context.Context, p1 address.Address, p2 address.Address, p3 uint64, p4 address.Address, p5 address.Address) (cid.Cid, error) { + return *new(cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) MsigSwapPropose(p0 context.Context, p1 address.Address, p2 address.Address, p3 address.Address, p4 address.Address) (cid.Cid, error) { + if s.Internal.MsigSwapPropose == nil { + return *new(cid.Cid), ErrNotSupported + } + return s.Internal.MsigSwapPropose(p0, p1, p2, p3, p4) +} + +func (s *FullNodeStub) MsigSwapPropose(p0 context.Context, p1 address.Address, p2 address.Address, p3 address.Address, p4 address.Address) (cid.Cid, error) { + return *new(cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) PaychAllocateLane(p0 context.Context, p1 address.Address) (uint64, error) { + if s.Internal.PaychAllocateLane == nil { + return 0, ErrNotSupported + } + return s.Internal.PaychAllocateLane(p0, p1) +} + +func (s *FullNodeStub) PaychAllocateLane(p0 context.Context, p1 address.Address) (uint64, error) { + return 0, ErrNotSupported +} + +func (s *FullNodeStruct) PaychAvailableFunds(p0 context.Context, p1 address.Address) (*api.ChannelAvailableFunds, error) { + if s.Internal.PaychAvailableFunds == nil { + return nil, ErrNotSupported + } + return s.Internal.PaychAvailableFunds(p0, p1) +} + +func (s *FullNodeStub) PaychAvailableFunds(p0 context.Context, p1 address.Address) (*api.ChannelAvailableFunds, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) PaychAvailableFundsByFromTo(p0 context.Context, p1 address.Address, p2 address.Address) (*api.ChannelAvailableFunds, error) { + if s.Internal.PaychAvailableFundsByFromTo == nil { + return nil, ErrNotSupported + } + return s.Internal.PaychAvailableFundsByFromTo(p0, p1, p2) +} + +func (s *FullNodeStub) PaychAvailableFundsByFromTo(p0 context.Context, p1 address.Address, p2 address.Address) (*api.ChannelAvailableFunds, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) PaychCollect(p0 context.Context, p1 address.Address) (cid.Cid, error) { + if s.Internal.PaychCollect == nil { + return *new(cid.Cid), ErrNotSupported + } + return s.Internal.PaychCollect(p0, p1) +} + +func (s *FullNodeStub) PaychCollect(p0 context.Context, p1 address.Address) (cid.Cid, error) { + return *new(cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) PaychGet(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt) (*api.ChannelInfo, error) { + if s.Internal.PaychGet == nil { + return nil, ErrNotSupported + } + return s.Internal.PaychGet(p0, p1, p2, p3) +} + +func (s *FullNodeStub) PaychGet(p0 context.Context, p1 address.Address, p2 address.Address, p3 types.BigInt) (*api.ChannelInfo, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) PaychGetWaitReady(p0 context.Context, p1 cid.Cid) (address.Address, error) { + if s.Internal.PaychGetWaitReady == nil { + return *new(address.Address), ErrNotSupported + } + return s.Internal.PaychGetWaitReady(p0, p1) +} + +func (s *FullNodeStub) PaychGetWaitReady(p0 context.Context, p1 cid.Cid) (address.Address, error) { + return *new(address.Address), ErrNotSupported +} + +func (s *FullNodeStruct) PaychList(p0 context.Context) ([]address.Address, error) { + if s.Internal.PaychList == nil { + return *new([]address.Address), ErrNotSupported + } + return s.Internal.PaychList(p0) +} + +func (s *FullNodeStub) PaychList(p0 context.Context) ([]address.Address, error) { + return *new([]address.Address), ErrNotSupported +} + +func (s *FullNodeStruct) PaychNewPayment(p0 context.Context, p1 address.Address, p2 address.Address, p3 []api.VoucherSpec) (*api.PaymentInfo, error) { + if s.Internal.PaychNewPayment == nil { + return nil, ErrNotSupported + } + return s.Internal.PaychNewPayment(p0, p1, p2, p3) +} + +func (s *FullNodeStub) PaychNewPayment(p0 context.Context, p1 address.Address, p2 address.Address, p3 []api.VoucherSpec) (*api.PaymentInfo, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) PaychSettle(p0 context.Context, p1 address.Address) (cid.Cid, error) { + if s.Internal.PaychSettle == nil { + return *new(cid.Cid), ErrNotSupported + } + return s.Internal.PaychSettle(p0, p1) +} + +func (s *FullNodeStub) PaychSettle(p0 context.Context, p1 address.Address) (cid.Cid, error) { + return *new(cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) PaychStatus(p0 context.Context, p1 address.Address) (*api.PaychStatus, error) { + if s.Internal.PaychStatus == nil { + return nil, ErrNotSupported + } + return s.Internal.PaychStatus(p0, p1) +} + +func (s *FullNodeStub) PaychStatus(p0 context.Context, p1 address.Address) (*api.PaychStatus, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) PaychVoucherAdd(p0 context.Context, p1 address.Address, p2 *paych.SignedVoucher, p3 []byte, p4 types.BigInt) (types.BigInt, error) { + if s.Internal.PaychVoucherAdd == nil { + return *new(types.BigInt), ErrNotSupported + } + return s.Internal.PaychVoucherAdd(p0, p1, p2, p3, p4) +} + +func (s *FullNodeStub) PaychVoucherAdd(p0 context.Context, p1 address.Address, p2 *paych.SignedVoucher, p3 []byte, p4 types.BigInt) (types.BigInt, error) { + return *new(types.BigInt), ErrNotSupported +} + +func (s *FullNodeStruct) PaychVoucherCheckSpendable(p0 context.Context, p1 address.Address, p2 *paych.SignedVoucher, p3 []byte, p4 []byte) (bool, error) { + if s.Internal.PaychVoucherCheckSpendable == nil { + return false, ErrNotSupported + } + return s.Internal.PaychVoucherCheckSpendable(p0, p1, p2, p3, p4) +} + +func (s *FullNodeStub) PaychVoucherCheckSpendable(p0 context.Context, p1 address.Address, p2 *paych.SignedVoucher, p3 []byte, p4 []byte) (bool, error) { + return false, ErrNotSupported +} + +func (s *FullNodeStruct) PaychVoucherCheckValid(p0 context.Context, p1 address.Address, p2 *paych.SignedVoucher) error { + if s.Internal.PaychVoucherCheckValid == nil { + return ErrNotSupported + } + return s.Internal.PaychVoucherCheckValid(p0, p1, p2) +} + +func (s *FullNodeStub) PaychVoucherCheckValid(p0 context.Context, p1 address.Address, p2 *paych.SignedVoucher) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) PaychVoucherCreate(p0 context.Context, p1 address.Address, p2 types.BigInt, p3 uint64) (*api.VoucherCreateResult, error) { + if s.Internal.PaychVoucherCreate == nil { + return nil, ErrNotSupported + } + return s.Internal.PaychVoucherCreate(p0, p1, p2, p3) +} + +func (s *FullNodeStub) PaychVoucherCreate(p0 context.Context, p1 address.Address, p2 types.BigInt, p3 uint64) (*api.VoucherCreateResult, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) PaychVoucherList(p0 context.Context, p1 address.Address) ([]*paych.SignedVoucher, error) { + if s.Internal.PaychVoucherList == nil { + return *new([]*paych.SignedVoucher), ErrNotSupported + } + return s.Internal.PaychVoucherList(p0, p1) +} + +func (s *FullNodeStub) PaychVoucherList(p0 context.Context, p1 address.Address) ([]*paych.SignedVoucher, error) { + return *new([]*paych.SignedVoucher), ErrNotSupported +} + +func (s *FullNodeStruct) PaychVoucherSubmit(p0 context.Context, p1 address.Address, p2 *paych.SignedVoucher, p3 []byte, p4 []byte) (cid.Cid, error) { + if s.Internal.PaychVoucherSubmit == nil { + return *new(cid.Cid), ErrNotSupported + } + return s.Internal.PaychVoucherSubmit(p0, p1, p2, p3, p4) +} + +func (s *FullNodeStub) PaychVoucherSubmit(p0 context.Context, p1 address.Address, p2 *paych.SignedVoucher, p3 []byte, p4 []byte) (cid.Cid, error) { + return *new(cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) StateAccountKey(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) { + if s.Internal.StateAccountKey == nil { + return *new(address.Address), ErrNotSupported + } + return s.Internal.StateAccountKey(p0, p1, p2) +} + +func (s *FullNodeStub) StateAccountKey(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) { + return *new(address.Address), ErrNotSupported +} + +func (s *FullNodeStruct) StateActorCodeCIDs(p0 context.Context, p1 abinetwork.Version) (map[string]cid.Cid, error) { + if s.Internal.StateActorCodeCIDs == nil { + return *new(map[string]cid.Cid), ErrNotSupported + } + return s.Internal.StateActorCodeCIDs(p0, p1) +} + +func (s *FullNodeStub) StateActorCodeCIDs(p0 context.Context, p1 abinetwork.Version) (map[string]cid.Cid, error) { + return *new(map[string]cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) StateActorManifestCID(p0 context.Context, p1 abinetwork.Version) (cid.Cid, error) { + if s.Internal.StateActorManifestCID == nil { + return *new(cid.Cid), ErrNotSupported + } + return s.Internal.StateActorManifestCID(p0, p1) +} + +func (s *FullNodeStub) StateActorManifestCID(p0 context.Context, p1 abinetwork.Version) (cid.Cid, error) { + return *new(cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) StateAllMinerFaults(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) ([]*api.Fault, error) { + if s.Internal.StateAllMinerFaults == nil { + return *new([]*api.Fault), ErrNotSupported + } + return s.Internal.StateAllMinerFaults(p0, p1, p2) +} + +func (s *FullNodeStub) StateAllMinerFaults(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) ([]*api.Fault, error) { + return *new([]*api.Fault), ErrNotSupported +} + +func (s *FullNodeStruct) StateCall(p0 context.Context, p1 *types.Message, p2 types.TipSetKey) (*api.InvocResult, error) { + if s.Internal.StateCall == nil { + return nil, ErrNotSupported + } + return s.Internal.StateCall(p0, p1, p2) +} + +func (s *FullNodeStub) StateCall(p0 context.Context, p1 *types.Message, p2 types.TipSetKey) (*api.InvocResult, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateChangedActors(p0 context.Context, p1 cid.Cid, p2 cid.Cid) (map[string]types.Actor, error) { + if s.Internal.StateChangedActors == nil { + return *new(map[string]types.Actor), ErrNotSupported + } + return s.Internal.StateChangedActors(p0, p1, p2) +} + +func (s *FullNodeStub) StateChangedActors(p0 context.Context, p1 cid.Cid, p2 cid.Cid) (map[string]types.Actor, error) { + return *new(map[string]types.Actor), ErrNotSupported +} + +func (s *FullNodeStruct) StateCirculatingSupply(p0 context.Context, p1 types.TipSetKey) (abi.TokenAmount, error) { + if s.Internal.StateCirculatingSupply == nil { + return *new(abi.TokenAmount), ErrNotSupported + } + return s.Internal.StateCirculatingSupply(p0, p1) +} + +func (s *FullNodeStub) StateCirculatingSupply(p0 context.Context, p1 types.TipSetKey) (abi.TokenAmount, error) { + return *new(abi.TokenAmount), ErrNotSupported +} + +func (s *FullNodeStruct) StateCompute(p0 context.Context, p1 abi.ChainEpoch, p2 []*types.Message, p3 types.TipSetKey) (*api.ComputeStateOutput, error) { + if s.Internal.StateCompute == nil { + return nil, ErrNotSupported + } + return s.Internal.StateCompute(p0, p1, p2, p3) +} + +func (s *FullNodeStub) StateCompute(p0 context.Context, p1 abi.ChainEpoch, p2 []*types.Message, p3 types.TipSetKey) (*api.ComputeStateOutput, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateDealProviderCollateralBounds(p0 context.Context, p1 abi.PaddedPieceSize, p2 bool, p3 types.TipSetKey) (api.DealCollateralBounds, error) { + if s.Internal.StateDealProviderCollateralBounds == nil { + return *new(api.DealCollateralBounds), ErrNotSupported + } + return s.Internal.StateDealProviderCollateralBounds(p0, p1, p2, p3) +} + +func (s *FullNodeStub) StateDealProviderCollateralBounds(p0 context.Context, p1 abi.PaddedPieceSize, p2 bool, p3 types.TipSetKey) (api.DealCollateralBounds, error) { + return *new(api.DealCollateralBounds), ErrNotSupported +} + +func (s *FullNodeStruct) StateDecodeParams(p0 context.Context, p1 address.Address, p2 abi.MethodNum, p3 []byte, p4 types.TipSetKey) (interface{}, error) { + if s.Internal.StateDecodeParams == nil { + return nil, ErrNotSupported + } + return s.Internal.StateDecodeParams(p0, p1, p2, p3, p4) +} + +func (s *FullNodeStub) StateDecodeParams(p0 context.Context, p1 address.Address, p2 abi.MethodNum, p3 []byte, p4 types.TipSetKey) (interface{}, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateGetActor(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*types.Actor, error) { + if s.Internal.StateGetActor == nil { + return nil, ErrNotSupported + } + return s.Internal.StateGetActor(p0, p1, p2) +} + +func (s *FullNodeStub) StateGetActor(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*types.Actor, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateGetAllocation(p0 context.Context, p1 address.Address, p2 verifregtypes.AllocationId, p3 types.TipSetKey) (*verifregtypes.Allocation, error) { + if s.Internal.StateGetAllocation == nil { + return nil, ErrNotSupported + } + return s.Internal.StateGetAllocation(p0, p1, p2, p3) +} + +func (s *FullNodeStub) StateGetAllocation(p0 context.Context, p1 address.Address, p2 verifregtypes.AllocationId, p3 types.TipSetKey) (*verifregtypes.Allocation, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateGetAllocationForPendingDeal(p0 context.Context, p1 abi.DealID, p2 types.TipSetKey) (*verifregtypes.Allocation, error) { + if s.Internal.StateGetAllocationForPendingDeal == nil { + return nil, ErrNotSupported + } + return s.Internal.StateGetAllocationForPendingDeal(p0, p1, p2) +} + +func (s *FullNodeStub) StateGetAllocationForPendingDeal(p0 context.Context, p1 abi.DealID, p2 types.TipSetKey) (*verifregtypes.Allocation, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateGetAllocations(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (map[verifregtypes.AllocationId]verifregtypes.Allocation, error) { + if s.Internal.StateGetAllocations == nil { + return *new(map[verifregtypes.AllocationId]verifregtypes.Allocation), ErrNotSupported + } + return s.Internal.StateGetAllocations(p0, p1, p2) +} + +func (s *FullNodeStub) StateGetAllocations(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (map[verifregtypes.AllocationId]verifregtypes.Allocation, error) { + return *new(map[verifregtypes.AllocationId]verifregtypes.Allocation), ErrNotSupported +} + +func (s *FullNodeStruct) StateGetClaim(p0 context.Context, p1 address.Address, p2 verifregtypes.ClaimId, p3 types.TipSetKey) (*verifregtypes.Claim, error) { + if s.Internal.StateGetClaim == nil { + return nil, ErrNotSupported + } + return s.Internal.StateGetClaim(p0, p1, p2, p3) +} + +func (s *FullNodeStub) StateGetClaim(p0 context.Context, p1 address.Address, p2 verifregtypes.ClaimId, p3 types.TipSetKey) (*verifregtypes.Claim, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateGetClaims(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (map[verifregtypes.ClaimId]verifregtypes.Claim, error) { + if s.Internal.StateGetClaims == nil { + return *new(map[verifregtypes.ClaimId]verifregtypes.Claim), ErrNotSupported + } + return s.Internal.StateGetClaims(p0, p1, p2) +} + +func (s *FullNodeStub) StateGetClaims(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (map[verifregtypes.ClaimId]verifregtypes.Claim, error) { + return *new(map[verifregtypes.ClaimId]verifregtypes.Claim), ErrNotSupported +} + +func (s *FullNodeStruct) StateGetNetworkParams(p0 context.Context) (*api.NetworkParams, error) { + if s.Internal.StateGetNetworkParams == nil { + return nil, ErrNotSupported + } + return s.Internal.StateGetNetworkParams(p0) +} + +func (s *FullNodeStub) StateGetNetworkParams(p0 context.Context) (*api.NetworkParams, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateGetRandomnessFromBeacon(p0 context.Context, p1 crypto.DomainSeparationTag, p2 abi.ChainEpoch, p3 []byte, p4 types.TipSetKey) (abi.Randomness, error) { + if s.Internal.StateGetRandomnessFromBeacon == nil { + return *new(abi.Randomness), ErrNotSupported + } + return s.Internal.StateGetRandomnessFromBeacon(p0, p1, p2, p3, p4) +} + +func (s *FullNodeStub) StateGetRandomnessFromBeacon(p0 context.Context, p1 crypto.DomainSeparationTag, p2 abi.ChainEpoch, p3 []byte, p4 types.TipSetKey) (abi.Randomness, error) { + return *new(abi.Randomness), ErrNotSupported +} + +func (s *FullNodeStruct) StateGetRandomnessFromTickets(p0 context.Context, p1 crypto.DomainSeparationTag, p2 abi.ChainEpoch, p3 []byte, p4 types.TipSetKey) (abi.Randomness, error) { + if s.Internal.StateGetRandomnessFromTickets == nil { + return *new(abi.Randomness), ErrNotSupported + } + return s.Internal.StateGetRandomnessFromTickets(p0, p1, p2, p3, p4) +} + +func (s *FullNodeStub) StateGetRandomnessFromTickets(p0 context.Context, p1 crypto.DomainSeparationTag, p2 abi.ChainEpoch, p3 []byte, p4 types.TipSetKey) (abi.Randomness, error) { + return *new(abi.Randomness), ErrNotSupported +} + +func (s *FullNodeStruct) StateGetReceipt(p0 context.Context, p1 cid.Cid, p2 types.TipSetKey) (*types.MessageReceipt, error) { + if s.Internal.StateGetReceipt == nil { + return nil, ErrNotSupported + } + return s.Internal.StateGetReceipt(p0, p1, p2) +} + +func (s *FullNodeStub) StateGetReceipt(p0 context.Context, p1 cid.Cid, p2 types.TipSetKey) (*types.MessageReceipt, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateListActors(p0 context.Context, p1 types.TipSetKey) ([]address.Address, error) { + if s.Internal.StateListActors == nil { + return *new([]address.Address), ErrNotSupported + } + return s.Internal.StateListActors(p0, p1) +} + +func (s *FullNodeStub) StateListActors(p0 context.Context, p1 types.TipSetKey) ([]address.Address, error) { + return *new([]address.Address), ErrNotSupported +} + +func (s *FullNodeStruct) StateListMessages(p0 context.Context, p1 *api.MessageMatch, p2 types.TipSetKey, p3 abi.ChainEpoch) ([]cid.Cid, error) { + if s.Internal.StateListMessages == nil { + return *new([]cid.Cid), ErrNotSupported + } + return s.Internal.StateListMessages(p0, p1, p2, p3) +} + +func (s *FullNodeStub) StateListMessages(p0 context.Context, p1 *api.MessageMatch, p2 types.TipSetKey, p3 abi.ChainEpoch) ([]cid.Cid, error) { + return *new([]cid.Cid), ErrNotSupported +} + +func (s *FullNodeStruct) StateListMiners(p0 context.Context, p1 types.TipSetKey) ([]address.Address, error) { + if s.Internal.StateListMiners == nil { + return *new([]address.Address), ErrNotSupported + } + return s.Internal.StateListMiners(p0, p1) +} + +func (s *FullNodeStub) StateListMiners(p0 context.Context, p1 types.TipSetKey) ([]address.Address, error) { + return *new([]address.Address), ErrNotSupported +} + +func (s *FullNodeStruct) StateLookupID(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) { + if s.Internal.StateLookupID == nil { + return *new(address.Address), ErrNotSupported + } + return s.Internal.StateLookupID(p0, p1, p2) +} + +func (s *FullNodeStub) StateLookupID(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) { + return *new(address.Address), ErrNotSupported +} + +func (s *FullNodeStruct) StateMarketBalance(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (api.MarketBalance, error) { + if s.Internal.StateMarketBalance == nil { + return *new(api.MarketBalance), ErrNotSupported + } + return s.Internal.StateMarketBalance(p0, p1, p2) +} + +func (s *FullNodeStub) StateMarketBalance(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (api.MarketBalance, error) { + return *new(api.MarketBalance), ErrNotSupported +} + +func (s *FullNodeStruct) StateMarketDeals(p0 context.Context, p1 types.TipSetKey) (map[string]*api.MarketDeal, error) { + if s.Internal.StateMarketDeals == nil { + return *new(map[string]*api.MarketDeal), ErrNotSupported + } + return s.Internal.StateMarketDeals(p0, p1) +} + +func (s *FullNodeStub) StateMarketDeals(p0 context.Context, p1 types.TipSetKey) (map[string]*api.MarketDeal, error) { + return *new(map[string]*api.MarketDeal), ErrNotSupported +} + +func (s *FullNodeStruct) StateMarketParticipants(p0 context.Context, p1 types.TipSetKey) (map[string]api.MarketBalance, error) { + if s.Internal.StateMarketParticipants == nil { + return *new(map[string]api.MarketBalance), ErrNotSupported + } + return s.Internal.StateMarketParticipants(p0, p1) +} + +func (s *FullNodeStub) StateMarketParticipants(p0 context.Context, p1 types.TipSetKey) (map[string]api.MarketBalance, error) { + return *new(map[string]api.MarketBalance), ErrNotSupported +} + +func (s *FullNodeStruct) StateMarketStorageDeal(p0 context.Context, p1 abi.DealID, p2 types.TipSetKey) (*api.MarketDeal, error) { + if s.Internal.StateMarketStorageDeal == nil { + return nil, ErrNotSupported + } + return s.Internal.StateMarketStorageDeal(p0, p1, p2) +} + +func (s *FullNodeStub) StateMarketStorageDeal(p0 context.Context, p1 abi.DealID, p2 types.TipSetKey) (*api.MarketDeal, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateMinerActiveSectors(p0 context.Context, p1 address.Address, p2 types.TipSetKey) ([]*miner.SectorOnChainInfo, error) { + if s.Internal.StateMinerActiveSectors == nil { + return *new([]*miner.SectorOnChainInfo), ErrNotSupported + } + return s.Internal.StateMinerActiveSectors(p0, p1, p2) +} + +func (s *FullNodeStub) StateMinerActiveSectors(p0 context.Context, p1 address.Address, p2 types.TipSetKey) ([]*miner.SectorOnChainInfo, error) { + return *new([]*miner.SectorOnChainInfo), ErrNotSupported +} + +func (s *FullNodeStruct) StateMinerAvailableBalance(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (types.BigInt, error) { + if s.Internal.StateMinerAvailableBalance == nil { + return *new(types.BigInt), ErrNotSupported + } + return s.Internal.StateMinerAvailableBalance(p0, p1, p2) +} + +func (s *FullNodeStub) StateMinerAvailableBalance(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (types.BigInt, error) { + return *new(types.BigInt), ErrNotSupported +} + +func (s *FullNodeStruct) StateMinerDeadlines(p0 context.Context, p1 address.Address, p2 types.TipSetKey) ([]api.Deadline, error) { + if s.Internal.StateMinerDeadlines == nil { + return *new([]api.Deadline), ErrNotSupported + } + return s.Internal.StateMinerDeadlines(p0, p1, p2) +} + +func (s *FullNodeStub) StateMinerDeadlines(p0 context.Context, p1 address.Address, p2 types.TipSetKey) ([]api.Deadline, error) { + return *new([]api.Deadline), ErrNotSupported +} + +func (s *FullNodeStruct) StateMinerFaults(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (bitfield.BitField, error) { + if s.Internal.StateMinerFaults == nil { + return *new(bitfield.BitField), ErrNotSupported + } + return s.Internal.StateMinerFaults(p0, p1, p2) +} + +func (s *FullNodeStub) StateMinerFaults(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (bitfield.BitField, error) { + return *new(bitfield.BitField), ErrNotSupported +} + +func (s *FullNodeStruct) StateMinerInfo(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (api.MinerInfo, error) { + if s.Internal.StateMinerInfo == nil { + return *new(api.MinerInfo), ErrNotSupported + } + return s.Internal.StateMinerInfo(p0, p1, p2) +} + +func (s *FullNodeStub) StateMinerInfo(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (api.MinerInfo, error) { + return *new(api.MinerInfo), ErrNotSupported +} + +func (s *FullNodeStruct) StateMinerInitialPledgeCollateral(p0 context.Context, p1 address.Address, p2 miner.SectorPreCommitInfo, p3 types.TipSetKey) (types.BigInt, error) { + if s.Internal.StateMinerInitialPledgeCollateral == nil { + return *new(types.BigInt), ErrNotSupported + } + return s.Internal.StateMinerInitialPledgeCollateral(p0, p1, p2, p3) +} + +func (s *FullNodeStub) StateMinerInitialPledgeCollateral(p0 context.Context, p1 address.Address, p2 miner.SectorPreCommitInfo, p3 types.TipSetKey) (types.BigInt, error) { + return *new(types.BigInt), ErrNotSupported +} + +func (s *FullNodeStruct) StateMinerPartitions(p0 context.Context, p1 address.Address, p2 uint64, p3 types.TipSetKey) ([]api.Partition, error) { + if s.Internal.StateMinerPartitions == nil { + return *new([]api.Partition), ErrNotSupported + } + return s.Internal.StateMinerPartitions(p0, p1, p2, p3) +} + +func (s *FullNodeStub) StateMinerPartitions(p0 context.Context, p1 address.Address, p2 uint64, p3 types.TipSetKey) ([]api.Partition, error) { + return *new([]api.Partition), ErrNotSupported +} + +func (s *FullNodeStruct) StateMinerPower(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*api.MinerPower, error) { + if s.Internal.StateMinerPower == nil { + return nil, ErrNotSupported + } + return s.Internal.StateMinerPower(p0, p1, p2) +} + +func (s *FullNodeStub) StateMinerPower(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*api.MinerPower, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateMinerPreCommitDepositForPower(p0 context.Context, p1 address.Address, p2 miner.SectorPreCommitInfo, p3 types.TipSetKey) (types.BigInt, error) { + if s.Internal.StateMinerPreCommitDepositForPower == nil { + return *new(types.BigInt), ErrNotSupported + } + return s.Internal.StateMinerPreCommitDepositForPower(p0, p1, p2, p3) +} + +func (s *FullNodeStub) StateMinerPreCommitDepositForPower(p0 context.Context, p1 address.Address, p2 miner.SectorPreCommitInfo, p3 types.TipSetKey) (types.BigInt, error) { + return *new(types.BigInt), ErrNotSupported +} + +func (s *FullNodeStruct) StateMinerProvingDeadline(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*dline.Info, error) { + if s.Internal.StateMinerProvingDeadline == nil { + return nil, ErrNotSupported + } + return s.Internal.StateMinerProvingDeadline(p0, p1, p2) +} + +func (s *FullNodeStub) StateMinerProvingDeadline(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*dline.Info, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateMinerRecoveries(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (bitfield.BitField, error) { + if s.Internal.StateMinerRecoveries == nil { + return *new(bitfield.BitField), ErrNotSupported + } + return s.Internal.StateMinerRecoveries(p0, p1, p2) +} + +func (s *FullNodeStub) StateMinerRecoveries(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (bitfield.BitField, error) { + return *new(bitfield.BitField), ErrNotSupported +} + +func (s *FullNodeStruct) StateMinerSectorAllocated(p0 context.Context, p1 address.Address, p2 abi.SectorNumber, p3 types.TipSetKey) (bool, error) { + if s.Internal.StateMinerSectorAllocated == nil { + return false, ErrNotSupported + } + return s.Internal.StateMinerSectorAllocated(p0, p1, p2, p3) +} + +func (s *FullNodeStub) StateMinerSectorAllocated(p0 context.Context, p1 address.Address, p2 abi.SectorNumber, p3 types.TipSetKey) (bool, error) { + return false, ErrNotSupported +} + +func (s *FullNodeStruct) StateMinerSectorCount(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (api.MinerSectors, error) { + if s.Internal.StateMinerSectorCount == nil { + return *new(api.MinerSectors), ErrNotSupported + } + return s.Internal.StateMinerSectorCount(p0, p1, p2) +} + +func (s *FullNodeStub) StateMinerSectorCount(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (api.MinerSectors, error) { + return *new(api.MinerSectors), ErrNotSupported +} + +func (s *FullNodeStruct) StateMinerSectors(p0 context.Context, p1 address.Address, p2 *bitfield.BitField, p3 types.TipSetKey) ([]*miner.SectorOnChainInfo, error) { + if s.Internal.StateMinerSectors == nil { + return *new([]*miner.SectorOnChainInfo), ErrNotSupported + } + return s.Internal.StateMinerSectors(p0, p1, p2, p3) +} + +func (s *FullNodeStub) StateMinerSectors(p0 context.Context, p1 address.Address, p2 *bitfield.BitField, p3 types.TipSetKey) ([]*miner.SectorOnChainInfo, error) { + return *new([]*miner.SectorOnChainInfo), ErrNotSupported +} + +func (s *FullNodeStruct) StateNetworkName(p0 context.Context) (dtypes.NetworkName, error) { + if s.Internal.StateNetworkName == nil { + return *new(dtypes.NetworkName), ErrNotSupported + } + return s.Internal.StateNetworkName(p0) +} + +func (s *FullNodeStub) StateNetworkName(p0 context.Context) (dtypes.NetworkName, error) { + return *new(dtypes.NetworkName), ErrNotSupported +} + +func (s *FullNodeStruct) StateNetworkVersion(p0 context.Context, p1 types.TipSetKey) (apitypes.NetworkVersion, error) { + if s.Internal.StateNetworkVersion == nil { + return *new(apitypes.NetworkVersion), ErrNotSupported + } + return s.Internal.StateNetworkVersion(p0, p1) +} + +func (s *FullNodeStub) StateNetworkVersion(p0 context.Context, p1 types.TipSetKey) (apitypes.NetworkVersion, error) { + return *new(apitypes.NetworkVersion), ErrNotSupported +} + +func (s *FullNodeStruct) StateReadState(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*api.ActorState, error) { + if s.Internal.StateReadState == nil { + return nil, ErrNotSupported + } + return s.Internal.StateReadState(p0, p1, p2) +} + +func (s *FullNodeStub) StateReadState(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*api.ActorState, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateReplay(p0 context.Context, p1 types.TipSetKey, p2 cid.Cid) (*api.InvocResult, error) { + if s.Internal.StateReplay == nil { + return nil, ErrNotSupported + } + return s.Internal.StateReplay(p0, p1, p2) +} + +func (s *FullNodeStub) StateReplay(p0 context.Context, p1 types.TipSetKey, p2 cid.Cid) (*api.InvocResult, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateSearchMsg(p0 context.Context, p1 cid.Cid) (*api.MsgLookup, error) { + if s.Internal.StateSearchMsg == nil { + return nil, ErrNotSupported + } + return s.Internal.StateSearchMsg(p0, p1) +} + +func (s *FullNodeStub) StateSearchMsg(p0 context.Context, p1 cid.Cid) (*api.MsgLookup, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateSearchMsgLimited(p0 context.Context, p1 cid.Cid, p2 abi.ChainEpoch) (*api.MsgLookup, error) { + if s.Internal.StateSearchMsgLimited == nil { + return nil, ErrNotSupported + } + return s.Internal.StateSearchMsgLimited(p0, p1, p2) +} + +func (s *FullNodeStub) StateSearchMsgLimited(p0 context.Context, p1 cid.Cid, p2 abi.ChainEpoch) (*api.MsgLookup, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateSectorExpiration(p0 context.Context, p1 address.Address, p2 abi.SectorNumber, p3 types.TipSetKey) (*lminer.SectorExpiration, error) { + if s.Internal.StateSectorExpiration == nil { + return nil, ErrNotSupported + } + return s.Internal.StateSectorExpiration(p0, p1, p2, p3) +} + +func (s *FullNodeStub) StateSectorExpiration(p0 context.Context, p1 address.Address, p2 abi.SectorNumber, p3 types.TipSetKey) (*lminer.SectorExpiration, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateSectorGetInfo(p0 context.Context, p1 address.Address, p2 abi.SectorNumber, p3 types.TipSetKey) (*miner.SectorOnChainInfo, error) { + if s.Internal.StateSectorGetInfo == nil { + return nil, ErrNotSupported + } + return s.Internal.StateSectorGetInfo(p0, p1, p2, p3) +} + +func (s *FullNodeStub) StateSectorGetInfo(p0 context.Context, p1 address.Address, p2 abi.SectorNumber, p3 types.TipSetKey) (*miner.SectorOnChainInfo, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateSectorPartition(p0 context.Context, p1 address.Address, p2 abi.SectorNumber, p3 types.TipSetKey) (*lminer.SectorLocation, error) { + if s.Internal.StateSectorPartition == nil { + return nil, ErrNotSupported + } + return s.Internal.StateSectorPartition(p0, p1, p2, p3) +} + +func (s *FullNodeStub) StateSectorPartition(p0 context.Context, p1 address.Address, p2 abi.SectorNumber, p3 types.TipSetKey) (*lminer.SectorLocation, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateSectorPreCommitInfo(p0 context.Context, p1 address.Address, p2 abi.SectorNumber, p3 types.TipSetKey) (miner.SectorPreCommitOnChainInfo, error) { + if s.Internal.StateSectorPreCommitInfo == nil { + return *new(miner.SectorPreCommitOnChainInfo), ErrNotSupported + } + return s.Internal.StateSectorPreCommitInfo(p0, p1, p2, p3) +} + +func (s *FullNodeStub) StateSectorPreCommitInfo(p0 context.Context, p1 address.Address, p2 abi.SectorNumber, p3 types.TipSetKey) (miner.SectorPreCommitOnChainInfo, error) { + return *new(miner.SectorPreCommitOnChainInfo), ErrNotSupported +} + +func (s *FullNodeStruct) StateVMCirculatingSupplyInternal(p0 context.Context, p1 types.TipSetKey) (api.CirculatingSupply, error) { + if s.Internal.StateVMCirculatingSupplyInternal == nil { + return *new(api.CirculatingSupply), ErrNotSupported + } + return s.Internal.StateVMCirculatingSupplyInternal(p0, p1) +} + +func (s *FullNodeStub) StateVMCirculatingSupplyInternal(p0 context.Context, p1 types.TipSetKey) (api.CirculatingSupply, error) { + return *new(api.CirculatingSupply), ErrNotSupported +} + +func (s *FullNodeStruct) StateVerifiedClientStatus(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*abi.StoragePower, error) { + if s.Internal.StateVerifiedClientStatus == nil { + return nil, ErrNotSupported + } + return s.Internal.StateVerifiedClientStatus(p0, p1, p2) +} + +func (s *FullNodeStub) StateVerifiedClientStatus(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*abi.StoragePower, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateVerifiedRegistryRootKey(p0 context.Context, p1 types.TipSetKey) (address.Address, error) { + if s.Internal.StateVerifiedRegistryRootKey == nil { + return *new(address.Address), ErrNotSupported + } + return s.Internal.StateVerifiedRegistryRootKey(p0, p1) +} + +func (s *FullNodeStub) StateVerifiedRegistryRootKey(p0 context.Context, p1 types.TipSetKey) (address.Address, error) { + return *new(address.Address), ErrNotSupported +} + +func (s *FullNodeStruct) StateVerifierStatus(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*abi.StoragePower, error) { + if s.Internal.StateVerifierStatus == nil { + return nil, ErrNotSupported + } + return s.Internal.StateVerifierStatus(p0, p1, p2) +} + +func (s *FullNodeStub) StateVerifierStatus(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*abi.StoragePower, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateWaitMsg(p0 context.Context, p1 cid.Cid, p2 uint64) (*api.MsgLookup, error) { + if s.Internal.StateWaitMsg == nil { + return nil, ErrNotSupported + } + return s.Internal.StateWaitMsg(p0, p1, p2) +} + +func (s *FullNodeStub) StateWaitMsg(p0 context.Context, p1 cid.Cid, p2 uint64) (*api.MsgLookup, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) StateWaitMsgLimited(p0 context.Context, p1 cid.Cid, p2 uint64, p3 abi.ChainEpoch) (*api.MsgLookup, error) { + if s.Internal.StateWaitMsgLimited == nil { + return nil, ErrNotSupported + } + return s.Internal.StateWaitMsgLimited(p0, p1, p2, p3) +} + +func (s *FullNodeStub) StateWaitMsgLimited(p0 context.Context, p1 cid.Cid, p2 uint64, p3 abi.ChainEpoch) (*api.MsgLookup, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) SyncCheckBad(p0 context.Context, p1 cid.Cid) (string, error) { + if s.Internal.SyncCheckBad == nil { + return "", ErrNotSupported + } + return s.Internal.SyncCheckBad(p0, p1) +} + +func (s *FullNodeStub) SyncCheckBad(p0 context.Context, p1 cid.Cid) (string, error) { + return "", ErrNotSupported +} + +func (s *FullNodeStruct) SyncCheckpoint(p0 context.Context, p1 types.TipSetKey) error { + if s.Internal.SyncCheckpoint == nil { + return ErrNotSupported + } + return s.Internal.SyncCheckpoint(p0, p1) +} + +func (s *FullNodeStub) SyncCheckpoint(p0 context.Context, p1 types.TipSetKey) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) SyncIncomingBlocks(p0 context.Context) (<-chan *types.BlockHeader, error) { + if s.Internal.SyncIncomingBlocks == nil { + return nil, ErrNotSupported + } + return s.Internal.SyncIncomingBlocks(p0) +} + +func (s *FullNodeStub) SyncIncomingBlocks(p0 context.Context) (<-chan *types.BlockHeader, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) SyncMarkBad(p0 context.Context, p1 cid.Cid) error { + if s.Internal.SyncMarkBad == nil { + return ErrNotSupported + } + return s.Internal.SyncMarkBad(p0, p1) +} + +func (s *FullNodeStub) SyncMarkBad(p0 context.Context, p1 cid.Cid) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) SyncState(p0 context.Context) (*api.SyncState, error) { + if s.Internal.SyncState == nil { + return nil, ErrNotSupported + } + return s.Internal.SyncState(p0) +} + +func (s *FullNodeStub) SyncState(p0 context.Context) (*api.SyncState, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) SyncSubmitBlock(p0 context.Context, p1 *types.BlockMsg) error { + if s.Internal.SyncSubmitBlock == nil { + return ErrNotSupported + } + return s.Internal.SyncSubmitBlock(p0, p1) +} + +func (s *FullNodeStub) SyncSubmitBlock(p0 context.Context, p1 *types.BlockMsg) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) SyncUnmarkAllBad(p0 context.Context) error { + if s.Internal.SyncUnmarkAllBad == nil { + return ErrNotSupported + } + return s.Internal.SyncUnmarkAllBad(p0) +} + +func (s *FullNodeStub) SyncUnmarkAllBad(p0 context.Context) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) SyncUnmarkBad(p0 context.Context, p1 cid.Cid) error { + if s.Internal.SyncUnmarkBad == nil { + return ErrNotSupported + } + return s.Internal.SyncUnmarkBad(p0, p1) +} + +func (s *FullNodeStub) SyncUnmarkBad(p0 context.Context, p1 cid.Cid) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) SyncValidateTipset(p0 context.Context, p1 types.TipSetKey) (bool, error) { + if s.Internal.SyncValidateTipset == nil { + return false, ErrNotSupported + } + return s.Internal.SyncValidateTipset(p0, p1) +} + +func (s *FullNodeStub) SyncValidateTipset(p0 context.Context, p1 types.TipSetKey) (bool, error) { + return false, ErrNotSupported +} + +func (s *FullNodeStruct) WalletBalance(p0 context.Context, p1 address.Address) (types.BigInt, error) { + if s.Internal.WalletBalance == nil { + return *new(types.BigInt), ErrNotSupported + } + return s.Internal.WalletBalance(p0, p1) +} + +func (s *FullNodeStub) WalletBalance(p0 context.Context, p1 address.Address) (types.BigInt, error) { + return *new(types.BigInt), ErrNotSupported +} + +func (s *FullNodeStruct) WalletDefaultAddress(p0 context.Context) (address.Address, error) { + if s.Internal.WalletDefaultAddress == nil { + return *new(address.Address), ErrNotSupported + } + return s.Internal.WalletDefaultAddress(p0) +} + +func (s *FullNodeStub) WalletDefaultAddress(p0 context.Context) (address.Address, error) { + return *new(address.Address), ErrNotSupported +} + +func (s *FullNodeStruct) WalletDelete(p0 context.Context, p1 address.Address) error { + if s.Internal.WalletDelete == nil { + return ErrNotSupported + } + return s.Internal.WalletDelete(p0, p1) +} + +func (s *FullNodeStub) WalletDelete(p0 context.Context, p1 address.Address) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) WalletExport(p0 context.Context, p1 address.Address) (*types.KeyInfo, error) { + if s.Internal.WalletExport == nil { + return nil, ErrNotSupported + } + return s.Internal.WalletExport(p0, p1) +} + +func (s *FullNodeStub) WalletExport(p0 context.Context, p1 address.Address) (*types.KeyInfo, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) WalletHas(p0 context.Context, p1 address.Address) (bool, error) { + if s.Internal.WalletHas == nil { + return false, ErrNotSupported + } + return s.Internal.WalletHas(p0, p1) +} + +func (s *FullNodeStub) WalletHas(p0 context.Context, p1 address.Address) (bool, error) { + return false, ErrNotSupported +} + +func (s *FullNodeStruct) WalletImport(p0 context.Context, p1 *types.KeyInfo) (address.Address, error) { + if s.Internal.WalletImport == nil { + return *new(address.Address), ErrNotSupported + } + return s.Internal.WalletImport(p0, p1) +} + +func (s *FullNodeStub) WalletImport(p0 context.Context, p1 *types.KeyInfo) (address.Address, error) { + return *new(address.Address), ErrNotSupported +} + +func (s *FullNodeStruct) WalletList(p0 context.Context) ([]address.Address, error) { + if s.Internal.WalletList == nil { + return *new([]address.Address), ErrNotSupported + } + return s.Internal.WalletList(p0) +} + +func (s *FullNodeStub) WalletList(p0 context.Context) ([]address.Address, error) { + return *new([]address.Address), ErrNotSupported +} + +func (s *FullNodeStruct) WalletNew(p0 context.Context, p1 types.KeyType) (address.Address, error) { + if s.Internal.WalletNew == nil { + return *new(address.Address), ErrNotSupported + } + return s.Internal.WalletNew(p0, p1) +} + +func (s *FullNodeStub) WalletNew(p0 context.Context, p1 types.KeyType) (address.Address, error) { + return *new(address.Address), ErrNotSupported +} + +func (s *FullNodeStruct) WalletSetDefault(p0 context.Context, p1 address.Address) error { + if s.Internal.WalletSetDefault == nil { + return ErrNotSupported + } + return s.Internal.WalletSetDefault(p0, p1) +} + +func (s *FullNodeStub) WalletSetDefault(p0 context.Context, p1 address.Address) error { + return ErrNotSupported +} + +func (s *FullNodeStruct) WalletSign(p0 context.Context, p1 address.Address, p2 []byte) (*crypto.Signature, error) { + if s.Internal.WalletSign == nil { + return nil, ErrNotSupported + } + return s.Internal.WalletSign(p0, p1, p2) +} + +func (s *FullNodeStub) WalletSign(p0 context.Context, p1 address.Address, p2 []byte) (*crypto.Signature, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) WalletSignMessage(p0 context.Context, p1 address.Address, p2 *types.Message) (*types.SignedMessage, error) { + if s.Internal.WalletSignMessage == nil { + return nil, ErrNotSupported + } + return s.Internal.WalletSignMessage(p0, p1, p2) +} + +func (s *FullNodeStub) WalletSignMessage(p0 context.Context, p1 address.Address, p2 *types.Message) (*types.SignedMessage, error) { + return nil, ErrNotSupported +} + +func (s *FullNodeStruct) WalletValidateAddress(p0 context.Context, p1 string) (address.Address, error) { + if s.Internal.WalletValidateAddress == nil { + return *new(address.Address), ErrNotSupported + } + return s.Internal.WalletValidateAddress(p0, p1) +} + +func (s *FullNodeStub) WalletValidateAddress(p0 context.Context, p1 string) (address.Address, error) { + return *new(address.Address), ErrNotSupported +} + +func (s *FullNodeStruct) WalletVerify(p0 context.Context, p1 address.Address, p2 []byte, p3 *crypto.Signature) (bool, error) { + if s.Internal.WalletVerify == nil { + return false, ErrNotSupported + } + return s.Internal.WalletVerify(p0, p1, p2, p3) +} + +func (s *FullNodeStub) WalletVerify(p0 context.Context, p1 address.Address, p2 []byte, p3 *crypto.Signature) (bool, error) { + return false, ErrNotSupported +} + +func (s *GatewayStruct) ChainGetBlockMessages(p0 context.Context, p1 cid.Cid) (*api.BlockMessages, error) { + if s.Internal.ChainGetBlockMessages == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainGetBlockMessages(p0, p1) +} + +func (s *GatewayStub) ChainGetBlockMessages(p0 context.Context, p1 cid.Cid) (*api.BlockMessages, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) ChainGetMessage(p0 context.Context, p1 cid.Cid) (*types.Message, error) { + if s.Internal.ChainGetMessage == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainGetMessage(p0, p1) +} + +func (s *GatewayStub) ChainGetMessage(p0 context.Context, p1 cid.Cid) (*types.Message, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) ChainGetTipSet(p0 context.Context, p1 types.TipSetKey) (*types.TipSet, error) { + if s.Internal.ChainGetTipSet == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainGetTipSet(p0, p1) +} + +func (s *GatewayStub) ChainGetTipSet(p0 context.Context, p1 types.TipSetKey) (*types.TipSet, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) ChainGetTipSetByHeight(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) { + if s.Internal.ChainGetTipSetByHeight == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainGetTipSetByHeight(p0, p1, p2) +} + +func (s *GatewayStub) ChainGetTipSetByHeight(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) ChainHasObj(p0 context.Context, p1 cid.Cid) (bool, error) { + if s.Internal.ChainHasObj == nil { + return false, ErrNotSupported + } + return s.Internal.ChainHasObj(p0, p1) +} + +func (s *GatewayStub) ChainHasObj(p0 context.Context, p1 cid.Cid) (bool, error) { + return false, ErrNotSupported +} + +func (s *GatewayStruct) ChainHead(p0 context.Context) (*types.TipSet, error) { + if s.Internal.ChainHead == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainHead(p0) +} + +func (s *GatewayStub) ChainHead(p0 context.Context) (*types.TipSet, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) ChainNotify(p0 context.Context) (<-chan []*api.HeadChange, error) { + if s.Internal.ChainNotify == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainNotify(p0) +} + +func (s *GatewayStub) ChainNotify(p0 context.Context) (<-chan []*api.HeadChange, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) ChainPutObj(p0 context.Context, p1 blocks.Block) error { + if s.Internal.ChainPutObj == nil { + return ErrNotSupported + } + return s.Internal.ChainPutObj(p0, p1) +} + +func (s *GatewayStub) ChainPutObj(p0 context.Context, p1 blocks.Block) error { + return ErrNotSupported +} + +func (s *GatewayStruct) ChainReadObj(p0 context.Context, p1 cid.Cid) ([]byte, error) { + if s.Internal.ChainReadObj == nil { + return *new([]byte), ErrNotSupported + } + return s.Internal.ChainReadObj(p0, p1) +} + +func (s *GatewayStub) ChainReadObj(p0 context.Context, p1 cid.Cid) ([]byte, error) { + return *new([]byte), ErrNotSupported +} + +func (s *GatewayStruct) GasEstimateGasPremium(p0 context.Context, p1 uint64, p2 address.Address, p3 int64, p4 types.TipSetKey) (types.BigInt, error) { + if s.Internal.GasEstimateGasPremium == nil { + return *new(types.BigInt), ErrNotSupported + } + return s.Internal.GasEstimateGasPremium(p0, p1, p2, p3, p4) +} + +func (s *GatewayStub) GasEstimateGasPremium(p0 context.Context, p1 uint64, p2 address.Address, p3 int64, p4 types.TipSetKey) (types.BigInt, error) { + return *new(types.BigInt), ErrNotSupported +} + +func (s *GatewayStruct) GasEstimateMessageGas(p0 context.Context, p1 *types.Message, p2 *api.MessageSendSpec, p3 types.TipSetKey) (*types.Message, error) { + if s.Internal.GasEstimateMessageGas == nil { + return nil, ErrNotSupported + } + return s.Internal.GasEstimateMessageGas(p0, p1, p2, p3) +} + +func (s *GatewayStub) GasEstimateMessageGas(p0 context.Context, p1 *types.Message, p2 *api.MessageSendSpec, p3 types.TipSetKey) (*types.Message, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) MpoolGetNonce(p0 context.Context, p1 address.Address) (uint64, error) { + if s.Internal.MpoolGetNonce == nil { + return 0, ErrNotSupported + } + return s.Internal.MpoolGetNonce(p0, p1) +} + +func (s *GatewayStub) MpoolGetNonce(p0 context.Context, p1 address.Address) (uint64, error) { + return 0, ErrNotSupported +} + +func (s *GatewayStruct) MpoolPush(p0 context.Context, p1 *types.SignedMessage) (cid.Cid, error) { + if s.Internal.MpoolPush == nil { + return *new(cid.Cid), ErrNotSupported + } + return s.Internal.MpoolPush(p0, p1) +} + +func (s *GatewayStub) MpoolPush(p0 context.Context, p1 *types.SignedMessage) (cid.Cid, error) { + return *new(cid.Cid), ErrNotSupported +} + +func (s *GatewayStruct) MsigGetAvailableBalance(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (types.BigInt, error) { + if s.Internal.MsigGetAvailableBalance == nil { + return *new(types.BigInt), ErrNotSupported + } + return s.Internal.MsigGetAvailableBalance(p0, p1, p2) +} + +func (s *GatewayStub) MsigGetAvailableBalance(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (types.BigInt, error) { + return *new(types.BigInt), ErrNotSupported +} + +func (s *GatewayStruct) MsigGetPending(p0 context.Context, p1 address.Address, p2 types.TipSetKey) ([]*api.MsigTransaction, error) { + if s.Internal.MsigGetPending == nil { + return *new([]*api.MsigTransaction), ErrNotSupported + } + return s.Internal.MsigGetPending(p0, p1, p2) +} + +func (s *GatewayStub) MsigGetPending(p0 context.Context, p1 address.Address, p2 types.TipSetKey) ([]*api.MsigTransaction, error) { + return *new([]*api.MsigTransaction), ErrNotSupported +} + +func (s *GatewayStruct) MsigGetVested(p0 context.Context, p1 address.Address, p2 types.TipSetKey, p3 types.TipSetKey) (types.BigInt, error) { + if s.Internal.MsigGetVested == nil { + return *new(types.BigInt), ErrNotSupported + } + return s.Internal.MsigGetVested(p0, p1, p2, p3) +} + +func (s *GatewayStub) MsigGetVested(p0 context.Context, p1 address.Address, p2 types.TipSetKey, p3 types.TipSetKey) (types.BigInt, error) { + return *new(types.BigInt), ErrNotSupported +} + +func (s *GatewayStruct) StateAccountKey(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) { + if s.Internal.StateAccountKey == nil { + return *new(address.Address), ErrNotSupported + } + return s.Internal.StateAccountKey(p0, p1, p2) +} + +func (s *GatewayStub) StateAccountKey(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) { + return *new(address.Address), ErrNotSupported +} + +func (s *GatewayStruct) StateCall(p0 context.Context, p1 *types.Message, p2 types.TipSetKey) (*api.InvocResult, error) { + if s.Internal.StateCall == nil { + return nil, ErrNotSupported + } + return s.Internal.StateCall(p0, p1, p2) +} + +func (s *GatewayStub) StateCall(p0 context.Context, p1 *types.Message, p2 types.TipSetKey) (*api.InvocResult, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) StateDealProviderCollateralBounds(p0 context.Context, p1 abi.PaddedPieceSize, p2 bool, p3 types.TipSetKey) (api.DealCollateralBounds, error) { + if s.Internal.StateDealProviderCollateralBounds == nil { + return *new(api.DealCollateralBounds), ErrNotSupported + } + return s.Internal.StateDealProviderCollateralBounds(p0, p1, p2, p3) +} + +func (s *GatewayStub) StateDealProviderCollateralBounds(p0 context.Context, p1 abi.PaddedPieceSize, p2 bool, p3 types.TipSetKey) (api.DealCollateralBounds, error) { + return *new(api.DealCollateralBounds), ErrNotSupported +} + +func (s *GatewayStruct) StateDecodeParams(p0 context.Context, p1 address.Address, p2 abi.MethodNum, p3 []byte, p4 types.TipSetKey) (interface{}, error) { + if s.Internal.StateDecodeParams == nil { + return nil, ErrNotSupported + } + return s.Internal.StateDecodeParams(p0, p1, p2, p3, p4) +} + +func (s *GatewayStub) StateDecodeParams(p0 context.Context, p1 address.Address, p2 abi.MethodNum, p3 []byte, p4 types.TipSetKey) (interface{}, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) StateGetActor(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*types.Actor, error) { + if s.Internal.StateGetActor == nil { + return nil, ErrNotSupported + } + return s.Internal.StateGetActor(p0, p1, p2) +} + +func (s *GatewayStub) StateGetActor(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*types.Actor, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) StateGetReceipt(p0 context.Context, p1 cid.Cid, p2 types.TipSetKey) (*types.MessageReceipt, error) { + if s.Internal.StateGetReceipt == nil { + return nil, ErrNotSupported + } + return s.Internal.StateGetReceipt(p0, p1, p2) +} + +func (s *GatewayStub) StateGetReceipt(p0 context.Context, p1 cid.Cid, p2 types.TipSetKey) (*types.MessageReceipt, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) StateListMiners(p0 context.Context, p1 types.TipSetKey) ([]address.Address, error) { + if s.Internal.StateListMiners == nil { + return *new([]address.Address), ErrNotSupported + } + return s.Internal.StateListMiners(p0, p1) +} + +func (s *GatewayStub) StateListMiners(p0 context.Context, p1 types.TipSetKey) ([]address.Address, error) { + return *new([]address.Address), ErrNotSupported +} + +func (s *GatewayStruct) StateLookupID(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) { + if s.Internal.StateLookupID == nil { + return *new(address.Address), ErrNotSupported + } + return s.Internal.StateLookupID(p0, p1, p2) +} + +func (s *GatewayStub) StateLookupID(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) { + return *new(address.Address), ErrNotSupported +} + +func (s *GatewayStruct) StateMarketBalance(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (api.MarketBalance, error) { + if s.Internal.StateMarketBalance == nil { + return *new(api.MarketBalance), ErrNotSupported + } + return s.Internal.StateMarketBalance(p0, p1, p2) +} + +func (s *GatewayStub) StateMarketBalance(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (api.MarketBalance, error) { + return *new(api.MarketBalance), ErrNotSupported +} + +func (s *GatewayStruct) StateMarketStorageDeal(p0 context.Context, p1 abi.DealID, p2 types.TipSetKey) (*api.MarketDeal, error) { + if s.Internal.StateMarketStorageDeal == nil { + return nil, ErrNotSupported + } + return s.Internal.StateMarketStorageDeal(p0, p1, p2) +} + +func (s *GatewayStub) StateMarketStorageDeal(p0 context.Context, p1 abi.DealID, p2 types.TipSetKey) (*api.MarketDeal, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) StateMinerInfo(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (api.MinerInfo, error) { + if s.Internal.StateMinerInfo == nil { + return *new(api.MinerInfo), ErrNotSupported + } + return s.Internal.StateMinerInfo(p0, p1, p2) +} + +func (s *GatewayStub) StateMinerInfo(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (api.MinerInfo, error) { + return *new(api.MinerInfo), ErrNotSupported +} + +func (s *GatewayStruct) StateMinerPower(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*api.MinerPower, error) { + if s.Internal.StateMinerPower == nil { + return nil, ErrNotSupported + } + return s.Internal.StateMinerPower(p0, p1, p2) +} + +func (s *GatewayStub) StateMinerPower(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*api.MinerPower, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) StateMinerProvingDeadline(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*dline.Info, error) { + if s.Internal.StateMinerProvingDeadline == nil { + return nil, ErrNotSupported + } + return s.Internal.StateMinerProvingDeadline(p0, p1, p2) +} + +func (s *GatewayStub) StateMinerProvingDeadline(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*dline.Info, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) StateMinerSectorCount(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (api.MinerSectors, error) { + if s.Internal.StateMinerSectorCount == nil { + return *new(api.MinerSectors), ErrNotSupported + } + return s.Internal.StateMinerSectorCount(p0, p1, p2) +} + +func (s *GatewayStub) StateMinerSectorCount(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (api.MinerSectors, error) { + return *new(api.MinerSectors), ErrNotSupported +} + +func (s *GatewayStruct) StateNetworkName(p0 context.Context) (dtypes.NetworkName, error) { + if s.Internal.StateNetworkName == nil { + return *new(dtypes.NetworkName), ErrNotSupported + } + return s.Internal.StateNetworkName(p0) +} + +func (s *GatewayStub) StateNetworkName(p0 context.Context) (dtypes.NetworkName, error) { + return *new(dtypes.NetworkName), ErrNotSupported +} + +func (s *GatewayStruct) StateNetworkVersion(p0 context.Context, p1 types.TipSetKey) (abinetwork.Version, error) { + if s.Internal.StateNetworkVersion == nil { + return *new(abinetwork.Version), ErrNotSupported + } + return s.Internal.StateNetworkVersion(p0, p1) +} + +func (s *GatewayStub) StateNetworkVersion(p0 context.Context, p1 types.TipSetKey) (abinetwork.Version, error) { + return *new(abinetwork.Version), ErrNotSupported +} + +func (s *GatewayStruct) StateReplay(p0 context.Context, p1 types.TipSetKey, p2 cid.Cid) (*api.InvocResult, error) { + if s.Internal.StateReplay == nil { + return nil, ErrNotSupported + } + return s.Internal.StateReplay(p0, p1, p2) +} + +func (s *GatewayStub) StateReplay(p0 context.Context, p1 types.TipSetKey, p2 cid.Cid) (*api.InvocResult, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) StateSearchMsg(p0 context.Context, p1 cid.Cid) (*api.MsgLookup, error) { + if s.Internal.StateSearchMsg == nil { + return nil, ErrNotSupported + } + return s.Internal.StateSearchMsg(p0, p1) +} + +func (s *GatewayStub) StateSearchMsg(p0 context.Context, p1 cid.Cid) (*api.MsgLookup, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) StateSectorGetInfo(p0 context.Context, p1 address.Address, p2 abi.SectorNumber, p3 types.TipSetKey) (*miner.SectorOnChainInfo, error) { + if s.Internal.StateSectorGetInfo == nil { + return nil, ErrNotSupported + } + return s.Internal.StateSectorGetInfo(p0, p1, p2, p3) +} + +func (s *GatewayStub) StateSectorGetInfo(p0 context.Context, p1 address.Address, p2 abi.SectorNumber, p3 types.TipSetKey) (*miner.SectorOnChainInfo, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) StateVerifiedClientStatus(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*abi.StoragePower, error) { + if s.Internal.StateVerifiedClientStatus == nil { + return nil, ErrNotSupported + } + return s.Internal.StateVerifiedClientStatus(p0, p1, p2) +} + +func (s *GatewayStub) StateVerifiedClientStatus(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (*abi.StoragePower, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) StateWaitMsg(p0 context.Context, p1 cid.Cid, p2 uint64) (*api.MsgLookup, error) { + if s.Internal.StateWaitMsg == nil { + return nil, ErrNotSupported + } + return s.Internal.StateWaitMsg(p0, p1, p2) +} + +func (s *GatewayStub) StateWaitMsg(p0 context.Context, p1 cid.Cid, p2 uint64) (*api.MsgLookup, error) { + return nil, ErrNotSupported +} + +func (s *GatewayStruct) Version(p0 context.Context) (api.APIVersion, error) { + if s.Internal.Version == nil { + return *new(api.APIVersion), ErrNotSupported + } + return s.Internal.Version(p0) +} + +func (s *GatewayStub) Version(p0 context.Context) (api.APIVersion, error) { + return *new(api.APIVersion), ErrNotSupported +} + +func (s *GatewayStruct) WalletBalance(p0 context.Context, p1 address.Address) (types.BigInt, error) { + if s.Internal.WalletBalance == nil { + return *new(types.BigInt), ErrNotSupported + } + return s.Internal.WalletBalance(p0, p1) +} + +func (s *GatewayStub) WalletBalance(p0 context.Context, p1 address.Address) (types.BigInt, error) { + return *new(types.BigInt), ErrNotSupported +} + +var _ FullNode = new(FullNodeStruct) +var _ Gateway = new(GatewayStruct) diff --git a/api/v0api/v0mocks/mock_full.go b/api/v0api/v0mocks/mock_full.go new file mode 100644 index 000000000..7a722ed25 --- /dev/null +++ b/api/v0api/v0mocks/mock_full.go @@ -0,0 +1,3382 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/filecoin-project/lotus/api/v0api (interfaces: FullNode) + +// Package v0mocks is a generated GoMock package. +package v0mocks + +import ( + context "context" + reflect "reflect" + time "time" + + gomock "github.com/golang/mock/gomock" + uuid "github.com/google/uuid" + blocks "github.com/ipfs/go-block-format" + cid "github.com/ipfs/go-cid" + metrics "github.com/libp2p/go-libp2p/core/metrics" + network0 "github.com/libp2p/go-libp2p/core/network" + peer "github.com/libp2p/go-libp2p/core/peer" + protocol "github.com/libp2p/go-libp2p/core/protocol" + + address "github.com/filecoin-project/go-address" + bitfield "github.com/filecoin-project/go-bitfield" + datatransfer "github.com/filecoin-project/go-data-transfer/v2" + retrievalmarket "github.com/filecoin-project/go-fil-markets/retrievalmarket" + storagemarket "github.com/filecoin-project/go-fil-markets/storagemarket" + auth "github.com/filecoin-project/go-jsonrpc/auth" + abi "github.com/filecoin-project/go-state-types/abi" + big "github.com/filecoin-project/go-state-types/big" + paych "github.com/filecoin-project/go-state-types/builtin/v8/paych" + miner "github.com/filecoin-project/go-state-types/builtin/v9/miner" + verifreg "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" + crypto "github.com/filecoin-project/go-state-types/crypto" + dline "github.com/filecoin-project/go-state-types/dline" + network "github.com/filecoin-project/go-state-types/network" + + api "github.com/filecoin-project/lotus/api" + apitypes "github.com/filecoin-project/lotus/api/types" + v0api "github.com/filecoin-project/lotus/api/v0api" + miner0 "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + types "github.com/filecoin-project/lotus/chain/types" + alerting "github.com/filecoin-project/lotus/journal/alerting" + marketevents "github.com/filecoin-project/lotus/markets/loggers" + dtypes "github.com/filecoin-project/lotus/node/modules/dtypes" + imports "github.com/filecoin-project/lotus/node/repo/imports" +) + +// MockFullNode is a mock of FullNode interface. +type MockFullNode struct { + ctrl *gomock.Controller + recorder *MockFullNodeMockRecorder +} + +// MockFullNodeMockRecorder is the mock recorder for MockFullNode. +type MockFullNodeMockRecorder struct { + mock *MockFullNode +} + +// NewMockFullNode creates a new mock instance. +func NewMockFullNode(ctrl *gomock.Controller) *MockFullNode { + mock := &MockFullNode{ctrl: ctrl} + mock.recorder = &MockFullNodeMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockFullNode) EXPECT() *MockFullNodeMockRecorder { + return m.recorder +} + +// AuthNew mocks base method. +func (m *MockFullNode) AuthNew(arg0 context.Context, arg1 []auth.Permission) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AuthNew", arg0, arg1) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AuthNew indicates an expected call of AuthNew. +func (mr *MockFullNodeMockRecorder) AuthNew(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthNew", reflect.TypeOf((*MockFullNode)(nil).AuthNew), arg0, arg1) +} + +// AuthVerify mocks base method. +func (m *MockFullNode) AuthVerify(arg0 context.Context, arg1 string) ([]auth.Permission, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AuthVerify", arg0, arg1) + ret0, _ := ret[0].([]auth.Permission) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AuthVerify indicates an expected call of AuthVerify. +func (mr *MockFullNodeMockRecorder) AuthVerify(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AuthVerify", reflect.TypeOf((*MockFullNode)(nil).AuthVerify), arg0, arg1) +} + +// BeaconGetEntry mocks base method. +func (m *MockFullNode) BeaconGetEntry(arg0 context.Context, arg1 abi.ChainEpoch) (*types.BeaconEntry, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BeaconGetEntry", arg0, arg1) + ret0, _ := ret[0].(*types.BeaconEntry) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// BeaconGetEntry indicates an expected call of BeaconGetEntry. +func (mr *MockFullNodeMockRecorder) BeaconGetEntry(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BeaconGetEntry", reflect.TypeOf((*MockFullNode)(nil).BeaconGetEntry), arg0, arg1) +} + +// ChainDeleteObj mocks base method. +func (m *MockFullNode) ChainDeleteObj(arg0 context.Context, arg1 cid.Cid) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainDeleteObj", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// ChainDeleteObj indicates an expected call of ChainDeleteObj. +func (mr *MockFullNodeMockRecorder) ChainDeleteObj(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainDeleteObj", reflect.TypeOf((*MockFullNode)(nil).ChainDeleteObj), arg0, arg1) +} + +// ChainExport mocks base method. +func (m *MockFullNode) ChainExport(arg0 context.Context, arg1 abi.ChainEpoch, arg2 bool, arg3 types.TipSetKey) (<-chan []byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainExport", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(<-chan []byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainExport indicates an expected call of ChainExport. +func (mr *MockFullNodeMockRecorder) ChainExport(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainExport", reflect.TypeOf((*MockFullNode)(nil).ChainExport), arg0, arg1, arg2, arg3) +} + +// ChainGetBlock mocks base method. +func (m *MockFullNode) ChainGetBlock(arg0 context.Context, arg1 cid.Cid) (*types.BlockHeader, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainGetBlock", arg0, arg1) + ret0, _ := ret[0].(*types.BlockHeader) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainGetBlock indicates an expected call of ChainGetBlock. +func (mr *MockFullNodeMockRecorder) ChainGetBlock(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainGetBlock", reflect.TypeOf((*MockFullNode)(nil).ChainGetBlock), arg0, arg1) +} + +// ChainGetBlockMessages mocks base method. +func (m *MockFullNode) ChainGetBlockMessages(arg0 context.Context, arg1 cid.Cid) (*api.BlockMessages, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainGetBlockMessages", arg0, arg1) + ret0, _ := ret[0].(*api.BlockMessages) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainGetBlockMessages indicates an expected call of ChainGetBlockMessages. +func (mr *MockFullNodeMockRecorder) ChainGetBlockMessages(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainGetBlockMessages", reflect.TypeOf((*MockFullNode)(nil).ChainGetBlockMessages), arg0, arg1) +} + +// ChainGetGenesis mocks base method. +func (m *MockFullNode) ChainGetGenesis(arg0 context.Context) (*types.TipSet, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainGetGenesis", arg0) + ret0, _ := ret[0].(*types.TipSet) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainGetGenesis indicates an expected call of ChainGetGenesis. +func (mr *MockFullNodeMockRecorder) ChainGetGenesis(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainGetGenesis", reflect.TypeOf((*MockFullNode)(nil).ChainGetGenesis), arg0) +} + +// ChainGetMessage mocks base method. +func (m *MockFullNode) ChainGetMessage(arg0 context.Context, arg1 cid.Cid) (*types.Message, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainGetMessage", arg0, arg1) + ret0, _ := ret[0].(*types.Message) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainGetMessage indicates an expected call of ChainGetMessage. +func (mr *MockFullNodeMockRecorder) ChainGetMessage(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainGetMessage", reflect.TypeOf((*MockFullNode)(nil).ChainGetMessage), arg0, arg1) +} + +// ChainGetMessagesInTipset mocks base method. +func (m *MockFullNode) ChainGetMessagesInTipset(arg0 context.Context, arg1 types.TipSetKey) ([]api.Message, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainGetMessagesInTipset", arg0, arg1) + ret0, _ := ret[0].([]api.Message) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainGetMessagesInTipset indicates an expected call of ChainGetMessagesInTipset. +func (mr *MockFullNodeMockRecorder) ChainGetMessagesInTipset(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainGetMessagesInTipset", reflect.TypeOf((*MockFullNode)(nil).ChainGetMessagesInTipset), arg0, arg1) +} + +// ChainGetNode mocks base method. +func (m *MockFullNode) ChainGetNode(arg0 context.Context, arg1 string) (*api.IpldObject, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainGetNode", arg0, arg1) + ret0, _ := ret[0].(*api.IpldObject) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainGetNode indicates an expected call of ChainGetNode. +func (mr *MockFullNodeMockRecorder) ChainGetNode(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainGetNode", reflect.TypeOf((*MockFullNode)(nil).ChainGetNode), arg0, arg1) +} + +// ChainGetParentMessages mocks base method. +func (m *MockFullNode) ChainGetParentMessages(arg0 context.Context, arg1 cid.Cid) ([]api.Message, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainGetParentMessages", arg0, arg1) + ret0, _ := ret[0].([]api.Message) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainGetParentMessages indicates an expected call of ChainGetParentMessages. +func (mr *MockFullNodeMockRecorder) ChainGetParentMessages(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainGetParentMessages", reflect.TypeOf((*MockFullNode)(nil).ChainGetParentMessages), arg0, arg1) +} + +// ChainGetParentReceipts mocks base method. +func (m *MockFullNode) ChainGetParentReceipts(arg0 context.Context, arg1 cid.Cid) ([]*types.MessageReceipt, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainGetParentReceipts", arg0, arg1) + ret0, _ := ret[0].([]*types.MessageReceipt) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainGetParentReceipts indicates an expected call of ChainGetParentReceipts. +func (mr *MockFullNodeMockRecorder) ChainGetParentReceipts(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainGetParentReceipts", reflect.TypeOf((*MockFullNode)(nil).ChainGetParentReceipts), arg0, arg1) +} + +// ChainGetPath mocks base method. +func (m *MockFullNode) ChainGetPath(arg0 context.Context, arg1, arg2 types.TipSetKey) ([]*api.HeadChange, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainGetPath", arg0, arg1, arg2) + ret0, _ := ret[0].([]*api.HeadChange) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainGetPath indicates an expected call of ChainGetPath. +func (mr *MockFullNodeMockRecorder) ChainGetPath(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainGetPath", reflect.TypeOf((*MockFullNode)(nil).ChainGetPath), arg0, arg1, arg2) +} + +// ChainGetRandomnessFromBeacon mocks base method. +func (m *MockFullNode) ChainGetRandomnessFromBeacon(arg0 context.Context, arg1 types.TipSetKey, arg2 crypto.DomainSeparationTag, arg3 abi.ChainEpoch, arg4 []byte) (abi.Randomness, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainGetRandomnessFromBeacon", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(abi.Randomness) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainGetRandomnessFromBeacon indicates an expected call of ChainGetRandomnessFromBeacon. +func (mr *MockFullNodeMockRecorder) ChainGetRandomnessFromBeacon(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainGetRandomnessFromBeacon", reflect.TypeOf((*MockFullNode)(nil).ChainGetRandomnessFromBeacon), arg0, arg1, arg2, arg3, arg4) +} + +// ChainGetRandomnessFromTickets mocks base method. +func (m *MockFullNode) ChainGetRandomnessFromTickets(arg0 context.Context, arg1 types.TipSetKey, arg2 crypto.DomainSeparationTag, arg3 abi.ChainEpoch, arg4 []byte) (abi.Randomness, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainGetRandomnessFromTickets", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(abi.Randomness) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainGetRandomnessFromTickets indicates an expected call of ChainGetRandomnessFromTickets. +func (mr *MockFullNodeMockRecorder) ChainGetRandomnessFromTickets(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainGetRandomnessFromTickets", reflect.TypeOf((*MockFullNode)(nil).ChainGetRandomnessFromTickets), arg0, arg1, arg2, arg3, arg4) +} + +// ChainGetTipSet mocks base method. +func (m *MockFullNode) ChainGetTipSet(arg0 context.Context, arg1 types.TipSetKey) (*types.TipSet, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainGetTipSet", arg0, arg1) + ret0, _ := ret[0].(*types.TipSet) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainGetTipSet indicates an expected call of ChainGetTipSet. +func (mr *MockFullNodeMockRecorder) ChainGetTipSet(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainGetTipSet", reflect.TypeOf((*MockFullNode)(nil).ChainGetTipSet), arg0, arg1) +} + +// ChainGetTipSetByHeight mocks base method. +func (m *MockFullNode) ChainGetTipSetByHeight(arg0 context.Context, arg1 abi.ChainEpoch, arg2 types.TipSetKey) (*types.TipSet, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainGetTipSetByHeight", arg0, arg1, arg2) + ret0, _ := ret[0].(*types.TipSet) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainGetTipSetByHeight indicates an expected call of ChainGetTipSetByHeight. +func (mr *MockFullNodeMockRecorder) ChainGetTipSetByHeight(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainGetTipSetByHeight", reflect.TypeOf((*MockFullNode)(nil).ChainGetTipSetByHeight), arg0, arg1, arg2) +} + +// ChainHasObj mocks base method. +func (m *MockFullNode) ChainHasObj(arg0 context.Context, arg1 cid.Cid) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainHasObj", arg0, arg1) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainHasObj indicates an expected call of ChainHasObj. +func (mr *MockFullNodeMockRecorder) ChainHasObj(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainHasObj", reflect.TypeOf((*MockFullNode)(nil).ChainHasObj), arg0, arg1) +} + +// ChainHead mocks base method. +func (m *MockFullNode) ChainHead(arg0 context.Context) (*types.TipSet, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainHead", arg0) + ret0, _ := ret[0].(*types.TipSet) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainHead indicates an expected call of ChainHead. +func (mr *MockFullNodeMockRecorder) ChainHead(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainHead", reflect.TypeOf((*MockFullNode)(nil).ChainHead), arg0) +} + +// ChainNotify mocks base method. +func (m *MockFullNode) ChainNotify(arg0 context.Context) (<-chan []*api.HeadChange, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainNotify", arg0) + ret0, _ := ret[0].(<-chan []*api.HeadChange) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainNotify indicates an expected call of ChainNotify. +func (mr *MockFullNodeMockRecorder) ChainNotify(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainNotify", reflect.TypeOf((*MockFullNode)(nil).ChainNotify), arg0) +} + +// ChainPutObj mocks base method. +func (m *MockFullNode) ChainPutObj(arg0 context.Context, arg1 blocks.Block) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainPutObj", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// ChainPutObj indicates an expected call of ChainPutObj. +func (mr *MockFullNodeMockRecorder) ChainPutObj(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainPutObj", reflect.TypeOf((*MockFullNode)(nil).ChainPutObj), arg0, arg1) +} + +// ChainReadObj mocks base method. +func (m *MockFullNode) ChainReadObj(arg0 context.Context, arg1 cid.Cid) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainReadObj", arg0, arg1) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainReadObj indicates an expected call of ChainReadObj. +func (mr *MockFullNodeMockRecorder) ChainReadObj(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainReadObj", reflect.TypeOf((*MockFullNode)(nil).ChainReadObj), arg0, arg1) +} + +// ChainSetHead mocks base method. +func (m *MockFullNode) ChainSetHead(arg0 context.Context, arg1 types.TipSetKey) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainSetHead", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// ChainSetHead indicates an expected call of ChainSetHead. +func (mr *MockFullNodeMockRecorder) ChainSetHead(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainSetHead", reflect.TypeOf((*MockFullNode)(nil).ChainSetHead), arg0, arg1) +} + +// ChainStatObj mocks base method. +func (m *MockFullNode) ChainStatObj(arg0 context.Context, arg1, arg2 cid.Cid) (api.ObjStat, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainStatObj", arg0, arg1, arg2) + ret0, _ := ret[0].(api.ObjStat) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainStatObj indicates an expected call of ChainStatObj. +func (mr *MockFullNodeMockRecorder) ChainStatObj(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainStatObj", reflect.TypeOf((*MockFullNode)(nil).ChainStatObj), arg0, arg1, arg2) +} + +// ChainTipSetWeight mocks base method. +func (m *MockFullNode) ChainTipSetWeight(arg0 context.Context, arg1 types.TipSetKey) (big.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainTipSetWeight", arg0, arg1) + ret0, _ := ret[0].(big.Int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainTipSetWeight indicates an expected call of ChainTipSetWeight. +func (mr *MockFullNodeMockRecorder) ChainTipSetWeight(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainTipSetWeight", reflect.TypeOf((*MockFullNode)(nil).ChainTipSetWeight), arg0, arg1) +} + +// ClientCalcCommP mocks base method. +func (m *MockFullNode) ClientCalcCommP(arg0 context.Context, arg1 string) (*api.CommPRet, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientCalcCommP", arg0, arg1) + ret0, _ := ret[0].(*api.CommPRet) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientCalcCommP indicates an expected call of ClientCalcCommP. +func (mr *MockFullNodeMockRecorder) ClientCalcCommP(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientCalcCommP", reflect.TypeOf((*MockFullNode)(nil).ClientCalcCommP), arg0, arg1) +} + +// ClientCancelDataTransfer mocks base method. +func (m *MockFullNode) ClientCancelDataTransfer(arg0 context.Context, arg1 datatransfer.TransferID, arg2 peer.ID, arg3 bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientCancelDataTransfer", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(error) + return ret0 +} + +// ClientCancelDataTransfer indicates an expected call of ClientCancelDataTransfer. +func (mr *MockFullNodeMockRecorder) ClientCancelDataTransfer(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientCancelDataTransfer", reflect.TypeOf((*MockFullNode)(nil).ClientCancelDataTransfer), arg0, arg1, arg2, arg3) +} + +// ClientCancelRetrievalDeal mocks base method. +func (m *MockFullNode) ClientCancelRetrievalDeal(arg0 context.Context, arg1 retrievalmarket.DealID) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientCancelRetrievalDeal", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// ClientCancelRetrievalDeal indicates an expected call of ClientCancelRetrievalDeal. +func (mr *MockFullNodeMockRecorder) ClientCancelRetrievalDeal(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientCancelRetrievalDeal", reflect.TypeOf((*MockFullNode)(nil).ClientCancelRetrievalDeal), arg0, arg1) +} + +// ClientDataTransferUpdates mocks base method. +func (m *MockFullNode) ClientDataTransferUpdates(arg0 context.Context) (<-chan api.DataTransferChannel, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientDataTransferUpdates", arg0) + ret0, _ := ret[0].(<-chan api.DataTransferChannel) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientDataTransferUpdates indicates an expected call of ClientDataTransferUpdates. +func (mr *MockFullNodeMockRecorder) ClientDataTransferUpdates(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientDataTransferUpdates", reflect.TypeOf((*MockFullNode)(nil).ClientDataTransferUpdates), arg0) +} + +// ClientDealPieceCID mocks base method. +func (m *MockFullNode) ClientDealPieceCID(arg0 context.Context, arg1 cid.Cid) (api.DataCIDSize, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientDealPieceCID", arg0, arg1) + ret0, _ := ret[0].(api.DataCIDSize) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientDealPieceCID indicates an expected call of ClientDealPieceCID. +func (mr *MockFullNodeMockRecorder) ClientDealPieceCID(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientDealPieceCID", reflect.TypeOf((*MockFullNode)(nil).ClientDealPieceCID), arg0, arg1) +} + +// ClientDealSize mocks base method. +func (m *MockFullNode) ClientDealSize(arg0 context.Context, arg1 cid.Cid) (api.DataSize, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientDealSize", arg0, arg1) + ret0, _ := ret[0].(api.DataSize) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientDealSize indicates an expected call of ClientDealSize. +func (mr *MockFullNodeMockRecorder) ClientDealSize(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientDealSize", reflect.TypeOf((*MockFullNode)(nil).ClientDealSize), arg0, arg1) +} + +// ClientFindData mocks base method. +func (m *MockFullNode) ClientFindData(arg0 context.Context, arg1 cid.Cid, arg2 *cid.Cid) ([]api.QueryOffer, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientFindData", arg0, arg1, arg2) + ret0, _ := ret[0].([]api.QueryOffer) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientFindData indicates an expected call of ClientFindData. +func (mr *MockFullNodeMockRecorder) ClientFindData(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientFindData", reflect.TypeOf((*MockFullNode)(nil).ClientFindData), arg0, arg1, arg2) +} + +// ClientGenCar mocks base method. +func (m *MockFullNode) ClientGenCar(arg0 context.Context, arg1 api.FileRef, arg2 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientGenCar", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// ClientGenCar indicates an expected call of ClientGenCar. +func (mr *MockFullNodeMockRecorder) ClientGenCar(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientGenCar", reflect.TypeOf((*MockFullNode)(nil).ClientGenCar), arg0, arg1, arg2) +} + +// ClientGetDealInfo mocks base method. +func (m *MockFullNode) ClientGetDealInfo(arg0 context.Context, arg1 cid.Cid) (*api.DealInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientGetDealInfo", arg0, arg1) + ret0, _ := ret[0].(*api.DealInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientGetDealInfo indicates an expected call of ClientGetDealInfo. +func (mr *MockFullNodeMockRecorder) ClientGetDealInfo(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientGetDealInfo", reflect.TypeOf((*MockFullNode)(nil).ClientGetDealInfo), arg0, arg1) +} + +// ClientGetDealStatus mocks base method. +func (m *MockFullNode) ClientGetDealStatus(arg0 context.Context, arg1 uint64) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientGetDealStatus", arg0, arg1) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientGetDealStatus indicates an expected call of ClientGetDealStatus. +func (mr *MockFullNodeMockRecorder) ClientGetDealStatus(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientGetDealStatus", reflect.TypeOf((*MockFullNode)(nil).ClientGetDealStatus), arg0, arg1) +} + +// ClientGetDealUpdates mocks base method. +func (m *MockFullNode) ClientGetDealUpdates(arg0 context.Context) (<-chan api.DealInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientGetDealUpdates", arg0) + ret0, _ := ret[0].(<-chan api.DealInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientGetDealUpdates indicates an expected call of ClientGetDealUpdates. +func (mr *MockFullNodeMockRecorder) ClientGetDealUpdates(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientGetDealUpdates", reflect.TypeOf((*MockFullNode)(nil).ClientGetDealUpdates), arg0) +} + +// ClientGetRetrievalUpdates mocks base method. +func (m *MockFullNode) ClientGetRetrievalUpdates(arg0 context.Context) (<-chan api.RetrievalInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientGetRetrievalUpdates", arg0) + ret0, _ := ret[0].(<-chan api.RetrievalInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientGetRetrievalUpdates indicates an expected call of ClientGetRetrievalUpdates. +func (mr *MockFullNodeMockRecorder) ClientGetRetrievalUpdates(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientGetRetrievalUpdates", reflect.TypeOf((*MockFullNode)(nil).ClientGetRetrievalUpdates), arg0) +} + +// ClientHasLocal mocks base method. +func (m *MockFullNode) ClientHasLocal(arg0 context.Context, arg1 cid.Cid) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientHasLocal", arg0, arg1) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientHasLocal indicates an expected call of ClientHasLocal. +func (mr *MockFullNodeMockRecorder) ClientHasLocal(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientHasLocal", reflect.TypeOf((*MockFullNode)(nil).ClientHasLocal), arg0, arg1) +} + +// ClientImport mocks base method. +func (m *MockFullNode) ClientImport(arg0 context.Context, arg1 api.FileRef) (*api.ImportRes, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientImport", arg0, arg1) + ret0, _ := ret[0].(*api.ImportRes) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientImport indicates an expected call of ClientImport. +func (mr *MockFullNodeMockRecorder) ClientImport(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientImport", reflect.TypeOf((*MockFullNode)(nil).ClientImport), arg0, arg1) +} + +// ClientListDataTransfers mocks base method. +func (m *MockFullNode) ClientListDataTransfers(arg0 context.Context) ([]api.DataTransferChannel, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientListDataTransfers", arg0) + ret0, _ := ret[0].([]api.DataTransferChannel) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientListDataTransfers indicates an expected call of ClientListDataTransfers. +func (mr *MockFullNodeMockRecorder) ClientListDataTransfers(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientListDataTransfers", reflect.TypeOf((*MockFullNode)(nil).ClientListDataTransfers), arg0) +} + +// ClientListDeals mocks base method. +func (m *MockFullNode) ClientListDeals(arg0 context.Context) ([]api.DealInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientListDeals", arg0) + ret0, _ := ret[0].([]api.DealInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientListDeals indicates an expected call of ClientListDeals. +func (mr *MockFullNodeMockRecorder) ClientListDeals(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientListDeals", reflect.TypeOf((*MockFullNode)(nil).ClientListDeals), arg0) +} + +// ClientListImports mocks base method. +func (m *MockFullNode) ClientListImports(arg0 context.Context) ([]api.Import, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientListImports", arg0) + ret0, _ := ret[0].([]api.Import) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientListImports indicates an expected call of ClientListImports. +func (mr *MockFullNodeMockRecorder) ClientListImports(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientListImports", reflect.TypeOf((*MockFullNode)(nil).ClientListImports), arg0) +} + +// ClientListRetrievals mocks base method. +func (m *MockFullNode) ClientListRetrievals(arg0 context.Context) ([]api.RetrievalInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientListRetrievals", arg0) + ret0, _ := ret[0].([]api.RetrievalInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientListRetrievals indicates an expected call of ClientListRetrievals. +func (mr *MockFullNodeMockRecorder) ClientListRetrievals(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientListRetrievals", reflect.TypeOf((*MockFullNode)(nil).ClientListRetrievals), arg0) +} + +// ClientMinerQueryOffer mocks base method. +func (m *MockFullNode) ClientMinerQueryOffer(arg0 context.Context, arg1 address.Address, arg2 cid.Cid, arg3 *cid.Cid) (api.QueryOffer, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientMinerQueryOffer", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(api.QueryOffer) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientMinerQueryOffer indicates an expected call of ClientMinerQueryOffer. +func (mr *MockFullNodeMockRecorder) ClientMinerQueryOffer(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientMinerQueryOffer", reflect.TypeOf((*MockFullNode)(nil).ClientMinerQueryOffer), arg0, arg1, arg2, arg3) +} + +// ClientQueryAsk mocks base method. +func (m *MockFullNode) ClientQueryAsk(arg0 context.Context, arg1 peer.ID, arg2 address.Address) (*storagemarket.StorageAsk, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientQueryAsk", arg0, arg1, arg2) + ret0, _ := ret[0].(*storagemarket.StorageAsk) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientQueryAsk indicates an expected call of ClientQueryAsk. +func (mr *MockFullNodeMockRecorder) ClientQueryAsk(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientQueryAsk", reflect.TypeOf((*MockFullNode)(nil).ClientQueryAsk), arg0, arg1, arg2) +} + +// ClientRemoveImport mocks base method. +func (m *MockFullNode) ClientRemoveImport(arg0 context.Context, arg1 imports.ID) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientRemoveImport", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// ClientRemoveImport indicates an expected call of ClientRemoveImport. +func (mr *MockFullNodeMockRecorder) ClientRemoveImport(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientRemoveImport", reflect.TypeOf((*MockFullNode)(nil).ClientRemoveImport), arg0, arg1) +} + +// ClientRestartDataTransfer mocks base method. +func (m *MockFullNode) ClientRestartDataTransfer(arg0 context.Context, arg1 datatransfer.TransferID, arg2 peer.ID, arg3 bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientRestartDataTransfer", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(error) + return ret0 +} + +// ClientRestartDataTransfer indicates an expected call of ClientRestartDataTransfer. +func (mr *MockFullNodeMockRecorder) ClientRestartDataTransfer(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientRestartDataTransfer", reflect.TypeOf((*MockFullNode)(nil).ClientRestartDataTransfer), arg0, arg1, arg2, arg3) +} + +// ClientRetrieve mocks base method. +func (m *MockFullNode) ClientRetrieve(arg0 context.Context, arg1 v0api.RetrievalOrder, arg2 *api.FileRef) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientRetrieve", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// ClientRetrieve indicates an expected call of ClientRetrieve. +func (mr *MockFullNodeMockRecorder) ClientRetrieve(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientRetrieve", reflect.TypeOf((*MockFullNode)(nil).ClientRetrieve), arg0, arg1, arg2) +} + +// ClientRetrieveTryRestartInsufficientFunds mocks base method. +func (m *MockFullNode) ClientRetrieveTryRestartInsufficientFunds(arg0 context.Context, arg1 address.Address) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientRetrieveTryRestartInsufficientFunds", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// ClientRetrieveTryRestartInsufficientFunds indicates an expected call of ClientRetrieveTryRestartInsufficientFunds. +func (mr *MockFullNodeMockRecorder) ClientRetrieveTryRestartInsufficientFunds(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientRetrieveTryRestartInsufficientFunds", reflect.TypeOf((*MockFullNode)(nil).ClientRetrieveTryRestartInsufficientFunds), arg0, arg1) +} + +// ClientRetrieveWithEvents mocks base method. +func (m *MockFullNode) ClientRetrieveWithEvents(arg0 context.Context, arg1 v0api.RetrievalOrder, arg2 *api.FileRef) (<-chan marketevents.RetrievalEvent, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientRetrieveWithEvents", arg0, arg1, arg2) + ret0, _ := ret[0].(<-chan marketevents.RetrievalEvent) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientRetrieveWithEvents indicates an expected call of ClientRetrieveWithEvents. +func (mr *MockFullNodeMockRecorder) ClientRetrieveWithEvents(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientRetrieveWithEvents", reflect.TypeOf((*MockFullNode)(nil).ClientRetrieveWithEvents), arg0, arg1, arg2) +} + +// ClientStartDeal mocks base method. +func (m *MockFullNode) ClientStartDeal(arg0 context.Context, arg1 *api.StartDealParams) (*cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientStartDeal", arg0, arg1) + ret0, _ := ret[0].(*cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientStartDeal indicates an expected call of ClientStartDeal. +func (mr *MockFullNodeMockRecorder) ClientStartDeal(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientStartDeal", reflect.TypeOf((*MockFullNode)(nil).ClientStartDeal), arg0, arg1) +} + +// ClientStatelessDeal mocks base method. +func (m *MockFullNode) ClientStatelessDeal(arg0 context.Context, arg1 *api.StartDealParams) (*cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClientStatelessDeal", arg0, arg1) + ret0, _ := ret[0].(*cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ClientStatelessDeal indicates an expected call of ClientStatelessDeal. +func (mr *MockFullNodeMockRecorder) ClientStatelessDeal(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientStatelessDeal", reflect.TypeOf((*MockFullNode)(nil).ClientStatelessDeal), arg0, arg1) +} + +// Closing mocks base method. +func (m *MockFullNode) Closing(arg0 context.Context) (<-chan struct{}, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Closing", arg0) + ret0, _ := ret[0].(<-chan struct{}) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Closing indicates an expected call of Closing. +func (mr *MockFullNodeMockRecorder) Closing(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Closing", reflect.TypeOf((*MockFullNode)(nil).Closing), arg0) +} + +// CreateBackup mocks base method. +func (m *MockFullNode) CreateBackup(arg0 context.Context, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateBackup", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateBackup indicates an expected call of CreateBackup. +func (mr *MockFullNodeMockRecorder) CreateBackup(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateBackup", reflect.TypeOf((*MockFullNode)(nil).CreateBackup), arg0, arg1) +} + +// Discover mocks base method. +func (m *MockFullNode) Discover(arg0 context.Context) (apitypes.OpenRPCDocument, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Discover", arg0) + ret0, _ := ret[0].(apitypes.OpenRPCDocument) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Discover indicates an expected call of Discover. +func (mr *MockFullNodeMockRecorder) Discover(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Discover", reflect.TypeOf((*MockFullNode)(nil).Discover), arg0) +} + +// GasEstimateFeeCap mocks base method. +func (m *MockFullNode) GasEstimateFeeCap(arg0 context.Context, arg1 *types.Message, arg2 int64, arg3 types.TipSetKey) (big.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GasEstimateFeeCap", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(big.Int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GasEstimateFeeCap indicates an expected call of GasEstimateFeeCap. +func (mr *MockFullNodeMockRecorder) GasEstimateFeeCap(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GasEstimateFeeCap", reflect.TypeOf((*MockFullNode)(nil).GasEstimateFeeCap), arg0, arg1, arg2, arg3) +} + +// GasEstimateGasLimit mocks base method. +func (m *MockFullNode) GasEstimateGasLimit(arg0 context.Context, arg1 *types.Message, arg2 types.TipSetKey) (int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GasEstimateGasLimit", arg0, arg1, arg2) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GasEstimateGasLimit indicates an expected call of GasEstimateGasLimit. +func (mr *MockFullNodeMockRecorder) GasEstimateGasLimit(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GasEstimateGasLimit", reflect.TypeOf((*MockFullNode)(nil).GasEstimateGasLimit), arg0, arg1, arg2) +} + +// GasEstimateGasPremium mocks base method. +func (m *MockFullNode) GasEstimateGasPremium(arg0 context.Context, arg1 uint64, arg2 address.Address, arg3 int64, arg4 types.TipSetKey) (big.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GasEstimateGasPremium", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(big.Int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GasEstimateGasPremium indicates an expected call of GasEstimateGasPremium. +func (mr *MockFullNodeMockRecorder) GasEstimateGasPremium(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GasEstimateGasPremium", reflect.TypeOf((*MockFullNode)(nil).GasEstimateGasPremium), arg0, arg1, arg2, arg3, arg4) +} + +// GasEstimateMessageGas mocks base method. +func (m *MockFullNode) GasEstimateMessageGas(arg0 context.Context, arg1 *types.Message, arg2 *api.MessageSendSpec, arg3 types.TipSetKey) (*types.Message, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GasEstimateMessageGas", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*types.Message) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GasEstimateMessageGas indicates an expected call of GasEstimateMessageGas. +func (mr *MockFullNodeMockRecorder) GasEstimateMessageGas(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GasEstimateMessageGas", reflect.TypeOf((*MockFullNode)(nil).GasEstimateMessageGas), arg0, arg1, arg2, arg3) +} + +// ID mocks base method. +func (m *MockFullNode) ID(arg0 context.Context) (peer.ID, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ID", arg0) + ret0, _ := ret[0].(peer.ID) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ID indicates an expected call of ID. +func (mr *MockFullNodeMockRecorder) ID(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ID", reflect.TypeOf((*MockFullNode)(nil).ID), arg0) +} + +// LogAlerts mocks base method. +func (m *MockFullNode) LogAlerts(arg0 context.Context) ([]alerting.Alert, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LogAlerts", arg0) + ret0, _ := ret[0].([]alerting.Alert) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LogAlerts indicates an expected call of LogAlerts. +func (mr *MockFullNodeMockRecorder) LogAlerts(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogAlerts", reflect.TypeOf((*MockFullNode)(nil).LogAlerts), arg0) +} + +// LogList mocks base method. +func (m *MockFullNode) LogList(arg0 context.Context) ([]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LogList", arg0) + ret0, _ := ret[0].([]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LogList indicates an expected call of LogList. +func (mr *MockFullNodeMockRecorder) LogList(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogList", reflect.TypeOf((*MockFullNode)(nil).LogList), arg0) +} + +// LogSetLevel mocks base method. +func (m *MockFullNode) LogSetLevel(arg0 context.Context, arg1, arg2 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LogSetLevel", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// LogSetLevel indicates an expected call of LogSetLevel. +func (mr *MockFullNodeMockRecorder) LogSetLevel(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogSetLevel", reflect.TypeOf((*MockFullNode)(nil).LogSetLevel), arg0, arg1, arg2) +} + +// MarketAddBalance mocks base method. +func (m *MockFullNode) MarketAddBalance(arg0 context.Context, arg1, arg2 address.Address, arg3 big.Int) (cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MarketAddBalance", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MarketAddBalance indicates an expected call of MarketAddBalance. +func (mr *MockFullNodeMockRecorder) MarketAddBalance(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarketAddBalance", reflect.TypeOf((*MockFullNode)(nil).MarketAddBalance), arg0, arg1, arg2, arg3) +} + +// MarketGetReserved mocks base method. +func (m *MockFullNode) MarketGetReserved(arg0 context.Context, arg1 address.Address) (big.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MarketGetReserved", arg0, arg1) + ret0, _ := ret[0].(big.Int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MarketGetReserved indicates an expected call of MarketGetReserved. +func (mr *MockFullNodeMockRecorder) MarketGetReserved(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarketGetReserved", reflect.TypeOf((*MockFullNode)(nil).MarketGetReserved), arg0, arg1) +} + +// MarketReleaseFunds mocks base method. +func (m *MockFullNode) MarketReleaseFunds(arg0 context.Context, arg1 address.Address, arg2 big.Int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MarketReleaseFunds", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// MarketReleaseFunds indicates an expected call of MarketReleaseFunds. +func (mr *MockFullNodeMockRecorder) MarketReleaseFunds(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarketReleaseFunds", reflect.TypeOf((*MockFullNode)(nil).MarketReleaseFunds), arg0, arg1, arg2) +} + +// MarketReserveFunds mocks base method. +func (m *MockFullNode) MarketReserveFunds(arg0 context.Context, arg1, arg2 address.Address, arg3 big.Int) (cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MarketReserveFunds", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MarketReserveFunds indicates an expected call of MarketReserveFunds. +func (mr *MockFullNodeMockRecorder) MarketReserveFunds(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarketReserveFunds", reflect.TypeOf((*MockFullNode)(nil).MarketReserveFunds), arg0, arg1, arg2, arg3) +} + +// MarketWithdraw mocks base method. +func (m *MockFullNode) MarketWithdraw(arg0 context.Context, arg1, arg2 address.Address, arg3 big.Int) (cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MarketWithdraw", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MarketWithdraw indicates an expected call of MarketWithdraw. +func (mr *MockFullNodeMockRecorder) MarketWithdraw(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarketWithdraw", reflect.TypeOf((*MockFullNode)(nil).MarketWithdraw), arg0, arg1, arg2, arg3) +} + +// MinerCreateBlock mocks base method. +func (m *MockFullNode) MinerCreateBlock(arg0 context.Context, arg1 *api.BlockTemplate) (*types.BlockMsg, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MinerCreateBlock", arg0, arg1) + ret0, _ := ret[0].(*types.BlockMsg) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MinerCreateBlock indicates an expected call of MinerCreateBlock. +func (mr *MockFullNodeMockRecorder) MinerCreateBlock(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MinerCreateBlock", reflect.TypeOf((*MockFullNode)(nil).MinerCreateBlock), arg0, arg1) +} + +// MinerGetBaseInfo mocks base method. +func (m *MockFullNode) MinerGetBaseInfo(arg0 context.Context, arg1 address.Address, arg2 abi.ChainEpoch, arg3 types.TipSetKey) (*api.MiningBaseInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MinerGetBaseInfo", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*api.MiningBaseInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MinerGetBaseInfo indicates an expected call of MinerGetBaseInfo. +func (mr *MockFullNodeMockRecorder) MinerGetBaseInfo(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MinerGetBaseInfo", reflect.TypeOf((*MockFullNode)(nil).MinerGetBaseInfo), arg0, arg1, arg2, arg3) +} + +// MpoolBatchPush mocks base method. +func (m *MockFullNode) MpoolBatchPush(arg0 context.Context, arg1 []*types.SignedMessage) ([]cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MpoolBatchPush", arg0, arg1) + ret0, _ := ret[0].([]cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MpoolBatchPush indicates an expected call of MpoolBatchPush. +func (mr *MockFullNodeMockRecorder) MpoolBatchPush(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MpoolBatchPush", reflect.TypeOf((*MockFullNode)(nil).MpoolBatchPush), arg0, arg1) +} + +// MpoolBatchPushMessage mocks base method. +func (m *MockFullNode) MpoolBatchPushMessage(arg0 context.Context, arg1 []*types.Message, arg2 *api.MessageSendSpec) ([]*types.SignedMessage, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MpoolBatchPushMessage", arg0, arg1, arg2) + ret0, _ := ret[0].([]*types.SignedMessage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MpoolBatchPushMessage indicates an expected call of MpoolBatchPushMessage. +func (mr *MockFullNodeMockRecorder) MpoolBatchPushMessage(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MpoolBatchPushMessage", reflect.TypeOf((*MockFullNode)(nil).MpoolBatchPushMessage), arg0, arg1, arg2) +} + +// MpoolBatchPushUntrusted mocks base method. +func (m *MockFullNode) MpoolBatchPushUntrusted(arg0 context.Context, arg1 []*types.SignedMessage) ([]cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MpoolBatchPushUntrusted", arg0, arg1) + ret0, _ := ret[0].([]cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MpoolBatchPushUntrusted indicates an expected call of MpoolBatchPushUntrusted. +func (mr *MockFullNodeMockRecorder) MpoolBatchPushUntrusted(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MpoolBatchPushUntrusted", reflect.TypeOf((*MockFullNode)(nil).MpoolBatchPushUntrusted), arg0, arg1) +} + +// MpoolClear mocks base method. +func (m *MockFullNode) MpoolClear(arg0 context.Context, arg1 bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MpoolClear", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// MpoolClear indicates an expected call of MpoolClear. +func (mr *MockFullNodeMockRecorder) MpoolClear(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MpoolClear", reflect.TypeOf((*MockFullNode)(nil).MpoolClear), arg0, arg1) +} + +// MpoolGetConfig mocks base method. +func (m *MockFullNode) MpoolGetConfig(arg0 context.Context) (*types.MpoolConfig, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MpoolGetConfig", arg0) + ret0, _ := ret[0].(*types.MpoolConfig) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MpoolGetConfig indicates an expected call of MpoolGetConfig. +func (mr *MockFullNodeMockRecorder) MpoolGetConfig(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MpoolGetConfig", reflect.TypeOf((*MockFullNode)(nil).MpoolGetConfig), arg0) +} + +// MpoolGetNonce mocks base method. +func (m *MockFullNode) MpoolGetNonce(arg0 context.Context, arg1 address.Address) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MpoolGetNonce", arg0, arg1) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MpoolGetNonce indicates an expected call of MpoolGetNonce. +func (mr *MockFullNodeMockRecorder) MpoolGetNonce(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MpoolGetNonce", reflect.TypeOf((*MockFullNode)(nil).MpoolGetNonce), arg0, arg1) +} + +// MpoolPending mocks base method. +func (m *MockFullNode) MpoolPending(arg0 context.Context, arg1 types.TipSetKey) ([]*types.SignedMessage, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MpoolPending", arg0, arg1) + ret0, _ := ret[0].([]*types.SignedMessage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MpoolPending indicates an expected call of MpoolPending. +func (mr *MockFullNodeMockRecorder) MpoolPending(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MpoolPending", reflect.TypeOf((*MockFullNode)(nil).MpoolPending), arg0, arg1) +} + +// MpoolPush mocks base method. +func (m *MockFullNode) MpoolPush(arg0 context.Context, arg1 *types.SignedMessage) (cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MpoolPush", arg0, arg1) + ret0, _ := ret[0].(cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MpoolPush indicates an expected call of MpoolPush. +func (mr *MockFullNodeMockRecorder) MpoolPush(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MpoolPush", reflect.TypeOf((*MockFullNode)(nil).MpoolPush), arg0, arg1) +} + +// MpoolPushMessage mocks base method. +func (m *MockFullNode) MpoolPushMessage(arg0 context.Context, arg1 *types.Message, arg2 *api.MessageSendSpec) (*types.SignedMessage, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MpoolPushMessage", arg0, arg1, arg2) + ret0, _ := ret[0].(*types.SignedMessage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MpoolPushMessage indicates an expected call of MpoolPushMessage. +func (mr *MockFullNodeMockRecorder) MpoolPushMessage(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MpoolPushMessage", reflect.TypeOf((*MockFullNode)(nil).MpoolPushMessage), arg0, arg1, arg2) +} + +// MpoolPushUntrusted mocks base method. +func (m *MockFullNode) MpoolPushUntrusted(arg0 context.Context, arg1 *types.SignedMessage) (cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MpoolPushUntrusted", arg0, arg1) + ret0, _ := ret[0].(cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MpoolPushUntrusted indicates an expected call of MpoolPushUntrusted. +func (mr *MockFullNodeMockRecorder) MpoolPushUntrusted(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MpoolPushUntrusted", reflect.TypeOf((*MockFullNode)(nil).MpoolPushUntrusted), arg0, arg1) +} + +// MpoolSelect mocks base method. +func (m *MockFullNode) MpoolSelect(arg0 context.Context, arg1 types.TipSetKey, arg2 float64) ([]*types.SignedMessage, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MpoolSelect", arg0, arg1, arg2) + ret0, _ := ret[0].([]*types.SignedMessage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MpoolSelect indicates an expected call of MpoolSelect. +func (mr *MockFullNodeMockRecorder) MpoolSelect(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MpoolSelect", reflect.TypeOf((*MockFullNode)(nil).MpoolSelect), arg0, arg1, arg2) +} + +// MpoolSetConfig mocks base method. +func (m *MockFullNode) MpoolSetConfig(arg0 context.Context, arg1 *types.MpoolConfig) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MpoolSetConfig", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// MpoolSetConfig indicates an expected call of MpoolSetConfig. +func (mr *MockFullNodeMockRecorder) MpoolSetConfig(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MpoolSetConfig", reflect.TypeOf((*MockFullNode)(nil).MpoolSetConfig), arg0, arg1) +} + +// MpoolSub mocks base method. +func (m *MockFullNode) MpoolSub(arg0 context.Context) (<-chan api.MpoolUpdate, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MpoolSub", arg0) + ret0, _ := ret[0].(<-chan api.MpoolUpdate) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MpoolSub indicates an expected call of MpoolSub. +func (mr *MockFullNodeMockRecorder) MpoolSub(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MpoolSub", reflect.TypeOf((*MockFullNode)(nil).MpoolSub), arg0) +} + +// MsigAddApprove mocks base method. +func (m *MockFullNode) MsigAddApprove(arg0 context.Context, arg1, arg2 address.Address, arg3 uint64, arg4, arg5 address.Address, arg6 bool) (cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MsigAddApprove", arg0, arg1, arg2, arg3, arg4, arg5, arg6) + ret0, _ := ret[0].(cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MsigAddApprove indicates an expected call of MsigAddApprove. +func (mr *MockFullNodeMockRecorder) MsigAddApprove(arg0, arg1, arg2, arg3, arg4, arg5, arg6 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MsigAddApprove", reflect.TypeOf((*MockFullNode)(nil).MsigAddApprove), arg0, arg1, arg2, arg3, arg4, arg5, arg6) +} + +// MsigAddCancel mocks base method. +func (m *MockFullNode) MsigAddCancel(arg0 context.Context, arg1, arg2 address.Address, arg3 uint64, arg4 address.Address, arg5 bool) (cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MsigAddCancel", arg0, arg1, arg2, arg3, arg4, arg5) + ret0, _ := ret[0].(cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MsigAddCancel indicates an expected call of MsigAddCancel. +func (mr *MockFullNodeMockRecorder) MsigAddCancel(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MsigAddCancel", reflect.TypeOf((*MockFullNode)(nil).MsigAddCancel), arg0, arg1, arg2, arg3, arg4, arg5) +} + +// MsigAddPropose mocks base method. +func (m *MockFullNode) MsigAddPropose(arg0 context.Context, arg1, arg2, arg3 address.Address, arg4 bool) (cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MsigAddPropose", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MsigAddPropose indicates an expected call of MsigAddPropose. +func (mr *MockFullNodeMockRecorder) MsigAddPropose(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MsigAddPropose", reflect.TypeOf((*MockFullNode)(nil).MsigAddPropose), arg0, arg1, arg2, arg3, arg4) +} + +// MsigApprove mocks base method. +func (m *MockFullNode) MsigApprove(arg0 context.Context, arg1 address.Address, arg2 uint64, arg3 address.Address) (cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MsigApprove", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MsigApprove indicates an expected call of MsigApprove. +func (mr *MockFullNodeMockRecorder) MsigApprove(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MsigApprove", reflect.TypeOf((*MockFullNode)(nil).MsigApprove), arg0, arg1, arg2, arg3) +} + +// MsigApproveTxnHash mocks base method. +func (m *MockFullNode) MsigApproveTxnHash(arg0 context.Context, arg1 address.Address, arg2 uint64, arg3, arg4 address.Address, arg5 big.Int, arg6 address.Address, arg7 uint64, arg8 []byte) (cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MsigApproveTxnHash", arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) + ret0, _ := ret[0].(cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MsigApproveTxnHash indicates an expected call of MsigApproveTxnHash. +func (mr *MockFullNodeMockRecorder) MsigApproveTxnHash(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MsigApproveTxnHash", reflect.TypeOf((*MockFullNode)(nil).MsigApproveTxnHash), arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) +} + +// MsigCancel mocks base method. +func (m *MockFullNode) MsigCancel(arg0 context.Context, arg1 address.Address, arg2 uint64, arg3 address.Address, arg4 big.Int, arg5 address.Address, arg6 uint64, arg7 []byte) (cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MsigCancel", arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) + ret0, _ := ret[0].(cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MsigCancel indicates an expected call of MsigCancel. +func (mr *MockFullNodeMockRecorder) MsigCancel(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MsigCancel", reflect.TypeOf((*MockFullNode)(nil).MsigCancel), arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) +} + +// MsigCreate mocks base method. +func (m *MockFullNode) MsigCreate(arg0 context.Context, arg1 uint64, arg2 []address.Address, arg3 abi.ChainEpoch, arg4 big.Int, arg5 address.Address, arg6 big.Int) (cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MsigCreate", arg0, arg1, arg2, arg3, arg4, arg5, arg6) + ret0, _ := ret[0].(cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MsigCreate indicates an expected call of MsigCreate. +func (mr *MockFullNodeMockRecorder) MsigCreate(arg0, arg1, arg2, arg3, arg4, arg5, arg6 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MsigCreate", reflect.TypeOf((*MockFullNode)(nil).MsigCreate), arg0, arg1, arg2, arg3, arg4, arg5, arg6) +} + +// MsigGetAvailableBalance mocks base method. +func (m *MockFullNode) MsigGetAvailableBalance(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (big.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MsigGetAvailableBalance", arg0, arg1, arg2) + ret0, _ := ret[0].(big.Int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MsigGetAvailableBalance indicates an expected call of MsigGetAvailableBalance. +func (mr *MockFullNodeMockRecorder) MsigGetAvailableBalance(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MsigGetAvailableBalance", reflect.TypeOf((*MockFullNode)(nil).MsigGetAvailableBalance), arg0, arg1, arg2) +} + +// MsigGetPending mocks base method. +func (m *MockFullNode) MsigGetPending(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) ([]*api.MsigTransaction, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MsigGetPending", arg0, arg1, arg2) + ret0, _ := ret[0].([]*api.MsigTransaction) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MsigGetPending indicates an expected call of MsigGetPending. +func (mr *MockFullNodeMockRecorder) MsigGetPending(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MsigGetPending", reflect.TypeOf((*MockFullNode)(nil).MsigGetPending), arg0, arg1, arg2) +} + +// MsigGetVested mocks base method. +func (m *MockFullNode) MsigGetVested(arg0 context.Context, arg1 address.Address, arg2, arg3 types.TipSetKey) (big.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MsigGetVested", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(big.Int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MsigGetVested indicates an expected call of MsigGetVested. +func (mr *MockFullNodeMockRecorder) MsigGetVested(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MsigGetVested", reflect.TypeOf((*MockFullNode)(nil).MsigGetVested), arg0, arg1, arg2, arg3) +} + +// MsigGetVestingSchedule mocks base method. +func (m *MockFullNode) MsigGetVestingSchedule(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (api.MsigVesting, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MsigGetVestingSchedule", arg0, arg1, arg2) + ret0, _ := ret[0].(api.MsigVesting) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MsigGetVestingSchedule indicates an expected call of MsigGetVestingSchedule. +func (mr *MockFullNodeMockRecorder) MsigGetVestingSchedule(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MsigGetVestingSchedule", reflect.TypeOf((*MockFullNode)(nil).MsigGetVestingSchedule), arg0, arg1, arg2) +} + +// MsigPropose mocks base method. +func (m *MockFullNode) MsigPropose(arg0 context.Context, arg1, arg2 address.Address, arg3 big.Int, arg4 address.Address, arg5 uint64, arg6 []byte) (cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MsigPropose", arg0, arg1, arg2, arg3, arg4, arg5, arg6) + ret0, _ := ret[0].(cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MsigPropose indicates an expected call of MsigPropose. +func (mr *MockFullNodeMockRecorder) MsigPropose(arg0, arg1, arg2, arg3, arg4, arg5, arg6 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MsigPropose", reflect.TypeOf((*MockFullNode)(nil).MsigPropose), arg0, arg1, arg2, arg3, arg4, arg5, arg6) +} + +// MsigRemoveSigner mocks base method. +func (m *MockFullNode) MsigRemoveSigner(arg0 context.Context, arg1, arg2, arg3 address.Address, arg4 bool) (cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MsigRemoveSigner", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MsigRemoveSigner indicates an expected call of MsigRemoveSigner. +func (mr *MockFullNodeMockRecorder) MsigRemoveSigner(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MsigRemoveSigner", reflect.TypeOf((*MockFullNode)(nil).MsigRemoveSigner), arg0, arg1, arg2, arg3, arg4) +} + +// MsigSwapApprove mocks base method. +func (m *MockFullNode) MsigSwapApprove(arg0 context.Context, arg1, arg2 address.Address, arg3 uint64, arg4, arg5, arg6 address.Address) (cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MsigSwapApprove", arg0, arg1, arg2, arg3, arg4, arg5, arg6) + ret0, _ := ret[0].(cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MsigSwapApprove indicates an expected call of MsigSwapApprove. +func (mr *MockFullNodeMockRecorder) MsigSwapApprove(arg0, arg1, arg2, arg3, arg4, arg5, arg6 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MsigSwapApprove", reflect.TypeOf((*MockFullNode)(nil).MsigSwapApprove), arg0, arg1, arg2, arg3, arg4, arg5, arg6) +} + +// MsigSwapCancel mocks base method. +func (m *MockFullNode) MsigSwapCancel(arg0 context.Context, arg1, arg2 address.Address, arg3 uint64, arg4, arg5 address.Address) (cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MsigSwapCancel", arg0, arg1, arg2, arg3, arg4, arg5) + ret0, _ := ret[0].(cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MsigSwapCancel indicates an expected call of MsigSwapCancel. +func (mr *MockFullNodeMockRecorder) MsigSwapCancel(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MsigSwapCancel", reflect.TypeOf((*MockFullNode)(nil).MsigSwapCancel), arg0, arg1, arg2, arg3, arg4, arg5) +} + +// MsigSwapPropose mocks base method. +func (m *MockFullNode) MsigSwapPropose(arg0 context.Context, arg1, arg2, arg3, arg4 address.Address) (cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MsigSwapPropose", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MsigSwapPropose indicates an expected call of MsigSwapPropose. +func (mr *MockFullNodeMockRecorder) MsigSwapPropose(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MsigSwapPropose", reflect.TypeOf((*MockFullNode)(nil).MsigSwapPropose), arg0, arg1, arg2, arg3, arg4) +} + +// NetAddrsListen mocks base method. +func (m *MockFullNode) NetAddrsListen(arg0 context.Context) (peer.AddrInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetAddrsListen", arg0) + ret0, _ := ret[0].(peer.AddrInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetAddrsListen indicates an expected call of NetAddrsListen. +func (mr *MockFullNodeMockRecorder) NetAddrsListen(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetAddrsListen", reflect.TypeOf((*MockFullNode)(nil).NetAddrsListen), arg0) +} + +// NetAgentVersion mocks base method. +func (m *MockFullNode) NetAgentVersion(arg0 context.Context, arg1 peer.ID) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetAgentVersion", arg0, arg1) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetAgentVersion indicates an expected call of NetAgentVersion. +func (mr *MockFullNodeMockRecorder) NetAgentVersion(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetAgentVersion", reflect.TypeOf((*MockFullNode)(nil).NetAgentVersion), arg0, arg1) +} + +// NetAutoNatStatus mocks base method. +func (m *MockFullNode) NetAutoNatStatus(arg0 context.Context) (api.NatInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetAutoNatStatus", arg0) + ret0, _ := ret[0].(api.NatInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetAutoNatStatus indicates an expected call of NetAutoNatStatus. +func (mr *MockFullNodeMockRecorder) NetAutoNatStatus(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetAutoNatStatus", reflect.TypeOf((*MockFullNode)(nil).NetAutoNatStatus), arg0) +} + +// NetBandwidthStats mocks base method. +func (m *MockFullNode) NetBandwidthStats(arg0 context.Context) (metrics.Stats, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetBandwidthStats", arg0) + ret0, _ := ret[0].(metrics.Stats) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetBandwidthStats indicates an expected call of NetBandwidthStats. +func (mr *MockFullNodeMockRecorder) NetBandwidthStats(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetBandwidthStats", reflect.TypeOf((*MockFullNode)(nil).NetBandwidthStats), arg0) +} + +// NetBandwidthStatsByPeer mocks base method. +func (m *MockFullNode) NetBandwidthStatsByPeer(arg0 context.Context) (map[string]metrics.Stats, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetBandwidthStatsByPeer", arg0) + ret0, _ := ret[0].(map[string]metrics.Stats) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetBandwidthStatsByPeer indicates an expected call of NetBandwidthStatsByPeer. +func (mr *MockFullNodeMockRecorder) NetBandwidthStatsByPeer(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetBandwidthStatsByPeer", reflect.TypeOf((*MockFullNode)(nil).NetBandwidthStatsByPeer), arg0) +} + +// NetBandwidthStatsByProtocol mocks base method. +func (m *MockFullNode) NetBandwidthStatsByProtocol(arg0 context.Context) (map[protocol.ID]metrics.Stats, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetBandwidthStatsByProtocol", arg0) + ret0, _ := ret[0].(map[protocol.ID]metrics.Stats) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetBandwidthStatsByProtocol indicates an expected call of NetBandwidthStatsByProtocol. +func (mr *MockFullNodeMockRecorder) NetBandwidthStatsByProtocol(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetBandwidthStatsByProtocol", reflect.TypeOf((*MockFullNode)(nil).NetBandwidthStatsByProtocol), arg0) +} + +// NetBlockAdd mocks base method. +func (m *MockFullNode) NetBlockAdd(arg0 context.Context, arg1 api.NetBlockList) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetBlockAdd", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// NetBlockAdd indicates an expected call of NetBlockAdd. +func (mr *MockFullNodeMockRecorder) NetBlockAdd(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetBlockAdd", reflect.TypeOf((*MockFullNode)(nil).NetBlockAdd), arg0, arg1) +} + +// NetBlockList mocks base method. +func (m *MockFullNode) NetBlockList(arg0 context.Context) (api.NetBlockList, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetBlockList", arg0) + ret0, _ := ret[0].(api.NetBlockList) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetBlockList indicates an expected call of NetBlockList. +func (mr *MockFullNodeMockRecorder) NetBlockList(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetBlockList", reflect.TypeOf((*MockFullNode)(nil).NetBlockList), arg0) +} + +// NetBlockRemove mocks base method. +func (m *MockFullNode) NetBlockRemove(arg0 context.Context, arg1 api.NetBlockList) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetBlockRemove", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// NetBlockRemove indicates an expected call of NetBlockRemove. +func (mr *MockFullNodeMockRecorder) NetBlockRemove(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetBlockRemove", reflect.TypeOf((*MockFullNode)(nil).NetBlockRemove), arg0, arg1) +} + +// NetConnect mocks base method. +func (m *MockFullNode) NetConnect(arg0 context.Context, arg1 peer.AddrInfo) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetConnect", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// NetConnect indicates an expected call of NetConnect. +func (mr *MockFullNodeMockRecorder) NetConnect(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetConnect", reflect.TypeOf((*MockFullNode)(nil).NetConnect), arg0, arg1) +} + +// NetConnectedness mocks base method. +func (m *MockFullNode) NetConnectedness(arg0 context.Context, arg1 peer.ID) (network0.Connectedness, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetConnectedness", arg0, arg1) + ret0, _ := ret[0].(network0.Connectedness) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetConnectedness indicates an expected call of NetConnectedness. +func (mr *MockFullNodeMockRecorder) NetConnectedness(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetConnectedness", reflect.TypeOf((*MockFullNode)(nil).NetConnectedness), arg0, arg1) +} + +// NetDisconnect mocks base method. +func (m *MockFullNode) NetDisconnect(arg0 context.Context, arg1 peer.ID) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetDisconnect", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// NetDisconnect indicates an expected call of NetDisconnect. +func (mr *MockFullNodeMockRecorder) NetDisconnect(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetDisconnect", reflect.TypeOf((*MockFullNode)(nil).NetDisconnect), arg0, arg1) +} + +// NetFindPeer mocks base method. +func (m *MockFullNode) NetFindPeer(arg0 context.Context, arg1 peer.ID) (peer.AddrInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetFindPeer", arg0, arg1) + ret0, _ := ret[0].(peer.AddrInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetFindPeer indicates an expected call of NetFindPeer. +func (mr *MockFullNodeMockRecorder) NetFindPeer(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetFindPeer", reflect.TypeOf((*MockFullNode)(nil).NetFindPeer), arg0, arg1) +} + +// NetLimit mocks base method. +func (m *MockFullNode) NetLimit(arg0 context.Context, arg1 string) (api.NetLimit, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetLimit", arg0, arg1) + ret0, _ := ret[0].(api.NetLimit) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetLimit indicates an expected call of NetLimit. +func (mr *MockFullNodeMockRecorder) NetLimit(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetLimit", reflect.TypeOf((*MockFullNode)(nil).NetLimit), arg0, arg1) +} + +// NetPeerInfo mocks base method. +func (m *MockFullNode) NetPeerInfo(arg0 context.Context, arg1 peer.ID) (*api.ExtendedPeerInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetPeerInfo", arg0, arg1) + ret0, _ := ret[0].(*api.ExtendedPeerInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetPeerInfo indicates an expected call of NetPeerInfo. +func (mr *MockFullNodeMockRecorder) NetPeerInfo(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetPeerInfo", reflect.TypeOf((*MockFullNode)(nil).NetPeerInfo), arg0, arg1) +} + +// NetPeers mocks base method. +func (m *MockFullNode) NetPeers(arg0 context.Context) ([]peer.AddrInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetPeers", arg0) + ret0, _ := ret[0].([]peer.AddrInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetPeers indicates an expected call of NetPeers. +func (mr *MockFullNodeMockRecorder) NetPeers(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetPeers", reflect.TypeOf((*MockFullNode)(nil).NetPeers), arg0) +} + +// NetPing mocks base method. +func (m *MockFullNode) NetPing(arg0 context.Context, arg1 peer.ID) (time.Duration, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetPing", arg0, arg1) + ret0, _ := ret[0].(time.Duration) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetPing indicates an expected call of NetPing. +func (mr *MockFullNodeMockRecorder) NetPing(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetPing", reflect.TypeOf((*MockFullNode)(nil).NetPing), arg0, arg1) +} + +// NetProtectAdd mocks base method. +func (m *MockFullNode) NetProtectAdd(arg0 context.Context, arg1 []peer.ID) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetProtectAdd", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// NetProtectAdd indicates an expected call of NetProtectAdd. +func (mr *MockFullNodeMockRecorder) NetProtectAdd(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetProtectAdd", reflect.TypeOf((*MockFullNode)(nil).NetProtectAdd), arg0, arg1) +} + +// NetProtectList mocks base method. +func (m *MockFullNode) NetProtectList(arg0 context.Context) ([]peer.ID, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetProtectList", arg0) + ret0, _ := ret[0].([]peer.ID) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetProtectList indicates an expected call of NetProtectList. +func (mr *MockFullNodeMockRecorder) NetProtectList(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetProtectList", reflect.TypeOf((*MockFullNode)(nil).NetProtectList), arg0) +} + +// NetProtectRemove mocks base method. +func (m *MockFullNode) NetProtectRemove(arg0 context.Context, arg1 []peer.ID) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetProtectRemove", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// NetProtectRemove indicates an expected call of NetProtectRemove. +func (mr *MockFullNodeMockRecorder) NetProtectRemove(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetProtectRemove", reflect.TypeOf((*MockFullNode)(nil).NetProtectRemove), arg0, arg1) +} + +// NetPubsubScores mocks base method. +func (m *MockFullNode) NetPubsubScores(arg0 context.Context) ([]api.PubsubScore, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetPubsubScores", arg0) + ret0, _ := ret[0].([]api.PubsubScore) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetPubsubScores indicates an expected call of NetPubsubScores. +func (mr *MockFullNodeMockRecorder) NetPubsubScores(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetPubsubScores", reflect.TypeOf((*MockFullNode)(nil).NetPubsubScores), arg0) +} + +// NetSetLimit mocks base method. +func (m *MockFullNode) NetSetLimit(arg0 context.Context, arg1 string, arg2 api.NetLimit) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetSetLimit", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// NetSetLimit indicates an expected call of NetSetLimit. +func (mr *MockFullNodeMockRecorder) NetSetLimit(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetSetLimit", reflect.TypeOf((*MockFullNode)(nil).NetSetLimit), arg0, arg1, arg2) +} + +// NetStat mocks base method. +func (m *MockFullNode) NetStat(arg0 context.Context, arg1 string) (api.NetStat, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetStat", arg0, arg1) + ret0, _ := ret[0].(api.NetStat) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetStat indicates an expected call of NetStat. +func (mr *MockFullNodeMockRecorder) NetStat(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetStat", reflect.TypeOf((*MockFullNode)(nil).NetStat), arg0, arg1) +} + +// PaychAllocateLane mocks base method. +func (m *MockFullNode) PaychAllocateLane(arg0 context.Context, arg1 address.Address) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaychAllocateLane", arg0, arg1) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PaychAllocateLane indicates an expected call of PaychAllocateLane. +func (mr *MockFullNodeMockRecorder) PaychAllocateLane(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychAllocateLane", reflect.TypeOf((*MockFullNode)(nil).PaychAllocateLane), arg0, arg1) +} + +// PaychAvailableFunds mocks base method. +func (m *MockFullNode) PaychAvailableFunds(arg0 context.Context, arg1 address.Address) (*api.ChannelAvailableFunds, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaychAvailableFunds", arg0, arg1) + ret0, _ := ret[0].(*api.ChannelAvailableFunds) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PaychAvailableFunds indicates an expected call of PaychAvailableFunds. +func (mr *MockFullNodeMockRecorder) PaychAvailableFunds(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychAvailableFunds", reflect.TypeOf((*MockFullNode)(nil).PaychAvailableFunds), arg0, arg1) +} + +// PaychAvailableFundsByFromTo mocks base method. +func (m *MockFullNode) PaychAvailableFundsByFromTo(arg0 context.Context, arg1, arg2 address.Address) (*api.ChannelAvailableFunds, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaychAvailableFundsByFromTo", arg0, arg1, arg2) + ret0, _ := ret[0].(*api.ChannelAvailableFunds) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PaychAvailableFundsByFromTo indicates an expected call of PaychAvailableFundsByFromTo. +func (mr *MockFullNodeMockRecorder) PaychAvailableFundsByFromTo(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychAvailableFundsByFromTo", reflect.TypeOf((*MockFullNode)(nil).PaychAvailableFundsByFromTo), arg0, arg1, arg2) +} + +// PaychCollect mocks base method. +func (m *MockFullNode) PaychCollect(arg0 context.Context, arg1 address.Address) (cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaychCollect", arg0, arg1) + ret0, _ := ret[0].(cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PaychCollect indicates an expected call of PaychCollect. +func (mr *MockFullNodeMockRecorder) PaychCollect(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychCollect", reflect.TypeOf((*MockFullNode)(nil).PaychCollect), arg0, arg1) +} + +// PaychGet mocks base method. +func (m *MockFullNode) PaychGet(arg0 context.Context, arg1, arg2 address.Address, arg3 big.Int) (*api.ChannelInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaychGet", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*api.ChannelInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PaychGet indicates an expected call of PaychGet. +func (mr *MockFullNodeMockRecorder) PaychGet(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychGet", reflect.TypeOf((*MockFullNode)(nil).PaychGet), arg0, arg1, arg2, arg3) +} + +// PaychGetWaitReady mocks base method. +func (m *MockFullNode) PaychGetWaitReady(arg0 context.Context, arg1 cid.Cid) (address.Address, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaychGetWaitReady", arg0, arg1) + ret0, _ := ret[0].(address.Address) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PaychGetWaitReady indicates an expected call of PaychGetWaitReady. +func (mr *MockFullNodeMockRecorder) PaychGetWaitReady(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychGetWaitReady", reflect.TypeOf((*MockFullNode)(nil).PaychGetWaitReady), arg0, arg1) +} + +// PaychList mocks base method. +func (m *MockFullNode) PaychList(arg0 context.Context) ([]address.Address, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaychList", arg0) + ret0, _ := ret[0].([]address.Address) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PaychList indicates an expected call of PaychList. +func (mr *MockFullNodeMockRecorder) PaychList(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychList", reflect.TypeOf((*MockFullNode)(nil).PaychList), arg0) +} + +// PaychNewPayment mocks base method. +func (m *MockFullNode) PaychNewPayment(arg0 context.Context, arg1, arg2 address.Address, arg3 []api.VoucherSpec) (*api.PaymentInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaychNewPayment", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*api.PaymentInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PaychNewPayment indicates an expected call of PaychNewPayment. +func (mr *MockFullNodeMockRecorder) PaychNewPayment(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychNewPayment", reflect.TypeOf((*MockFullNode)(nil).PaychNewPayment), arg0, arg1, arg2, arg3) +} + +// PaychSettle mocks base method. +func (m *MockFullNode) PaychSettle(arg0 context.Context, arg1 address.Address) (cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaychSettle", arg0, arg1) + ret0, _ := ret[0].(cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PaychSettle indicates an expected call of PaychSettle. +func (mr *MockFullNodeMockRecorder) PaychSettle(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychSettle", reflect.TypeOf((*MockFullNode)(nil).PaychSettle), arg0, arg1) +} + +// PaychStatus mocks base method. +func (m *MockFullNode) PaychStatus(arg0 context.Context, arg1 address.Address) (*api.PaychStatus, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaychStatus", arg0, arg1) + ret0, _ := ret[0].(*api.PaychStatus) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PaychStatus indicates an expected call of PaychStatus. +func (mr *MockFullNodeMockRecorder) PaychStatus(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychStatus", reflect.TypeOf((*MockFullNode)(nil).PaychStatus), arg0, arg1) +} + +// PaychVoucherAdd mocks base method. +func (m *MockFullNode) PaychVoucherAdd(arg0 context.Context, arg1 address.Address, arg2 *paych.SignedVoucher, arg3 []byte, arg4 big.Int) (big.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaychVoucherAdd", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(big.Int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PaychVoucherAdd indicates an expected call of PaychVoucherAdd. +func (mr *MockFullNodeMockRecorder) PaychVoucherAdd(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychVoucherAdd", reflect.TypeOf((*MockFullNode)(nil).PaychVoucherAdd), arg0, arg1, arg2, arg3, arg4) +} + +// PaychVoucherCheckSpendable mocks base method. +func (m *MockFullNode) PaychVoucherCheckSpendable(arg0 context.Context, arg1 address.Address, arg2 *paych.SignedVoucher, arg3, arg4 []byte) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaychVoucherCheckSpendable", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PaychVoucherCheckSpendable indicates an expected call of PaychVoucherCheckSpendable. +func (mr *MockFullNodeMockRecorder) PaychVoucherCheckSpendable(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychVoucherCheckSpendable", reflect.TypeOf((*MockFullNode)(nil).PaychVoucherCheckSpendable), arg0, arg1, arg2, arg3, arg4) +} + +// PaychVoucherCheckValid mocks base method. +func (m *MockFullNode) PaychVoucherCheckValid(arg0 context.Context, arg1 address.Address, arg2 *paych.SignedVoucher) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaychVoucherCheckValid", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// PaychVoucherCheckValid indicates an expected call of PaychVoucherCheckValid. +func (mr *MockFullNodeMockRecorder) PaychVoucherCheckValid(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychVoucherCheckValid", reflect.TypeOf((*MockFullNode)(nil).PaychVoucherCheckValid), arg0, arg1, arg2) +} + +// PaychVoucherCreate mocks base method. +func (m *MockFullNode) PaychVoucherCreate(arg0 context.Context, arg1 address.Address, arg2 big.Int, arg3 uint64) (*api.VoucherCreateResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaychVoucherCreate", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*api.VoucherCreateResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PaychVoucherCreate indicates an expected call of PaychVoucherCreate. +func (mr *MockFullNodeMockRecorder) PaychVoucherCreate(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychVoucherCreate", reflect.TypeOf((*MockFullNode)(nil).PaychVoucherCreate), arg0, arg1, arg2, arg3) +} + +// PaychVoucherList mocks base method. +func (m *MockFullNode) PaychVoucherList(arg0 context.Context, arg1 address.Address) ([]*paych.SignedVoucher, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaychVoucherList", arg0, arg1) + ret0, _ := ret[0].([]*paych.SignedVoucher) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PaychVoucherList indicates an expected call of PaychVoucherList. +func (mr *MockFullNodeMockRecorder) PaychVoucherList(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychVoucherList", reflect.TypeOf((*MockFullNode)(nil).PaychVoucherList), arg0, arg1) +} + +// PaychVoucherSubmit mocks base method. +func (m *MockFullNode) PaychVoucherSubmit(arg0 context.Context, arg1 address.Address, arg2 *paych.SignedVoucher, arg3, arg4 []byte) (cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PaychVoucherSubmit", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PaychVoucherSubmit indicates an expected call of PaychVoucherSubmit. +func (mr *MockFullNodeMockRecorder) PaychVoucherSubmit(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PaychVoucherSubmit", reflect.TypeOf((*MockFullNode)(nil).PaychVoucherSubmit), arg0, arg1, arg2, arg3, arg4) +} + +// Session mocks base method. +func (m *MockFullNode) Session(arg0 context.Context) (uuid.UUID, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Session", arg0) + ret0, _ := ret[0].(uuid.UUID) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Session indicates an expected call of Session. +func (mr *MockFullNodeMockRecorder) Session(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Session", reflect.TypeOf((*MockFullNode)(nil).Session), arg0) +} + +// Shutdown mocks base method. +func (m *MockFullNode) Shutdown(arg0 context.Context) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Shutdown", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// Shutdown indicates an expected call of Shutdown. +func (mr *MockFullNodeMockRecorder) Shutdown(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Shutdown", reflect.TypeOf((*MockFullNode)(nil).Shutdown), arg0) +} + +// StartTime mocks base method. +func (m *MockFullNode) StartTime(arg0 context.Context) (time.Time, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StartTime", arg0) + ret0, _ := ret[0].(time.Time) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StartTime indicates an expected call of StartTime. +func (mr *MockFullNodeMockRecorder) StartTime(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartTime", reflect.TypeOf((*MockFullNode)(nil).StartTime), arg0) +} + +// StateAccountKey mocks base method. +func (m *MockFullNode) StateAccountKey(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (address.Address, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateAccountKey", arg0, arg1, arg2) + ret0, _ := ret[0].(address.Address) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateAccountKey indicates an expected call of StateAccountKey. +func (mr *MockFullNodeMockRecorder) StateAccountKey(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateAccountKey", reflect.TypeOf((*MockFullNode)(nil).StateAccountKey), arg0, arg1, arg2) +} + +// StateActorCodeCIDs mocks base method. +func (m *MockFullNode) StateActorCodeCIDs(arg0 context.Context, arg1 network.Version) (map[string]cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateActorCodeCIDs", arg0, arg1) + ret0, _ := ret[0].(map[string]cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateActorCodeCIDs indicates an expected call of StateActorCodeCIDs. +func (mr *MockFullNodeMockRecorder) StateActorCodeCIDs(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateActorCodeCIDs", reflect.TypeOf((*MockFullNode)(nil).StateActorCodeCIDs), arg0, arg1) +} + +// StateActorManifestCID mocks base method. +func (m *MockFullNode) StateActorManifestCID(arg0 context.Context, arg1 network.Version) (cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateActorManifestCID", arg0, arg1) + ret0, _ := ret[0].(cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateActorManifestCID indicates an expected call of StateActorManifestCID. +func (mr *MockFullNodeMockRecorder) StateActorManifestCID(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateActorManifestCID", reflect.TypeOf((*MockFullNode)(nil).StateActorManifestCID), arg0, arg1) +} + +// StateAllMinerFaults mocks base method. +func (m *MockFullNode) StateAllMinerFaults(arg0 context.Context, arg1 abi.ChainEpoch, arg2 types.TipSetKey) ([]*api.Fault, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateAllMinerFaults", arg0, arg1, arg2) + ret0, _ := ret[0].([]*api.Fault) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateAllMinerFaults indicates an expected call of StateAllMinerFaults. +func (mr *MockFullNodeMockRecorder) StateAllMinerFaults(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateAllMinerFaults", reflect.TypeOf((*MockFullNode)(nil).StateAllMinerFaults), arg0, arg1, arg2) +} + +// StateCall mocks base method. +func (m *MockFullNode) StateCall(arg0 context.Context, arg1 *types.Message, arg2 types.TipSetKey) (*api.InvocResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateCall", arg0, arg1, arg2) + ret0, _ := ret[0].(*api.InvocResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateCall indicates an expected call of StateCall. +func (mr *MockFullNodeMockRecorder) StateCall(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateCall", reflect.TypeOf((*MockFullNode)(nil).StateCall), arg0, arg1, arg2) +} + +// StateChangedActors mocks base method. +func (m *MockFullNode) StateChangedActors(arg0 context.Context, arg1, arg2 cid.Cid) (map[string]types.ActorV5, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateChangedActors", arg0, arg1, arg2) + ret0, _ := ret[0].(map[string]types.ActorV5) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateChangedActors indicates an expected call of StateChangedActors. +func (mr *MockFullNodeMockRecorder) StateChangedActors(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateChangedActors", reflect.TypeOf((*MockFullNode)(nil).StateChangedActors), arg0, arg1, arg2) +} + +// StateCirculatingSupply mocks base method. +func (m *MockFullNode) StateCirculatingSupply(arg0 context.Context, arg1 types.TipSetKey) (big.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateCirculatingSupply", arg0, arg1) + ret0, _ := ret[0].(big.Int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateCirculatingSupply indicates an expected call of StateCirculatingSupply. +func (mr *MockFullNodeMockRecorder) StateCirculatingSupply(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateCirculatingSupply", reflect.TypeOf((*MockFullNode)(nil).StateCirculatingSupply), arg0, arg1) +} + +// StateCompute mocks base method. +func (m *MockFullNode) StateCompute(arg0 context.Context, arg1 abi.ChainEpoch, arg2 []*types.Message, arg3 types.TipSetKey) (*api.ComputeStateOutput, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateCompute", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*api.ComputeStateOutput) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateCompute indicates an expected call of StateCompute. +func (mr *MockFullNodeMockRecorder) StateCompute(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateCompute", reflect.TypeOf((*MockFullNode)(nil).StateCompute), arg0, arg1, arg2, arg3) +} + +// StateDealProviderCollateralBounds mocks base method. +func (m *MockFullNode) StateDealProviderCollateralBounds(arg0 context.Context, arg1 abi.PaddedPieceSize, arg2 bool, arg3 types.TipSetKey) (api.DealCollateralBounds, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateDealProviderCollateralBounds", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(api.DealCollateralBounds) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateDealProviderCollateralBounds indicates an expected call of StateDealProviderCollateralBounds. +func (mr *MockFullNodeMockRecorder) StateDealProviderCollateralBounds(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateDealProviderCollateralBounds", reflect.TypeOf((*MockFullNode)(nil).StateDealProviderCollateralBounds), arg0, arg1, arg2, arg3) +} + +// StateDecodeParams mocks base method. +func (m *MockFullNode) StateDecodeParams(arg0 context.Context, arg1 address.Address, arg2 abi.MethodNum, arg3 []byte, arg4 types.TipSetKey) (interface{}, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateDecodeParams", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(interface{}) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateDecodeParams indicates an expected call of StateDecodeParams. +func (mr *MockFullNodeMockRecorder) StateDecodeParams(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateDecodeParams", reflect.TypeOf((*MockFullNode)(nil).StateDecodeParams), arg0, arg1, arg2, arg3, arg4) +} + +// StateGetActor mocks base method. +func (m *MockFullNode) StateGetActor(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (*types.ActorV5, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateGetActor", arg0, arg1, arg2) + ret0, _ := ret[0].(*types.ActorV5) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateGetActor indicates an expected call of StateGetActor. +func (mr *MockFullNodeMockRecorder) StateGetActor(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateGetActor", reflect.TypeOf((*MockFullNode)(nil).StateGetActor), arg0, arg1, arg2) +} + +// StateGetAllocation mocks base method. +func (m *MockFullNode) StateGetAllocation(arg0 context.Context, arg1 address.Address, arg2 verifreg.AllocationId, arg3 types.TipSetKey) (*verifreg.Allocation, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateGetAllocation", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*verifreg.Allocation) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateGetAllocation indicates an expected call of StateGetAllocation. +func (mr *MockFullNodeMockRecorder) StateGetAllocation(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateGetAllocation", reflect.TypeOf((*MockFullNode)(nil).StateGetAllocation), arg0, arg1, arg2, arg3) +} + +// StateGetAllocationForPendingDeal mocks base method. +func (m *MockFullNode) StateGetAllocationForPendingDeal(arg0 context.Context, arg1 abi.DealID, arg2 types.TipSetKey) (*verifreg.Allocation, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateGetAllocationForPendingDeal", arg0, arg1, arg2) + ret0, _ := ret[0].(*verifreg.Allocation) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateGetAllocationForPendingDeal indicates an expected call of StateGetAllocationForPendingDeal. +func (mr *MockFullNodeMockRecorder) StateGetAllocationForPendingDeal(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateGetAllocationForPendingDeal", reflect.TypeOf((*MockFullNode)(nil).StateGetAllocationForPendingDeal), arg0, arg1, arg2) +} + +// StateGetAllocations mocks base method. +func (m *MockFullNode) StateGetAllocations(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (map[verifreg.AllocationId]verifreg.Allocation, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateGetAllocations", arg0, arg1, arg2) + ret0, _ := ret[0].(map[verifreg.AllocationId]verifreg.Allocation) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateGetAllocations indicates an expected call of StateGetAllocations. +func (mr *MockFullNodeMockRecorder) StateGetAllocations(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateGetAllocations", reflect.TypeOf((*MockFullNode)(nil).StateGetAllocations), arg0, arg1, arg2) +} + +// StateGetClaim mocks base method. +func (m *MockFullNode) StateGetClaim(arg0 context.Context, arg1 address.Address, arg2 verifreg.ClaimId, arg3 types.TipSetKey) (*verifreg.Claim, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateGetClaim", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*verifreg.Claim) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateGetClaim indicates an expected call of StateGetClaim. +func (mr *MockFullNodeMockRecorder) StateGetClaim(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateGetClaim", reflect.TypeOf((*MockFullNode)(nil).StateGetClaim), arg0, arg1, arg2, arg3) +} + +// StateGetClaims mocks base method. +func (m *MockFullNode) StateGetClaims(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (map[verifreg.ClaimId]verifreg.Claim, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateGetClaims", arg0, arg1, arg2) + ret0, _ := ret[0].(map[verifreg.ClaimId]verifreg.Claim) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateGetClaims indicates an expected call of StateGetClaims. +func (mr *MockFullNodeMockRecorder) StateGetClaims(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateGetClaims", reflect.TypeOf((*MockFullNode)(nil).StateGetClaims), arg0, arg1, arg2) +} + +// StateGetNetworkParams mocks base method. +func (m *MockFullNode) StateGetNetworkParams(arg0 context.Context) (*api.NetworkParams, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateGetNetworkParams", arg0) + ret0, _ := ret[0].(*api.NetworkParams) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateGetNetworkParams indicates an expected call of StateGetNetworkParams. +func (mr *MockFullNodeMockRecorder) StateGetNetworkParams(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateGetNetworkParams", reflect.TypeOf((*MockFullNode)(nil).StateGetNetworkParams), arg0) +} + +// StateGetRandomnessFromBeacon mocks base method. +func (m *MockFullNode) StateGetRandomnessFromBeacon(arg0 context.Context, arg1 crypto.DomainSeparationTag, arg2 abi.ChainEpoch, arg3 []byte, arg4 types.TipSetKey) (abi.Randomness, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateGetRandomnessFromBeacon", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(abi.Randomness) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateGetRandomnessFromBeacon indicates an expected call of StateGetRandomnessFromBeacon. +func (mr *MockFullNodeMockRecorder) StateGetRandomnessFromBeacon(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateGetRandomnessFromBeacon", reflect.TypeOf((*MockFullNode)(nil).StateGetRandomnessFromBeacon), arg0, arg1, arg2, arg3, arg4) +} + +// StateGetRandomnessFromTickets mocks base method. +func (m *MockFullNode) StateGetRandomnessFromTickets(arg0 context.Context, arg1 crypto.DomainSeparationTag, arg2 abi.ChainEpoch, arg3 []byte, arg4 types.TipSetKey) (abi.Randomness, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateGetRandomnessFromTickets", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(abi.Randomness) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateGetRandomnessFromTickets indicates an expected call of StateGetRandomnessFromTickets. +func (mr *MockFullNodeMockRecorder) StateGetRandomnessFromTickets(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateGetRandomnessFromTickets", reflect.TypeOf((*MockFullNode)(nil).StateGetRandomnessFromTickets), arg0, arg1, arg2, arg3, arg4) +} + +// StateGetReceipt mocks base method. +func (m *MockFullNode) StateGetReceipt(arg0 context.Context, arg1 cid.Cid, arg2 types.TipSetKey) (*types.MessageReceipt, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateGetReceipt", arg0, arg1, arg2) + ret0, _ := ret[0].(*types.MessageReceipt) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateGetReceipt indicates an expected call of StateGetReceipt. +func (mr *MockFullNodeMockRecorder) StateGetReceipt(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateGetReceipt", reflect.TypeOf((*MockFullNode)(nil).StateGetReceipt), arg0, arg1, arg2) +} + +// StateListActors mocks base method. +func (m *MockFullNode) StateListActors(arg0 context.Context, arg1 types.TipSetKey) ([]address.Address, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateListActors", arg0, arg1) + ret0, _ := ret[0].([]address.Address) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateListActors indicates an expected call of StateListActors. +func (mr *MockFullNodeMockRecorder) StateListActors(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateListActors", reflect.TypeOf((*MockFullNode)(nil).StateListActors), arg0, arg1) +} + +// StateListMessages mocks base method. +func (m *MockFullNode) StateListMessages(arg0 context.Context, arg1 *api.MessageMatch, arg2 types.TipSetKey, arg3 abi.ChainEpoch) ([]cid.Cid, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateListMessages", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].([]cid.Cid) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateListMessages indicates an expected call of StateListMessages. +func (mr *MockFullNodeMockRecorder) StateListMessages(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateListMessages", reflect.TypeOf((*MockFullNode)(nil).StateListMessages), arg0, arg1, arg2, arg3) +} + +// StateListMiners mocks base method. +func (m *MockFullNode) StateListMiners(arg0 context.Context, arg1 types.TipSetKey) ([]address.Address, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateListMiners", arg0, arg1) + ret0, _ := ret[0].([]address.Address) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateListMiners indicates an expected call of StateListMiners. +func (mr *MockFullNodeMockRecorder) StateListMiners(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateListMiners", reflect.TypeOf((*MockFullNode)(nil).StateListMiners), arg0, arg1) +} + +// StateLookupID mocks base method. +func (m *MockFullNode) StateLookupID(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (address.Address, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateLookupID", arg0, arg1, arg2) + ret0, _ := ret[0].(address.Address) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateLookupID indicates an expected call of StateLookupID. +func (mr *MockFullNodeMockRecorder) StateLookupID(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateLookupID", reflect.TypeOf((*MockFullNode)(nil).StateLookupID), arg0, arg1, arg2) +} + +// StateMarketBalance mocks base method. +func (m *MockFullNode) StateMarketBalance(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (api.MarketBalance, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMarketBalance", arg0, arg1, arg2) + ret0, _ := ret[0].(api.MarketBalance) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMarketBalance indicates an expected call of StateMarketBalance. +func (mr *MockFullNodeMockRecorder) StateMarketBalance(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMarketBalance", reflect.TypeOf((*MockFullNode)(nil).StateMarketBalance), arg0, arg1, arg2) +} + +// StateMarketDeals mocks base method. +func (m *MockFullNode) StateMarketDeals(arg0 context.Context, arg1 types.TipSetKey) (map[string]*api.MarketDeal, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMarketDeals", arg0, arg1) + ret0, _ := ret[0].(map[string]*api.MarketDeal) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMarketDeals indicates an expected call of StateMarketDeals. +func (mr *MockFullNodeMockRecorder) StateMarketDeals(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMarketDeals", reflect.TypeOf((*MockFullNode)(nil).StateMarketDeals), arg0, arg1) +} + +// StateMarketParticipants mocks base method. +func (m *MockFullNode) StateMarketParticipants(arg0 context.Context, arg1 types.TipSetKey) (map[string]api.MarketBalance, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMarketParticipants", arg0, arg1) + ret0, _ := ret[0].(map[string]api.MarketBalance) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMarketParticipants indicates an expected call of StateMarketParticipants. +func (mr *MockFullNodeMockRecorder) StateMarketParticipants(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMarketParticipants", reflect.TypeOf((*MockFullNode)(nil).StateMarketParticipants), arg0, arg1) +} + +// StateMarketStorageDeal mocks base method. +func (m *MockFullNode) StateMarketStorageDeal(arg0 context.Context, arg1 abi.DealID, arg2 types.TipSetKey) (*api.MarketDeal, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMarketStorageDeal", arg0, arg1, arg2) + ret0, _ := ret[0].(*api.MarketDeal) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMarketStorageDeal indicates an expected call of StateMarketStorageDeal. +func (mr *MockFullNodeMockRecorder) StateMarketStorageDeal(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMarketStorageDeal", reflect.TypeOf((*MockFullNode)(nil).StateMarketStorageDeal), arg0, arg1, arg2) +} + +// StateMinerActiveSectors mocks base method. +func (m *MockFullNode) StateMinerActiveSectors(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) ([]*miner.SectorOnChainInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerActiveSectors", arg0, arg1, arg2) + ret0, _ := ret[0].([]*miner.SectorOnChainInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerActiveSectors indicates an expected call of StateMinerActiveSectors. +func (mr *MockFullNodeMockRecorder) StateMinerActiveSectors(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerActiveSectors", reflect.TypeOf((*MockFullNode)(nil).StateMinerActiveSectors), arg0, arg1, arg2) +} + +// StateMinerAvailableBalance mocks base method. +func (m *MockFullNode) StateMinerAvailableBalance(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (big.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerAvailableBalance", arg0, arg1, arg2) + ret0, _ := ret[0].(big.Int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerAvailableBalance indicates an expected call of StateMinerAvailableBalance. +func (mr *MockFullNodeMockRecorder) StateMinerAvailableBalance(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerAvailableBalance", reflect.TypeOf((*MockFullNode)(nil).StateMinerAvailableBalance), arg0, arg1, arg2) +} + +// StateMinerDeadlines mocks base method. +func (m *MockFullNode) StateMinerDeadlines(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) ([]api.Deadline, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerDeadlines", arg0, arg1, arg2) + ret0, _ := ret[0].([]api.Deadline) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerDeadlines indicates an expected call of StateMinerDeadlines. +func (mr *MockFullNodeMockRecorder) StateMinerDeadlines(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerDeadlines", reflect.TypeOf((*MockFullNode)(nil).StateMinerDeadlines), arg0, arg1, arg2) +} + +// StateMinerFaults mocks base method. +func (m *MockFullNode) StateMinerFaults(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (bitfield.BitField, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerFaults", arg0, arg1, arg2) + ret0, _ := ret[0].(bitfield.BitField) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerFaults indicates an expected call of StateMinerFaults. +func (mr *MockFullNodeMockRecorder) StateMinerFaults(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerFaults", reflect.TypeOf((*MockFullNode)(nil).StateMinerFaults), arg0, arg1, arg2) +} + +// StateMinerInfo mocks base method. +func (m *MockFullNode) StateMinerInfo(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (api.MinerInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerInfo", arg0, arg1, arg2) + ret0, _ := ret[0].(api.MinerInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerInfo indicates an expected call of StateMinerInfo. +func (mr *MockFullNodeMockRecorder) StateMinerInfo(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerInfo", reflect.TypeOf((*MockFullNode)(nil).StateMinerInfo), arg0, arg1, arg2) +} + +// StateMinerInitialPledgeCollateral mocks base method. +func (m *MockFullNode) StateMinerInitialPledgeCollateral(arg0 context.Context, arg1 address.Address, arg2 miner.SectorPreCommitInfo, arg3 types.TipSetKey) (big.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerInitialPledgeCollateral", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(big.Int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerInitialPledgeCollateral indicates an expected call of StateMinerInitialPledgeCollateral. +func (mr *MockFullNodeMockRecorder) StateMinerInitialPledgeCollateral(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerInitialPledgeCollateral", reflect.TypeOf((*MockFullNode)(nil).StateMinerInitialPledgeCollateral), arg0, arg1, arg2, arg3) +} + +// StateMinerPartitions mocks base method. +func (m *MockFullNode) StateMinerPartitions(arg0 context.Context, arg1 address.Address, arg2 uint64, arg3 types.TipSetKey) ([]api.Partition, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerPartitions", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].([]api.Partition) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerPartitions indicates an expected call of StateMinerPartitions. +func (mr *MockFullNodeMockRecorder) StateMinerPartitions(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerPartitions", reflect.TypeOf((*MockFullNode)(nil).StateMinerPartitions), arg0, arg1, arg2, arg3) +} + +// StateMinerPower mocks base method. +func (m *MockFullNode) StateMinerPower(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (*api.MinerPower, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerPower", arg0, arg1, arg2) + ret0, _ := ret[0].(*api.MinerPower) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerPower indicates an expected call of StateMinerPower. +func (mr *MockFullNodeMockRecorder) StateMinerPower(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerPower", reflect.TypeOf((*MockFullNode)(nil).StateMinerPower), arg0, arg1, arg2) +} + +// StateMinerPreCommitDepositForPower mocks base method. +func (m *MockFullNode) StateMinerPreCommitDepositForPower(arg0 context.Context, arg1 address.Address, arg2 miner.SectorPreCommitInfo, arg3 types.TipSetKey) (big.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerPreCommitDepositForPower", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(big.Int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerPreCommitDepositForPower indicates an expected call of StateMinerPreCommitDepositForPower. +func (mr *MockFullNodeMockRecorder) StateMinerPreCommitDepositForPower(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerPreCommitDepositForPower", reflect.TypeOf((*MockFullNode)(nil).StateMinerPreCommitDepositForPower), arg0, arg1, arg2, arg3) +} + +// StateMinerProvingDeadline mocks base method. +func (m *MockFullNode) StateMinerProvingDeadline(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (*dline.Info, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerProvingDeadline", arg0, arg1, arg2) + ret0, _ := ret[0].(*dline.Info) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerProvingDeadline indicates an expected call of StateMinerProvingDeadline. +func (mr *MockFullNodeMockRecorder) StateMinerProvingDeadline(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerProvingDeadline", reflect.TypeOf((*MockFullNode)(nil).StateMinerProvingDeadline), arg0, arg1, arg2) +} + +// StateMinerRecoveries mocks base method. +func (m *MockFullNode) StateMinerRecoveries(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (bitfield.BitField, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerRecoveries", arg0, arg1, arg2) + ret0, _ := ret[0].(bitfield.BitField) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerRecoveries indicates an expected call of StateMinerRecoveries. +func (mr *MockFullNodeMockRecorder) StateMinerRecoveries(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerRecoveries", reflect.TypeOf((*MockFullNode)(nil).StateMinerRecoveries), arg0, arg1, arg2) +} + +// StateMinerSectorAllocated mocks base method. +func (m *MockFullNode) StateMinerSectorAllocated(arg0 context.Context, arg1 address.Address, arg2 abi.SectorNumber, arg3 types.TipSetKey) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerSectorAllocated", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerSectorAllocated indicates an expected call of StateMinerSectorAllocated. +func (mr *MockFullNodeMockRecorder) StateMinerSectorAllocated(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerSectorAllocated", reflect.TypeOf((*MockFullNode)(nil).StateMinerSectorAllocated), arg0, arg1, arg2, arg3) +} + +// StateMinerSectorCount mocks base method. +func (m *MockFullNode) StateMinerSectorCount(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (api.MinerSectors, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerSectorCount", arg0, arg1, arg2) + ret0, _ := ret[0].(api.MinerSectors) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerSectorCount indicates an expected call of StateMinerSectorCount. +func (mr *MockFullNodeMockRecorder) StateMinerSectorCount(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerSectorCount", reflect.TypeOf((*MockFullNode)(nil).StateMinerSectorCount), arg0, arg1, arg2) +} + +// StateMinerSectors mocks base method. +func (m *MockFullNode) StateMinerSectors(arg0 context.Context, arg1 address.Address, arg2 *bitfield.BitField, arg3 types.TipSetKey) ([]*miner.SectorOnChainInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateMinerSectors", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].([]*miner.SectorOnChainInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateMinerSectors indicates an expected call of StateMinerSectors. +func (mr *MockFullNodeMockRecorder) StateMinerSectors(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateMinerSectors", reflect.TypeOf((*MockFullNode)(nil).StateMinerSectors), arg0, arg1, arg2, arg3) +} + +// StateNetworkName mocks base method. +func (m *MockFullNode) StateNetworkName(arg0 context.Context) (dtypes.NetworkName, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateNetworkName", arg0) + ret0, _ := ret[0].(dtypes.NetworkName) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateNetworkName indicates an expected call of StateNetworkName. +func (mr *MockFullNodeMockRecorder) StateNetworkName(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateNetworkName", reflect.TypeOf((*MockFullNode)(nil).StateNetworkName), arg0) +} + +// StateNetworkVersion mocks base method. +func (m *MockFullNode) StateNetworkVersion(arg0 context.Context, arg1 types.TipSetKey) (network.Version, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateNetworkVersion", arg0, arg1) + ret0, _ := ret[0].(network.Version) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateNetworkVersion indicates an expected call of StateNetworkVersion. +func (mr *MockFullNodeMockRecorder) StateNetworkVersion(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateNetworkVersion", reflect.TypeOf((*MockFullNode)(nil).StateNetworkVersion), arg0, arg1) +} + +// StateReadState mocks base method. +func (m *MockFullNode) StateReadState(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (*api.ActorState, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateReadState", arg0, arg1, arg2) + ret0, _ := ret[0].(*api.ActorState) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateReadState indicates an expected call of StateReadState. +func (mr *MockFullNodeMockRecorder) StateReadState(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateReadState", reflect.TypeOf((*MockFullNode)(nil).StateReadState), arg0, arg1, arg2) +} + +// StateReplay mocks base method. +func (m *MockFullNode) StateReplay(arg0 context.Context, arg1 types.TipSetKey, arg2 cid.Cid) (*api.InvocResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateReplay", arg0, arg1, arg2) + ret0, _ := ret[0].(*api.InvocResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateReplay indicates an expected call of StateReplay. +func (mr *MockFullNodeMockRecorder) StateReplay(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateReplay", reflect.TypeOf((*MockFullNode)(nil).StateReplay), arg0, arg1, arg2) +} + +// StateSearchMsg mocks base method. +func (m *MockFullNode) StateSearchMsg(arg0 context.Context, arg1 cid.Cid) (*api.MsgLookup, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateSearchMsg", arg0, arg1) + ret0, _ := ret[0].(*api.MsgLookup) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateSearchMsg indicates an expected call of StateSearchMsg. +func (mr *MockFullNodeMockRecorder) StateSearchMsg(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateSearchMsg", reflect.TypeOf((*MockFullNode)(nil).StateSearchMsg), arg0, arg1) +} + +// StateSearchMsgLimited mocks base method. +func (m *MockFullNode) StateSearchMsgLimited(arg0 context.Context, arg1 cid.Cid, arg2 abi.ChainEpoch) (*api.MsgLookup, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateSearchMsgLimited", arg0, arg1, arg2) + ret0, _ := ret[0].(*api.MsgLookup) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateSearchMsgLimited indicates an expected call of StateSearchMsgLimited. +func (mr *MockFullNodeMockRecorder) StateSearchMsgLimited(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateSearchMsgLimited", reflect.TypeOf((*MockFullNode)(nil).StateSearchMsgLimited), arg0, arg1, arg2) +} + +// StateSectorExpiration mocks base method. +func (m *MockFullNode) StateSectorExpiration(arg0 context.Context, arg1 address.Address, arg2 abi.SectorNumber, arg3 types.TipSetKey) (*miner0.SectorExpiration, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateSectorExpiration", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*miner0.SectorExpiration) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateSectorExpiration indicates an expected call of StateSectorExpiration. +func (mr *MockFullNodeMockRecorder) StateSectorExpiration(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateSectorExpiration", reflect.TypeOf((*MockFullNode)(nil).StateSectorExpiration), arg0, arg1, arg2, arg3) +} + +// StateSectorGetInfo mocks base method. +func (m *MockFullNode) StateSectorGetInfo(arg0 context.Context, arg1 address.Address, arg2 abi.SectorNumber, arg3 types.TipSetKey) (*miner.SectorOnChainInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateSectorGetInfo", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*miner.SectorOnChainInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateSectorGetInfo indicates an expected call of StateSectorGetInfo. +func (mr *MockFullNodeMockRecorder) StateSectorGetInfo(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateSectorGetInfo", reflect.TypeOf((*MockFullNode)(nil).StateSectorGetInfo), arg0, arg1, arg2, arg3) +} + +// StateSectorPartition mocks base method. +func (m *MockFullNode) StateSectorPartition(arg0 context.Context, arg1 address.Address, arg2 abi.SectorNumber, arg3 types.TipSetKey) (*miner0.SectorLocation, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateSectorPartition", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*miner0.SectorLocation) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateSectorPartition indicates an expected call of StateSectorPartition. +func (mr *MockFullNodeMockRecorder) StateSectorPartition(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateSectorPartition", reflect.TypeOf((*MockFullNode)(nil).StateSectorPartition), arg0, arg1, arg2, arg3) +} + +// StateSectorPreCommitInfo mocks base method. +func (m *MockFullNode) StateSectorPreCommitInfo(arg0 context.Context, arg1 address.Address, arg2 abi.SectorNumber, arg3 types.TipSetKey) (miner.SectorPreCommitOnChainInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateSectorPreCommitInfo", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(miner.SectorPreCommitOnChainInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateSectorPreCommitInfo indicates an expected call of StateSectorPreCommitInfo. +func (mr *MockFullNodeMockRecorder) StateSectorPreCommitInfo(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateSectorPreCommitInfo", reflect.TypeOf((*MockFullNode)(nil).StateSectorPreCommitInfo), arg0, arg1, arg2, arg3) +} + +// StateVMCirculatingSupplyInternal mocks base method. +func (m *MockFullNode) StateVMCirculatingSupplyInternal(arg0 context.Context, arg1 types.TipSetKey) (api.CirculatingSupply, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateVMCirculatingSupplyInternal", arg0, arg1) + ret0, _ := ret[0].(api.CirculatingSupply) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateVMCirculatingSupplyInternal indicates an expected call of StateVMCirculatingSupplyInternal. +func (mr *MockFullNodeMockRecorder) StateVMCirculatingSupplyInternal(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateVMCirculatingSupplyInternal", reflect.TypeOf((*MockFullNode)(nil).StateVMCirculatingSupplyInternal), arg0, arg1) +} + +// StateVerifiedClientStatus mocks base method. +func (m *MockFullNode) StateVerifiedClientStatus(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (*big.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateVerifiedClientStatus", arg0, arg1, arg2) + ret0, _ := ret[0].(*big.Int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateVerifiedClientStatus indicates an expected call of StateVerifiedClientStatus. +func (mr *MockFullNodeMockRecorder) StateVerifiedClientStatus(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateVerifiedClientStatus", reflect.TypeOf((*MockFullNode)(nil).StateVerifiedClientStatus), arg0, arg1, arg2) +} + +// StateVerifiedRegistryRootKey mocks base method. +func (m *MockFullNode) StateVerifiedRegistryRootKey(arg0 context.Context, arg1 types.TipSetKey) (address.Address, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateVerifiedRegistryRootKey", arg0, arg1) + ret0, _ := ret[0].(address.Address) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateVerifiedRegistryRootKey indicates an expected call of StateVerifiedRegistryRootKey. +func (mr *MockFullNodeMockRecorder) StateVerifiedRegistryRootKey(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateVerifiedRegistryRootKey", reflect.TypeOf((*MockFullNode)(nil).StateVerifiedRegistryRootKey), arg0, arg1) +} + +// StateVerifierStatus mocks base method. +func (m *MockFullNode) StateVerifierStatus(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (*big.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateVerifierStatus", arg0, arg1, arg2) + ret0, _ := ret[0].(*big.Int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateVerifierStatus indicates an expected call of StateVerifierStatus. +func (mr *MockFullNodeMockRecorder) StateVerifierStatus(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateVerifierStatus", reflect.TypeOf((*MockFullNode)(nil).StateVerifierStatus), arg0, arg1, arg2) +} + +// StateWaitMsg mocks base method. +func (m *MockFullNode) StateWaitMsg(arg0 context.Context, arg1 cid.Cid, arg2 uint64) (*api.MsgLookup, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateWaitMsg", arg0, arg1, arg2) + ret0, _ := ret[0].(*api.MsgLookup) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateWaitMsg indicates an expected call of StateWaitMsg. +func (mr *MockFullNodeMockRecorder) StateWaitMsg(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateWaitMsg", reflect.TypeOf((*MockFullNode)(nil).StateWaitMsg), arg0, arg1, arg2) +} + +// StateWaitMsgLimited mocks base method. +func (m *MockFullNode) StateWaitMsgLimited(arg0 context.Context, arg1 cid.Cid, arg2 uint64, arg3 abi.ChainEpoch) (*api.MsgLookup, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateWaitMsgLimited", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*api.MsgLookup) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateWaitMsgLimited indicates an expected call of StateWaitMsgLimited. +func (mr *MockFullNodeMockRecorder) StateWaitMsgLimited(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateWaitMsgLimited", reflect.TypeOf((*MockFullNode)(nil).StateWaitMsgLimited), arg0, arg1, arg2, arg3) +} + +// SyncCheckBad mocks base method. +func (m *MockFullNode) SyncCheckBad(arg0 context.Context, arg1 cid.Cid) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SyncCheckBad", arg0, arg1) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SyncCheckBad indicates an expected call of SyncCheckBad. +func (mr *MockFullNodeMockRecorder) SyncCheckBad(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncCheckBad", reflect.TypeOf((*MockFullNode)(nil).SyncCheckBad), arg0, arg1) +} + +// SyncCheckpoint mocks base method. +func (m *MockFullNode) SyncCheckpoint(arg0 context.Context, arg1 types.TipSetKey) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SyncCheckpoint", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// SyncCheckpoint indicates an expected call of SyncCheckpoint. +func (mr *MockFullNodeMockRecorder) SyncCheckpoint(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncCheckpoint", reflect.TypeOf((*MockFullNode)(nil).SyncCheckpoint), arg0, arg1) +} + +// SyncIncomingBlocks mocks base method. +func (m *MockFullNode) SyncIncomingBlocks(arg0 context.Context) (<-chan *types.BlockHeader, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SyncIncomingBlocks", arg0) + ret0, _ := ret[0].(<-chan *types.BlockHeader) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SyncIncomingBlocks indicates an expected call of SyncIncomingBlocks. +func (mr *MockFullNodeMockRecorder) SyncIncomingBlocks(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncIncomingBlocks", reflect.TypeOf((*MockFullNode)(nil).SyncIncomingBlocks), arg0) +} + +// SyncMarkBad mocks base method. +func (m *MockFullNode) SyncMarkBad(arg0 context.Context, arg1 cid.Cid) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SyncMarkBad", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// SyncMarkBad indicates an expected call of SyncMarkBad. +func (mr *MockFullNodeMockRecorder) SyncMarkBad(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncMarkBad", reflect.TypeOf((*MockFullNode)(nil).SyncMarkBad), arg0, arg1) +} + +// SyncState mocks base method. +func (m *MockFullNode) SyncState(arg0 context.Context) (*api.SyncState, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SyncState", arg0) + ret0, _ := ret[0].(*api.SyncState) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SyncState indicates an expected call of SyncState. +func (mr *MockFullNodeMockRecorder) SyncState(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncState", reflect.TypeOf((*MockFullNode)(nil).SyncState), arg0) +} + +// SyncSubmitBlock mocks base method. +func (m *MockFullNode) SyncSubmitBlock(arg0 context.Context, arg1 *types.BlockMsg) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SyncSubmitBlock", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// SyncSubmitBlock indicates an expected call of SyncSubmitBlock. +func (mr *MockFullNodeMockRecorder) SyncSubmitBlock(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncSubmitBlock", reflect.TypeOf((*MockFullNode)(nil).SyncSubmitBlock), arg0, arg1) +} + +// SyncUnmarkAllBad mocks base method. +func (m *MockFullNode) SyncUnmarkAllBad(arg0 context.Context) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SyncUnmarkAllBad", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// SyncUnmarkAllBad indicates an expected call of SyncUnmarkAllBad. +func (mr *MockFullNodeMockRecorder) SyncUnmarkAllBad(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncUnmarkAllBad", reflect.TypeOf((*MockFullNode)(nil).SyncUnmarkAllBad), arg0) +} + +// SyncUnmarkBad mocks base method. +func (m *MockFullNode) SyncUnmarkBad(arg0 context.Context, arg1 cid.Cid) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SyncUnmarkBad", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// SyncUnmarkBad indicates an expected call of SyncUnmarkBad. +func (mr *MockFullNodeMockRecorder) SyncUnmarkBad(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncUnmarkBad", reflect.TypeOf((*MockFullNode)(nil).SyncUnmarkBad), arg0, arg1) +} + +// SyncValidateTipset mocks base method. +func (m *MockFullNode) SyncValidateTipset(arg0 context.Context, arg1 types.TipSetKey) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SyncValidateTipset", arg0, arg1) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SyncValidateTipset indicates an expected call of SyncValidateTipset. +func (mr *MockFullNodeMockRecorder) SyncValidateTipset(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SyncValidateTipset", reflect.TypeOf((*MockFullNode)(nil).SyncValidateTipset), arg0, arg1) +} + +// Version mocks base method. +func (m *MockFullNode) Version(arg0 context.Context) (api.APIVersion, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Version", arg0) + ret0, _ := ret[0].(api.APIVersion) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Version indicates an expected call of Version. +func (mr *MockFullNodeMockRecorder) Version(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Version", reflect.TypeOf((*MockFullNode)(nil).Version), arg0) +} + +// WalletBalance mocks base method. +func (m *MockFullNode) WalletBalance(arg0 context.Context, arg1 address.Address) (big.Int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WalletBalance", arg0, arg1) + ret0, _ := ret[0].(big.Int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WalletBalance indicates an expected call of WalletBalance. +func (mr *MockFullNodeMockRecorder) WalletBalance(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WalletBalance", reflect.TypeOf((*MockFullNode)(nil).WalletBalance), arg0, arg1) +} + +// WalletDefaultAddress mocks base method. +func (m *MockFullNode) WalletDefaultAddress(arg0 context.Context) (address.Address, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WalletDefaultAddress", arg0) + ret0, _ := ret[0].(address.Address) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WalletDefaultAddress indicates an expected call of WalletDefaultAddress. +func (mr *MockFullNodeMockRecorder) WalletDefaultAddress(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WalletDefaultAddress", reflect.TypeOf((*MockFullNode)(nil).WalletDefaultAddress), arg0) +} + +// WalletDelete mocks base method. +func (m *MockFullNode) WalletDelete(arg0 context.Context, arg1 address.Address) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WalletDelete", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// WalletDelete indicates an expected call of WalletDelete. +func (mr *MockFullNodeMockRecorder) WalletDelete(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WalletDelete", reflect.TypeOf((*MockFullNode)(nil).WalletDelete), arg0, arg1) +} + +// WalletExport mocks base method. +func (m *MockFullNode) WalletExport(arg0 context.Context, arg1 address.Address) (*types.KeyInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WalletExport", arg0, arg1) + ret0, _ := ret[0].(*types.KeyInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WalletExport indicates an expected call of WalletExport. +func (mr *MockFullNodeMockRecorder) WalletExport(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WalletExport", reflect.TypeOf((*MockFullNode)(nil).WalletExport), arg0, arg1) +} + +// WalletHas mocks base method. +func (m *MockFullNode) WalletHas(arg0 context.Context, arg1 address.Address) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WalletHas", arg0, arg1) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WalletHas indicates an expected call of WalletHas. +func (mr *MockFullNodeMockRecorder) WalletHas(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WalletHas", reflect.TypeOf((*MockFullNode)(nil).WalletHas), arg0, arg1) +} + +// WalletImport mocks base method. +func (m *MockFullNode) WalletImport(arg0 context.Context, arg1 *types.KeyInfo) (address.Address, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WalletImport", arg0, arg1) + ret0, _ := ret[0].(address.Address) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WalletImport indicates an expected call of WalletImport. +func (mr *MockFullNodeMockRecorder) WalletImport(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WalletImport", reflect.TypeOf((*MockFullNode)(nil).WalletImport), arg0, arg1) +} + +// WalletList mocks base method. +func (m *MockFullNode) WalletList(arg0 context.Context) ([]address.Address, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WalletList", arg0) + ret0, _ := ret[0].([]address.Address) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WalletList indicates an expected call of WalletList. +func (mr *MockFullNodeMockRecorder) WalletList(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WalletList", reflect.TypeOf((*MockFullNode)(nil).WalletList), arg0) +} + +// WalletNew mocks base method. +func (m *MockFullNode) WalletNew(arg0 context.Context, arg1 types.KeyType) (address.Address, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WalletNew", arg0, arg1) + ret0, _ := ret[0].(address.Address) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WalletNew indicates an expected call of WalletNew. +func (mr *MockFullNodeMockRecorder) WalletNew(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WalletNew", reflect.TypeOf((*MockFullNode)(nil).WalletNew), arg0, arg1) +} + +// WalletSetDefault mocks base method. +func (m *MockFullNode) WalletSetDefault(arg0 context.Context, arg1 address.Address) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WalletSetDefault", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// WalletSetDefault indicates an expected call of WalletSetDefault. +func (mr *MockFullNodeMockRecorder) WalletSetDefault(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WalletSetDefault", reflect.TypeOf((*MockFullNode)(nil).WalletSetDefault), arg0, arg1) +} + +// WalletSign mocks base method. +func (m *MockFullNode) WalletSign(arg0 context.Context, arg1 address.Address, arg2 []byte) (*crypto.Signature, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WalletSign", arg0, arg1, arg2) + ret0, _ := ret[0].(*crypto.Signature) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WalletSign indicates an expected call of WalletSign. +func (mr *MockFullNodeMockRecorder) WalletSign(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WalletSign", reflect.TypeOf((*MockFullNode)(nil).WalletSign), arg0, arg1, arg2) +} + +// WalletSignMessage mocks base method. +func (m *MockFullNode) WalletSignMessage(arg0 context.Context, arg1 address.Address, arg2 *types.Message) (*types.SignedMessage, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WalletSignMessage", arg0, arg1, arg2) + ret0, _ := ret[0].(*types.SignedMessage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WalletSignMessage indicates an expected call of WalletSignMessage. +func (mr *MockFullNodeMockRecorder) WalletSignMessage(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WalletSignMessage", reflect.TypeOf((*MockFullNode)(nil).WalletSignMessage), arg0, arg1, arg2) +} + +// WalletValidateAddress mocks base method. +func (m *MockFullNode) WalletValidateAddress(arg0 context.Context, arg1 string) (address.Address, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WalletValidateAddress", arg0, arg1) + ret0, _ := ret[0].(address.Address) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WalletValidateAddress indicates an expected call of WalletValidateAddress. +func (mr *MockFullNodeMockRecorder) WalletValidateAddress(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WalletValidateAddress", reflect.TypeOf((*MockFullNode)(nil).WalletValidateAddress), arg0, arg1) +} + +// WalletVerify mocks base method. +func (m *MockFullNode) WalletVerify(arg0 context.Context, arg1 address.Address, arg2 []byte, arg3 *crypto.Signature) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WalletVerify", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WalletVerify indicates an expected call of WalletVerify. +func (mr *MockFullNodeMockRecorder) WalletVerify(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WalletVerify", reflect.TypeOf((*MockFullNode)(nil).WalletVerify), arg0, arg1, arg2, arg3) +} diff --git a/api/v0api/v1_wrapper.go b/api/v0api/v1_wrapper.go new file mode 100644 index 000000000..f58b0420f --- /dev/null +++ b/api/v0api/v1_wrapper.go @@ -0,0 +1,369 @@ +package v0api + +import ( + "context" + + "github.com/ipfs/go-cid" + "github.com/libp2p/go-libp2p/core/peer" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-fil-markets/retrievalmarket" + "github.com/filecoin-project/go-fil-markets/storagemarket" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/builtin/v9/miner" + "github.com/filecoin-project/go-state-types/crypto" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/api/v1api" + "github.com/filecoin-project/lotus/chain/types" + marketevents "github.com/filecoin-project/lotus/markets/loggers" +) + +type WrapperV1Full struct { + v1api.FullNode +} + +func (w *WrapperV1Full) StateSectorPreCommitInfo(ctx context.Context, maddr address.Address, s abi.SectorNumber, tsk types.TipSetKey) (miner.SectorPreCommitOnChainInfo, error) { + pi, err := w.FullNode.StateSectorPreCommitInfo(ctx, maddr, s, tsk) + if err != nil { + return miner.SectorPreCommitOnChainInfo{}, err + } + if pi == nil { + return miner.SectorPreCommitOnChainInfo{}, xerrors.Errorf("precommit info does not exist") + } + + return *pi, nil +} + +func (w *WrapperV1Full) StateSearchMsg(ctx context.Context, msg cid.Cid) (*api.MsgLookup, error) { + return w.FullNode.StateSearchMsg(ctx, types.EmptyTSK, msg, api.LookbackNoLimit, true) +} + +func (w *WrapperV1Full) StateSearchMsgLimited(ctx context.Context, msg cid.Cid, limit abi.ChainEpoch) (*api.MsgLookup, error) { + return w.FullNode.StateSearchMsg(ctx, types.EmptyTSK, msg, limit, true) +} + +func (w *WrapperV1Full) StateWaitMsg(ctx context.Context, msg cid.Cid, confidence uint64) (*api.MsgLookup, error) { + return w.FullNode.StateWaitMsg(ctx, msg, confidence, api.LookbackNoLimit, true) +} + +func (w *WrapperV1Full) StateWaitMsgLimited(ctx context.Context, msg cid.Cid, confidence uint64, limit abi.ChainEpoch) (*api.MsgLookup, error) { + return w.FullNode.StateWaitMsg(ctx, msg, confidence, limit, true) +} + +func (w *WrapperV1Full) StateGetReceipt(ctx context.Context, msg cid.Cid, from types.TipSetKey) (*types.MessageReceipt, error) { + ml, err := w.FullNode.StateSearchMsg(ctx, from, msg, api.LookbackNoLimit, true) + if err != nil { + return nil, err + } + + if ml == nil { + return nil, nil + } + + return &ml.Receipt, nil +} + +func (w *WrapperV1Full) Version(ctx context.Context) (api.APIVersion, error) { + ver, err := w.FullNode.Version(ctx) + if err != nil { + return api.APIVersion{}, err + } + + ver.APIVersion = api.FullAPIVersion0 + + return ver, nil +} + +func (w *WrapperV1Full) executePrototype(ctx context.Context, p *api.MessagePrototype) (cid.Cid, error) { + sm, err := w.FullNode.MpoolPushMessage(ctx, &p.Message, nil) + if err != nil { + return cid.Undef, xerrors.Errorf("pushing message: %w", err) + } + + return sm.Cid(), nil +} +func (w *WrapperV1Full) MsigCreate(ctx context.Context, req uint64, addrs []address.Address, duration abi.ChainEpoch, val types.BigInt, src address.Address, gp types.BigInt) (cid.Cid, error) { + + p, err := w.FullNode.MsigCreate(ctx, req, addrs, duration, val, src, gp) + if err != nil { + return cid.Undef, xerrors.Errorf("creating prototype: %w", err) + } + + return w.executePrototype(ctx, p) +} + +func (w *WrapperV1Full) MsigPropose(ctx context.Context, msig address.Address, to address.Address, amt types.BigInt, src address.Address, method uint64, params []byte) (cid.Cid, error) { + + p, err := w.FullNode.MsigPropose(ctx, msig, to, amt, src, method, params) + if err != nil { + return cid.Undef, xerrors.Errorf("creating prototype: %w", err) + } + + return w.executePrototype(ctx, p) +} +func (w *WrapperV1Full) MsigApprove(ctx context.Context, msig address.Address, txID uint64, src address.Address) (cid.Cid, error) { + + p, err := w.FullNode.MsigApprove(ctx, msig, txID, src) + if err != nil { + return cid.Undef, xerrors.Errorf("creating prototype: %w", err) + } + + return w.executePrototype(ctx, p) +} + +func (w *WrapperV1Full) MsigApproveTxnHash(ctx context.Context, msig address.Address, txID uint64, proposer address.Address, to address.Address, amt types.BigInt, src address.Address, method uint64, params []byte) (cid.Cid, error) { + p, err := w.FullNode.MsigApproveTxnHash(ctx, msig, txID, proposer, to, amt, src, method, params) + if err != nil { + return cid.Undef, xerrors.Errorf("creating prototype: %w", err) + } + + return w.executePrototype(ctx, p) +} + +func (w *WrapperV1Full) MsigCancel(ctx context.Context, msig address.Address, txID uint64, to address.Address, amt types.BigInt, src address.Address, method uint64, params []byte) (cid.Cid, error) { + p, err := w.FullNode.MsigCancelTxnHash(ctx, msig, txID, to, amt, src, method, params) + if err != nil { + return cid.Undef, xerrors.Errorf("creating prototype: %w", err) + } + + return w.executePrototype(ctx, p) +} + +func (w *WrapperV1Full) MsigAddPropose(ctx context.Context, msig address.Address, src address.Address, newAdd address.Address, inc bool) (cid.Cid, error) { + + p, err := w.FullNode.MsigAddPropose(ctx, msig, src, newAdd, inc) + if err != nil { + return cid.Undef, xerrors.Errorf("creating prototype: %w", err) + } + + return w.executePrototype(ctx, p) +} + +func (w *WrapperV1Full) MsigAddApprove(ctx context.Context, msig address.Address, src address.Address, txID uint64, proposer address.Address, newAdd address.Address, inc bool) (cid.Cid, error) { + + p, err := w.FullNode.MsigAddApprove(ctx, msig, src, txID, proposer, newAdd, inc) + if err != nil { + return cid.Undef, xerrors.Errorf("creating prototype: %w", err) + } + + return w.executePrototype(ctx, p) +} + +func (w *WrapperV1Full) MsigAddCancel(ctx context.Context, msig address.Address, src address.Address, txID uint64, newAdd address.Address, inc bool) (cid.Cid, error) { + + p, err := w.FullNode.MsigAddCancel(ctx, msig, src, txID, newAdd, inc) + if err != nil { + return cid.Undef, xerrors.Errorf("creating prototype: %w", err) + } + + return w.executePrototype(ctx, p) +} + +func (w *WrapperV1Full) MsigSwapPropose(ctx context.Context, msig address.Address, src address.Address, oldAdd address.Address, newAdd address.Address) (cid.Cid, error) { + + p, err := w.FullNode.MsigSwapPropose(ctx, msig, src, oldAdd, newAdd) + if err != nil { + return cid.Undef, xerrors.Errorf("creating prototype: %w", err) + } + + return w.executePrototype(ctx, p) +} + +func (w *WrapperV1Full) MsigSwapApprove(ctx context.Context, msig address.Address, src address.Address, txID uint64, proposer address.Address, oldAdd address.Address, newAdd address.Address) (cid.Cid, error) { + + p, err := w.FullNode.MsigSwapApprove(ctx, msig, src, txID, proposer, oldAdd, newAdd) + if err != nil { + return cid.Undef, xerrors.Errorf("creating prototype: %w", err) + } + + return w.executePrototype(ctx, p) +} + +func (w *WrapperV1Full) MsigSwapCancel(ctx context.Context, msig address.Address, src address.Address, txID uint64, oldAdd address.Address, newAdd address.Address) (cid.Cid, error) { + + p, err := w.FullNode.MsigSwapCancel(ctx, msig, src, txID, oldAdd, newAdd) + if err != nil { + return cid.Undef, xerrors.Errorf("creating prototype: %w", err) + } + + return w.executePrototype(ctx, p) +} + +func (w *WrapperV1Full) MsigRemoveSigner(ctx context.Context, msig address.Address, proposer address.Address, toRemove address.Address, decrease bool) (cid.Cid, error) { + + p, err := w.FullNode.MsigRemoveSigner(ctx, msig, proposer, toRemove, decrease) + if err != nil { + return cid.Undef, xerrors.Errorf("creating prototype: %w", err) + } + + return w.executePrototype(ctx, p) +} + +func (w *WrapperV1Full) ChainGetRandomnessFromTickets(ctx context.Context, tsk types.TipSetKey, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) (abi.Randomness, error) { + return w.StateGetRandomnessFromTickets(ctx, personalization, randEpoch, entropy, tsk) +} + +func (w *WrapperV1Full) ChainGetRandomnessFromBeacon(ctx context.Context, tsk types.TipSetKey, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) (abi.Randomness, error) { + return w.StateGetRandomnessFromBeacon(ctx, personalization, randEpoch, entropy, tsk) +} + +func (w *WrapperV1Full) ClientRetrieve(ctx context.Context, order RetrievalOrder, ref *api.FileRef) error { + events := make(chan marketevents.RetrievalEvent) + go w.clientRetrieve(ctx, order, ref, events) + + for { + select { + case evt, ok := <-events: + if !ok { // done successfully + return nil + } + + if evt.Err != "" { + return xerrors.Errorf("retrieval failed: %s", evt.Err) + } + case <-ctx.Done(): + return xerrors.Errorf("retrieval timed out") + } + } +} + +func (w *WrapperV1Full) ClientRetrieveWithEvents(ctx context.Context, order RetrievalOrder, ref *api.FileRef) (<-chan marketevents.RetrievalEvent, error) { + events := make(chan marketevents.RetrievalEvent) + go w.clientRetrieve(ctx, order, ref, events) + return events, nil +} + +func readSubscribeEvents(ctx context.Context, dealID retrievalmarket.DealID, subscribeEvents <-chan api.RetrievalInfo, events chan marketevents.RetrievalEvent) error { + for { + var subscribeEvent api.RetrievalInfo + var evt retrievalmarket.ClientEvent + select { + case <-ctx.Done(): + return xerrors.New("Retrieval Timed Out") + case subscribeEvent = <-subscribeEvents: + if subscribeEvent.ID != dealID { + // we can't check the deal ID ahead of time because: + // 1. We need to subscribe before retrieving. + // 2. We won't know the deal ID until after retrieving. + continue + } + if subscribeEvent.Event != nil { + evt = *subscribeEvent.Event + } + } + + select { + case <-ctx.Done(): + return xerrors.New("Retrieval Timed Out") + case events <- marketevents.RetrievalEvent{ + Event: evt, + Status: subscribeEvent.Status, + BytesReceived: subscribeEvent.BytesReceived, + FundsSpent: subscribeEvent.TotalPaid, + }: + } + + switch subscribeEvent.Status { + case retrievalmarket.DealStatusCompleted: + return nil + case retrievalmarket.DealStatusRejected: + return xerrors.Errorf("Retrieval Proposal Rejected: %s", subscribeEvent.Message) + case + retrievalmarket.DealStatusDealNotFound, + retrievalmarket.DealStatusErrored: + return xerrors.Errorf("Retrieval Error: %s", subscribeEvent.Message) + } + } +} + +func (w *WrapperV1Full) clientRetrieve(ctx context.Context, order RetrievalOrder, ref *api.FileRef, events chan marketevents.RetrievalEvent) { + defer close(events) + + finish := func(e error) { + if e != nil { + events <- marketevents.RetrievalEvent{Err: e.Error(), FundsSpent: big.Zero()} + } + } + + var dealID retrievalmarket.DealID + if order.FromLocalCAR == "" { + // Subscribe to events before retrieving to avoid losing events. + subscribeCtx, cancel := context.WithCancel(ctx) + defer cancel() + retrievalEvents, err := w.ClientGetRetrievalUpdates(subscribeCtx) + + if err != nil { + finish(xerrors.Errorf("GetRetrievalUpdates failed: %w", err)) + return + } + + retrievalRes, err := w.FullNode.ClientRetrieve(ctx, api.RetrievalOrder{ + Root: order.Root, + Piece: order.Piece, + Size: order.Size, + Total: order.Total, + UnsealPrice: order.UnsealPrice, + PaymentInterval: order.PaymentInterval, + PaymentIntervalIncrease: order.PaymentIntervalIncrease, + Client: order.Client, + Miner: order.Miner, + MinerPeer: order.MinerPeer, + }) + + if err != nil { + finish(xerrors.Errorf("Retrieve failed: %w", err)) + return + } + + dealID = retrievalRes.DealID + + err = readSubscribeEvents(ctx, retrievalRes.DealID, retrievalEvents, events) + if err != nil { + finish(xerrors.Errorf("Retrieve: %w", err)) + return + } + } + + // If ref is nil, it only fetches the data into the configured blockstore. + if ref == nil { + finish(nil) + return + } + + eref := api.ExportRef{ + Root: order.Root, + FromLocalCAR: order.FromLocalCAR, + DealID: dealID, + } + + if order.DatamodelPathSelector != nil { + s := api.Selector(*order.DatamodelPathSelector) + eref.DAGs = append(eref.DAGs, api.DagSpec{ + DataSelector: &s, + ExportMerkleProof: true, + }) + } + + finish(w.ClientExport(ctx, eref, *ref)) +} + +func (w *WrapperV1Full) PaychGet(ctx context.Context, from, to address.Address, amt types.BigInt) (*api.ChannelInfo, error) { + return w.FullNode.PaychFund(ctx, from, to, amt) +} + +func (w *WrapperV1Full) ClientQueryAsk(ctx context.Context, p peer.ID, miner address.Address) (*storagemarket.StorageAsk, error) { + a, err := w.FullNode.ClientQueryAsk(ctx, p, miner) + if err != nil { + return nil, err + } + return a.Response, nil +} + +func (w *WrapperV1Full) BeaconGetEntry(ctx context.Context, epoch abi.ChainEpoch) (*types.BeaconEntry, error) { + return w.StateGetBeaconEntry(ctx, epoch) +} + +var _ FullNode = &WrapperV1Full{} diff --git a/api/v1api/latest.go b/api/v1api/latest.go new file mode 100644 index 000000000..aefb1543b --- /dev/null +++ b/api/v1api/latest.go @@ -0,0 +1,14 @@ +package v1api + +import ( + "github.com/filecoin-project/lotus/api" +) + +type FullNode = api.FullNode +type FullNodeStruct = api.FullNodeStruct + +type RawFullNodeAPI FullNode + +func PermissionedFullAPI(a FullNode) FullNode { + return api.PermissionedFullAPI(a) +} diff --git a/api/version.go b/api/version.go new file mode 100644 index 000000000..9c2113578 --- /dev/null +++ b/api/version.go @@ -0,0 +1,73 @@ +package api + +import ( + "fmt" + + "golang.org/x/xerrors" +) + +type Version uint32 + +func newVer(major, minor, patch uint8) Version { + return Version(uint32(major)<<16 | uint32(minor)<<8 | uint32(patch)) +} + +// Ints returns (major, minor, patch) versions +func (ve Version) Ints() (uint32, uint32, uint32) { + v := uint32(ve) + return (v & majorOnlyMask) >> 16, (v & minorOnlyMask) >> 8, v & patchOnlyMask +} + +func (ve Version) String() string { + vmj, vmi, vp := ve.Ints() + return fmt.Sprintf("%d.%d.%d", vmj, vmi, vp) +} + +func (ve Version) EqMajorMinor(v2 Version) bool { + return ve&minorMask == v2&minorMask +} + +type NodeType int + +const ( + NodeUnknown NodeType = iota + + NodeFull + NodeMiner + NodeWorker +) + +var RunningNodeType NodeType + +func VersionForType(nodeType NodeType) (Version, error) { + switch nodeType { + case NodeFull: + return FullAPIVersion1, nil + case NodeMiner: + return MinerAPIVersion0, nil + case NodeWorker: + return WorkerAPIVersion0, nil + default: + return Version(0), xerrors.Errorf("unknown node type %d", nodeType) + } +} + +// semver versions of the rpc api exposed +var ( + FullAPIVersion0 = newVer(1, 5, 0) + FullAPIVersion1 = newVer(2, 3, 0) + + MinerAPIVersion0 = newVer(1, 5, 0) + WorkerAPIVersion0 = newVer(1, 7, 0) +) + +//nolint:varcheck,deadcode +const ( + majorMask = 0xff0000 + minorMask = 0xffff00 + patchMask = 0xffffff + + majorOnlyMask = 0xff0000 + minorOnlyMask = 0x00ff00 + patchOnlyMask = 0x0000ff +) diff --git a/api/wrap.go b/api/wrap.go new file mode 100644 index 000000000..b26489a42 --- /dev/null +++ b/api/wrap.go @@ -0,0 +1,53 @@ +package api + +import ( + "reflect" +) + +// Wrap adapts partial api impl to another version +// proxyT is the proxy type used as input in wrapperT +// Usage: Wrap(new(v1api.FullNodeStruct), new(v0api.WrapperV1Full), eventsApi).(EventAPI) +func Wrap(proxyT, wrapperT, impl interface{}) interface{} { + proxy := reflect.New(reflect.TypeOf(proxyT).Elem()) + proxyMethods := proxy.Elem().FieldByName("Internal") + ri := reflect.ValueOf(impl) + + for i := 0; i < ri.NumMethod(); i++ { + mt := ri.Type().Method(i) + if proxyMethods.FieldByName(mt.Name).Kind() == reflect.Invalid { + continue + } + + fn := ri.Method(i) + of := proxyMethods.FieldByName(mt.Name) + + proxyMethods.FieldByName(mt.Name).Set(reflect.MakeFunc(of.Type(), func(args []reflect.Value) (results []reflect.Value) { + return fn.Call(args) + })) + } + + 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/blockstore/api.go b/blockstore/api.go new file mode 100644 index 000000000..090f53e5a --- /dev/null +++ b/blockstore/api.go @@ -0,0 +1,73 @@ +package blockstore + +import ( + "context" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" +) + +type ChainIO interface { + ChainReadObj(context.Context, cid.Cid) ([]byte, error) + ChainHasObj(context.Context, cid.Cid) (bool, error) + ChainPutObj(context.Context, blocks.Block) error +} + +type apiBlockstore struct { + api ChainIO +} + +// This blockstore is adapted in the constructor. +var _ BasicBlockstore = (*apiBlockstore)(nil) + +func NewAPIBlockstore(cio ChainIO) Blockstore { + bs := &apiBlockstore{api: cio} + return Adapt(bs) // return an adapted blockstore. +} + +func (a *apiBlockstore) DeleteBlock(context.Context, cid.Cid) error { + return xerrors.New("not supported") +} + +func (a *apiBlockstore) Has(ctx context.Context, c cid.Cid) (bool, error) { + return a.api.ChainHasObj(ctx, c) +} + +func (a *apiBlockstore) Get(ctx context.Context, c cid.Cid) (blocks.Block, error) { + bb, err := a.api.ChainReadObj(ctx, c) + if err != nil { + return nil, err + } + return blocks.NewBlockWithCid(bb, c) +} + +func (a *apiBlockstore) GetSize(ctx context.Context, c cid.Cid) (int, error) { + bb, err := a.api.ChainReadObj(ctx, c) + if err != nil { + return 0, err + } + return len(bb), nil +} + +func (a *apiBlockstore) Put(ctx context.Context, block blocks.Block) error { + return a.api.ChainPutObj(ctx, block) +} + +func (a *apiBlockstore) PutMany(ctx context.Context, blocks []blocks.Block) error { + for _, block := range blocks { + err := a.api.ChainPutObj(ctx, block) + if err != nil { + return err + } + } + return nil +} + +func (a *apiBlockstore) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { + return nil, xerrors.New("not supported") +} + +func (a *apiBlockstore) HashOnRead(enabled bool) { + return +} diff --git a/blockstore/autobatch.go b/blockstore/autobatch.go new file mode 100644 index 000000000..d41d521ef --- /dev/null +++ b/blockstore/autobatch.go @@ -0,0 +1,266 @@ +package blockstore + +import ( + "context" + "sync" + "time" + + block "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + ipld "github.com/ipfs/go-ipld-format" + "golang.org/x/xerrors" +) + +// autolog is a logger for the autobatching blockstore. It is subscoped from the +// blockstore logger. +var autolog = log.Named("auto") + +// contains the same set of blocks twice, once as an ordered list for flushing, and as a map for fast access +type blockBatch struct { + blockList []block.Block + blockMap map[cid.Cid]block.Block +} + +type AutobatchBlockstore struct { + // TODO: drop if memory consumption is too high + addedCids map[cid.Cid]struct{} + + stateLock sync.Mutex + bufferedBatch blockBatch + + flushingBatch blockBatch + flushErr error + + flushCh chan struct{} + + doFlushLock sync.Mutex + flushRetryDelay time.Duration + doneCh chan struct{} + shutdown context.CancelFunc + + backingBs Blockstore + + bufferCapacity int + bufferSize int +} + +func NewAutobatch(ctx context.Context, backingBs Blockstore, bufferCapacity int) *AutobatchBlockstore { + ctx, cancel := context.WithCancel(ctx) + bs := &AutobatchBlockstore{ + addedCids: make(map[cid.Cid]struct{}), + backingBs: backingBs, + bufferCapacity: bufferCapacity, + flushCh: make(chan struct{}, 1), + doneCh: make(chan struct{}), + // could be made configable + flushRetryDelay: time.Millisecond * 100, + shutdown: cancel, + } + + bs.bufferedBatch.blockMap = make(map[cid.Cid]block.Block) + + go bs.flushWorker(ctx) + + return bs +} + +func (bs *AutobatchBlockstore) Put(ctx context.Context, blk block.Block) error { + bs.stateLock.Lock() + defer bs.stateLock.Unlock() + + _, ok := bs.addedCids[blk.Cid()] + if !ok { + bs.addedCids[blk.Cid()] = struct{}{} + bs.bufferedBatch.blockList = append(bs.bufferedBatch.blockList, blk) + bs.bufferedBatch.blockMap[blk.Cid()] = blk + bs.bufferSize += len(blk.RawData()) + if bs.bufferSize >= bs.bufferCapacity { + // signal that a flush is appropriate, may be ignored + select { + case bs.flushCh <- struct{}{}: + default: + // do nothing + } + } + } + + return nil +} + +func (bs *AutobatchBlockstore) flushWorker(ctx context.Context) { + defer close(bs.doneCh) + for { + select { + case <-bs.flushCh: + // TODO: check if we _should_ actually flush. We could get a spurious wakeup + // here. + putErr := bs.doFlush(ctx, false) + for putErr != nil { + select { + case <-ctx.Done(): + return + case <-time.After(bs.flushRetryDelay): + autolog.Errorf("FLUSH ERRORED: %w, retrying after %v", putErr, bs.flushRetryDelay) + putErr = bs.doFlush(ctx, true) + } + } + case <-ctx.Done(): + // Do one last flush. + _ = bs.doFlush(ctx, false) + return + } + } +} + +// caller must NOT hold stateLock +// set retryOnly to true to only retry a failed flush and not flush anything new. +func (bs *AutobatchBlockstore) doFlush(ctx context.Context, retryOnly bool) error { + bs.doFlushLock.Lock() + defer bs.doFlushLock.Unlock() + + // If we failed to flush last time, try flushing again. + if bs.flushErr != nil { + bs.flushErr = bs.backingBs.PutMany(ctx, bs.flushingBatch.blockList) + } + + // If we failed, or we're _only_ retrying, bail. + if retryOnly || bs.flushErr != nil { + return bs.flushErr + } + + // Then take the current batch... + bs.stateLock.Lock() + // We do NOT clear addedCids here, because its purpose is to expedite Puts + bs.flushingBatch = bs.bufferedBatch + bs.bufferedBatch.blockList = make([]block.Block, 0, len(bs.flushingBatch.blockList)) + bs.bufferedBatch.blockMap = make(map[cid.Cid]block.Block, len(bs.flushingBatch.blockMap)) + bs.stateLock.Unlock() + + // And try to flush it. + bs.flushErr = bs.backingBs.PutMany(ctx, bs.flushingBatch.blockList) + + // If we succeeded, reset the batch. Otherwise, we'll try again next time. + if bs.flushErr == nil { + bs.stateLock.Lock() + bs.flushingBatch = blockBatch{} + bs.stateLock.Unlock() + } + + return bs.flushErr +} + +// caller must NOT hold stateLock +func (bs *AutobatchBlockstore) Flush(ctx context.Context) error { + return bs.doFlush(ctx, false) +} + +func (bs *AutobatchBlockstore) Shutdown(ctx context.Context) error { + // TODO: Prevent puts after we call this to avoid losing data. + bs.shutdown() + select { + case <-bs.doneCh: + case <-ctx.Done(): + return ctx.Err() + } + + bs.doFlushLock.Lock() + defer bs.doFlushLock.Unlock() + + return bs.flushErr +} + +func (bs *AutobatchBlockstore) Get(ctx context.Context, c cid.Cid) (block.Block, error) { + // may seem backward to check the backingBs first, but that is the likeliest case + blk, err := bs.backingBs.Get(ctx, c) + if err == nil { + return blk, nil + } + + if !ipld.IsNotFound(err) { + return blk, err + } + + bs.stateLock.Lock() + v, ok := bs.flushingBatch.blockMap[c] + if ok { + bs.stateLock.Unlock() + return v, nil + } + + v, ok = bs.bufferedBatch.blockMap[c] + if ok { + bs.stateLock.Unlock() + return v, nil + } + bs.stateLock.Unlock() + + // We have to check the backing store one more time because it may have been flushed by the + // time we were able to take the lock above. + return bs.backingBs.Get(ctx, c) +} + +func (bs *AutobatchBlockstore) DeleteBlock(context.Context, cid.Cid) error { + // if we wanted to support this, we would have to: + // - flush + // - delete from the backingBs (if present) + // - remove from addedCids (if present) + // - if present in addedCids, also walk the ordered lists and remove if present + return xerrors.New("deletion is unsupported") +} + +func (bs *AutobatchBlockstore) DeleteMany(ctx context.Context, cids []cid.Cid) error { + // see note in DeleteBlock() + return xerrors.New("deletion is unsupported") +} + +func (bs *AutobatchBlockstore) Has(ctx context.Context, c cid.Cid) (bool, error) { + _, err := bs.Get(ctx, c) + if err == nil { + return true, nil + } + if ipld.IsNotFound(err) { + return false, nil + } + + return false, err +} + +func (bs *AutobatchBlockstore) GetSize(ctx context.Context, c cid.Cid) (int, error) { + blk, err := bs.Get(ctx, c) + if err != nil { + return 0, err + } + + return len(blk.RawData()), nil +} + +func (bs *AutobatchBlockstore) PutMany(ctx context.Context, blks []block.Block) error { + for _, blk := range blks { + if err := bs.Put(ctx, blk); err != nil { + return err + } + } + + return nil +} + +func (bs *AutobatchBlockstore) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { + if err := bs.Flush(ctx); err != nil { + return nil, err + } + + return bs.backingBs.AllKeysChan(ctx) +} + +func (bs *AutobatchBlockstore) HashOnRead(enabled bool) { + bs.backingBs.HashOnRead(enabled) +} + +func (bs *AutobatchBlockstore) View(ctx context.Context, cid cid.Cid, callback func([]byte) error) error { + blk, err := bs.Get(ctx, cid) + if err != nil { + return err + } + + return callback(blk.RawData()) +} diff --git a/blockstore/autobatch_test.go b/blockstore/autobatch_test.go new file mode 100644 index 000000000..495c6c4db --- /dev/null +++ b/blockstore/autobatch_test.go @@ -0,0 +1,39 @@ +package blockstore + +import ( + "context" + "testing" + + ipld "github.com/ipfs/go-ipld-format" + "github.com/stretchr/testify/require" +) + +func TestAutobatchBlockstore(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + ab := NewAutobatch(ctx, NewMemory(), len(b0.RawData())+len(b1.RawData())-1) + + require.NoError(t, ab.Put(ctx, b0)) + require.NoError(t, ab.Put(ctx, b1)) + require.NoError(t, ab.Put(ctx, b2)) + + v0, err := ab.Get(ctx, b0.Cid()) + require.NoError(t, err) + require.Equal(t, b0.RawData(), v0.RawData()) + + v1, err := ab.Get(ctx, b1.Cid()) + require.NoError(t, err) + require.Equal(t, b1.RawData(), v1.RawData()) + + v2, err := ab.Get(ctx, b2.Cid()) + require.NoError(t, err) + require.Equal(t, b2.RawData(), v2.RawData()) + + // Regression test for a deadlock. + _, err = ab.Get(ctx, b3.Cid()) + require.True(t, ipld.IsNotFound(err)) + + require.NoError(t, ab.Flush(ctx)) + require.NoError(t, ab.Shutdown(ctx)) +} diff --git a/blockstore/badger/blockstore.go b/blockstore/badger/blockstore.go new file mode 100644 index 000000000..a49e88f85 --- /dev/null +++ b/blockstore/badger/blockstore.go @@ -0,0 +1,1099 @@ +package badgerbs + +import ( + "context" + "fmt" + "io" + "os" + "path/filepath" + "runtime" + "sync" + "time" + + "github.com/dgraph-io/badger/v2" + "github.com/dgraph-io/badger/v2/options" + "github.com/dgraph-io/badger/v2/pb" + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + ipld "github.com/ipfs/go-ipld-format" + logger "github.com/ipfs/go-log/v2" + pool "github.com/libp2p/go-buffer-pool" + "github.com/multiformats/go-base32" + "go.uber.org/zap" + "golang.org/x/xerrors" + + "github.com/filecoin-project/lotus/blockstore" +) + +var ( + // KeyPool is the buffer pool we use to compute storage keys. + KeyPool *pool.BufferPool = pool.GlobalPool +) + +var ( + // ErrBlockstoreClosed is returned from blockstore operations after + // the blockstore has been closed. + ErrBlockstoreClosed = fmt.Errorf("badger blockstore closed") + + log = logger.Logger("badgerbs") +) + +// aliases to mask badger dependencies. +const ( + // FileIO is equivalent to badger/options.FileIO. + FileIO = options.FileIO + // MemoryMap is equivalent to badger/options.MemoryMap. + MemoryMap = options.MemoryMap + // LoadToRAM is equivalent to badger/options.LoadToRAM. + LoadToRAM = options.LoadToRAM + defaultGCThreshold = 0.125 +) + +// Options embeds the badger options themselves, and augments them with +// blockstore-specific options. +type Options struct { + badger.Options + + // Prefix is an optional prefix to prepend to keys. Default: "". + Prefix string +} + +func DefaultOptions(path string) Options { + return Options{ + Options: badger.DefaultOptions(path), + Prefix: "", + } +} + +// badgerLogger is a local wrapper for go-log to make the interface +// compatible with badger.Logger (namely, aliasing Warnf to Warningf) +type badgerLogger struct { + *zap.SugaredLogger // skips 1 caller to get useful line info, skipping over badger.Options. + + skip2 *zap.SugaredLogger // skips 2 callers, just like above + this logger. +} + +// Warningf is required by the badger logger APIs. +func (b *badgerLogger) Warningf(format string, args ...interface{}) { + b.skip2.Warnf(format, args...) +} + +// bsState is the current blockstore state +type bsState int + +const ( + // stateOpen signifies an open blockstore + stateOpen bsState = iota + // stateClosing signifies a blockstore that is currently closing + stateClosing + // stateClosed signifies a blockstore that has been colosed + stateClosed +) + +type bsMoveState int + +const ( + // moveStateNone signifies that there is no move in progress + moveStateNone bsMoveState = iota + // moveStateMoving signifies that there is a move in a progress + moveStateMoving + // moveStateCleanup signifies that a move has completed or aborted and we are cleaning up + moveStateCleanup + // moveStateLock signifies that an exclusive lock has been acquired + moveStateLock +) + +// Blockstore is a badger-backed IPLD blockstore. +type Blockstore struct { + stateLk sync.RWMutex + state bsState + viewers sync.WaitGroup + + moveMx sync.Mutex + moveCond sync.Cond + moveState bsMoveState + rlock int + + db *badger.DB + dbNext *badger.DB // when moving + opts Options + + prefixing bool + prefix []byte + prefixLen int +} + +var _ blockstore.Blockstore = (*Blockstore)(nil) +var _ blockstore.Viewer = (*Blockstore)(nil) +var _ blockstore.BlockstoreIterator = (*Blockstore)(nil) +var _ blockstore.BlockstoreGC = (*Blockstore)(nil) +var _ blockstore.BlockstoreSize = (*Blockstore)(nil) +var _ io.Closer = (*Blockstore)(nil) + +// Open creates a new badger-backed blockstore, with the supplied options. +func Open(opts Options) (*Blockstore, error) { + opts.Logger = &badgerLogger{ + SugaredLogger: log.Desugar().WithOptions(zap.AddCallerSkip(1)).Sugar(), + skip2: log.Desugar().WithOptions(zap.AddCallerSkip(2)).Sugar(), + } + + db, err := badger.Open(opts.Options) + if err != nil { + return nil, fmt.Errorf("failed to open badger blockstore: %w", err) + } + + bs := &Blockstore{db: db, opts: opts} + if p := opts.Prefix; p != "" { + bs.prefixing = true + bs.prefix = []byte(p) + bs.prefixLen = len(bs.prefix) + } + + bs.moveCond.L = &bs.moveMx + + return bs, nil +} + +// Close closes the store. If the store has already been closed, this noops and +// returns an error, even if the first closure resulted in error. +func (b *Blockstore) Close() error { + b.stateLk.Lock() + if b.state != stateOpen { + b.stateLk.Unlock() + return nil + } + b.state = stateClosing + b.stateLk.Unlock() + + defer func() { + b.stateLk.Lock() + b.state = stateClosed + b.stateLk.Unlock() + }() + + // wait for all accesses to complete + b.viewers.Wait() + + return b.db.Close() +} + +func (b *Blockstore) access() error { + b.stateLk.RLock() + defer b.stateLk.RUnlock() + + if b.state != stateOpen { + return ErrBlockstoreClosed + } + + b.viewers.Add(1) + return nil +} + +func (b *Blockstore) isOpen() bool { + b.stateLk.RLock() + defer b.stateLk.RUnlock() + + return b.state == stateOpen +} + +// lockDB/unlockDB implement a recursive lock contingent on move state +func (b *Blockstore) lockDB() { + b.moveMx.Lock() + defer b.moveMx.Unlock() + + if b.rlock == 0 { + for b.moveState == moveStateLock { + b.moveCond.Wait() + } + } + + b.rlock++ +} + +func (b *Blockstore) unlockDB() { + b.moveMx.Lock() + defer b.moveMx.Unlock() + + b.rlock-- + if b.rlock == 0 && b.moveState == moveStateLock { + b.moveCond.Broadcast() + } +} + +// lockMove/unlockMove implement an exclusive lock of move state +func (b *Blockstore) lockMove() { + b.moveMx.Lock() + b.moveState = moveStateLock + for b.rlock > 0 { + b.moveCond.Wait() + } +} + +func (b *Blockstore) unlockMove(state bsMoveState) { + b.moveState = state + b.moveCond.Broadcast() + b.moveMx.Unlock() +} + +// movingGC moves the blockstore to a new path, adjacent to the current path, and creates +// a symlink from the current path to the new path; the old blockstore is deleted. +// +// The blockstore MUST accept new writes during the move and ensure that these +// are persisted to the new blockstore; if a failure occurs aboring the move, +// then they must be peristed to the old blockstore. +// In short, the blockstore must not lose data from new writes during the move. +func (b *Blockstore) movingGC() error { + // this inlines moveLock/moveUnlock for the initial state check to prevent a second move + // while one is in progress without clobbering state + b.moveMx.Lock() + if b.moveState != moveStateNone { + b.moveMx.Unlock() + return fmt.Errorf("move in progress") + } + + b.moveState = moveStateLock + for b.rlock > 0 { + b.moveCond.Wait() + } + + b.moveState = moveStateMoving + b.moveCond.Broadcast() + b.moveMx.Unlock() + + var newPath string + + defer func() { + b.lockMove() + + dbNext := b.dbNext + b.dbNext = nil + + var state bsMoveState + if dbNext != nil { + state = moveStateCleanup + } else { + state = moveStateNone + } + + b.unlockMove(state) + + if dbNext != nil { + // the move failed and we have a left-over db; delete it. + err := dbNext.Close() + if err != nil { + log.Warnf("error closing badger db: %s", err) + } + b.deleteDB(newPath) + + b.lockMove() + b.unlockMove(moveStateNone) + } + }() + + // we resolve symlinks to create the new path in the adjacent to the old path. + // this allows the user to symlink the db directory into a separate filesystem. + basePath := b.opts.Dir + linkPath, err := filepath.EvalSymlinks(basePath) + if err != nil { + return fmt.Errorf("error resolving symlink %s: %w", basePath, err) + } + + if basePath == linkPath { + newPath = basePath + } else { + // we do this dance to create a name adjacent to the current one, while avoiding clown + // shoes with multiple moves (i.e. we can't just take the basename of the linkPath, as it + // could have been created in a previous move and have the timestamp suffix, which would then + // perpetuate itself. + name := filepath.Base(basePath) + dir := filepath.Dir(linkPath) + newPath = filepath.Join(dir, name) + } + newPath = fmt.Sprintf("%s.%d", newPath, time.Now().UnixNano()) + + log.Infof("moving blockstore from %s to %s", b.opts.Dir, newPath) + + opts := b.opts + opts.Dir = newPath + opts.ValueDir = newPath + + dbNew, err := badger.Open(opts.Options) + if err != nil { + return fmt.Errorf("failed to open badger blockstore in %s: %w", newPath, err) + } + + b.lockMove() + b.dbNext = dbNew + b.unlockMove(moveStateMoving) + + log.Info("copying blockstore") + err = b.doCopy(b.db, b.dbNext) + if err != nil { + return fmt.Errorf("error moving badger blockstore to %s: %w", newPath, err) + } + + b.lockMove() + dbOld := b.db + b.db = b.dbNext + b.dbNext = nil + b.unlockMove(moveStateCleanup) + + err = dbOld.Close() + if err != nil { + log.Warnf("error closing old badger db: %s", err) + } + + // this is the canonical db path; this is where our db lives. + dbPath := b.opts.Dir + + // we first move the existing db out of the way, and only delete it after we have symlinked the + // new db to the canonical path + backupPath := fmt.Sprintf("%s.old.%d", dbPath, time.Now().Unix()) + if err = os.Rename(dbPath, backupPath); err != nil { + // this is not catastrophic in the sense that we have not lost any data. + // but it is pretty bad, as the db path points to the old db, while we are now using to the new + // db; we can't continue and leave a ticking bomb for the next restart. + // so a panic is appropriate and user can fix. + panic(fmt.Errorf("error renaming old badger db dir from %s to %s: %w; USER ACTION REQUIRED", dbPath, backupPath, err)) //nolint + } + + if err = symlink(newPath, dbPath); err != nil { + // same here; the db path is pointing to the void. panic and let the user fix. + panic(fmt.Errorf("error symlinking new badger db dir from %s to %s: %w; USER ACTION REQUIRED", newPath, dbPath, err)) //nolint + } + + // the delete follows symlinks + b.deleteDB(backupPath) + + log.Info("moving blockstore done") + return nil +} + +// symlink creates a symlink from path to linkTo; the link is relative if the two are +// in the same directory +func symlink(path, linkTo string) error { + resolvedPathDir, err := filepath.EvalSymlinks(filepath.Dir(path)) + if err != nil { + return fmt.Errorf("error resolving links in %s: %w", path, err) + } + + resolvedLinkDir, err := filepath.EvalSymlinks(filepath.Dir(linkTo)) + if err != nil { + return fmt.Errorf("error resolving links in %s: %w", linkTo, err) + } + + if resolvedPathDir == resolvedLinkDir { + path = filepath.Base(path) + } + + return os.Symlink(path, linkTo) +} + +// doCopy copies a badger blockstore to another, with an optional filter; if the filter +// is not nil, then only cids that satisfy the filter will be copied. +func (b *Blockstore) doCopy(from, to *badger.DB) error { + workers := runtime.NumCPU() / 2 + if workers < 2 { + workers = 2 + } + if workers > 8 { + workers = 8 + } + + stream := from.NewStream() + stream.NumGo = workers + stream.LogPrefix = "doCopy" + stream.Send = func(list *pb.KVList) error { + batch := to.NewWriteBatch() + defer batch.Cancel() + + for _, kv := range list.Kv { + if kv.Key == nil || kv.Value == nil { + continue + } + if err := batch.Set(kv.Key, kv.Value); err != nil { + return err + } + } + + return batch.Flush() + } + + return stream.Orchestrate(context.Background()) +} + +func (b *Blockstore) deleteDB(path string) { + // follow symbolic links, otherwise the data wil be left behind + linkPath, err := filepath.EvalSymlinks(path) + if err != nil { + log.Warnf("error resolving symlinks in %s", path) + return + } + + log.Infof("removing data directory %s", linkPath) + if err := os.RemoveAll(linkPath); err != nil { + log.Warnf("error deleting db at %s: %s", linkPath, err) + return + } + + if path != linkPath { + log.Infof("removing link %s", path) + if err := os.Remove(path); err != nil { + log.Warnf("error removing symbolic link %s", err) + } + } +} + +func (b *Blockstore) onlineGC(ctx context.Context, threshold float64, checkFreq time.Duration, check func() error) error { + b.lockDB() + defer b.unlockDB() + + // compact first to gather the necessary statistics for GC + nworkers := runtime.NumCPU() / 2 + if nworkers < 2 { + nworkers = 2 + } + if nworkers > 7 { // max out at 1 goroutine per badger level + nworkers = 7 + } + + err := b.db.Flatten(nworkers) + if err != nil { + return err + } + checkTick := time.NewTimer(checkFreq) + defer checkTick.Stop() + for err == nil { + select { + case <-ctx.Done(): + err = ctx.Err() + case <-checkTick.C: + err = check() + checkTick.Reset(checkFreq) + default: + err = b.db.RunValueLogGC(threshold) + } + } + + if err == badger.ErrNoRewrite { + // not really an error in this case, it signals the end of GC + return nil + } + + return err +} + +// CollectGarbage compacts and runs garbage collection on the value log; +// implements the BlockstoreGC trait +func (b *Blockstore) CollectGarbage(ctx context.Context, opts ...blockstore.BlockstoreGCOption) error { + if err := b.access(); err != nil { + return err + } + defer b.viewers.Done() + + var options blockstore.BlockstoreGCOptions + for _, opt := range opts { + err := opt(&options) + if err != nil { + return err + } + } + + if options.FullGC { + return b.movingGC() + } + threshold := options.Threshold + if threshold == 0 { + threshold = defaultGCThreshold + } + checkFreq := options.CheckFreq + if checkFreq < 30*time.Second { // disallow checking more frequently than block time + checkFreq = 30 * time.Second + } + check := options.Check + if check == nil { + check = func() error { + return nil + } + } + return b.onlineGC(ctx, threshold, checkFreq, check) +} + +// GCOnce runs garbage collection on the value log; +// implements BlockstoreGCOnce trait +func (b *Blockstore) GCOnce(ctx context.Context, opts ...blockstore.BlockstoreGCOption) error { + if err := b.access(); err != nil { + return err + } + defer b.viewers.Done() + + var options blockstore.BlockstoreGCOptions + for _, opt := range opts { + err := opt(&options) + if err != nil { + return err + } + } + if options.FullGC { + return xerrors.Errorf("FullGC option specified for GCOnce but full GC is non incremental") + } + + threshold := options.Threshold + if threshold == 0 { + threshold = defaultGCThreshold + } + + b.lockDB() + defer b.unlockDB() + + // Note no compaction needed before single GC as we will hit at most one vlog anyway + err := b.db.RunValueLogGC(threshold) + if err == badger.ErrNoRewrite { + // not really an error in this case, it signals the end of GC + return nil + } + + return err +} + +// Size returns the aggregate size of the blockstore +func (b *Blockstore) Size() (int64, error) { + if err := b.access(); err != nil { + return 0, err + } + defer b.viewers.Done() + + b.lockDB() + defer b.unlockDB() + + lsm, vlog := b.db.Size() + size := lsm + vlog + + if size == 0 { + // badger reports a 0 size on symlinked directories... sigh + dir := b.opts.Dir + entries, err := os.ReadDir(dir) + if err != nil { + return 0, err + } + + for _, e := range entries { + path := filepath.Join(dir, e.Name()) + finfo, err := os.Stat(path) + if err != nil { + return 0, err + } + size += finfo.Size() + } + } + + return size, nil +} + +// View implements blockstore.Viewer, which leverages zero-copy read-only +// access to values. +func (b *Blockstore) View(ctx context.Context, cid cid.Cid, fn func([]byte) error) error { + if err := b.access(); err != nil { + return err + } + defer b.viewers.Done() + + b.lockDB() + defer b.unlockDB() + + k, pooled := b.PooledStorageKey(cid) + if pooled { + defer KeyPool.Put(k) + } + + return b.db.View(func(txn *badger.Txn) error { + switch item, err := txn.Get(k); err { + case nil: + return item.Value(fn) + case badger.ErrKeyNotFound: + return ipld.ErrNotFound{Cid: cid} + default: + return fmt.Errorf("failed to view block from badger blockstore: %w", err) + } + }) +} + +func (b *Blockstore) Flush(context.Context) error { + if err := b.access(); err != nil { + return err + } + defer b.viewers.Done() + + b.lockDB() + defer b.unlockDB() + + return b.db.Sync() +} + +// Has implements Blockstore.Has. +func (b *Blockstore) Has(ctx context.Context, cid cid.Cid) (bool, error) { + if err := b.access(); err != nil { + return false, err + } + defer b.viewers.Done() + + b.lockDB() + defer b.unlockDB() + + k, pooled := b.PooledStorageKey(cid) + if pooled { + defer KeyPool.Put(k) + } + + err := b.db.View(func(txn *badger.Txn) error { + _, err := txn.Get(k) + return err + }) + + switch err { + case badger.ErrKeyNotFound: + return false, nil + case nil: + return true, nil + default: + return false, fmt.Errorf("failed to check if block exists in badger blockstore: %w", err) + } +} + +// Get implements Blockstore.Get. +func (b *Blockstore) Get(ctx context.Context, cid cid.Cid) (blocks.Block, error) { + if !cid.Defined() { + return nil, ipld.ErrNotFound{Cid: cid} + } + + if err := b.access(); err != nil { + return nil, err + } + defer b.viewers.Done() + + b.lockDB() + defer b.unlockDB() + + k, pooled := b.PooledStorageKey(cid) + if pooled { + defer KeyPool.Put(k) + } + + var val []byte + err := b.db.View(func(txn *badger.Txn) error { + switch item, err := txn.Get(k); err { + case nil: + val, err = item.ValueCopy(nil) + return err + case badger.ErrKeyNotFound: + return ipld.ErrNotFound{Cid: cid} + default: + return fmt.Errorf("failed to get block from badger blockstore: %w", err) + } + }) + if err != nil { + return nil, err + } + return blocks.NewBlockWithCid(val, cid) +} + +// GetSize implements Blockstore.GetSize. +func (b *Blockstore) GetSize(ctx context.Context, cid cid.Cid) (int, error) { + if err := b.access(); err != nil { + return 0, err + } + defer b.viewers.Done() + + b.lockDB() + defer b.unlockDB() + + k, pooled := b.PooledStorageKey(cid) + if pooled { + defer KeyPool.Put(k) + } + + var size int + err := b.db.View(func(txn *badger.Txn) error { + switch item, err := txn.Get(k); err { + case nil: + size = int(item.ValueSize()) + case badger.ErrKeyNotFound: + return ipld.ErrNotFound{Cid: cid} + default: + return fmt.Errorf("failed to get block size from badger blockstore: %w", err) + } + return nil + }) + if err != nil { + size = -1 + } + return size, err +} + +// Put implements Blockstore.Put. +func (b *Blockstore) Put(ctx context.Context, block blocks.Block) error { + if err := b.access(); err != nil { + return err + } + defer b.viewers.Done() + + b.lockDB() + defer b.unlockDB() + + k, pooled := b.PooledStorageKey(block.Cid()) + if pooled { + defer KeyPool.Put(k) + } + + put := func(db *badger.DB) error { + // Check if we have it before writing it. + switch err := db.View(func(txn *badger.Txn) error { + _, err := txn.Get(k) + return err + }); err { + case badger.ErrKeyNotFound: + case nil: + // Already exists, skip the put. + return nil + default: + return err + } + + // Then write it. + err := db.Update(func(txn *badger.Txn) error { + return txn.Set(k, block.RawData()) + }) + if err != nil { + return fmt.Errorf("failed to put block in badger blockstore: %w", err) + } + + return nil + } + + if err := put(b.db); err != nil { + return err + } + + if b.dbNext != nil { + if err := put(b.dbNext); err != nil { + return err + } + } + + return nil +} + +// PutMany implements Blockstore.PutMany. +func (b *Blockstore) PutMany(ctx context.Context, blocks []blocks.Block) error { + if err := b.access(); err != nil { + return err + } + defer b.viewers.Done() + + b.lockDB() + defer b.unlockDB() + + // toReturn tracks the byte slices to return to the pool, if we're using key + // prefixing. we can't return each slice to the pool after each Set, because + // badger holds on to the slice. + var toReturn [][]byte + if b.prefixing { + toReturn = make([][]byte, 0, len(blocks)) + defer func() { + for _, b := range toReturn { + KeyPool.Put(b) + } + }() + } + + keys := make([][]byte, 0, len(blocks)) + for _, block := range blocks { + k, pooled := b.PooledStorageKey(block.Cid()) + if pooled { + toReturn = append(toReturn, k) + } + keys = append(keys, k) + } + + err := b.db.View(func(txn *badger.Txn) error { + for i, k := range keys { + switch _, err := txn.Get(k); err { + case badger.ErrKeyNotFound: + case nil: + keys[i] = nil + default: + // Something is actually wrong + return err + } + } + return nil + }) + if err != nil { + return err + } + + put := func(db *badger.DB) error { + batch := db.NewWriteBatch() + defer batch.Cancel() + + for i, block := range blocks { + k := keys[i] + if k == nil { + // skipped because we already have it. + continue + } + if err := batch.Set(k, block.RawData()); err != nil { + return err + } + } + + err := batch.Flush() + if err != nil { + return fmt.Errorf("failed to put blocks in badger blockstore: %w", err) + } + + return nil + } + + if err := put(b.db); err != nil { + return err + } + + if b.dbNext != nil { + if err := put(b.dbNext); err != nil { + return err + } + } + + return nil +} + +// DeleteBlock implements Blockstore.DeleteBlock. +func (b *Blockstore) DeleteBlock(ctx context.Context, cid cid.Cid) error { + if err := b.access(); err != nil { + return err + } + defer b.viewers.Done() + + b.lockDB() + defer b.unlockDB() + + k, pooled := b.PooledStorageKey(cid) + if pooled { + defer KeyPool.Put(k) + } + + return b.db.Update(func(txn *badger.Txn) error { + return txn.Delete(k) + }) +} + +func (b *Blockstore) DeleteMany(ctx context.Context, cids []cid.Cid) error { + if err := b.access(); err != nil { + return err + } + defer b.viewers.Done() + + b.lockDB() + defer b.unlockDB() + + // toReturn tracks the byte slices to return to the pool, if we're using key + // prefixing. we can't return each slice to the pool after each Set, because + // badger holds on to the slice. + var toReturn [][]byte + if b.prefixing { + toReturn = make([][]byte, 0, len(cids)) + defer func() { + for _, b := range toReturn { + KeyPool.Put(b) + } + }() + } + + batch := b.db.NewWriteBatch() + defer batch.Cancel() + + for _, cid := range cids { + k, pooled := b.PooledStorageKey(cid) + if pooled { + toReturn = append(toReturn, k) + } + if err := batch.Delete(k); err != nil { + return err + } + } + + err := batch.Flush() + if err != nil { + err = fmt.Errorf("failed to delete blocks from badger blockstore: %w", err) + } + return err +} + +// AllKeysChan implements Blockstore.AllKeysChan. +func (b *Blockstore) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { + if err := b.access(); err != nil { + return nil, err + } + + b.lockDB() + defer b.unlockDB() + + txn := b.db.NewTransaction(false) + opts := badger.IteratorOptions{PrefetchSize: 100} + if b.prefixing { + opts.Prefix = b.prefix + } + iter := txn.NewIterator(opts) + + ch := make(chan cid.Cid) + go func() { + defer b.viewers.Done() + defer close(ch) + defer iter.Close() + + // NewCidV1 makes a copy of the multihash buffer, so we can reuse it to + // contain allocs. + var buf []byte + for iter.Rewind(); iter.Valid(); iter.Next() { + if ctx.Err() != nil { + return // context has fired. + } + if !b.isOpen() { + // open iterators will run even after the database is closed... + return // closing, yield. + } + k := iter.Item().Key() + if b.prefixing { + k = k[b.prefixLen:] + } + + if reqlen := base32.RawStdEncoding.DecodedLen(len(k)); len(buf) < reqlen { + buf = make([]byte, reqlen) + } + if n, err := base32.RawStdEncoding.Decode(buf, k); err == nil { + select { + case ch <- cid.NewCidV1(cid.Raw, buf[:n]): + case <-ctx.Done(): + return + } + } else { + log.Warnf("failed to decode key %s in badger AllKeysChan; err: %s", k, err) + } + } + }() + + return ch, nil +} + +// Implementation of BlockstoreIterator interface +func (b *Blockstore) ForEachKey(f func(cid.Cid) error) error { + if err := b.access(); err != nil { + return err + } + defer b.viewers.Done() + + b.lockDB() + defer b.unlockDB() + + txn := b.db.NewTransaction(false) + defer txn.Discard() + + opts := badger.IteratorOptions{PrefetchSize: 100} + if b.prefixing { + opts.Prefix = b.prefix + } + + iter := txn.NewIterator(opts) + defer iter.Close() + + var buf []byte + for iter.Rewind(); iter.Valid(); iter.Next() { + if !b.isOpen() { + return ErrBlockstoreClosed + } + + k := iter.Item().Key() + if b.prefixing { + k = k[b.prefixLen:] + } + + klen := base32.RawStdEncoding.DecodedLen(len(k)) + if klen > len(buf) { + buf = make([]byte, klen) + } + + n, err := base32.RawStdEncoding.Decode(buf, k) + if err != nil { + return err + } + + c := cid.NewCidV1(cid.Raw, buf[:n]) + + err = f(c) + if err != nil { + return err + } + } + + return nil +} + +// HashOnRead implements Blockstore.HashOnRead. It is not supported by this +// blockstore. +func (b *Blockstore) HashOnRead(_ bool) { + log.Warnf("called HashOnRead on badger blockstore; function not supported; ignoring") +} + +// PooledStorageKey returns the storage key under which this CID is stored. +// +// The key is: prefix + base32_no_padding(cid.Hash) +// +// This method may return pooled byte slice, which MUST be returned to the +// KeyPool if pooled=true, or a leak will occur. +func (b *Blockstore) PooledStorageKey(cid cid.Cid) (key []byte, pooled bool) { + h := cid.Hash() + size := base32.RawStdEncoding.EncodedLen(len(h)) + if !b.prefixing { // optimize for branch prediction. + k := pool.Get(size) + base32.RawStdEncoding.Encode(k, h) + return k, true // slicing upto length unnecessary; the pool has already done this. + } + + size += b.prefixLen + k := pool.Get(size) + copy(k, b.prefix) + base32.RawStdEncoding.Encode(k[b.prefixLen:], h) + return k, true // slicing upto length unnecessary; the pool has already done this. +} + +// Storage acts like PooledStorageKey, but attempts to write the storage key +// into the provided slice. If the slice capacity is insufficient, it allocates +// a new byte slice with enough capacity to accommodate the result. This method +// returns the resulting slice. +func (b *Blockstore) StorageKey(dst []byte, cid cid.Cid) []byte { + h := cid.Hash() + reqsize := base32.RawStdEncoding.EncodedLen(len(h)) + b.prefixLen + if reqsize > cap(dst) { + // passed slice is smaller than required size; create new. + dst = make([]byte, reqsize) + } else if reqsize > len(dst) { + // passed slice has enough capacity, but its length is + // restricted, expand. + dst = dst[:cap(dst)] + } + + if b.prefixing { // optimize for branch prediction. + copy(dst, b.prefix) + base32.RawStdEncoding.Encode(dst[b.prefixLen:], h) + } else { + base32.RawStdEncoding.Encode(dst, h) + } + return dst[:reqsize] +} + +// this method is added for lotus-shed needs +// WARNING: THIS IS COMPLETELY UNSAFE; DONT USE THIS IN PRODUCTION CODE +func (b *Blockstore) DB() *badger.DB { + return b.db +} diff --git a/blockstore/badger/blockstore_test.go b/blockstore/badger/blockstore_test.go new file mode 100644 index 000000000..d253f37d9 --- /dev/null +++ b/blockstore/badger/blockstore_test.go @@ -0,0 +1,272 @@ +// stm: #unit +package badgerbs + +import ( + "bytes" + "context" + "fmt" + "os" + "path/filepath" + "strings" + "testing" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + "github.com/stretchr/testify/require" + "golang.org/x/sync/errgroup" + + "github.com/filecoin-project/lotus/blockstore" +) + +func TestBadgerBlockstore(t *testing.T) { + //stm: @SPLITSTORE_BADGER_PUT_001, @SPLITSTORE_BADGER_POOLED_STORAGE_KEY_001 + //stm: @SPLITSTORE_BADGER_OPEN_001, @SPLITSTORE_BADGER_CLOSE_001 + (&Suite{ + NewBlockstore: newBlockstore(DefaultOptions), + OpenBlockstore: openBlockstore(DefaultOptions), + }).RunTests(t, "non_prefixed") + + prefixed := func(path string) Options { + opts := DefaultOptions(path) + opts.Prefix = "/prefixed/" + return opts + } + + (&Suite{ + NewBlockstore: newBlockstore(prefixed), + OpenBlockstore: openBlockstore(prefixed), + }).RunTests(t, "prefixed") +} + +func TestStorageKey(t *testing.T) { + //stm: @SPLITSTORE_BADGER_OPEN_001, @SPLITSTORE_BADGER_CLOSE_001 + //stm: @SPLITSTORE_BADGER_STORAGE_KEY_001 + bs, _ := newBlockstore(DefaultOptions)(t) + bbs := bs.(*Blockstore) + defer bbs.Close() //nolint:errcheck + + cid1 := blocks.NewBlock([]byte("some data")).Cid() + cid2 := blocks.NewBlock([]byte("more data")).Cid() + cid3 := blocks.NewBlock([]byte("a little more data")).Cid() + require.NotEqual(t, cid1, cid2) // sanity check + require.NotEqual(t, cid2, cid3) // sanity check + + // nil slice; let StorageKey allocate for us. + k1 := bbs.StorageKey(nil, cid1) + require.Len(t, k1, 55) + require.True(t, cap(k1) == len(k1)) + + // k1's backing array is reused. + k2 := bbs.StorageKey(k1, cid2) + require.Len(t, k2, 55) + require.True(t, cap(k2) == len(k1)) + + // bring k2 to len=0, and verify that its backing array gets reused + // (i.e. k1 and k2 are overwritten) + k3 := bbs.StorageKey(k2[:0], cid3) + require.Len(t, k3, 55) + require.True(t, cap(k3) == len(k3)) + + // backing array of k1 and k2 has been modified, i.e. memory is shared. + require.Equal(t, k3, k1) + require.Equal(t, k3, k2) +} + +func newBlockstore(optsSupplier func(path string) Options) func(tb testing.TB) (bs blockstore.BasicBlockstore, path string) { + return func(tb testing.TB) (bs blockstore.BasicBlockstore, path string) { + tb.Helper() + + path = tb.TempDir() + + db, err := Open(optsSupplier(path)) + if err != nil { + tb.Fatal(err) + } + + return db, path + } +} + +func openBlockstore(optsSupplier func(path string) Options) func(tb testing.TB, path string) (bs blockstore.BasicBlockstore, err error) { + return func(tb testing.TB, path string) (bs blockstore.BasicBlockstore, err error) { + tb.Helper() + return Open(optsSupplier(path)) + } +} + +func testMove(t *testing.T, optsF func(string) Options) { + ctx := context.Background() + basePath := t.TempDir() + + dbPath := filepath.Join(basePath, "db") + + db, err := Open(optsF(dbPath)) + if err != nil { + t.Fatal(err) + } + + defer db.Close() //nolint + + var have []blocks.Block + var deleted []cid.Cid + + // add some blocks + for i := 0; i < 10; i++ { + blk := blocks.NewBlock([]byte(fmt.Sprintf("some data %d", i))) + err := db.Put(ctx, blk) + if err != nil { + t.Fatal(err) + } + have = append(have, blk) + } + + // delete some of them + for i := 5; i < 10; i++ { + c := have[i].Cid() + err := db.DeleteBlock(ctx, c) + if err != nil { + t.Fatal(err) + } + deleted = append(deleted, c) + } + have = have[:5] + + // start a move concurrent with some more puts + g := new(errgroup.Group) + g.Go(func() error { + for i := 10; i < 1000; i++ { + blk := blocks.NewBlock([]byte(fmt.Sprintf("some data %d", i))) + err := db.Put(ctx, blk) + if err != nil { + return err + } + have = append(have, blk) + } + return nil + }) + g.Go(func() error { + return db.CollectGarbage(ctx, blockstore.WithFullGC(true)) + }) + + err = g.Wait() + if err != nil { + t.Fatal(err) + } + + // now check that we have all the blocks in have and none in the deleted lists + checkBlocks := func() { + for _, blk := range have { + has, err := db.Has(ctx, blk.Cid()) + if err != nil { + t.Fatal(err) + } + + if !has { + t.Fatal("missing block") + } + + blk2, err := db.Get(ctx, blk.Cid()) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(blk.RawData(), blk2.RawData()) { + t.Fatal("data mismatch") + } + } + + for _, c := range deleted { + has, err := db.Has(ctx, c) + if err != nil { + t.Fatal(err) + } + + if has { + t.Fatal("resurrected block") + } + } + } + + checkBlocks() + + // check the basePath -- it should contain a directory with name db.{timestamp}, soft-linked + // to db and nothing else + checkPath := func() { + entries, err := os.ReadDir(basePath) + if err != nil { + t.Fatal(err) + } + + if len(entries) != 2 { + t.Fatalf("too many entries; expected %d but got %d", 2, len(entries)) + } + + var haveDB, haveDBLink bool + for _, e := range entries { + if e.Name() == "db" { + if (e.Type() & os.ModeSymlink) == 0 { + t.Fatal("found db, but it's not a symlink") + } + haveDBLink = true + continue + } + if strings.HasPrefix(e.Name(), "db.") { + if !e.Type().IsDir() { + t.Fatal("found db prefix, but it's not a directory") + } + haveDB = true + continue + } + } + + if !haveDB { + t.Fatal("db directory is missing") + } + if !haveDBLink { + t.Fatal("db link is missing") + } + } + + checkPath() + + // now do another FullGC to test the double move and following of symlinks + if err := db.CollectGarbage(ctx, blockstore.WithFullGC(true)); err != nil { + t.Fatal(err) + } + + checkBlocks() + checkPath() + + // reopen the db to make sure our relative link works: + err = db.Close() + if err != nil { + t.Fatal(err) + } + + db, err = Open(optsF(dbPath)) + if err != nil { + t.Fatal(err) + } + + // db.Close() is already deferred + + checkBlocks() +} + +func TestMoveNoPrefix(t *testing.T) { + //stm: @SPLITSTORE_BADGER_OPEN_001, @SPLITSTORE_BADGER_CLOSE_001 + //stm: @SPLITSTORE_BADGER_PUT_001, @SPLITSTORE_BADGER_POOLED_STORAGE_KEY_001 + //stm: @SPLITSTORE_BADGER_DELETE_001, @SPLITSTORE_BADGER_COLLECT_GARBAGE_001 + testMove(t, DefaultOptions) +} + +func TestMoveWithPrefix(t *testing.T) { + //stm: @SPLITSTORE_BADGER_OPEN_001, @SPLITSTORE_BADGER_CLOSE_001 + //stm: @SPLITSTORE_BADGER_PUT_001, @SPLITSTORE_BADGER_POOLED_STORAGE_KEY_001 + //stm: @SPLITSTORE_BADGER_DELETE_001, @SPLITSTORE_BADGER_COLLECT_GARBAGE_001 + testMove(t, func(path string) Options { + opts := DefaultOptions(path) + opts.Prefix = "/prefixed/" + return opts + }) +} diff --git a/blockstore/badger/blockstore_test_suite.go b/blockstore/badger/blockstore_test_suite.go new file mode 100644 index 000000000..7db155901 --- /dev/null +++ b/blockstore/badger/blockstore_test_suite.go @@ -0,0 +1,358 @@ +// stm: #unit +package badgerbs + +import ( + "context" + "fmt" + "io" + "reflect" + "strings" + "testing" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + u "github.com/ipfs/go-ipfs-util" + ipld "github.com/ipfs/go-ipld-format" + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/lotus/blockstore" +) + +// TODO: move this to go-ipfs-blockstore. +type Suite struct { + NewBlockstore func(tb testing.TB) (bs blockstore.BasicBlockstore, path string) + OpenBlockstore func(tb testing.TB, path string) (bs blockstore.BasicBlockstore, err error) +} + +func (s *Suite) RunTests(t *testing.T, prefix string) { + v := reflect.TypeOf(s) + f := func(t *testing.T) { + for i := 0; i < v.NumMethod(); i++ { + if m := v.Method(i); strings.HasPrefix(m.Name, "Test") { + f := m.Func.Interface().(func(*Suite, *testing.T)) + t.Run(m.Name, func(t *testing.T) { + f(s, t) + }) + } + } + } + + if prefix == "" { + f(t) + } else { + t.Run(prefix, f) + } +} + +func (s *Suite) TestGetWhenKeyNotPresent(t *testing.T) { + //stm: @SPLITSTORE_BADGER_OPEN_001, @SPLITSTORE_BADGER_CLOSE_001 + //stm: @SPLITSTORE_BADGER_GET_001, @SPLITSTORE_BADGER_POOLED_STORAGE_KEY_001 + ctx := context.Background() + bs, _ := s.NewBlockstore(t) + if c, ok := bs.(io.Closer); ok { + defer func() { require.NoError(t, c.Close()) }() + } + + c := cid.NewCidV0(u.Hash([]byte("stuff"))) + bl, err := bs.Get(ctx, c) + require.Nil(t, bl) + require.Equal(t, ipld.ErrNotFound{Cid: c}, err) +} + +func (s *Suite) TestGetWhenKeyIsNil(t *testing.T) { + //stm: @SPLITSTORE_BADGER_OPEN_001, @SPLITSTORE_BADGER_CLOSE_001 + //stm: @SPLITSTORE_BADGER_GET_001 + ctx := context.Background() + bs, _ := s.NewBlockstore(t) + if c, ok := bs.(io.Closer); ok { + defer func() { require.NoError(t, c.Close()) }() + } + + _, err := bs.Get(ctx, cid.Undef) + require.Equal(t, ipld.ErrNotFound{Cid: cid.Undef}, err) +} + +func (s *Suite) TestPutThenGetBlock(t *testing.T) { + //stm: @SPLITSTORE_BADGER_OPEN_001, @SPLITSTORE_BADGER_CLOSE_001 + //stm: @SPLITSTORE_BADGER_PUT_001, @SPLITSTORE_BADGER_POOLED_STORAGE_KEY_001 + //stm: @SPLITSTORE_BADGER_GET_001 + ctx := context.Background() + bs, _ := s.NewBlockstore(t) + if c, ok := bs.(io.Closer); ok { + defer func() { require.NoError(t, c.Close()) }() + } + + orig := blocks.NewBlock([]byte("some data")) + + err := bs.Put(ctx, orig) + require.NoError(t, err) + + fetched, err := bs.Get(ctx, orig.Cid()) + require.NoError(t, err) + require.Equal(t, orig.RawData(), fetched.RawData()) +} + +func (s *Suite) TestHas(t *testing.T) { + //stm: @SPLITSTORE_BADGER_OPEN_001, @SPLITSTORE_BADGER_CLOSE_001 + //stm: @SPLITSTORE_BADGER_HAS_001, @SPLITSTORE_BADGER_POOLED_STORAGE_KEY_001 + ctx := context.Background() + bs, _ := s.NewBlockstore(t) + if c, ok := bs.(io.Closer); ok { + defer func() { require.NoError(t, c.Close()) }() + } + + orig := blocks.NewBlock([]byte("some data")) + + err := bs.Put(ctx, orig) + require.NoError(t, err) + + ok, err := bs.Has(ctx, orig.Cid()) + require.NoError(t, err) + require.True(t, ok) + + ok, err = bs.Has(ctx, blocks.NewBlock([]byte("another thing")).Cid()) + require.NoError(t, err) + require.False(t, ok) +} + +func (s *Suite) TestCidv0v1(t *testing.T) { + //stm: @SPLITSTORE_BADGER_OPEN_001, @SPLITSTORE_BADGER_CLOSE_001 + //stm: @SPLITSTORE_BADGER_PUT_001, @SPLITSTORE_BADGER_POOLED_STORAGE_KEY_001 + //stm: @SPLITSTORE_BADGER_GET_001 + ctx := context.Background() + bs, _ := s.NewBlockstore(t) + if c, ok := bs.(io.Closer); ok { + defer func() { require.NoError(t, c.Close()) }() + } + + orig := blocks.NewBlock([]byte("some data")) + + err := bs.Put(ctx, orig) + require.NoError(t, err) + + fetched, err := bs.Get(ctx, cid.NewCidV1(cid.DagProtobuf, orig.Cid().Hash())) + require.NoError(t, err) + require.Equal(t, orig.RawData(), fetched.RawData()) +} + +func (s *Suite) TestPutThenGetSizeBlock(t *testing.T) { + //stm: @SPLITSTORE_BADGER_OPEN_001, @SPLITSTORE_BADGER_CLOSE_001 + //stm: @SPLITSTORE_BADGER_PUT_001, @SPLITSTORE_BADGER_POOLED_STORAGE_KEY_001 + //stm: @SPLITSTORE_BADGER_GET_SIZE_001 + ctx := context.Background() + + bs, _ := s.NewBlockstore(t) + if c, ok := bs.(io.Closer); ok { + defer func() { require.NoError(t, c.Close()) }() + } + + block := blocks.NewBlock([]byte("some data")) + missingBlock := blocks.NewBlock([]byte("missingBlock")) + emptyBlock := blocks.NewBlock([]byte{}) + + err := bs.Put(ctx, block) + require.NoError(t, err) + + blockSize, err := bs.GetSize(ctx, block.Cid()) + require.NoError(t, err) + require.Len(t, block.RawData(), blockSize) + + err = bs.Put(ctx, emptyBlock) + require.NoError(t, err) + + emptySize, err := bs.GetSize(ctx, emptyBlock.Cid()) + require.NoError(t, err) + require.Zero(t, emptySize) + + missingCid := missingBlock.Cid() + missingSize, err := bs.GetSize(ctx, missingCid) + require.Equal(t, ipld.ErrNotFound{Cid: missingCid}, err) + require.Equal(t, -1, missingSize) +} + +func (s *Suite) TestAllKeysSimple(t *testing.T) { + //stm: @SPLITSTORE_BADGER_OPEN_001, @SPLITSTORE_BADGER_CLOSE_001 + //stm: @SPLITSTORE_BADGER_PUT_001, @SPLITSTORE_BADGER_POOLED_STORAGE_KEY_001 + bs, _ := s.NewBlockstore(t) + if c, ok := bs.(io.Closer); ok { + defer func() { require.NoError(t, c.Close()) }() + } + + keys := insertBlocks(t, bs, 100) + + ctx := context.Background() + ch, err := bs.AllKeysChan(ctx) + require.NoError(t, err) + actual := collect(ch) + + require.ElementsMatch(t, keys, actual) +} + +func (s *Suite) TestAllKeysRespectsContext(t *testing.T) { + //stm: @SPLITSTORE_BADGER_OPEN_001, @SPLITSTORE_BADGER_CLOSE_001 + //stm: @SPLITSTORE_BADGER_PUT_001, @SPLITSTORE_BADGER_POOLED_STORAGE_KEY_001 + //stm: @SPLITSTORE_BADGER_ALL_KEYS_CHAN_001 + bs, _ := s.NewBlockstore(t) + if c, ok := bs.(io.Closer); ok { + defer func() { require.NoError(t, c.Close()) }() + } + + _ = insertBlocks(t, bs, 100) + + ctx, cancel := context.WithCancel(context.Background()) + ch, err := bs.AllKeysChan(ctx) + require.NoError(t, err) + + // consume 2, then cancel context. + v, ok := <-ch + require.NotEqual(t, cid.Undef, v) + require.True(t, ok) + + v, ok = <-ch + require.NotEqual(t, cid.Undef, v) + require.True(t, ok) + + cancel() + // pull one value out to avoid race + _, _ = <-ch + + v, ok = <-ch + require.Equal(t, cid.Undef, v) + require.False(t, ok) +} + +func (s *Suite) TestDoubleClose(t *testing.T) { + //stm: @SPLITSTORE_BADGER_OPEN_001, @SPLITSTORE_BADGER_CLOSE_001 + bs, _ := s.NewBlockstore(t) + c, ok := bs.(io.Closer) + if !ok { + t.SkipNow() + } + require.NoError(t, c.Close()) + require.NoError(t, c.Close()) +} + +func (s *Suite) TestReopenPutGet(t *testing.T) { + //stm: @SPLITSTORE_BADGER_OPEN_001, @SPLITSTORE_BADGER_CLOSE_001 + //stm: @SPLITSTORE_BADGER_PUT_001, @SPLITSTORE_BADGER_POOLED_STORAGE_KEY_001 + //stm: @SPLITSTORE_BADGER_GET_001 + ctx := context.Background() + bs, path := s.NewBlockstore(t) + c, ok := bs.(io.Closer) + if !ok { + t.SkipNow() + } + + orig := blocks.NewBlock([]byte("some data")) + err := bs.Put(ctx, orig) + require.NoError(t, err) + + err = c.Close() + require.NoError(t, err) + + bs, err = s.OpenBlockstore(t, path) + require.NoError(t, err) + + fetched, err := bs.Get(ctx, orig.Cid()) + require.NoError(t, err) + require.Equal(t, orig.RawData(), fetched.RawData()) + + err = bs.(io.Closer).Close() + require.NoError(t, err) +} + +func (s *Suite) TestPutMany(t *testing.T) { + //stm: @SPLITSTORE_BADGER_OPEN_001, @SPLITSTORE_BADGER_CLOSE_001 + //stm: @SPLITSTORE_BADGER_HAS_001, @SPLITSTORE_BADGER_POOLED_STORAGE_KEY_001 + //stm: @SPLITSTORE_BADGER_GET_001, @SPLITSTORE_BADGER_PUT_MANY_001 + //stm: @SPLITSTORE_BADGER_ALL_KEYS_CHAN_001 + ctx := context.Background() + bs, _ := s.NewBlockstore(t) + if c, ok := bs.(io.Closer); ok { + defer func() { require.NoError(t, c.Close()) }() + } + + blks := []blocks.Block{ + blocks.NewBlock([]byte("foo1")), + blocks.NewBlock([]byte("foo2")), + blocks.NewBlock([]byte("foo3")), + } + err := bs.PutMany(ctx, blks) + require.NoError(t, err) + + for _, blk := range blks { + fetched, err := bs.Get(ctx, blk.Cid()) + require.NoError(t, err) + require.Equal(t, blk.RawData(), fetched.RawData()) + + ok, err := bs.Has(ctx, blk.Cid()) + require.NoError(t, err) + require.True(t, ok) + } + + ch, err := bs.AllKeysChan(context.Background()) + require.NoError(t, err) + + cids := collect(ch) + require.Len(t, cids, 3) +} + +func (s *Suite) TestDelete(t *testing.T) { + //stm: @SPLITSTORE_BADGER_PUT_001, @SPLITSTORE_BADGER_POOLED_STORAGE_KEY_001 + //stm: @SPLITSTORE_BADGER_DELETE_001, @SPLITSTORE_BADGER_POOLED_STORAGE_HAS_001 + //stm: @SPLITSTORE_BADGER_ALL_KEYS_CHAN_001, @SPLITSTORE_BADGER_HAS_001 + //stm: @SPLITSTORE_BADGER_PUT_MANY_001 + + ctx := context.Background() + bs, _ := s.NewBlockstore(t) + if c, ok := bs.(io.Closer); ok { + defer func() { require.NoError(t, c.Close()) }() + } + + blks := []blocks.Block{ + blocks.NewBlock([]byte("foo1")), + blocks.NewBlock([]byte("foo2")), + blocks.NewBlock([]byte("foo3")), + } + err := bs.PutMany(ctx, blks) + require.NoError(t, err) + + err = bs.DeleteBlock(ctx, blks[1].Cid()) + require.NoError(t, err) + + ch, err := bs.AllKeysChan(context.Background()) + require.NoError(t, err) + + cids := collect(ch) + require.Len(t, cids, 2) + require.ElementsMatch(t, cids, []cid.Cid{ + cid.NewCidV1(cid.Raw, blks[0].Cid().Hash()), + cid.NewCidV1(cid.Raw, blks[2].Cid().Hash()), + }) + + has, err := bs.Has(ctx, blks[1].Cid()) + require.NoError(t, err) + require.False(t, has) +} + +func insertBlocks(t *testing.T, bs blockstore.BasicBlockstore, count int) []cid.Cid { + ctx := context.Background() + keys := make([]cid.Cid, count) + for i := 0; i < count; i++ { + block := blocks.NewBlock([]byte(fmt.Sprintf("some data %d", i))) + err := bs.Put(ctx, block) + require.NoError(t, err) + // NewBlock assigns a CIDv0; we convert it to CIDv1 because that's what + // the store returns. + keys[i] = cid.NewCidV1(cid.Raw, block.Multihash()) + } + return keys +} + +func collect(ch <-chan cid.Cid) []cid.Cid { + var keys []cid.Cid + for k := range ch { + keys = append(keys, k) + } + return keys +} diff --git a/blockstore/blockstore.go b/blockstore/blockstore.go new file mode 100644 index 000000000..195e991e1 --- /dev/null +++ b/blockstore/blockstore.go @@ -0,0 +1,169 @@ +package blockstore + +import ( + "context" + "time" + + "github.com/ipfs/go-cid" + ds "github.com/ipfs/go-datastore" + blockstore "github.com/ipfs/go-ipfs-blockstore" + logging "github.com/ipfs/go-log/v2" +) + +var log = logging.Logger("blockstore") + +// Blockstore is the blockstore interface used by Lotus. It is the union +// of the basic go-ipfs blockstore, with other capabilities required by Lotus, +// e.g. View or Sync. +type Blockstore interface { + blockstore.Blockstore + blockstore.Viewer + BatchDeleter + Flusher +} + +// BasicBlockstore is an alias to the original IPFS Blockstore. +type BasicBlockstore = blockstore.Blockstore + +type Viewer = blockstore.Viewer + +type Flusher interface { + Flush(context.Context) error +} + +type BatchDeleter interface { + DeleteMany(ctx context.Context, cids []cid.Cid) error +} + +// BlockstoreIterator is a trait for efficient iteration +type BlockstoreIterator interface { + ForEachKey(func(cid.Cid) error) error +} + +// BlockstoreGC is a trait for blockstores that support online garbage collection +type BlockstoreGC interface { + CollectGarbage(ctx context.Context, options ...BlockstoreGCOption) error +} + +// BlockstoreGCOnce is a trait for a blockstore that supports incremental online garbage collection +type BlockstoreGCOnce interface { + GCOnce(ctx context.Context, options ...BlockstoreGCOption) error +} + +// BlockstoreGCOption is a functional interface for controlling blockstore GC options +type BlockstoreGCOption = func(*BlockstoreGCOptions) error + +// BlockstoreGCOptions is a struct with GC options +type BlockstoreGCOptions struct { + FullGC bool + // fraction of garbage in badger vlog before its worth processing in online GC + Threshold float64 + // how often to call the check function + CheckFreq time.Duration + // function to call periodically to pause or early terminate GC + Check func() error +} + +func WithFullGC(fullgc bool) BlockstoreGCOption { + return func(opts *BlockstoreGCOptions) error { + opts.FullGC = fullgc + return nil + } +} + +func WithThreshold(threshold float64) BlockstoreGCOption { + return func(opts *BlockstoreGCOptions) error { + opts.Threshold = threshold + return nil + } +} + +func WithCheckFreq(f time.Duration) BlockstoreGCOption { + return func(opts *BlockstoreGCOptions) error { + opts.CheckFreq = f + return nil + } +} + +func WithCheck(check func() error) BlockstoreGCOption { + return func(opts *BlockstoreGCOptions) error { + opts.Check = check + return nil + } +} + +// BlockstoreSize is a trait for on-disk blockstores that can report their size +type BlockstoreSize interface { + Size() (int64, error) +} + +// WrapIDStore wraps the underlying blockstore in an "identity" blockstore. +// The ID store filters out all puts for blocks with CIDs using the "identity" +// hash function. It also extracts inlined blocks from CIDs using the identity +// hash function and returns them on get/has, ignoring the contents of the +// blockstore. +func WrapIDStore(bstore blockstore.Blockstore) Blockstore { + if is, ok := bstore.(*idstore); ok { + // already wrapped + return is + } + + if bs, ok := bstore.(Blockstore); ok { + // we need to wrap our own because we don't want to neuter the DeleteMany method + // the underlying blockstore has implemented an (efficient) DeleteMany + return NewIDStore(bs) + } + + // The underlying blockstore does not implement DeleteMany, so we need to shim it. + // This is less efficient as it'll iterate and perform single deletes. + return NewIDStore(Adapt(bstore)) +} + +// FromDatastore creates a new blockstore backed by the given datastore. +func FromDatastore(dstore ds.Batching) Blockstore { + return WrapIDStore(blockstore.NewBlockstore(dstore)) +} + +type adaptedBlockstore struct { + blockstore.Blockstore +} + +var _ Blockstore = (*adaptedBlockstore)(nil) + +func (a *adaptedBlockstore) Flush(ctx context.Context) error { + if flusher, canFlush := a.Blockstore.(Flusher); canFlush { + return flusher.Flush(ctx) + } + return nil +} + +func (a *adaptedBlockstore) View(ctx context.Context, cid cid.Cid, callback func([]byte) error) error { + blk, err := a.Get(ctx, cid) + if err != nil { + return err + } + return callback(blk.RawData()) +} + +func (a *adaptedBlockstore) DeleteMany(ctx context.Context, cids []cid.Cid) error { + for _, cid := range cids { + err := a.DeleteBlock(ctx, cid) + if err != nil { + return err + } + } + + return nil +} + +// Adapt adapts a standard blockstore to a Lotus blockstore by +// enriching it with the extra methods that Lotus requires (e.g. View, Sync). +// +// View proxies over to Get and calls the callback with the value supplied by Get. +// Sync noops. +func Adapt(bs blockstore.Blockstore) Blockstore { + if ret, ok := bs.(Blockstore); ok { + return ret + } + return &adaptedBlockstore{bs} +} diff --git a/blockstore/buffered.go b/blockstore/buffered.go new file mode 100644 index 000000000..2a789b637 --- /dev/null +++ b/blockstore/buffered.go @@ -0,0 +1,177 @@ +package blockstore + +import ( + "context" + "os" + + block "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + ipld "github.com/ipfs/go-ipld-format" +) + +// buflog is a logger for the buffered blockstore. It is subscoped from the +// blockstore logger. +var buflog = log.Named("buf") + +type BufferedBlockstore struct { + read Blockstore + write Blockstore +} + +func NewBuffered(base Blockstore) *BufferedBlockstore { + var buf Blockstore + if os.Getenv("LOTUS_DISABLE_VM_BUF") == "iknowitsabadidea" { + buflog.Warn("VM BLOCKSTORE BUFFERING IS DISABLED") + buf = base + } else { + buf = NewMemory() + } + + bs := &BufferedBlockstore{ + read: base, + write: buf, + } + return bs +} + +func NewTieredBstore(r Blockstore, w Blockstore) *BufferedBlockstore { + return &BufferedBlockstore{ + read: r, + write: w, + } +} + +var ( + _ Blockstore = (*BufferedBlockstore)(nil) + _ Viewer = (*BufferedBlockstore)(nil) +) + +func (bs *BufferedBlockstore) Flush(ctx context.Context) error { return bs.write.Flush(ctx) } + +func (bs *BufferedBlockstore) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { + a, err := bs.read.AllKeysChan(ctx) + if err != nil { + return nil, err + } + + b, err := bs.write.AllKeysChan(ctx) + if err != nil { + return nil, err + } + + out := make(chan cid.Cid) + go func() { + defer close(out) + for a != nil || b != nil { + select { + case val, ok := <-a: + if !ok { + a = nil + } else { + select { + case out <- val: + case <-ctx.Done(): + return + } + } + case val, ok := <-b: + if !ok { + b = nil + } else { + select { + case out <- val: + case <-ctx.Done(): + return + } + } + } + } + }() + + return out, nil +} + +func (bs *BufferedBlockstore) DeleteBlock(ctx context.Context, c cid.Cid) error { + if err := bs.read.DeleteBlock(ctx, c); err != nil { + return err + } + + return bs.write.DeleteBlock(ctx, c) +} + +func (bs *BufferedBlockstore) DeleteMany(ctx context.Context, cids []cid.Cid) error { + if err := bs.read.DeleteMany(ctx, cids); err != nil { + return err + } + + return bs.write.DeleteMany(ctx, cids) +} + +func (bs *BufferedBlockstore) View(ctx context.Context, c cid.Cid, callback func([]byte) error) error { + // both stores are viewable. + if err := bs.write.View(ctx, c, callback); ipld.IsNotFound(err) { + // not found in write blockstore; fall through. + } else { + return err // propagate errors, or nil, i.e. found. + } + return bs.read.View(ctx, c, callback) +} + +func (bs *BufferedBlockstore) Get(ctx context.Context, c cid.Cid) (block.Block, error) { + if out, err := bs.write.Get(ctx, c); err != nil { + if !ipld.IsNotFound(err) { + return nil, err + } + } else { + return out, nil + } + + return bs.read.Get(ctx, c) +} + +func (bs *BufferedBlockstore) GetSize(ctx context.Context, c cid.Cid) (int, error) { + s, err := bs.read.GetSize(ctx, c) + if ipld.IsNotFound(err) || s == 0 { + return bs.write.GetSize(ctx, c) + } + + return s, err +} + +func (bs *BufferedBlockstore) Put(ctx context.Context, blk block.Block) error { + has, err := bs.read.Has(ctx, blk.Cid()) // TODO: consider dropping this check + if err != nil { + return err + } + + if has { + return nil + } + + return bs.write.Put(ctx, blk) +} + +func (bs *BufferedBlockstore) Has(ctx context.Context, c cid.Cid) (bool, error) { + has, err := bs.write.Has(ctx, c) + if err != nil { + return false, err + } + if has { + return true, nil + } + + return bs.read.Has(ctx, c) +} + +func (bs *BufferedBlockstore) HashOnRead(hor bool) { + bs.read.HashOnRead(hor) + bs.write.HashOnRead(hor) +} + +func (bs *BufferedBlockstore) PutMany(ctx context.Context, blks []block.Block) error { + return bs.write.PutMany(ctx, blks) +} + +func (bs *BufferedBlockstore) Read() Blockstore { + return bs.read +} diff --git a/blockstore/cbor_gen.go b/blockstore/cbor_gen.go new file mode 100644 index 000000000..b8ebdb474 --- /dev/null +++ b/blockstore/cbor_gen.go @@ -0,0 +1,441 @@ +// Code generated by github.com/whyrusleeping/cbor-gen. DO NOT EDIT. + +package blockstore + +import ( + "fmt" + "io" + "math" + "sort" + + cid "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + xerrors "golang.org/x/xerrors" +) + +var _ = xerrors.Errorf +var _ = cid.Undef +var _ = math.E +var _ = sort.Sort + +var lengthBufNetRpcReq = []byte{132} + +func (t *NetRpcReq) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufNetRpcReq); err != nil { + return err + } + + // t.Type (blockstore.NetRPCReqType) (uint8) + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Type)); err != nil { + return err + } + + // t.ID (uint64) (uint64) + + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.ID)); err != nil { + return err + } + + // t.Cid ([]cid.Cid) (slice) + if len(t.Cid) > cbg.MaxLength { + return xerrors.Errorf("Slice value in field t.Cid was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Cid))); err != nil { + return err + } + for _, v := range t.Cid { + if err := cbg.WriteCid(w, v); err != nil { + return xerrors.Errorf("failed writing cid field t.Cid: %w", err) + } + } + + // t.Data ([][]uint8) (slice) + if len(t.Data) > cbg.MaxLength { + return xerrors.Errorf("Slice value in field t.Data was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Data))); err != nil { + return err + } + for _, v := range t.Data { + if len(v) > cbg.ByteArrayMaxLen { + return xerrors.Errorf("Byte array in field v was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajByteString, uint64(len(v))); err != nil { + return err + } + + if _, err := cw.Write(v[:]); err != nil { + return err + } + } + return nil +} + +func (t *NetRpcReq) UnmarshalCBOR(r io.Reader) (err error) { + *t = NetRpcReq{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != 4 { + return fmt.Errorf("cbor input had wrong number of fields") + } + + // t.Type (blockstore.NetRPCReqType) (uint8) + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint8 field") + } + if extra > math.MaxUint8 { + return fmt.Errorf("integer in input was too large for uint8 field") + } + t.Type = NetRPCReqType(extra) + // t.ID (uint64) (uint64) + + { + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.ID = uint64(extra) + + } + // t.Cid ([]cid.Cid) (slice) + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > cbg.MaxLength { + return fmt.Errorf("t.Cid: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.Cid = make([]cid.Cid, extra) + } + + for i := 0; i < int(extra); i++ { + + c, err := cbg.ReadCid(cr) + if err != nil { + return xerrors.Errorf("reading cid field t.Cid failed: %w", err) + } + t.Cid[i] = c + } + + // t.Data ([][]uint8) (slice) + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > cbg.MaxLength { + return fmt.Errorf("t.Data: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.Data = make([][]uint8, extra) + } + + for i := 0; i < int(extra); i++ { + { + var maj byte + var extra uint64 + var err error + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > cbg.ByteArrayMaxLen { + return fmt.Errorf("t.Data[i]: byte array too large (%d)", extra) + } + if maj != cbg.MajByteString { + return fmt.Errorf("expected byte array") + } + + if extra > 0 { + t.Data[i] = make([]uint8, extra) + } + + if _, err := io.ReadFull(cr, t.Data[i][:]); err != nil { + return err + } + } + } + + return nil +} + +var lengthBufNetRpcResp = []byte{131} + +func (t *NetRpcResp) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufNetRpcResp); err != nil { + return err + } + + // t.Type (blockstore.NetRPCRespType) (uint8) + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Type)); err != nil { + return err + } + + // t.ID (uint64) (uint64) + + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.ID)); err != nil { + return err + } + + // t.Data ([]uint8) (slice) + if len(t.Data) > cbg.ByteArrayMaxLen { + return xerrors.Errorf("Byte array in field t.Data was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajByteString, uint64(len(t.Data))); err != nil { + return err + } + + if _, err := cw.Write(t.Data[:]); err != nil { + return err + } + return nil +} + +func (t *NetRpcResp) UnmarshalCBOR(r io.Reader) (err error) { + *t = NetRpcResp{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != 3 { + return fmt.Errorf("cbor input had wrong number of fields") + } + + // t.Type (blockstore.NetRPCRespType) (uint8) + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint8 field") + } + if extra > math.MaxUint8 { + return fmt.Errorf("integer in input was too large for uint8 field") + } + t.Type = NetRPCRespType(extra) + // t.ID (uint64) (uint64) + + { + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.ID = uint64(extra) + + } + // t.Data ([]uint8) (slice) + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > cbg.ByteArrayMaxLen { + return fmt.Errorf("t.Data: byte array too large (%d)", extra) + } + if maj != cbg.MajByteString { + return fmt.Errorf("expected byte array") + } + + if extra > 0 { + t.Data = make([]uint8, extra) + } + + if _, err := io.ReadFull(cr, t.Data[:]); err != nil { + return err + } + return nil +} + +var lengthBufNetRpcErr = []byte{131} + +func (t *NetRpcErr) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufNetRpcErr); err != nil { + return err + } + + // t.Type (blockstore.NetRPCErrType) (uint8) + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Type)); err != nil { + return err + } + + // t.Msg (string) (string) + if len(t.Msg) > cbg.MaxLength { + return xerrors.Errorf("Value in field t.Msg was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Msg))); err != nil { + return err + } + if _, err := io.WriteString(w, string(t.Msg)); err != nil { + return err + } + + // t.Cid (cid.Cid) (struct) + + if t.Cid == nil { + if _, err := cw.Write(cbg.CborNull); err != nil { + return err + } + } else { + if err := cbg.WriteCid(cw, *t.Cid); err != nil { + return xerrors.Errorf("failed to write cid field t.Cid: %w", err) + } + } + + return nil +} + +func (t *NetRpcErr) UnmarshalCBOR(r io.Reader) (err error) { + *t = NetRpcErr{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != 3 { + return fmt.Errorf("cbor input had wrong number of fields") + } + + // t.Type (blockstore.NetRPCErrType) (uint8) + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint8 field") + } + if extra > math.MaxUint8 { + return fmt.Errorf("integer in input was too large for uint8 field") + } + t.Type = NetRPCErrType(extra) + // t.Msg (string) (string) + + { + sval, err := cbg.ReadString(cr) + if err != nil { + return err + } + + t.Msg = string(sval) + } + // t.Cid (cid.Cid) (struct) + + { + + b, err := cr.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { + return err + } + + c, err := cbg.ReadCid(cr) + if err != nil { + return xerrors.Errorf("failed to read cid field t.Cid: %w", err) + } + + t.Cid = &c + } + + } + return nil +} diff --git a/blockstore/context.go b/blockstore/context.go new file mode 100644 index 000000000..ebb6fafe3 --- /dev/null +++ b/blockstore/context.go @@ -0,0 +1,21 @@ +package blockstore + +import ( + "context" +) + +type hotViewKey struct{} + +var hotView = hotViewKey{} + +// WithHotView constructs a new context with an option that provides a hint to the blockstore +// (e.g. the splitstore) that the object (and its ipld references) should be kept hot. +func WithHotView(ctx context.Context) context.Context { + return context.WithValue(ctx, hotView, struct{}{}) +} + +// IsHotView returns true if the hot view option is set in the context +func IsHotView(ctx context.Context) bool { + v := ctx.Value(hotView) + return v != nil +} diff --git a/blockstore/discard.go b/blockstore/discard.go new file mode 100644 index 000000000..158c7cfbb --- /dev/null +++ b/blockstore/discard.go @@ -0,0 +1,70 @@ +package blockstore + +import ( + "context" + "io" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" +) + +var _ Blockstore = (*discardstore)(nil) + +type discardstore struct { + bs Blockstore +} + +func NewDiscardStore(bs Blockstore) Blockstore { + return &discardstore{bs: bs} +} + +func (b *discardstore) Has(ctx context.Context, cid cid.Cid) (bool, error) { + return b.bs.Has(ctx, cid) +} + +func (b *discardstore) HashOnRead(hor bool) { + b.bs.HashOnRead(hor) +} + +func (b *discardstore) Get(ctx context.Context, cid cid.Cid) (blocks.Block, error) { + return b.bs.Get(ctx, cid) +} + +func (b *discardstore) GetSize(ctx context.Context, cid cid.Cid) (int, error) { + return b.bs.GetSize(ctx, cid) +} + +func (b *discardstore) View(ctx context.Context, cid cid.Cid, f func([]byte) error) error { + return b.bs.View(ctx, cid, f) +} + +func (b *discardstore) Flush(ctx context.Context) error { + return nil +} + +func (b *discardstore) Put(ctx context.Context, blk blocks.Block) error { + return nil +} + +func (b *discardstore) PutMany(ctx context.Context, blks []blocks.Block) error { + return nil +} + +func (b *discardstore) DeleteBlock(ctx context.Context, cid cid.Cid) error { + return nil +} + +func (b *discardstore) DeleteMany(ctx context.Context, cids []cid.Cid) error { + return nil +} + +func (b *discardstore) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { + return b.bs.AllKeysChan(ctx) +} + +func (b *discardstore) Close() error { + if c, ok := b.bs.(io.Closer); ok { + return c.Close() + } + return nil +} diff --git a/blockstore/doc.go b/blockstore/doc.go new file mode 100644 index 000000000..fea1126f5 --- /dev/null +++ b/blockstore/doc.go @@ -0,0 +1,9 @@ +// Package blockstore and subpackages contain most of the blockstore +// implementations used by Lotus. +// +// Blockstores not ultimately constructed out of the building blocks in this +// package may not work properly. +// +// This package re-exports parts of the go-ipfs-blockstore package such that +// no other package needs to import it directly, for ergonomics and traceability. +package blockstore diff --git a/blockstore/fallback.go b/blockstore/fallback.go new file mode 100644 index 000000000..de948b346 --- /dev/null +++ b/blockstore/fallback.go @@ -0,0 +1,106 @@ +package blockstore + +import ( + "context" + "sync" + "time" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + ipld "github.com/ipfs/go-ipld-format" + "golang.org/x/xerrors" +) + +// UnwrapFallbackStore takes a blockstore, and returns the underlying blockstore +// if it was a FallbackStore. Otherwise, it just returns the supplied store +// unmodified. +func UnwrapFallbackStore(bs Blockstore) (Blockstore, bool) { + if fbs, ok := bs.(*FallbackStore); ok { + return fbs.Blockstore, true + } + return bs, false +} + +// FallbackStore is a read-through store that queries another (potentially +// remote) source if the block is not found locally. If the block is found +// during the fallback, it stores it in the local store. +type FallbackStore struct { + Blockstore + + lk sync.RWMutex + // missFn is the function that will be invoked on a local miss to pull the + // block from elsewhere. + missFn func(context.Context, cid.Cid) (blocks.Block, error) +} + +var _ Blockstore = (*FallbackStore)(nil) + +func (fbs *FallbackStore) SetFallback(missFn func(context.Context, cid.Cid) (blocks.Block, error)) { + fbs.lk.Lock() + defer fbs.lk.Unlock() + + fbs.missFn = missFn +} + +func (fbs *FallbackStore) getFallback(c cid.Cid) (blocks.Block, error) { + log.Warnf("fallbackstore: block not found locally, fetching from the network; cid: %s", c) + fbs.lk.RLock() + defer fbs.lk.RUnlock() + + if fbs.missFn == nil { + // FallbackStore wasn't configured yet (chainstore/bitswap aren't up yet) + // Wait for a bit and retry + fbs.lk.RUnlock() + time.Sleep(5 * time.Second) + fbs.lk.RLock() + + if fbs.missFn == nil { + log.Errorw("fallbackstore: missFn not configured yet") + return nil, ipld.ErrNotFound{Cid: c} + } + } + + ctx, cancel := context.WithTimeout(context.TODO(), 120*time.Second) + defer cancel() + + b, err := fbs.missFn(ctx, c) + if err != nil { + return nil, err + } + + // chain bitswap puts blocks in temp blockstore which is cleaned up + // every few min (to drop any messages we fetched but don't want) + // in this case we want to keep this block around + if err := fbs.Put(ctx, b); err != nil { + return nil, xerrors.Errorf("persisting fallback-fetched block: %w", err) + } + return b, nil +} + +func (fbs *FallbackStore) Get(ctx context.Context, c cid.Cid) (blocks.Block, error) { + b, err := fbs.Blockstore.Get(ctx, c) + switch { + case err == nil: + return b, nil + case ipld.IsNotFound(err): + return fbs.getFallback(c) + default: + return b, err + } +} + +func (fbs *FallbackStore) GetSize(ctx context.Context, c cid.Cid) (int, error) { + sz, err := fbs.Blockstore.GetSize(ctx, c) + switch { + case err == nil: + return sz, nil + case ipld.IsNotFound(err): + b, err := fbs.getFallback(c) + if err != nil { + return 0, err + } + return len(b.RawData()), nil + default: + return sz, err + } +} diff --git a/blockstore/idstore.go b/blockstore/idstore.go new file mode 100644 index 000000000..fb575dca7 --- /dev/null +++ b/blockstore/idstore.go @@ -0,0 +1,185 @@ +package blockstore + +import ( + "context" + "io" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + mh "github.com/multiformats/go-multihash" + "golang.org/x/xerrors" +) + +var _ Blockstore = (*idstore)(nil) + +type idstore struct { + bs Blockstore +} + +func NewIDStore(bs Blockstore) Blockstore { + return &idstore{bs: bs} +} + +func decodeCid(cid cid.Cid) (inline bool, data []byte, err error) { + if cid.Prefix().MhType != mh.IDENTITY { + return false, nil, nil + } + + dmh, err := mh.Decode(cid.Hash()) + if err != nil { + return false, nil, err + } + + if dmh.Code == mh.IDENTITY { + return true, dmh.Digest, nil + } + + return false, nil, err +} + +func (b *idstore) Has(ctx context.Context, cid cid.Cid) (bool, error) { + inline, _, err := decodeCid(cid) + if err != nil { + return false, xerrors.Errorf("error decoding Cid: %w", err) + } + + if inline { + return true, nil + } + + return b.bs.Has(ctx, cid) +} + +func (b *idstore) Get(ctx context.Context, cid cid.Cid) (blocks.Block, error) { + inline, data, err := decodeCid(cid) + if err != nil { + return nil, xerrors.Errorf("error decoding Cid: %w", err) + } + + if inline { + return blocks.NewBlockWithCid(data, cid) + } + + return b.bs.Get(ctx, cid) +} + +func (b *idstore) GetSize(ctx context.Context, cid cid.Cid) (int, error) { + inline, data, err := decodeCid(cid) + if err != nil { + return 0, xerrors.Errorf("error decoding Cid: %w", err) + } + + if inline { + return len(data), err + } + + return b.bs.GetSize(ctx, cid) +} + +func (b *idstore) View(ctx context.Context, cid cid.Cid, cb func([]byte) error) error { + inline, data, err := decodeCid(cid) + if err != nil { + return xerrors.Errorf("error decoding Cid: %w", err) + } + + if inline { + return cb(data) + } + + return b.bs.View(ctx, cid, cb) +} + +func (b *idstore) Put(ctx context.Context, blk blocks.Block) error { + inline, _, err := decodeCid(blk.Cid()) + if err != nil { + return xerrors.Errorf("error decoding Cid: %w", err) + } + + if inline { + return nil + } + + return b.bs.Put(ctx, blk) +} + +func (b *idstore) ForEachKey(f func(cid.Cid) error) error { + iterBstore, ok := b.bs.(BlockstoreIterator) + if !ok { + return xerrors.Errorf("underlying blockstore (type %T) doesn't support fast iteration", b.bs) + } + return iterBstore.ForEachKey(f) +} + +func (b *idstore) PutMany(ctx context.Context, blks []blocks.Block) error { + toPut := make([]blocks.Block, 0, len(blks)) + for _, blk := range blks { + inline, _, err := decodeCid(blk.Cid()) + if err != nil { + return xerrors.Errorf("error decoding Cid: %w", err) + } + + if inline { + continue + } + toPut = append(toPut, blk) + } + + if len(toPut) > 0 { + return b.bs.PutMany(ctx, toPut) + } + + return nil +} + +func (b *idstore) DeleteBlock(ctx context.Context, cid cid.Cid) error { + inline, _, err := decodeCid(cid) + if err != nil { + return xerrors.Errorf("error decoding Cid: %w", err) + } + + if inline { + return nil + } + + return b.bs.DeleteBlock(ctx, cid) +} + +func (b *idstore) DeleteMany(ctx context.Context, cids []cid.Cid) error { + toDelete := make([]cid.Cid, 0, len(cids)) + for _, cid := range cids { + inline, _, err := decodeCid(cid) + if err != nil { + return xerrors.Errorf("error decoding Cid: %w", err) + } + + if inline { + continue + } + toDelete = append(toDelete, cid) + } + + if len(toDelete) > 0 { + return b.bs.DeleteMany(ctx, toDelete) + } + + return nil +} + +func (b *idstore) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { + return b.bs.AllKeysChan(ctx) +} + +func (b *idstore) HashOnRead(enabled bool) { + b.bs.HashOnRead(enabled) +} + +func (b *idstore) Close() error { + if c, ok := b.bs.(io.Closer); ok { + return c.Close() + } + return nil +} + +func (b *idstore) Flush(ctx context.Context) error { + return b.bs.Flush(ctx) +} diff --git a/blockstore/ipfs.go b/blockstore/ipfs.go new file mode 100644 index 000000000..7b356cd0e --- /dev/null +++ b/blockstore/ipfs.go @@ -0,0 +1,153 @@ +package blockstore + +import ( + "bytes" + "context" + "io" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + httpapi "github.com/ipfs/go-ipfs-http-client" + iface "github.com/ipfs/interface-go-ipfs-core" + "github.com/ipfs/interface-go-ipfs-core/options" + "github.com/ipfs/interface-go-ipfs-core/path" + "github.com/multiformats/go-multiaddr" + "github.com/multiformats/go-multihash" + "golang.org/x/xerrors" +) + +type IPFSBlockstore struct { + ctx context.Context + api, offlineAPI iface.CoreAPI +} + +var _ BasicBlockstore = (*IPFSBlockstore)(nil) + +func NewLocalIPFSBlockstore(ctx context.Context, onlineMode bool) (Blockstore, error) { + localApi, err := httpapi.NewLocalApi() + if err != nil { + return nil, xerrors.Errorf("getting local ipfs api: %w", err) + } + api, err := localApi.WithOptions(options.Api.Offline(!onlineMode)) + if err != nil { + return nil, xerrors.Errorf("setting offline mode: %s", err) + } + + offlineAPI := api + if onlineMode { + offlineAPI, err = localApi.WithOptions(options.Api.Offline(true)) + if err != nil { + return nil, xerrors.Errorf("applying offline mode: %s", err) + } + } + + bs := &IPFSBlockstore{ + ctx: ctx, + api: api, + offlineAPI: offlineAPI, + } + + return Adapt(bs), nil +} + +func NewRemoteIPFSBlockstore(ctx context.Context, maddr multiaddr.Multiaddr, onlineMode bool) (Blockstore, error) { + httpApi, err := httpapi.NewApi(maddr) + if err != nil { + return nil, xerrors.Errorf("setting remote ipfs api: %w", err) + } + api, err := httpApi.WithOptions(options.Api.Offline(!onlineMode)) + if err != nil { + return nil, xerrors.Errorf("applying offline mode: %s", err) + } + + offlineAPI := api + if onlineMode { + offlineAPI, err = httpApi.WithOptions(options.Api.Offline(true)) + if err != nil { + return nil, xerrors.Errorf("applying offline mode: %s", err) + } + } + + bs := &IPFSBlockstore{ + ctx: ctx, + api: api, + offlineAPI: offlineAPI, + } + + return Adapt(bs), nil +} + +func (i *IPFSBlockstore) DeleteBlock(ctx context.Context, cid cid.Cid) error { + return xerrors.Errorf("not supported") +} + +func (i *IPFSBlockstore) Has(ctx context.Context, cid cid.Cid) (bool, error) { + _, err := i.offlineAPI.Block().Stat(ctx, path.IpldPath(cid)) + if err != nil { + // The underlying client is running in Offline mode. + // Stat() will fail with an err if the block isn't in the + // blockstore. If that's the case, return false without + // an error since that's the original intention of this method. + if err.Error() == "blockservice: key not found" { + return false, nil + } + return false, xerrors.Errorf("getting ipfs block: %w", err) + } + + return true, nil +} + +func (i *IPFSBlockstore) Get(ctx context.Context, cid cid.Cid) (blocks.Block, error) { + rd, err := i.api.Block().Get(ctx, path.IpldPath(cid)) + if err != nil { + return nil, xerrors.Errorf("getting ipfs block: %w", err) + } + + data, err := io.ReadAll(rd) + if err != nil { + return nil, err + } + + return blocks.NewBlockWithCid(data, cid) +} + +func (i *IPFSBlockstore) GetSize(ctx context.Context, cid cid.Cid) (int, error) { + st, err := i.api.Block().Stat(ctx, path.IpldPath(cid)) + if err != nil { + return 0, xerrors.Errorf("getting ipfs block: %w", err) + } + + return st.Size(), nil +} + +func (i *IPFSBlockstore) Put(ctx context.Context, block blocks.Block) error { + mhd, err := multihash.Decode(block.Cid().Hash()) + if err != nil { + return err + } + + _, err = i.api.Block().Put(ctx, bytes.NewReader(block.RawData()), + options.Block.Hash(mhd.Code, mhd.Length), + options.Block.Format(multihash.Codes[block.Cid().Type()])) + return err +} + +func (i *IPFSBlockstore) PutMany(ctx context.Context, blocks []blocks.Block) error { + // TODO: could be done in parallel + + for _, block := range blocks { + if err := i.Put(ctx, block); err != nil { + return err + } + } + + return nil +} + +func (i *IPFSBlockstore) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { + return nil, xerrors.Errorf("not supported") +} + +func (i *IPFSBlockstore) HashOnRead(enabled bool) { + return // TODO: We could technically support this, but.. +} diff --git a/blockstore/mem.go b/blockstore/mem.go new file mode 100644 index 000000000..8dbf4b719 --- /dev/null +++ b/blockstore/mem.go @@ -0,0 +1,109 @@ +package blockstore + +import ( + "context" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + ipld "github.com/ipfs/go-ipld-format" +) + +// NewMemory returns a temporary memory-backed blockstore. +func NewMemory() MemBlockstore { + return make(MemBlockstore) +} + +// MemBlockstore is a terminal blockstore that keeps blocks in memory. +// To match behavior of badger blockstore we index by multihash only. +type MemBlockstore map[string]blocks.Block + +func (MemBlockstore) Flush(context.Context) error { return nil } + +func (m MemBlockstore) DeleteBlock(ctx context.Context, k cid.Cid) error { + delete(m, string(k.Hash())) + return nil +} + +func (m MemBlockstore) DeleteMany(ctx context.Context, ks []cid.Cid) error { + for _, k := range ks { + delete(m, string(k.Hash())) + } + return nil +} + +func (m MemBlockstore) Has(ctx context.Context, k cid.Cid) (bool, error) { + _, ok := m[string(k.Hash())] + return ok, nil +} + +func (m MemBlockstore) View(ctx context.Context, k cid.Cid, callback func([]byte) error) error { + b, ok := m[string(k.Hash())] + if !ok { + return ipld.ErrNotFound{Cid: k} + } + return callback(b.RawData()) +} + +func (m MemBlockstore) Get(ctx context.Context, k cid.Cid) (blocks.Block, error) { + b, ok := m[string(k.Hash())] + if !ok { + return nil, ipld.ErrNotFound{Cid: k} + } + if b.Cid().Prefix().Codec != k.Prefix().Codec { + return blocks.NewBlockWithCid(b.RawData(), k) + } + return b, nil +} + +// GetSize returns the CIDs mapped BlockSize +func (m MemBlockstore) GetSize(ctx context.Context, k cid.Cid) (int, error) { + b, ok := m[string(k.Hash())] + if !ok { + return 0, ipld.ErrNotFound{Cid: k} + } + return len(b.RawData()), nil +} + +// Put puts a given block to the underlying datastore +func (m MemBlockstore) Put(ctx context.Context, b blocks.Block) error { + // Convert to a basic block for safety, but try to reuse the existing + // block if it's already a basic block. + k := string(b.Cid().Hash()) + if _, ok := b.(*blocks.BasicBlock); !ok { + // If we already have the block, abort. + if _, ok := m[k]; ok { + return nil + } + // the error is only for debugging. + b, _ = blocks.NewBlockWithCid(b.RawData(), b.Cid()) + } + m[k] = b + return nil +} + +// PutMany puts a slice of blocks at the same time using batching +// capabilities of the underlying datastore whenever possible. +func (m MemBlockstore) PutMany(ctx context.Context, bs []blocks.Block) error { + for _, b := range bs { + _ = m.Put(ctx, b) // can't fail + } + return nil +} + +// AllKeysChan returns a channel from which +// the CIDs in the Blockstore can be read. It should respect +// the given context, closing the channel if it becomes Done. +func (m MemBlockstore) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { + ch := make(chan cid.Cid, len(m)) + for _, b := range m { + ch <- b.Cid() + } + close(ch) + return ch, nil +} + +// HashOnRead specifies if every read block should be +// rehashed to make sure it matches its CID. +func (m MemBlockstore) HashOnRead(enabled bool) { + // no-op +} diff --git a/blockstore/mem_test.go b/blockstore/mem_test.go new file mode 100644 index 000000000..4d4a77624 --- /dev/null +++ b/blockstore/mem_test.go @@ -0,0 +1,45 @@ +package blockstore + +import ( + "context" + "testing" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + mh "github.com/multiformats/go-multihash" + "github.com/stretchr/testify/require" +) + +func TestMemGetCodec(t *testing.T) { + ctx := context.Background() + bs := NewMemory() + + cborArr := []byte{0x82, 1, 2} + + h, err := mh.Sum(cborArr, mh.SHA2_256, -1) + require.NoError(t, err) + + rawCid := cid.NewCidV1(cid.Raw, h) + rawBlk, err := blocks.NewBlockWithCid(cborArr, rawCid) + require.NoError(t, err) + + err = bs.Put(ctx, rawBlk) + require.NoError(t, err) + + cborCid := cid.NewCidV1(cid.DagCBOR, h) + + cborBlk, err := bs.Get(ctx, cborCid) + require.NoError(t, err) + + require.Equal(t, cborCid.Prefix(), cborBlk.Cid().Prefix()) + require.EqualValues(t, cborArr, cborBlk.RawData()) + + // was allocated + require.NotEqual(t, cborBlk, rawBlk) + + gotRawBlk, err := bs.Get(ctx, rawCid) + require.NoError(t, err) + + // not allocated + require.Equal(t, rawBlk, gotRawBlk) +} diff --git a/blockstore/metrics.go b/blockstore/metrics.go new file mode 100644 index 000000000..737690a11 --- /dev/null +++ b/blockstore/metrics.go @@ -0,0 +1,154 @@ +package blockstore + +import ( + "time" + + "go.opencensus.io/stats" + "go.opencensus.io/stats/view" + "go.opencensus.io/tag" +) + +// +// Currently unused, but kept in repo in case we introduce one of the candidate +// cache implementations (Freecache, Ristretto), both of which report these +// metrics. +// + +// CacheMetricsEmitInterval is the interval at which metrics are emitted onto +// OpenCensus. +var CacheMetricsEmitInterval = 5 * time.Second + +var ( + CacheName, _ = tag.NewKey("cache_name") +) + +// CacheMeasures groups all metrics emitted by the blockstore caches. +var CacheMeasures = struct { + HitRatio *stats.Float64Measure + Hits *stats.Int64Measure + Misses *stats.Int64Measure + Entries *stats.Int64Measure + QueriesServed *stats.Int64Measure + Adds *stats.Int64Measure + Updates *stats.Int64Measure + Evictions *stats.Int64Measure + CostAdded *stats.Int64Measure + CostEvicted *stats.Int64Measure + SetsDropped *stats.Int64Measure + SetsRejected *stats.Int64Measure + QueriesDropped *stats.Int64Measure +}{ + HitRatio: stats.Float64("blockstore/cache/hit_ratio", "Hit ratio of blockstore cache", stats.UnitDimensionless), + Hits: stats.Int64("blockstore/cache/hits", "Total number of hits at blockstore cache", stats.UnitDimensionless), + Misses: stats.Int64("blockstore/cache/misses", "Total number of misses at blockstore cache", stats.UnitDimensionless), + Entries: stats.Int64("blockstore/cache/entry_count", "Total number of entries currently in the blockstore cache", stats.UnitDimensionless), + QueriesServed: stats.Int64("blockstore/cache/queries_served", "Total number of queries served by the blockstore cache", stats.UnitDimensionless), + Adds: stats.Int64("blockstore/cache/adds", "Total number of adds to blockstore cache", stats.UnitDimensionless), + Updates: stats.Int64("blockstore/cache/updates", "Total number of updates in blockstore cache", stats.UnitDimensionless), + Evictions: stats.Int64("blockstore/cache/evictions", "Total number of evictions from blockstore cache", stats.UnitDimensionless), + CostAdded: stats.Int64("blockstore/cache/cost_added", "Total cost (byte size) of entries added into blockstore cache", stats.UnitBytes), + CostEvicted: stats.Int64("blockstore/cache/cost_evicted", "Total cost (byte size) of entries evicted by blockstore cache", stats.UnitBytes), + SetsDropped: stats.Int64("blockstore/cache/sets_dropped", "Total number of sets dropped by blockstore cache", stats.UnitDimensionless), + SetsRejected: stats.Int64("blockstore/cache/sets_rejected", "Total number of sets rejected by blockstore cache", stats.UnitDimensionless), + QueriesDropped: stats.Int64("blockstore/cache/queries_dropped", "Total number of queries dropped by blockstore cache", stats.UnitDimensionless), +} + +// CacheViews groups all cache-related default views. +var CacheViews = struct { + HitRatio *view.View + Hits *view.View + Misses *view.View + Entries *view.View + QueriesServed *view.View + Adds *view.View + Updates *view.View + Evictions *view.View + CostAdded *view.View + CostEvicted *view.View + SetsDropped *view.View + SetsRejected *view.View + QueriesDropped *view.View +}{ + HitRatio: &view.View{ + Measure: CacheMeasures.HitRatio, + Aggregation: view.LastValue(), + TagKeys: []tag.Key{CacheName}, + }, + Hits: &view.View{ + Measure: CacheMeasures.Hits, + Aggregation: view.LastValue(), + TagKeys: []tag.Key{CacheName}, + }, + Misses: &view.View{ + Measure: CacheMeasures.Misses, + Aggregation: view.LastValue(), + TagKeys: []tag.Key{CacheName}, + }, + Entries: &view.View{ + Measure: CacheMeasures.Entries, + Aggregation: view.LastValue(), + TagKeys: []tag.Key{CacheName}, + }, + QueriesServed: &view.View{ + Measure: CacheMeasures.QueriesServed, + Aggregation: view.LastValue(), + TagKeys: []tag.Key{CacheName}, + }, + Adds: &view.View{ + Measure: CacheMeasures.Adds, + Aggregation: view.LastValue(), + TagKeys: []tag.Key{CacheName}, + }, + Updates: &view.View{ + Measure: CacheMeasures.Updates, + Aggregation: view.LastValue(), + TagKeys: []tag.Key{CacheName}, + }, + Evictions: &view.View{ + Measure: CacheMeasures.Evictions, + Aggregation: view.LastValue(), + TagKeys: []tag.Key{CacheName}, + }, + CostAdded: &view.View{ + Measure: CacheMeasures.CostAdded, + Aggregation: view.LastValue(), + TagKeys: []tag.Key{CacheName}, + }, + CostEvicted: &view.View{ + Measure: CacheMeasures.CostEvicted, + Aggregation: view.LastValue(), + TagKeys: []tag.Key{CacheName}, + }, + SetsDropped: &view.View{ + Measure: CacheMeasures.SetsDropped, + Aggregation: view.LastValue(), + TagKeys: []tag.Key{CacheName}, + }, + SetsRejected: &view.View{ + Measure: CacheMeasures.SetsRejected, + Aggregation: view.LastValue(), + TagKeys: []tag.Key{CacheName}, + }, + QueriesDropped: &view.View{ + Measure: CacheMeasures.QueriesDropped, + Aggregation: view.LastValue(), + TagKeys: []tag.Key{CacheName}, + }, +} + +// DefaultViews exports all default views for this package. +var DefaultViews = []*view.View{ + CacheViews.HitRatio, + CacheViews.Hits, + CacheViews.Misses, + CacheViews.Entries, + CacheViews.QueriesServed, + CacheViews.Adds, + CacheViews.Updates, + CacheViews.Evictions, + CacheViews.CostAdded, + CacheViews.CostEvicted, + CacheViews.SetsDropped, + CacheViews.SetsRejected, + CacheViews.QueriesDropped, +} diff --git a/blockstore/net.go b/blockstore/net.go new file mode 100644 index 000000000..c4a88cfc9 --- /dev/null +++ b/blockstore/net.go @@ -0,0 +1,426 @@ +package blockstore + +import ( + "bytes" + "context" + "encoding/binary" + "fmt" + "sync" + "sync/atomic" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + ipld "github.com/ipfs/go-ipld-format" + "github.com/libp2p/go-msgio" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" +) + +type NetRPCReqType byte + +const ( + NRpcHas NetRPCReqType = iota + NRpcGet + NRpcGetSize + NRpcPut + NRpcDelete + + // todo cancel req +) + +type NetRPCRespType byte + +const ( + NRpcOK NetRPCRespType = iota + NRpcErr + NRpcMore +) + +type NetRPCErrType byte + +const ( + NRpcErrGeneric NetRPCErrType = iota + NRpcErrNotFound +) + +type NetRpcReq struct { + Type NetRPCReqType + ID uint64 + + Cid []cid.Cid // todo maxsize? + Data [][]byte // todo maxsize? +} + +type NetRpcResp struct { + Type NetRPCRespType + ID uint64 + + // error or cids in allkeys + Data []byte // todo maxsize? + + next <-chan NetRpcResp +} + +type NetRpcErr struct { + Type NetRPCErrType + + Msg string + + // in case of NRpcErrNotFound + Cid *cid.Cid +} + +type NetworkStore struct { + // note: writer is thread-safe + msgStream msgio.ReadWriteCloser + + // atomic + reqCount uint64 + + respLk sync.Mutex + + // respMap is nil after store closes + respMap map[uint64]chan<- NetRpcResp + + closing chan struct{} + closed chan struct{} + + closeLk sync.Mutex + onClose []func() +} + +func NewNetworkStore(mss msgio.ReadWriteCloser) *NetworkStore { + ns := &NetworkStore{ + msgStream: mss, + + respMap: map[uint64]chan<- NetRpcResp{}, + + closing: make(chan struct{}), + closed: make(chan struct{}), + } + + go ns.receive() + + return ns +} + +func (n *NetworkStore) shutdown(msg string) { + if err := n.msgStream.Close(); err != nil { + log.Errorw("closing netstore msg stream", "error", err) + } + + nerr := NetRpcErr{ + Type: NRpcErrGeneric, + Msg: msg, + Cid: nil, + } + + var errb bytes.Buffer + if err := nerr.MarshalCBOR(&errb); err != nil { + log.Errorw("netstore shutdown: error marshaling error", "err", err) + } + + n.respLk.Lock() + for id, resps := range n.respMap { + resps <- NetRpcResp{ + Type: NRpcErr, + ID: id, + Data: errb.Bytes(), + } + } + + n.respMap = nil + + n.respLk.Unlock() +} + +func (n *NetworkStore) OnClose(cb func()) { + n.closeLk.Lock() + defer n.closeLk.Unlock() + + select { + case <-n.closed: + cb() + default: + n.onClose = append(n.onClose, cb) + } +} + +func (n *NetworkStore) receive() { + defer func() { + n.closeLk.Lock() + defer n.closeLk.Unlock() + + close(n.closed) + if n.onClose != nil { + for _, f := range n.onClose { + f() + } + } + }() + + for { + select { + case <-n.closing: + n.shutdown("netstore stopping") + return + default: + } + + msg, err := n.msgStream.ReadMsg() + if err != nil { + n.shutdown(fmt.Sprintf("netstore ReadMsg: %s", err)) + return + } + + var resp NetRpcResp + if err := resp.UnmarshalCBOR(bytes.NewReader(msg)); err != nil { + n.shutdown(fmt.Sprintf("unmarshaling netstore response: %s", err)) + return + } + + n.msgStream.ReleaseMsg(msg) + + n.respLk.Lock() + if ch, ok := n.respMap[resp.ID]; ok { + if resp.Type == NRpcMore { + nch := make(chan NetRpcResp, 1) + resp.next = nch + n.respMap[resp.ID] = nch + } else { + delete(n.respMap, resp.ID) + } + + ch <- resp + } + n.respLk.Unlock() + } +} + +func (n *NetworkStore) sendRpc(rt NetRPCReqType, cids []cid.Cid, data [][]byte) (uint64, <-chan NetRpcResp, error) { + rid := atomic.AddUint64(&n.reqCount, 1) + + respCh := make(chan NetRpcResp, 1) // todo pool? + + n.respLk.Lock() + if n.respMap == nil { + n.respLk.Unlock() + return 0, nil, xerrors.Errorf("netstore closed") + } + n.respMap[rid] = respCh + n.respLk.Unlock() + + req := NetRpcReq{ + Type: rt, + ID: rid, + Cid: cids, + Data: data, + } + + var rbuf bytes.Buffer // todo buffer pool + if err := req.MarshalCBOR(&rbuf); err != nil { + n.respLk.Lock() + defer n.respLk.Unlock() + + if n.respMap == nil { + return 0, nil, xerrors.Errorf("netstore closed") + } + delete(n.respMap, rid) + + return 0, nil, err + } + + if err := n.msgStream.WriteMsg(rbuf.Bytes()); err != nil { + n.respLk.Lock() + defer n.respLk.Unlock() + + if n.respMap == nil { + return 0, nil, xerrors.Errorf("netstore closed") + } + delete(n.respMap, rid) + + return 0, nil, err + } + + return rid, respCh, nil +} + +func (n *NetworkStore) waitResp(ctx context.Context, rch <-chan NetRpcResp, rid uint64) (NetRpcResp, error) { + select { + case resp := <-rch: + if resp.Type == NRpcErr { + var e NetRpcErr + if err := e.UnmarshalCBOR(bytes.NewReader(resp.Data)); err != nil { + return NetRpcResp{}, xerrors.Errorf("unmarshaling error data: %w", err) + } + + var err error + switch e.Type { + case NRpcErrNotFound: + if e.Cid != nil { + err = ipld.ErrNotFound{ + Cid: *e.Cid, + } + } else { + err = xerrors.Errorf("block not found, but cid was null") + } + case NRpcErrGeneric: + err = xerrors.Errorf("generic error") + default: + err = xerrors.Errorf("unknown error type") + } + + return NetRpcResp{}, xerrors.Errorf("netstore error response: %s (%w)", e.Msg, err) + } + + return resp, nil + case <-ctx.Done(): + // todo send cancel req + + n.respLk.Lock() + if n.respMap != nil { + delete(n.respMap, rid) + } + n.respLk.Unlock() + + return NetRpcResp{}, ctx.Err() + } +} + +func (n *NetworkStore) Has(ctx context.Context, c cid.Cid) (bool, error) { + req, rch, err := n.sendRpc(NRpcHas, []cid.Cid{c}, nil) + if err != nil { + return false, err + } + + resp, err := n.waitResp(ctx, rch, req) + if err != nil { + return false, err + } + + if len(resp.Data) != 1 { + return false, xerrors.Errorf("expected reposnse length to be 1 byte") + } + switch resp.Data[0] { + case cbg.CborBoolTrue[0]: + return true, nil + case cbg.CborBoolFalse[0]: + return false, nil + default: + return false, xerrors.Errorf("has: bad response: %x", resp.Data[0]) + } +} + +func (n *NetworkStore) Get(ctx context.Context, c cid.Cid) (blocks.Block, error) { + req, rch, err := n.sendRpc(NRpcGet, []cid.Cid{c}, nil) + if err != nil { + return nil, err + } + + resp, err := n.waitResp(ctx, rch, req) + if err != nil { + return nil, err + } + + return blocks.NewBlockWithCid(resp.Data, c) +} + +func (n *NetworkStore) View(ctx context.Context, c cid.Cid, callback func([]byte) error) error { + req, rch, err := n.sendRpc(NRpcGet, []cid.Cid{c}, nil) + if err != nil { + return err + } + + resp, err := n.waitResp(ctx, rch, req) + if err != nil { + return err + } + + return callback(resp.Data) // todo return buf to pool +} + +func (n *NetworkStore) GetSize(ctx context.Context, c cid.Cid) (int, error) { + req, rch, err := n.sendRpc(NRpcGetSize, []cid.Cid{c}, nil) + if err != nil { + return 0, err + } + + resp, err := n.waitResp(ctx, rch, req) + if err != nil { + return 0, err + } + + if len(resp.Data) != 4 { + return 0, xerrors.Errorf("expected getsize response to be 4 bytes, was %d", resp.Data) + } + + return int(binary.LittleEndian.Uint32(resp.Data)), nil +} + +func (n *NetworkStore) Put(ctx context.Context, block blocks.Block) error { + return n.PutMany(ctx, []blocks.Block{block}) +} + +func (n *NetworkStore) PutMany(ctx context.Context, blocks []blocks.Block) error { + // todo pool + cids := make([]cid.Cid, len(blocks)) + blkDatas := make([][]byte, len(blocks)) + for i, block := range blocks { + cids[i] = block.Cid() + blkDatas[i] = block.RawData() + } + + req, rch, err := n.sendRpc(NRpcPut, cids, blkDatas) + if err != nil { + return err + } + + _, err = n.waitResp(ctx, rch, req) + if err != nil { + return err + } + + return nil +} + +func (n *NetworkStore) DeleteBlock(ctx context.Context, c cid.Cid) error { + return n.DeleteMany(ctx, []cid.Cid{c}) +} + +func (n *NetworkStore) DeleteMany(ctx context.Context, cids []cid.Cid) error { + req, rch, err := n.sendRpc(NRpcDelete, cids, nil) + if err != nil { + return err + } + + _, err = n.waitResp(ctx, rch, req) + if err != nil { + return err + } + + return nil +} + +func (n *NetworkStore) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { + return nil, xerrors.Errorf("not supported") +} + +func (n *NetworkStore) HashOnRead(enabled bool) { + // todo + return +} + +func (*NetworkStore) Flush(context.Context) error { return nil } + +func (n *NetworkStore) Stop(ctx context.Context) error { + close(n.closing) + + select { + case <-n.closed: + return nil + case <-ctx.Done(): + return ctx.Err() + } +} + +var _ Blockstore = &NetworkStore{} diff --git a/blockstore/net_serve.go b/blockstore/net_serve.go new file mode 100644 index 000000000..2540c845e --- /dev/null +++ b/blockstore/net_serve.go @@ -0,0 +1,237 @@ +package blockstore + +import ( + "bytes" + "context" + "encoding/binary" + + block "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + ipld "github.com/ipfs/go-ipld-format" + "github.com/libp2p/go-msgio" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" +) + +type NetworkStoreHandler struct { + msgStream msgio.ReadWriteCloser + + bs Blockstore +} + +// NOTE: This code isn't yet hardened to accept untrusted input. See TODOs here and in net.go +func HandleNetBstoreStream(ctx context.Context, bs Blockstore, mss msgio.ReadWriteCloser) *NetworkStoreHandler { + ns := &NetworkStoreHandler{ + msgStream: mss, + bs: bs, + } + + go ns.handle(ctx) + + return ns +} + +func (h *NetworkStoreHandler) handle(ctx context.Context) { + defer func() { + if err := h.msgStream.Close(); err != nil { + log.Errorw("error closing blockstore stream", "error", err) + } + }() + + for { + var req NetRpcReq + + ms, err := h.msgStream.ReadMsg() + if err != nil { + log.Warnw("bstore stream err", "error", err) + return + } + + if err := req.UnmarshalCBOR(bytes.NewReader(ms)); err != nil { + return + } + + h.msgStream.ReleaseMsg(ms) + + switch req.Type { + case NRpcHas: + if len(req.Cid) != 1 { + if err := h.respondError(req.ID, xerrors.New("expected request for 1 cid"), cid.Undef); err != nil { + log.Warnw("writing error response", "error", err) + return + } + continue + } + + res, err := h.bs.Has(ctx, req.Cid[0]) + if err != nil { + if err := h.respondError(req.ID, err, req.Cid[0]); err != nil { + log.Warnw("writing error response", "error", err) + return + } + continue + } + + var resData [1]byte + if res { + resData[0] = cbg.CborBoolTrue[0] + } else { + resData[0] = cbg.CborBoolFalse[0] + } + + if err := h.respond(req.ID, NRpcOK, resData[:]); err != nil { + log.Warnw("writing response", "error", err) + return + } + + case NRpcGet: + if len(req.Cid) != 1 { + if err := h.respondError(req.ID, xerrors.New("expected request for 1 cid"), cid.Undef); err != nil { + log.Warnw("writing error response", "error", err) + return + } + continue + } + + err := h.bs.View(ctx, req.Cid[0], func(bdata []byte) error { + return h.respond(req.ID, NRpcOK, bdata) + }) + if err != nil { + if err := h.respondError(req.ID, err, req.Cid[0]); err != nil { + log.Warnw("writing error response", "error", err) + return + } + continue + } + + case NRpcGetSize: + if len(req.Cid) != 1 { + if err := h.respondError(req.ID, xerrors.New("expected request for 1 cid"), cid.Undef); err != nil { + log.Warnw("writing error response", "error", err) + return + } + continue + } + + sz, err := h.bs.GetSize(ctx, req.Cid[0]) + if err != nil { + if err := h.respondError(req.ID, err, req.Cid[0]); err != nil { + log.Warnw("writing error response", "error", err) + return + } + continue + } + + var resData [4]byte + binary.LittleEndian.PutUint32(resData[:], uint32(sz)) + + if err := h.respond(req.ID, NRpcOK, resData[:]); err != nil { + log.Warnw("writing response", "error", err) + return + } + + case NRpcPut: + blocks := make([]block.Block, len(req.Cid)) + + if len(req.Cid) != len(req.Data) { + if err := h.respondError(req.ID, xerrors.New("cid count didn't match data count"), cid.Undef); err != nil { + log.Warnw("writing error response", "error", err) + } + return + } + + for i := range req.Cid { + blocks[i], err = block.NewBlockWithCid(req.Data[i], req.Cid[i]) + if err != nil { + log.Warnw("make block", "error", err) + return + } + } + + err := h.bs.PutMany(ctx, blocks) + if err != nil { + if err := h.respondError(req.ID, err, cid.Undef); err != nil { + log.Warnw("writing error response", "error", err) + return + } + continue + } + + if err := h.respond(req.ID, NRpcOK, []byte{}); err != nil { + log.Warnw("writing response", "error", err) + return + } + case NRpcDelete: + err := h.bs.DeleteMany(ctx, req.Cid) + if err != nil { + if err := h.respondError(req.ID, err, cid.Undef); err != nil { + log.Warnw("writing error response", "error", err) + return + } + continue + } + + if err := h.respond(req.ID, NRpcOK, []byte{}); err != nil { + log.Warnw("writing response", "error", err) + return + } + default: + if err := h.respondError(req.ID, xerrors.New("unsupported request type"), cid.Undef); err != nil { + log.Warnw("writing error response", "error", err) + return + } + continue + } + } +} + +func (h *NetworkStoreHandler) respondError(req uint64, uerr error, c cid.Cid) error { + var resp NetRpcResp + resp.ID = req + resp.Type = NRpcErr + + nerr := NetRpcErr{ + Type: NRpcErrGeneric, + Msg: uerr.Error(), + } + if ipld.IsNotFound(uerr) { + nerr.Type = NRpcErrNotFound + nerr.Cid = &c + } + + var edata bytes.Buffer + if err := nerr.MarshalCBOR(&edata); err != nil { + return xerrors.Errorf("marshaling error data: %w", err) + } + + resp.Data = edata.Bytes() + + var msg bytes.Buffer + if err := resp.MarshalCBOR(&msg); err != nil { + return xerrors.Errorf("marshaling error response: %w", err) + } + + if err := h.msgStream.WriteMsg(msg.Bytes()); err != nil { + return xerrors.Errorf("write error response: %w", err) + } + + return nil +} + +func (h *NetworkStoreHandler) respond(req uint64, rt NetRPCRespType, data []byte) error { + var resp NetRpcResp + resp.ID = req + resp.Type = rt + resp.Data = data + + var msg bytes.Buffer + if err := resp.MarshalCBOR(&msg); err != nil { + return xerrors.Errorf("marshaling response: %w", err) + } + + if err := h.msgStream.WriteMsg(msg.Bytes()); err != nil { + return xerrors.Errorf("write response: %w", err) + } + + return nil +} diff --git a/blockstore/net_test.go b/blockstore/net_test.go new file mode 100644 index 000000000..d8c33818e --- /dev/null +++ b/blockstore/net_test.go @@ -0,0 +1,63 @@ +package blockstore + +import ( + "context" + "fmt" + "io" + "testing" + + block "github.com/ipfs/go-block-format" + ipld "github.com/ipfs/go-ipld-format" + "github.com/libp2p/go-msgio" + "github.com/stretchr/testify/require" +) + +func TestNetBstore(t *testing.T) { + ctx := context.Background() + + cr, sw := io.Pipe() + sr, cw := io.Pipe() + + cm := msgio.Combine(msgio.NewWriter(cw), msgio.NewReader(cr)) + sm := msgio.Combine(msgio.NewWriter(sw), msgio.NewReader(sr)) + + bbs := NewMemorySync() + _ = HandleNetBstoreStream(ctx, bbs, sm) + + nbs := NewNetworkStore(cm) + + tb1 := block.NewBlock([]byte("aoeu")) + + h, err := nbs.Has(ctx, tb1.Cid()) + require.NoError(t, err) + require.False(t, h) + + err = nbs.Put(ctx, tb1) + require.NoError(t, err) + + h, err = nbs.Has(ctx, tb1.Cid()) + require.NoError(t, err) + require.True(t, h) + + sz, err := nbs.GetSize(ctx, tb1.Cid()) + require.NoError(t, err) + require.Equal(t, 4, sz) + + err = nbs.DeleteBlock(ctx, tb1.Cid()) + require.NoError(t, err) + + h, err = nbs.Has(ctx, tb1.Cid()) + require.NoError(t, err) + require.False(t, h) + + _, err = nbs.Get(ctx, tb1.Cid()) + fmt.Println(err) + require.True(t, ipld.IsNotFound(err)) + + err = nbs.Put(ctx, tb1) + require.NoError(t, err) + + b, err := nbs.Get(ctx, tb1.Cid()) + require.NoError(t, err) + require.Equal(t, "aoeu", string(b.RawData())) +} diff --git a/blockstore/net_ws.go b/blockstore/net_ws.go new file mode 100644 index 000000000..5c9a70d84 --- /dev/null +++ b/blockstore/net_ws.go @@ -0,0 +1,100 @@ +package blockstore + +import ( + "bytes" + "context" + + "github.com/gorilla/websocket" + "github.com/libp2p/go-msgio" + "golang.org/x/xerrors" +) + +type wsWrapper struct { + wc *websocket.Conn + + nextMsg []byte +} + +func (w *wsWrapper) Read(b []byte) (int, error) { + return 0, xerrors.New("read unsupported") +} + +func (w *wsWrapper) ReadMsg() ([]byte, error) { + if w.nextMsg != nil { + nm := w.nextMsg + w.nextMsg = nil + return nm, nil + } + + mt, r, err := w.wc.NextReader() + if err != nil { + return nil, err + } + + switch mt { + case websocket.BinaryMessage, websocket.TextMessage: + default: + return nil, xerrors.Errorf("unexpected message type") + } + + // todo pool + // todo limit sizes + var mbuf bytes.Buffer + if _, err := mbuf.ReadFrom(r); err != nil { + return nil, err + } + + return mbuf.Bytes(), nil +} + +func (w *wsWrapper) ReleaseMsg(bytes []byte) { + // todo use a pool +} + +func (w *wsWrapper) NextMsgLen() (int, error) { + if w.nextMsg != nil { + return len(w.nextMsg), nil + } + + mt, msg, err := w.wc.ReadMessage() + if err != nil { + return 0, err + } + + switch mt { + case websocket.BinaryMessage, websocket.TextMessage: + default: + return 0, xerrors.Errorf("unexpected message type") + } + + w.nextMsg = msg + return len(w.nextMsg), nil +} + +func (w *wsWrapper) Write(bytes []byte) (int, error) { + return 0, xerrors.New("write unsupported") +} + +func (w *wsWrapper) WriteMsg(bytes []byte) error { + return w.wc.WriteMessage(websocket.BinaryMessage, bytes) +} + +func (w *wsWrapper) Close() error { + return w.wc.Close() +} + +var _ msgio.ReadWriteCloser = &wsWrapper{} + +func wsConnToMio(wc *websocket.Conn) msgio.ReadWriteCloser { + return &wsWrapper{ + wc: wc, + } +} + +func HandleNetBstoreWS(ctx context.Context, bs Blockstore, wc *websocket.Conn) *NetworkStoreHandler { + return HandleNetBstoreStream(ctx, bs, wsConnToMio(wc)) +} + +func NewNetworkStoreWS(wc *websocket.Conn) *NetworkStore { + return NewNetworkStore(wsConnToMio(wc)) +} diff --git a/blockstore/splitstore/README.md b/blockstore/splitstore/README.md new file mode 100644 index 000000000..1490004cf --- /dev/null +++ b/blockstore/splitstore/README.md @@ -0,0 +1,132 @@ +# SplitStore: An actively scalable blockstore for the Filecoin chain + +The SplitStore was first introduced in lotus v1.5.1, as an experiment +in reducing the performance impact of large blockstores. + +With lotus v1.11.1, we introduce the next iteration in design and +implementation, which we call SplitStore v1. + +The new design (see [#6474](https://github.com/filecoin-project/lotus/pull/6474) +evolves the splitstore to be a freestanding compacting blockstore that +allows us to keep a small (60-100GB) working set in a hot blockstore +and reliably archive out of scope objects in a coldstore. The +coldstore can also be a discard store, whereby out of scope objects +are discarded or a regular badger blockstore (the default), which can +be periodically garbage collected according to configurable user +retention policies. + +To enable the splitstore, edit `.lotus/config.toml` and add the following: +``` +[Chainstore] + EnableSplitstore = true +``` + +If you intend to use the discard coldstore, your also need to add the following: +``` + [Chainstore.Splitstore] + ColdStoreType = "discard" +``` +In general you _should not_ have to use the discard store, unless you +are running a network assistive node (like a bootstrapper or booster) +or have very constrained hardware with not enough disk space to +maintain a coldstore, even with garbage collection. It is also appropriate +for small nodes that are simply watching the chain. + +*Warning:* Using the discard store for a general purpose node is discouraged, unless +you really know what you are doing. Use it at your own risk. + +## Configuration Options + +These are options in the `[Chainstore.Splitstore]` section of the configuration: + +- `HotStoreType` -- specifies the type of hotstore to use. + The only currently supported option is `"badger"`. +- `ColdStoreType` -- specifies the type of coldstore to use. + The default value is `"universal"`, which will use the initial monolith blockstore + as the coldstore. + The other possible value is `"discard"`, as outlined above, which is specialized for + running without a coldstore. Note that the discard store wraps the initial monolith + blockstore and discards writes; this is necessary to support syncing from a snapshot. +- `MarkSetType` -- specifies the type of markset to use during compaction. + The markset is the data structure used by compaction/gc to track live objects. + The default value is "badger", which will use a disk backed markset using badger. + If you have a lot of memory (48G or more) you can also use "map", which will use + an in memory markset, speeding up compaction at the cost of higher memory usage. + Note: If you are using a VPS with a network volume, you need to provision at least + 3000 IOPs with the badger markset. +- `HotStoreMessageRetention` -- specifies how many finalities, beyond the 4 + finalities maintained by default, to maintain messages and message receipts in the + hotstore. This is useful for assistive nodes that want to support syncing for other + nodes beyond 4 finalities, while running with the discard coldstore option. + It is also useful for miners who accept deals and need to lookback messages beyond + the 4 finalities, which would otherwise hit the coldstore. +- `HotStoreFullGCFrequency` -- specifies how frequenty to garbage collect the hotstore + using full (moving) GC. + The default value is 20, which uses full GC every 20 compactions (about once a week); + set to 0 to disable full GC altogether. + Rationale: badger supports online GC, and this is used by default. However it has proven to + be ineffective in practice with the hotstore size slowly creeping up. In order to address this, + we have added moving GC support in our badger wrapper, which can effectively reclaim all space. + The downside is that it takes a bit longer to perform a moving GC and you also need enough + space to house the new hotstore while the old one is still live. + + +## Operation + +When the splitstore is first enabled, the existing blockstore becomes +the coldstore and a fresh hotstore is initialized. + +The hotstore is warmed up on first startup so as to load all chain +headers and state roots in the current head. This allows us to +immediately gain the performance benefits of a smallerblockstore which +can be substantial for full archival nodes. + +All new writes are directed to the hotstore, while reads first hit the +hotstore, with fallback to the coldstore. + +Once 5 finalities have ellapsed, and every finality henceforth, the +blockstore _compacts_. Compaction is the process of moving all +unreachable objects within the last 4 finalities from the hotstore to +the coldstore. If the system is configured with a discard coldstore, +these objects are discarded. Note that chain headers, all the way to +genesis, are considered reachable. Stateroots and messages are +considered reachable only within the last 4 finalities, unless there +is a live reference to them. + +## Compaction + +Compaction works transactionally with the following algorithm: +- We prepare a transaction, whereby all i/o referenced objects through the API are tracked. +- We walk the chain and mark reachable objects, keeping 4 finalities of state roots and messages and all headers all the way to genesis. +- Once the chain walk is complete, we begin full transaction protection with concurrent marking; we walk and mark all references created during the chain walk. On the same time, all I/O through the API concurrently marks objects as live references. +- We collect cold objects by iterating through the hotstore and checking the mark set; if an object is not marked, then it is candidate for purge. +- When running with a coldstore, we next copy all cold objects to the coldstore. +- At this point we are ready to begin purging: + - We sort cold objects heaviest first, so as to never delete the consituents of a DAG before the DAG itself (which would leave dangling references) + - We delete in small batches taking a lock; each batch is checked again for marks, from the concurrent transactional mark, so as to never delete anything live +- We then end the transaction and compact/gc the hotstore. + +As of [#8008](https://github.com/filecoin-project/lotus/pull/8008) the compaction algorithm has been +modified to eliminate sorting and maintain the cold object set on disk. This drastically reduces +memory usage; in fact, when using badger as the markset compaction uses very little memory, and +it should be now possible to run splitstore with 32GB of RAM or less without danger of running out of +memory during compaction. + +## Garbage Collection + +TBD -- see [#6577](https://github.com/filecoin-project/lotus/issues/6577) + +## Utilities + +`lotus-shed` has a `splitstore` command which provides some utilities: + +- `rollback` -- rolls back a splitstore installation. + This command copies the hotstore on top of the coldstore, and then deletes the splitstore + directory and associated metadata keys. + It can also optionally compact/gc the coldstore after the copy (with the `--gc-coldstore` flag) + and automatically rewrite the lotus config to disable splitstore (with the `--rewrite-config` flag). + Note: the node *must be stopped* before running this command. +- `clear` -- clears a splitstore installation for restart from snapshot. +- `check` -- asynchronously runs a basic healthcheck on the splitstore. + The results are appended to `/datastore/splitstore/check.txt`. +- `info` -- prints some basic information about the splitstore. diff --git a/blockstore/splitstore/checkpoint.go b/blockstore/splitstore/checkpoint.go new file mode 100644 index 000000000..a8b41b605 --- /dev/null +++ b/blockstore/splitstore/checkpoint.go @@ -0,0 +1,117 @@ +package splitstore + +import ( + "bufio" + "io" + "os" + + "github.com/ipfs/go-cid" + mh "github.com/multiformats/go-multihash" + "golang.org/x/xerrors" +) + +type Checkpoint struct { + file *os.File + buf *bufio.Writer +} + +func NewCheckpoint(path string) (*Checkpoint, error) { + file, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY|os.O_SYNC, 0644) + if err != nil { + return nil, xerrors.Errorf("error creating checkpoint: %w", err) + } + buf := bufio.NewWriter(file) + + return &Checkpoint{ + file: file, + buf: buf, + }, nil +} + +func OpenCheckpoint(path string) (*Checkpoint, cid.Cid, error) { + filein, err := os.Open(path) + if err != nil { + return nil, cid.Undef, xerrors.Errorf("error opening checkpoint for reading: %w", err) + } + defer filein.Close() //nolint:errcheck + + bufin := bufio.NewReader(filein) + start, err := readRawCid(bufin, nil) + if err != nil && err != io.EOF { + return nil, cid.Undef, xerrors.Errorf("error reading cid from checkpoint: %w", err) + } + + fileout, err := os.OpenFile(path, os.O_WRONLY|os.O_SYNC, 0644) + if err != nil { + return nil, cid.Undef, xerrors.Errorf("error opening checkpoint for writing: %w", err) + } + bufout := bufio.NewWriter(fileout) + + return &Checkpoint{ + file: fileout, + buf: bufout, + }, start, nil +} + +func (cp *Checkpoint) Set(c cid.Cid) error { + if _, err := cp.file.Seek(0, io.SeekStart); err != nil { + return xerrors.Errorf("error seeking beginning of checkpoint: %w", err) + } + + if err := writeRawCid(cp.buf, c, true); err != nil { + return xerrors.Errorf("error writing cid to checkpoint: %w", err) + } + + return nil +} + +func (cp *Checkpoint) Close() error { + if cp.file == nil { + return nil + } + + err := cp.file.Close() + cp.file = nil + cp.buf = nil + + return err +} + +func readRawCid(buf *bufio.Reader, hbuf []byte) (cid.Cid, error) { + sz, err := buf.ReadByte() + if err != nil { + return cid.Undef, err // don't wrap EOF as it is not an error here + } + + if hbuf == nil { + hbuf = make([]byte, int(sz)) + } else { + hbuf = hbuf[:int(sz)] + } + + if _, err := io.ReadFull(buf, hbuf); err != nil { + return cid.Undef, xerrors.Errorf("error reading hash: %w", err) // wrap EOF, it's corrupt + } + + hash, err := mh.Cast(hbuf) + if err != nil { + return cid.Undef, xerrors.Errorf("error casting multihash: %w", err) + } + + return cid.NewCidV1(cid.Raw, hash), nil +} + +func writeRawCid(buf *bufio.Writer, c cid.Cid, flush bool) error { + hash := c.Hash() + if err := buf.WriteByte(byte(len(hash))); err != nil { + return err + } + if _, err := buf.Write(hash); err != nil { + return err + } + if flush { + return buf.Flush() + } + + return nil +} diff --git a/blockstore/splitstore/checkpoint_test.go b/blockstore/splitstore/checkpoint_test.go new file mode 100644 index 000000000..241707d65 --- /dev/null +++ b/blockstore/splitstore/checkpoint_test.go @@ -0,0 +1,138 @@ +package splitstore + +import ( + "path/filepath" + "testing" + + "github.com/ipfs/go-cid" + "github.com/multiformats/go-multihash" +) + +func TestCheckpoint(t *testing.T) { + dir := t.TempDir() + + path := filepath.Join(dir, "checkpoint") + + makeCid := func(key string) cid.Cid { + h, err := multihash.Sum([]byte(key), multihash.SHA2_256, -1) + if err != nil { + t.Fatal(err) + } + + return cid.NewCidV1(cid.Raw, h) + } + + k1 := makeCid("a") + k2 := makeCid("b") + k3 := makeCid("c") + k4 := makeCid("d") + + cp, err := NewCheckpoint(path) + if err != nil { + t.Fatal(err) + } + + if err := cp.Set(k1); err != nil { + t.Fatal(err) + } + if err := cp.Set(k2); err != nil { + t.Fatal(err) + } + + if err := cp.Close(); err != nil { + t.Fatal(err) + } + + cp, start, err := OpenCheckpoint(path) + if err != nil { + t.Fatal(err) + } + if !start.Equals(k2) { + t.Fatalf("expected start to be %s; got %s", k2, start) + } + + if err := cp.Set(k3); err != nil { + t.Fatal(err) + } + if err := cp.Set(k4); err != nil { + t.Fatal(err) + } + + if err := cp.Close(); err != nil { + t.Fatal(err) + } + + cp, start, err = OpenCheckpoint(path) + if err != nil { + t.Fatal(err) + } + if !start.Equals(k4) { + t.Fatalf("expected start to be %s; got %s", k4, start) + } + + if err := cp.Close(); err != nil { + t.Fatal(err) + } + + // also test correct operation with an empty checkpoint + cp, err = NewCheckpoint(path) + if err != nil { + t.Fatal(err) + } + + if err := cp.Close(); err != nil { + t.Fatal(err) + } + + cp, start, err = OpenCheckpoint(path) + if err != nil { + t.Fatal(err) + } + + if start.Defined() { + t.Fatal("expected start to be undefined") + } + + if err := cp.Set(k1); err != nil { + t.Fatal(err) + } + if err := cp.Set(k2); err != nil { + t.Fatal(err) + } + + if err := cp.Close(); err != nil { + t.Fatal(err) + } + + cp, start, err = OpenCheckpoint(path) + if err != nil { + t.Fatal(err) + } + if !start.Equals(k2) { + t.Fatalf("expected start to be %s; got %s", k2, start) + } + + if err := cp.Set(k3); err != nil { + t.Fatal(err) + } + if err := cp.Set(k4); err != nil { + t.Fatal(err) + } + + if err := cp.Close(); err != nil { + t.Fatal(err) + } + + cp, start, err = OpenCheckpoint(path) + if err != nil { + t.Fatal(err) + } + if !start.Equals(k4) { + t.Fatalf("expected start to be %s; got %s", k4, start) + } + + if err := cp.Close(); err != nil { + t.Fatal(err) + } + +} diff --git a/blockstore/splitstore/coldset.go b/blockstore/splitstore/coldset.go new file mode 100644 index 000000000..27dd30db4 --- /dev/null +++ b/blockstore/splitstore/coldset.go @@ -0,0 +1,101 @@ +package splitstore + +import ( + "bufio" + "io" + "os" + + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" +) + +type ColdSetWriter struct { + file *os.File + buf *bufio.Writer +} + +type ColdSetReader struct { + file *os.File + buf *bufio.Reader +} + +func NewColdSetWriter(path string) (*ColdSetWriter, error) { + file, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) + if err != nil { + return nil, xerrors.Errorf("error creating coldset: %w", err) + } + buf := bufio.NewWriter(file) + + return &ColdSetWriter{ + file: file, + buf: buf, + }, nil +} + +func NewColdSetReader(path string) (*ColdSetReader, error) { + file, err := os.Open(path) + if err != nil { + return nil, xerrors.Errorf("error opening coldset: %w", err) + } + buf := bufio.NewReader(file) + + return &ColdSetReader{ + file: file, + buf: buf, + }, nil +} + +func (s *ColdSetWriter) Write(c cid.Cid) error { + return writeRawCid(s.buf, c, false) +} + +func (s *ColdSetWriter) Close() error { + if s.file == nil { + return nil + } + + err1 := s.buf.Flush() + err2 := s.file.Close() + s.buf = nil + s.file = nil + + if err1 != nil { + return err1 + } + return err2 +} + +func (s *ColdSetReader) ForEach(f func(cid.Cid) error) error { + hbuf := make([]byte, 256) + for { + next, err := readRawCid(s.buf, hbuf) + if err != nil { + if err == io.EOF { + return nil + } + + return xerrors.Errorf("error reading coldset: %w", err) + } + + if err := f(next); err != nil { + return err + } + } +} + +func (s *ColdSetReader) Reset() error { + _, err := s.file.Seek(0, io.SeekStart) + return err +} + +func (s *ColdSetReader) Close() error { + if s.file == nil { + return nil + } + + err := s.file.Close() + s.file = nil + s.buf = nil + + return err +} diff --git a/blockstore/splitstore/coldset_test.go b/blockstore/splitstore/coldset_test.go new file mode 100644 index 000000000..8fc23a68e --- /dev/null +++ b/blockstore/splitstore/coldset_test.go @@ -0,0 +1,90 @@ +package splitstore + +import ( + "fmt" + "path/filepath" + "testing" + + "github.com/ipfs/go-cid" + "github.com/multiformats/go-multihash" +) + +func TestColdSet(t *testing.T) { + dir := t.TempDir() + + path := filepath.Join(dir, "coldset") + + makeCid := func(i int) cid.Cid { + h, err := multihash.Sum([]byte(fmt.Sprintf("cid.%d", i)), multihash.SHA2_256, -1) + if err != nil { + t.Fatal(err) + } + + return cid.NewCidV1(cid.Raw, h) + } + + const count = 1000 + cids := make([]cid.Cid, 0, count) + for i := 0; i < count; i++ { + cids = append(cids, makeCid(i)) + } + + cw, err := NewColdSetWriter(path) + if err != nil { + t.Fatal(err) + } + + for _, c := range cids { + if err := cw.Write(c); err != nil { + t.Fatal(err) + } + } + + if err := cw.Close(); err != nil { + t.Fatal(err) + } + + cr, err := NewColdSetReader(path) + if err != nil { + t.Fatal(err) + } + + index := 0 + err = cr.ForEach(func(c cid.Cid) error { + if index >= count { + t.Fatal("too many cids") + } + + if !c.Equals(cids[index]) { + t.Fatalf("wrong cid %d; expected %s but got %s", index, cids[index], c) + } + + index++ + return nil + }) + if err != nil { + t.Fatal(err) + } + + if err := cr.Reset(); err != nil { + t.Fatal(err) + } + + index = 0 + err = cr.ForEach(func(c cid.Cid) error { + if index >= count { + t.Fatal("too many cids") + } + + if !c.Equals(cids[index]) { + t.Fatalf("wrong cid; expected %s but got %s", cids[index], c) + } + + index++ + return nil + }) + if err != nil { + t.Fatal(err) + } + +} diff --git a/blockstore/splitstore/debug.go b/blockstore/splitstore/debug.go new file mode 100644 index 000000000..f059ae4ce --- /dev/null +++ b/blockstore/splitstore/debug.go @@ -0,0 +1,272 @@ +package splitstore + +import ( + "crypto/sha256" + "encoding/hex" + "fmt" + "os" + "os/exec" + "path/filepath" + "runtime/debug" + "strings" + "sync" + "time" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + "go.uber.org/multierr" + "golang.org/x/xerrors" +) + +type debugLog struct { + readLog, writeLog, deleteLog, stackLog *debugLogOp + + stackMx sync.Mutex + stackMap map[string]string +} + +type debugLogOp struct { + path string + mx sync.Mutex + log *os.File + count int +} + +func openDebugLog(path string) (*debugLog, error) { + basePath := filepath.Join(path, "debug") + err := os.MkdirAll(basePath, 0755) + if err != nil { + return nil, err + } + + readLog, err := openDebugLogOp(basePath, "read.log") + if err != nil { + return nil, err + } + + writeLog, err := openDebugLogOp(basePath, "write.log") + if err != nil { + _ = readLog.Close() + return nil, err + } + + deleteLog, err := openDebugLogOp(basePath, "delete.log") + if err != nil { + _ = readLog.Close() + _ = writeLog.Close() + return nil, err + } + + stackLog, err := openDebugLogOp(basePath, "stack.log") + if err != nil { + _ = readLog.Close() + _ = writeLog.Close() + _ = deleteLog.Close() + return nil, xerrors.Errorf("error opening stack log: %w", err) + } + + return &debugLog{ + readLog: readLog, + writeLog: writeLog, + deleteLog: deleteLog, + stackLog: stackLog, + stackMap: make(map[string]string), + }, nil +} + +func (d *debugLog) LogReadMiss(cid cid.Cid) { + if d == nil { + return + } + + stack := d.getStack() + err := d.readLog.Log("%s %s %s\n", d.timestamp(), cid, stack) + if err != nil { + log.Warnf("error writing read log: %s", err) + } +} + +func (d *debugLog) LogWrite(blk blocks.Block) { + if d == nil { + return + } + + var stack string + if enableDebugLogWriteTraces { + stack = " " + d.getStack() + } + + err := d.writeLog.Log("%s %s%s\n", d.timestamp(), blk.Cid(), stack) + if err != nil { + log.Warnf("error writing write log: %s", err) + } +} + +func (d *debugLog) LogWriteMany(blks []blocks.Block) { + if d == nil { + return + } + + var stack string + if enableDebugLogWriteTraces { + stack = " " + d.getStack() + } + + now := d.timestamp() + for _, blk := range blks { + err := d.writeLog.Log("%s %s%s\n", now, blk.Cid(), stack) + if err != nil { + log.Warnf("error writing write log: %s", err) + break + } + } +} + +func (d *debugLog) LogDelete(cids []cid.Cid) { + if d == nil { + return + } + + now := d.timestamp() + for _, c := range cids { + err := d.deleteLog.Log("%s %s\n", now, c) + if err != nil { + log.Warnf("error writing delete log: %s", err) + break + } + } +} + +func (d *debugLog) Flush() { + if d == nil { + return + } + + // rotate non-empty logs + d.readLog.Rotate() + d.writeLog.Rotate() + d.deleteLog.Rotate() + d.stackLog.Rotate() +} + +func (d *debugLog) Close() error { + if d == nil { + return nil + } + + err1 := d.readLog.Close() + err2 := d.writeLog.Close() + err3 := d.deleteLog.Close() + err4 := d.stackLog.Close() + + return multierr.Combine(err1, err2, err3, err4) +} + +func (d *debugLog) getStack() string { + sk := d.getNormalizedStackTrace() + hash := sha256.Sum256([]byte(sk)) + key := string(hash[:]) + + d.stackMx.Lock() + repr, ok := d.stackMap[key] + if !ok { + repr = hex.EncodeToString(hash[:]) + d.stackMap[key] = repr + + err := d.stackLog.Log("%s\n%s\n", repr, sk) + if err != nil { + log.Warnf("error writing stack trace for %s: %s", repr, err) + } + } + d.stackMx.Unlock() + + return repr +} + +func (d *debugLog) getNormalizedStackTrace() string { + sk := string(debug.Stack()) + + // Normalization for deduplication + // skip first line -- it's the goroutine + // for each line that ends in a ), remove the call args -- these are the registers + lines := strings.Split(sk, "\n")[1:] + for i, line := range lines { + if len(line) > 0 && line[len(line)-1] == ')' { + idx := strings.LastIndex(line, "(") + if idx < 0 { + continue + } + lines[i] = line[:idx] + } + } + + return strings.Join(lines, "\n") +} + +func (d *debugLog) timestamp() string { + ts, _ := time.Now().MarshalText() + return string(ts) +} + +func openDebugLogOp(basePath, name string) (*debugLogOp, error) { + path := filepath.Join(basePath, name) + file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) + if err != nil { + return nil, xerrors.Errorf("error opening %s: %w", name, err) + } + + return &debugLogOp{path: path, log: file}, nil +} + +func (d *debugLogOp) Close() error { + d.mx.Lock() + defer d.mx.Unlock() + + return d.log.Close() +} + +func (d *debugLogOp) Log(template string, arg ...interface{}) error { + d.mx.Lock() + defer d.mx.Unlock() + + d.count++ + _, err := fmt.Fprintf(d.log, template, arg...) + return err +} + +func (d *debugLogOp) Rotate() { + d.mx.Lock() + defer d.mx.Unlock() + + if d.count == 0 { + return + } + + err := d.log.Close() + if err != nil { + log.Warnf("error closing log (file: %s): %s", d.path, err) + return + } + + arxivPath := fmt.Sprintf("%s-%d", d.path, time.Now().Unix()) + err = os.Rename(d.path, arxivPath) + if err != nil { + log.Warnf("error moving log (file: %s): %s", d.path, err) + return + } + + go func() { + cmd := exec.Command("gzip", arxivPath) + err := cmd.Run() + if err != nil { + log.Warnf("error compressing log (file: %s): %s", arxivPath, err) + } + }() + + d.count = 0 + d.log, err = os.OpenFile(d.path, os.O_WRONLY|os.O_CREATE, 0644) + if err != nil { + log.Warnf("error opening log (file: %s): %s", d.path, err) + return + } +} diff --git a/blockstore/splitstore/markset.go b/blockstore/splitstore/markset.go new file mode 100644 index 000000000..7a584d027 --- /dev/null +++ b/blockstore/splitstore/markset.go @@ -0,0 +1,47 @@ +package splitstore + +import ( + "errors" + + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" +) + +var errMarkSetClosed = errors.New("markset closed") + +// MarkSet is an interface for tracking CIDs during chain and object walks +type MarkSet interface { + ObjectVisitor + Mark(cid.Cid) error + MarkMany([]cid.Cid) error + Has(cid.Cid) (bool, error) + Close() error + + // BeginCriticalSection ensures that the markset is persisted to disk for recovery in case + // of abnormal termination during the critical section span. + BeginCriticalSection() error + // EndCriticalSection ends the critical section span. + EndCriticalSection() +} + +type MarkSetEnv interface { + // New creates a new markset within the environment. + // name is a unique name for this markset, mapped to the filesystem for on-disk persistence. + // sizeHint is a hint about the expected size of the markset + New(name string, sizeHint int64) (MarkSet, error) + // Recover recovers an existing markset persisted on-disk. + Recover(name string) (MarkSet, error) + // Close closes the markset + Close() error +} + +func OpenMarkSetEnv(path string, mtype string) (MarkSetEnv, error) { + switch mtype { + case "map": + return NewMapMarkSetEnv(path) + case "badger": + return NewBadgerMarkSetEnv(path) + default: + return nil, xerrors.Errorf("unknown mark set type %s", mtype) + } +} diff --git a/blockstore/splitstore/markset_badger.go b/blockstore/splitstore/markset_badger.go new file mode 100644 index 000000000..2dac673cd --- /dev/null +++ b/blockstore/splitstore/markset_badger.go @@ -0,0 +1,451 @@ +package splitstore + +import ( + "os" + "path/filepath" + "runtime" + "sync" + + "github.com/dgraph-io/badger/v2" + "github.com/dgraph-io/badger/v2/options" + "github.com/ipfs/go-cid" + "go.uber.org/zap" + "golang.org/x/xerrors" + + "github.com/filecoin-project/lotus/system" +) + +type BadgerMarkSetEnv struct { + path string +} + +var _ MarkSetEnv = (*BadgerMarkSetEnv)(nil) + +type BadgerMarkSet struct { + mx sync.RWMutex + cond sync.Cond + pend map[string]struct{} + writing map[int]map[string]struct{} + writers int + seqno int + version int + persist bool + + db *badger.DB + path string +} + +var _ MarkSet = (*BadgerMarkSet)(nil) + +var badgerMarkSetBatchSize = 16384 + +func NewBadgerMarkSetEnv(path string) (MarkSetEnv, error) { + msPath := filepath.Join(path, "markset.badger") + err := os.MkdirAll(msPath, 0755) //nolint:gosec + if err != nil { + return nil, xerrors.Errorf("error creating markset directory: %w", err) + } + + return &BadgerMarkSetEnv{path: msPath}, nil +} + +func (e *BadgerMarkSetEnv) New(name string, sizeHint int64) (MarkSet, error) { + path := filepath.Join(e.path, name) + + db, err := openBadgerDB(path, false) + if err != nil { + return nil, xerrors.Errorf("error creating badger db: %w", err) + } + + ms := &BadgerMarkSet{ + pend: make(map[string]struct{}), + writing: make(map[int]map[string]struct{}), + db: db, + path: path, + } + ms.cond.L = &ms.mx + + return ms, nil +} + +func (e *BadgerMarkSetEnv) Recover(name string) (MarkSet, error) { + path := filepath.Join(e.path, name) + + if _, err := os.Stat(path); err != nil { + return nil, xerrors.Errorf("error stating badger db path: %w", err) + } + + db, err := openBadgerDB(path, true) + if err != nil { + return nil, xerrors.Errorf("error creating badger db: %w", err) + } + + ms := &BadgerMarkSet{ + pend: make(map[string]struct{}), + writing: make(map[int]map[string]struct{}), + db: db, + path: path, + persist: true, + } + ms.cond.L = &ms.mx + + return ms, nil +} + +func (e *BadgerMarkSetEnv) Close() error { + return nil +} + +func (s *BadgerMarkSet) BeginCriticalSection() error { + s.mx.Lock() + + if s.persist { + s.mx.Unlock() + return nil + } + + var write bool + var seqno int + if len(s.pend) > 0 { + write = true + seqno = s.nextBatch() + } + + s.persist = true + s.mx.Unlock() + + if write { + // all writes sync once perist is true + return s.write(seqno) + } + + // wait for any pending writes and sync + s.mx.Lock() + for s.writers > 0 { + s.cond.Wait() + } + s.mx.Unlock() + + return s.db.Sync() +} + +func (s *BadgerMarkSet) EndCriticalSection() { + s.mx.Lock() + defer s.mx.Unlock() + + s.persist = false +} + +func (s *BadgerMarkSet) Mark(c cid.Cid) error { + s.mx.Lock() + if s.pend == nil { + s.mx.Unlock() + return errMarkSetClosed + } + + write, seqno := s.put(string(c.Hash())) + s.mx.Unlock() + + if write { + return s.write(seqno) + } + + return nil +} + +func (s *BadgerMarkSet) MarkMany(batch []cid.Cid) error { + s.mx.Lock() + if s.pend == nil { + s.mx.Unlock() + return errMarkSetClosed + } + + write, seqno := s.putMany(batch) + s.mx.Unlock() + + if write { + return s.write(seqno) + } + + return nil +} + +func (s *BadgerMarkSet) Has(c cid.Cid) (bool, error) { + s.mx.RLock() + defer s.mx.RUnlock() + + key := c.Hash() + pendKey := string(key) + + has, err := s.tryPending(pendKey) + if has || err != nil { + return has, err + } + + return s.tryDB(key) +} + +func (s *BadgerMarkSet) Visit(c cid.Cid) (bool, error) { + key := c.Hash() + pendKey := string(key) + + s.mx.RLock() + + has, err := s.tryPending(pendKey) + if has || err != nil { + s.mx.RUnlock() + return false, err + } + + has, err = s.tryDB(key) + if has || err != nil { + s.mx.RUnlock() + return false, err + } + + // we need to upgrade the lock to exclusive in order to write; take the version count to see + // if there was another write while we were upgrading + version := s.version + s.mx.RUnlock() + + s.mx.Lock() + // we have to do the check dance again + has, err = s.tryPending(pendKey) + if has || err != nil { + s.mx.Unlock() + return false, err + } + + if version != s.version { + // something was written to the db, we need to check it + has, err = s.tryDB(key) + if has || err != nil { + s.mx.Unlock() + return false, err + } + } + + write, seqno := s.put(pendKey) + s.mx.Unlock() + + if write { + err = s.write(seqno) + } + + return true, err +} + +// reader holds the (r)lock +func (s *BadgerMarkSet) tryPending(key string) (has bool, err error) { + if s.pend == nil { + return false, errMarkSetClosed + } + + if _, ok := s.pend[key]; ok { + return true, nil + } + + for _, wr := range s.writing { + if _, ok := wr[key]; ok { + return true, nil + } + } + + return false, nil +} + +func (s *BadgerMarkSet) tryDB(key []byte) (has bool, err error) { + err = s.db.View(func(txn *badger.Txn) error { + _, err := txn.Get(key) + return err + }) + + switch err { + case nil: + return true, nil + + case badger.ErrKeyNotFound: + return false, nil + + default: + return false, err + } +} + +// writer holds the exclusive lock +func (s *BadgerMarkSet) put(key string) (write bool, seqno int) { + s.pend[key] = struct{}{} + if !s.persist && len(s.pend) < badgerMarkSetBatchSize { + return false, 0 + } + + seqno = s.nextBatch() + return true, seqno +} + +func (s *BadgerMarkSet) putMany(batch []cid.Cid) (write bool, seqno int) { + for _, c := range batch { + key := string(c.Hash()) + s.pend[key] = struct{}{} + } + + if !s.persist && len(s.pend) < badgerMarkSetBatchSize { + return false, 0 + } + + seqno = s.nextBatch() + return true, seqno +} + +func (s *BadgerMarkSet) nextBatch() int { + seqno := s.seqno + s.seqno++ + s.writing[seqno] = s.pend + s.pend = make(map[string]struct{}) + return seqno +} + +func (s *BadgerMarkSet) write(seqno int) (err error) { + s.mx.Lock() + if s.pend == nil { + s.mx.Unlock() + return errMarkSetClosed + } + + pend := s.writing[seqno] + s.writers++ + s.mx.Unlock() + + defer func() { + s.mx.Lock() + defer s.mx.Unlock() + + if err == nil { + delete(s.writing, seqno) + s.version++ + } + + s.writers-- + if s.writers == 0 { + s.cond.Broadcast() + } + }() + + empty := []byte{} // not nil + + batch := s.db.NewWriteBatch() + defer batch.Cancel() + + for k := range pend { + if err = batch.Set([]byte(k), empty); err != nil { + return xerrors.Errorf("error setting batch: %w", err) + } + } + + err = batch.Flush() + if err != nil { + return xerrors.Errorf("error flushing batch to badger markset: %w", err) + } + + s.mx.RLock() + persist := s.persist + s.mx.RUnlock() + + if persist && !system.BadgerFsyncDisable { // WARNING: disabling sync makes recovery from crash during critical section unsound + return s.db.Sync() + } + + return nil +} + +func (s *BadgerMarkSet) Close() error { + s.mx.Lock() + defer s.mx.Unlock() + + if s.pend == nil { + return nil + } + + for s.writers > 0 { + s.cond.Wait() + } + + s.pend = nil + db := s.db + s.db = nil + + return closeBadgerDB(db, s.path, s.persist) +} + +func openBadgerDB(path string, recover bool) (*badger.DB, error) { + // if it is not a recovery, clean up first + if !recover { + err := os.RemoveAll(path) + if err != nil { + return nil, xerrors.Errorf("error clearing markset directory: %w", err) + } + + err = os.MkdirAll(path, 0755) //nolint:gosec + if err != nil { + return nil, xerrors.Errorf("error creating markset directory: %w", err) + } + } + + opts := badger.DefaultOptions(path) + // we manually sync when we are in critical section + opts.SyncWrites = false + // no need to do that + opts.CompactL0OnClose = false + // we store hashes, not much to gain by compression + opts.Compression = options.None + // Note: We use FileIO for loading modes to avoid memory thrashing and interference + // between the system blockstore and the markset. + // It was observed that using the default memory mapped option resulted in + // significant interference and unacceptably high block validation times once the markset + // exceeded 1GB in size. + opts.TableLoadingMode = options.FileIO + opts.ValueLogLoadingMode = options.FileIO + // We increase the number of L0 tables before compaction to make it unlikely to + // be necessary. + opts.NumLevelZeroTables = 20 // default is 5 + opts.NumLevelZeroTablesStall = 30 // default is 10 + // increase the number of compactors from default 2 so that if we ever have to + // compact, it is fast + if runtime.NumCPU()/2 > opts.NumCompactors { + opts.NumCompactors = runtime.NumCPU() / 2 + } + opts.Logger = &badgerLogger{ + SugaredLogger: log.Desugar().WithOptions(zap.AddCallerSkip(1)).Sugar(), + skip2: log.Desugar().WithOptions(zap.AddCallerSkip(2)).Sugar(), + } + + return badger.Open(opts) +} + +func closeBadgerDB(db *badger.DB, path string, persist bool) error { + err := db.Close() + if err != nil { + return xerrors.Errorf("error closing badger markset: %w", err) + } + + if persist { + return nil + } + + err = os.RemoveAll(path) + if err != nil { + return xerrors.Errorf("error deleting badger markset: %w", err) + } + + return nil +} + +// badger logging through go-log +type badgerLogger struct { + *zap.SugaredLogger + skip2 *zap.SugaredLogger +} + +func (b *badgerLogger) Warningf(format string, args ...interface{}) {} +func (b *badgerLogger) Infof(format string, args ...interface{}) {} +func (b *badgerLogger) Debugf(format string, args ...interface{}) {} diff --git a/blockstore/splitstore/markset_map.go b/blockstore/splitstore/markset_map.go new file mode 100644 index 000000000..52efcb8a4 --- /dev/null +++ b/blockstore/splitstore/markset_map.go @@ -0,0 +1,288 @@ +package splitstore + +import ( + "bufio" + "io" + "os" + "path/filepath" + "sync" + + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" +) + +type MapMarkSetEnv struct { + path string +} + +var _ MarkSetEnv = (*MapMarkSetEnv)(nil) + +type MapMarkSet struct { + mx sync.RWMutex + set map[string]struct{} + + persist bool + file *os.File + buf *bufio.Writer + + path string +} + +var _ MarkSet = (*MapMarkSet)(nil) + +func NewMapMarkSetEnv(path string) (*MapMarkSetEnv, error) { + msPath := filepath.Join(path, "markset.map") + err := os.MkdirAll(msPath, 0755) //nolint:gosec + if err != nil { + return nil, xerrors.Errorf("error creating markset directory: %w", err) + } + + return &MapMarkSetEnv{path: msPath}, nil +} + +func (e *MapMarkSetEnv) New(name string, sizeHint int64) (MarkSet, error) { + path := filepath.Join(e.path, name) + return &MapMarkSet{ + set: make(map[string]struct{}, sizeHint), + path: path, + }, nil +} + +func (e *MapMarkSetEnv) Recover(name string) (MarkSet, error) { + path := filepath.Join(e.path, name) + s := &MapMarkSet{ + set: make(map[string]struct{}), + path: path, + } + + in, err := os.Open(path) + if err != nil { + return nil, xerrors.Errorf("error opening markset file for read: %w", err) + } + defer in.Close() //nolint:errcheck + + // wrap a buffered reader to make this faster + buf := bufio.NewReader(in) + for { + var sz byte + if sz, err = buf.ReadByte(); err != nil { + break + } + + key := make([]byte, int(sz)) + if _, err = io.ReadFull(buf, key); err != nil { + break + } + + s.set[string(key)] = struct{}{} + } + + if err != io.EOF { + return nil, xerrors.Errorf("error reading markset file: %w", err) + } + + file, err := os.OpenFile(s.path, os.O_WRONLY|os.O_APPEND, 0) + if err != nil { + return nil, xerrors.Errorf("error opening markset file for write: %w", err) + } + + s.persist = true + s.file = file + s.buf = bufio.NewWriter(file) + + return s, nil +} + +func (e *MapMarkSetEnv) Close() error { + return nil +} + +func (s *MapMarkSet) BeginCriticalSection() error { + s.mx.Lock() + defer s.mx.Unlock() + + if s.set == nil { + return errMarkSetClosed + } + + if s.persist { + return nil + } + + file, err := os.OpenFile(s.path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) + if err != nil { + return xerrors.Errorf("error opening markset file: %w", err) + } + + // wrap a buffered writer to make this faster + s.buf = bufio.NewWriter(file) + for key := range s.set { + if err := s.writeKey([]byte(key), false); err != nil { + _ = file.Close() + s.buf = nil + return err + } + } + if err := s.buf.Flush(); err != nil { + _ = file.Close() + s.buf = nil + return xerrors.Errorf("error flushing markset file buffer: %w", err) + } + + s.file = file + s.persist = true + + return nil +} + +func (s *MapMarkSet) EndCriticalSection() { + s.mx.Lock() + defer s.mx.Unlock() + + if !s.persist { + return + } + + _ = s.file.Close() + _ = os.Remove(s.path) + s.file = nil + s.buf = nil + s.persist = false +} + +func (s *MapMarkSet) Mark(c cid.Cid) error { + s.mx.Lock() + defer s.mx.Unlock() + + if s.set == nil { + return errMarkSetClosed + } + + hash := c.Hash() + s.set[string(hash)] = struct{}{} + + if s.persist { + if err := s.writeKey(hash, true); err != nil { + return err + } + + if err := s.file.Sync(); err != nil { + return xerrors.Errorf("error syncing markset: %w", err) + } + } + + return nil +} + +func (s *MapMarkSet) MarkMany(batch []cid.Cid) error { + s.mx.Lock() + defer s.mx.Unlock() + + if s.set == nil { + return errMarkSetClosed + } + + for _, c := range batch { + hash := c.Hash() + s.set[string(hash)] = struct{}{} + + if s.persist { + if err := s.writeKey(hash, false); err != nil { + return err + } + } + } + + if s.persist { + if err := s.buf.Flush(); err != nil { + return xerrors.Errorf("error flushing markset buffer to disk: %w", err) + } + + if err := s.file.Sync(); err != nil { + return xerrors.Errorf("error syncing markset: %w", err) + } + } + + return nil +} + +func (s *MapMarkSet) Has(cid cid.Cid) (bool, error) { + s.mx.RLock() + defer s.mx.RUnlock() + + if s.set == nil { + return false, errMarkSetClosed + } + + _, ok := s.set[string(cid.Hash())] + return ok, nil +} + +func (s *MapMarkSet) Visit(c cid.Cid) (bool, error) { + s.mx.Lock() + defer s.mx.Unlock() + + if s.set == nil { + return false, errMarkSetClosed + } + + hash := c.Hash() + key := string(hash) + if _, ok := s.set[key]; ok { + return false, nil + } + + s.set[key] = struct{}{} + + if s.persist { + if err := s.writeKey(hash, true); err != nil { + return false, err + } + if err := s.file.Sync(); err != nil { + return false, xerrors.Errorf("error syncing markset: %w", err) + } + } + + return true, nil +} + +func (s *MapMarkSet) Close() error { + s.mx.Lock() + defer s.mx.Unlock() + + if s.set == nil { + return nil + } + + s.set = nil + + if s.file != nil { + if err := s.file.Close(); err != nil { + log.Warnf("error closing markset file: %s", err) + } + + if !s.persist { + if err := os.Remove(s.path); err != nil { + log.Warnf("error removing markset file: %s", err) + } + } + } + + return nil +} + +func (s *MapMarkSet) writeKey(k []byte, flush bool) error { + if err := s.buf.WriteByte(byte(len(k))); err != nil { + return xerrors.Errorf("error writing markset key length to disk: %w", err) + } + if _, err := s.buf.Write(k); err != nil { + return xerrors.Errorf("error writing markset key to disk: %w", err) + } + if flush { + if err := s.buf.Flush(); err != nil { + return xerrors.Errorf("error flushing markset buffer to disk: %w", err) + } + } + + return nil +} diff --git a/blockstore/splitstore/markset_test.go b/blockstore/splitstore/markset_test.go new file mode 100644 index 000000000..6cc481852 --- /dev/null +++ b/blockstore/splitstore/markset_test.go @@ -0,0 +1,517 @@ +// stm: #unit +package splitstore + +import ( + "testing" + + "github.com/ipfs/go-cid" + "github.com/multiformats/go-multihash" +) + +func TestMapMarkSet(t *testing.T) { + //stm: @SPLITSTORE_MARKSET_CREATE_001, @SPLITSTORE_MARKSET_HAS_001, @@SPLITSTORE_MARKSET_MARK_001 + //stm: @SPLITSTORE_MARKSET_CLOSE_001, @SPLITSTORE_MARKSET_CREATE_VISITOR_001 + testMarkSet(t, "map") + testMarkSetRecovery(t, "map") + testMarkSetMarkMany(t, "map") + testMarkSetVisitor(t, "map") + testMarkSetVisitorRecovery(t, "map") +} + +func TestBadgerMarkSet(t *testing.T) { + //stm: @SPLITSTORE_MARKSET_CREATE_001, @SPLITSTORE_MARKSET_HAS_001, @@SPLITSTORE_MARKSET_MARK_001 + //stm: @SPLITSTORE_MARKSET_CLOSE_001, @SPLITSTORE_MARKSET_CREATE_VISITOR_001 + bs := badgerMarkSetBatchSize + badgerMarkSetBatchSize = 1 + t.Cleanup(func() { + badgerMarkSetBatchSize = bs + }) + testMarkSet(t, "badger") + testMarkSetRecovery(t, "badger") + testMarkSetMarkMany(t, "badger") + testMarkSetVisitor(t, "badger") + testMarkSetVisitorRecovery(t, "badger") +} + +func testMarkSet(t *testing.T, lsType string) { + path := t.TempDir() + + env, err := OpenMarkSetEnv(path, lsType) + if err != nil { + t.Fatal(err) + } + defer env.Close() //nolint:errcheck + + // stm: @SPLITSTORE_MARKSET_CREATE_001 + hotSet, err := env.New("hot", 0) + if err != nil { + t.Fatal(err) + } + + coldSet, err := env.New("cold", 0) + if err != nil { + t.Fatal(err) + } + + makeCid := func(key string) cid.Cid { + h, err := multihash.Sum([]byte(key), multihash.SHA2_256, -1) + if err != nil { + t.Fatal(err) + } + + return cid.NewCidV1(cid.Raw, h) + } + + // stm: @SPLITSTORE_MARKSET_HAS_001 + mustHave := func(s MarkSet, cid cid.Cid) { + t.Helper() + has, err := s.Has(cid) + if err != nil { + t.Fatal(err) + } + + if !has { + t.Fatal("mark not found") + } + } + + mustNotHave := func(s MarkSet, cid cid.Cid) { + t.Helper() + has, err := s.Has(cid) + if err != nil { + t.Fatal(err) + } + + if has { + t.Fatal("unexpected mark") + } + } + + k1 := makeCid("a") + k2 := makeCid("b") + k3 := makeCid("c") + k4 := makeCid("d") + + // stm: @SPLITSTORE_MARKSET_MARK_001 + hotSet.Mark(k1) //nolint + hotSet.Mark(k2) //nolint + coldSet.Mark(k3) //nolint + + mustHave(hotSet, k1) + mustHave(hotSet, k2) + mustNotHave(hotSet, k3) + mustNotHave(hotSet, k4) + + mustNotHave(coldSet, k1) + mustNotHave(coldSet, k2) + mustHave(coldSet, k3) + mustNotHave(coldSet, k4) + + // close them and reopen to redo the dance + + err = hotSet.Close() + if err != nil { + t.Fatal(err) + } + + err = coldSet.Close() + if err != nil { + t.Fatal(err) + } + + hotSet, err = env.New("hot", 0) + if err != nil { + t.Fatal(err) + } + + coldSet, err = env.New("cold", 0) + if err != nil { + t.Fatal(err) + } + + hotSet.Mark(k3) //nolint + hotSet.Mark(k4) //nolint + coldSet.Mark(k1) //nolint + + mustNotHave(hotSet, k1) + mustNotHave(hotSet, k2) + mustHave(hotSet, k3) + mustHave(hotSet, k4) + + mustHave(coldSet, k1) + mustNotHave(coldSet, k2) + mustNotHave(coldSet, k3) + mustNotHave(coldSet, k4) + + //stm: @SPLITSTORE_MARKSET_CLOSE_001 + err = hotSet.Close() + if err != nil { + t.Fatal(err) + } + + err = coldSet.Close() + if err != nil { + t.Fatal(err) + } +} + +func testMarkSetVisitor(t *testing.T, lsType string) { + path := t.TempDir() + + env, err := OpenMarkSetEnv(path, lsType) + if err != nil { + t.Fatal(err) + } + defer env.Close() //nolint:errcheck + + //stm: @SPLITSTORE_MARKSET_CREATE_VISITOR_001 + visitor, err := env.New("test", 0) + if err != nil { + t.Fatal(err) + } + defer visitor.Close() //nolint:errcheck + + makeCid := func(key string) cid.Cid { + h, err := multihash.Sum([]byte(key), multihash.SHA2_256, -1) + if err != nil { + t.Fatal(err) + } + + return cid.NewCidV1(cid.Raw, h) + } + + mustVisit := func(v ObjectVisitor, cid cid.Cid) { + visit, err := v.Visit(cid) + if err != nil { + t.Fatal(err) + } + + if !visit { + t.Fatal("object should be visited") + } + } + + mustNotVisit := func(v ObjectVisitor, cid cid.Cid) { + visit, err := v.Visit(cid) + if err != nil { + t.Fatal(err) + } + + if visit { + t.Fatal("unexpected visit") + } + } + + k1 := makeCid("a") + k2 := makeCid("b") + k3 := makeCid("c") + k4 := makeCid("d") + + mustVisit(visitor, k1) + mustVisit(visitor, k2) + mustVisit(visitor, k3) + mustVisit(visitor, k4) + + mustNotVisit(visitor, k1) + mustNotVisit(visitor, k2) + mustNotVisit(visitor, k3) + mustNotVisit(visitor, k4) +} + +func testMarkSetVisitorRecovery(t *testing.T, lsType string) { + path := t.TempDir() + + env, err := OpenMarkSetEnv(path, lsType) + if err != nil { + t.Fatal(err) + } + defer env.Close() //nolint:errcheck + + visitor, err := env.New("test", 0) + if err != nil { + t.Fatal(err) + } + defer visitor.Close() //nolint:errcheck + + makeCid := func(key string) cid.Cid { + h, err := multihash.Sum([]byte(key), multihash.SHA2_256, -1) + if err != nil { + t.Fatal(err) + } + + return cid.NewCidV1(cid.Raw, h) + } + + mustVisit := func(v ObjectVisitor, cid cid.Cid) { + visit, err := v.Visit(cid) + if err != nil { + t.Fatal(err) + } + + if !visit { + t.Fatal("object should be visited") + } + } + + mustNotVisit := func(v ObjectVisitor, cid cid.Cid) { + visit, err := v.Visit(cid) + if err != nil { + t.Fatal(err) + } + + if visit { + t.Fatal("unexpected visit") + } + } + + k1 := makeCid("a") + k2 := makeCid("b") + k3 := makeCid("c") + k4 := makeCid("d") + + mustVisit(visitor, k1) + mustVisit(visitor, k2) + + if err := visitor.BeginCriticalSection(); err != nil { + t.Fatal(err) + } + + mustVisit(visitor, k3) + mustVisit(visitor, k4) + + mustNotVisit(visitor, k1) + mustNotVisit(visitor, k2) + mustNotVisit(visitor, k3) + mustNotVisit(visitor, k4) + + if err := visitor.Close(); err != nil { + t.Fatal(err) + } + + visitor, err = env.Recover("test") + if err != nil { + t.Fatal(err) + } + + mustNotVisit(visitor, k1) + mustNotVisit(visitor, k2) + mustNotVisit(visitor, k3) + mustNotVisit(visitor, k4) + + visitor.EndCriticalSection() + + if err := visitor.Close(); err != nil { + t.Fatal(err) + } + + _, err = env.Recover("test") + if err == nil { + t.Fatal("expected recovery to fail") + } +} + +func testMarkSetRecovery(t *testing.T, lsType string) { + path := t.TempDir() + + env, err := OpenMarkSetEnv(path, lsType) + if err != nil { + t.Fatal(err) + } + defer env.Close() //nolint:errcheck + + markSet, err := env.New("test", 0) + if err != nil { + t.Fatal(err) + } + + makeCid := func(key string) cid.Cid { + h, err := multihash.Sum([]byte(key), multihash.SHA2_256, -1) + if err != nil { + t.Fatal(err) + } + + return cid.NewCidV1(cid.Raw, h) + } + + mustHave := func(s MarkSet, cid cid.Cid) { + t.Helper() + has, err := s.Has(cid) + if err != nil { + t.Fatal(err) + } + + if !has { + t.Fatal("mark not found") + } + } + + mustNotHave := func(s MarkSet, cid cid.Cid) { + t.Helper() + has, err := s.Has(cid) + if err != nil { + t.Fatal(err) + } + + if has { + t.Fatal("unexpected mark") + } + } + + k1 := makeCid("a") + k2 := makeCid("b") + k3 := makeCid("c") + k4 := makeCid("d") + + if err := markSet.Mark(k1); err != nil { + t.Fatal(err) + } + if err := markSet.Mark(k2); err != nil { + t.Fatal(err) + } + + mustHave(markSet, k1) + mustHave(markSet, k2) + mustNotHave(markSet, k3) + mustNotHave(markSet, k4) + + if err := markSet.BeginCriticalSection(); err != nil { + t.Fatal(err) + } + + if err := markSet.Mark(k3); err != nil { + t.Fatal(err) + } + if err := markSet.Mark(k4); err != nil { + t.Fatal(err) + } + + mustHave(markSet, k1) + mustHave(markSet, k2) + mustHave(markSet, k3) + mustHave(markSet, k4) + + if err := markSet.Close(); err != nil { + t.Fatal(err) + } + + markSet, err = env.Recover("test") + if err != nil { + t.Fatal(err) + } + + mustHave(markSet, k1) + mustHave(markSet, k2) + mustHave(markSet, k3) + mustHave(markSet, k4) + + markSet.EndCriticalSection() + + if err := markSet.Close(); err != nil { + t.Fatal(err) + } + + _, err = env.Recover("test") + if err == nil { + t.Fatal("expected recovery to fail") + } +} + +func testMarkSetMarkMany(t *testing.T, lsType string) { + path := t.TempDir() + + env, err := OpenMarkSetEnv(path, lsType) + if err != nil { + t.Fatal(err) + } + defer env.Close() //nolint:errcheck + + markSet, err := env.New("test", 0) + if err != nil { + t.Fatal(err) + } + + makeCid := func(key string) cid.Cid { + h, err := multihash.Sum([]byte(key), multihash.SHA2_256, -1) + if err != nil { + t.Fatal(err) + } + + return cid.NewCidV1(cid.Raw, h) + } + + mustHave := func(s MarkSet, cid cid.Cid) { + t.Helper() + has, err := s.Has(cid) + if err != nil { + t.Fatal(err) + } + + if !has { + t.Fatal("mark not found") + } + } + + mustNotHave := func(s MarkSet, cid cid.Cid) { + t.Helper() + has, err := s.Has(cid) + if err != nil { + t.Fatal(err) + } + + if has { + t.Fatal("unexpected mark") + } + } + + k1 := makeCid("a") + k2 := makeCid("b") + k3 := makeCid("c") + k4 := makeCid("d") + + if err := markSet.MarkMany([]cid.Cid{k1, k2}); err != nil { + t.Fatal(err) + } + + mustHave(markSet, k1) + mustHave(markSet, k2) + mustNotHave(markSet, k3) + mustNotHave(markSet, k4) + + if err := markSet.BeginCriticalSection(); err != nil { + t.Fatal(err) + } + + if err := markSet.MarkMany([]cid.Cid{k3, k4}); err != nil { + t.Fatal(err) + } + + mustHave(markSet, k1) + mustHave(markSet, k2) + mustHave(markSet, k3) + mustHave(markSet, k4) + + if err := markSet.Close(); err != nil { + t.Fatal(err) + } + + markSet, err = env.Recover("test") + if err != nil { + t.Fatal(err) + } + + mustHave(markSet, k1) + mustHave(markSet, k2) + mustHave(markSet, k3) + mustHave(markSet, k4) + + markSet.EndCriticalSection() + + if err := markSet.Close(); err != nil { + t.Fatal(err) + } + + _, err = env.Recover("test") + if err == nil { + t.Fatal("expected recovery to fail") + } +} diff --git a/blockstore/splitstore/splitstore.go b/blockstore/splitstore/splitstore.go new file mode 100644 index 000000000..1f1ba0e99 --- /dev/null +++ b/blockstore/splitstore/splitstore.go @@ -0,0 +1,862 @@ +package splitstore + +import ( + "context" + "errors" + "os" + "sync" + "sync/atomic" + "time" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + dstore "github.com/ipfs/go-datastore" + ipld "github.com/ipfs/go-ipld-format" + logging "github.com/ipfs/go-log/v2" + "go.opencensus.io/stats" + "go.uber.org/multierr" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-state-types/abi" + + bstore "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/metrics" +) + +var ( + // baseEpochKey stores the base epoch (last compaction epoch) in the + // metadata store. + baseEpochKey = dstore.NewKey("/splitstore/baseEpoch") + + // warmupEpochKey stores whether a hot store warmup has been performed. + // On first start, the splitstore will walk the state tree and will copy + // all active blocks into the hotstore. + warmupEpochKey = dstore.NewKey("/splitstore/warmupEpoch") + + // markSetSizeKey stores the current estimate for the mark set size. + // this is first computed at warmup and updated in every compaction + markSetSizeKey = dstore.NewKey("/splitstore/markSetSize") + + // compactionIndexKey stores the compaction index (serial number) + compactionIndexKey = dstore.NewKey("/splitstore/compactionIndex") + + // stores the prune index (serial number) + pruneIndexKey = dstore.NewKey("/splitstore/pruneIndex") + + // stores the base epoch of last prune in the metadata store + pruneEpochKey = dstore.NewKey("/splitstore/pruneEpoch") + + log = logging.Logger("splitstore") + + errClosing = errors.New("splitstore is closing") + + // set this to true if you are debugging the splitstore to enable debug logging + enableDebugLog = false + // set this to true if you want to track origin stack traces in the write log + enableDebugLogWriteTraces = false + + // upgradeBoundary is the boundary before and after an upgrade where we suppress compaction + upgradeBoundary = build.Finality +) + +type CompactType int + +const ( + none CompactType = iota + warmup + hot + cold + check +) + +func init() { + if os.Getenv("LOTUS_SPLITSTORE_DEBUG_LOG") == "1" { + enableDebugLog = true + } + + if os.Getenv("LOTUS_SPLITSTORE_DEBUG_LOG_WRITE_TRACES") == "1" { + enableDebugLogWriteTraces = true + } +} + +type Config struct { + // MarkSetType is the type of mark set to use. + // + // The default value is "map", which uses an in-memory map-backed markset. + // If you are constrained in memory (i.e. compaction runs out of memory), you + // can use "badger", which will use a disk-backed markset using badger. + // Note that compaction will take quite a bit longer when using the "badger" option, + // but that shouldn't really matter (as long as it is under 7.5hrs). + MarkSetType string + + // DiscardColdBlocks indicates whether to skip moving cold blocks to the coldstore. + // If the splitstore is running with a noop coldstore then this option is set to true + // which skips moving (as it is a noop, but still takes time to read all the cold objects) + // and directly purges cold blocks. + DiscardColdBlocks bool + + // UniversalColdBlocks indicates whether all blocks being garbage collected and purged + // from the hotstore should be written to the cold store + UniversalColdBlocks bool + + // HotstoreMessageRetention indicates the hotstore retention policy for messages. + // It has the following semantics: + // - a value of 0 will only retain messages within the compaction boundary (4 finalities) + // - a positive integer indicates the number of finalities, outside the compaction boundary, + // for which messages will be retained in the hotstore. + HotStoreMessageRetention uint64 + + // HotstoreFullGCFrequency indicates how frequently (in terms of compactions) to garbage collect + // the hotstore using full (moving) GC if supported by the hotstore. + // A value of 0 disables full GC entirely. + // A positive value is the number of compactions before a full GC is performed; + // a value of 1 will perform full GC in every compaction. + HotStoreFullGCFrequency uint64 + + // HotstoreMaxSpaceTarget suggests the max allowed space the hotstore can take. + // This is not a hard limit, it is possible for the hotstore to exceed the target + // for example if state grows massively between compactions. The splitstore + // will make a best effort to avoid overflowing the target and in practice should + // never overflow. This field is used when doing GC at the end of a compaction to + // adaptively choose moving GC + HotstoreMaxSpaceTarget uint64 + + // Moving GC will be triggered when total moving size exceeds + // HotstoreMaxSpaceTarget - HotstoreMaxSpaceThreshold + HotstoreMaxSpaceThreshold uint64 + + // Safety buffer to prevent moving GC from overflowing disk. + // Moving GC will not occur when total moving size exceeds + // HotstoreMaxSpaceTarget - HotstoreMaxSpaceSafetyBuffer + HotstoreMaxSpaceSafetyBuffer uint64 +} + +// ChainAccessor allows the Splitstore to access the chain. It will most likely +// be a ChainStore at runtime. +type ChainAccessor interface { + GetTipsetByHeight(context.Context, abi.ChainEpoch, *types.TipSet, bool) (*types.TipSet, error) + GetHeaviestTipSet() *types.TipSet + SubscribeHeadChanges(change func(revert []*types.TipSet, apply []*types.TipSet) error) +} + +// upgradeRange is a precomputed epoch range during which we shouldn't compact so as to not +// interfere with an upgrade +type upgradeRange struct { + start, end abi.ChainEpoch +} + +// hotstore is the interface that must be satisfied by the hot blockstore; it is an extension +// of the Blockstore interface with the traits we need for compaction. +type hotstore interface { + bstore.Blockstore + bstore.BlockstoreIterator +} + +type SplitStore struct { + compacting int32 // flag for when compaction is in progress + compactType CompactType // compaction type, protected by compacting atomic, only meaningful when compacting == 1 + closing int32 // the splitstore is closing + + cfg *Config + path string + + mx sync.Mutex + warmupEpoch atomic.Int64 + baseEpoch abi.ChainEpoch // protected by compaction lock + pruneEpoch abi.ChainEpoch // protected by compaction lock + + headChangeMx sync.Mutex + + chain ChainAccessor + ds dstore.Datastore + cold bstore.Blockstore + hot hotstore + + upgrades []upgradeRange + + markSetEnv MarkSetEnv + markSetSize int64 + + compactionIndex int64 + pruneIndex int64 + onlineGCCnt int64 + + ctx context.Context + cancel func() + + outOfSync int32 // for fast checking + chainSyncMx sync.Mutex + chainSyncCond sync.Cond + chainSyncFinished bool // protected by chainSyncMx + + debug *debugLog + + // transactional protection for concurrent read/writes during compaction + txnLk sync.RWMutex + txnViewsMx sync.Mutex + txnViewsCond sync.Cond + txnViews int + txnViewsWaiting bool + txnActive bool + txnRefsMx sync.Mutex + txnRefs map[cid.Cid]struct{} + txnMissing map[cid.Cid]struct{} + txnMarkSet MarkSet + txnSyncMx sync.Mutex + txnSyncCond sync.Cond + txnSync bool + + // background cold object reification + reifyWorkers sync.WaitGroup + reifyMx sync.Mutex + reifyCond sync.Cond + reifyPend map[cid.Cid]struct{} + reifyInProgress map[cid.Cid]struct{} + + // registered protectors + protectors []func(func(cid.Cid) error) error + + // dag sizes measured during latest compaction + // logged and used for GC strategy + + // protected by compaction lock + szWalk int64 + szProtectedTxns int64 + szKeys int64 // approximate, not counting keys protected when entering critical section + + // protected by txnLk + szMarkedLiveRefs int64 +} + +var _ bstore.Blockstore = (*SplitStore)(nil) + +// Open opens an existing splistore, or creates a new splitstore. The splitstore +// is backed by the provided hot and cold stores. The returned SplitStore MUST be +// attached to the ChainStore with Start in order to trigger compaction. +func Open(path string, ds dstore.Datastore, hot, cold bstore.Blockstore, cfg *Config) (*SplitStore, error) { + // hot blockstore must support the hotstore interface + hots, ok := hot.(hotstore) + if !ok { + // be specific about what is missing + if _, ok := hot.(bstore.BlockstoreIterator); !ok { + return nil, xerrors.Errorf("hot blockstore does not support efficient iteration: %T", hot) + } + + return nil, xerrors.Errorf("hot blockstore does not support the necessary traits: %T", hot) + } + + // the markset env + markSetEnv, err := OpenMarkSetEnv(path, cfg.MarkSetType) + if err != nil { + return nil, err + } + + // and now we can make a SplitStore + ss := &SplitStore{ + cfg: cfg, + path: path, + ds: ds, + cold: cold, + hot: hots, + markSetEnv: markSetEnv, + } + + ss.txnViewsCond.L = &ss.txnViewsMx + ss.txnSyncCond.L = &ss.txnSyncMx + ss.chainSyncCond.L = &ss.chainSyncMx + ss.ctx, ss.cancel = context.WithCancel(context.Background()) + + ss.reifyCond.L = &ss.reifyMx + ss.reifyPend = make(map[cid.Cid]struct{}) + ss.reifyInProgress = make(map[cid.Cid]struct{}) + + if enableDebugLog { + ss.debug, err = openDebugLog(path) + if err != nil { + return nil, err + } + } + + if ss.checkpointExists() { + log.Info("found compaction checkpoint; resuming compaction") + if err := ss.completeCompaction(); err != nil { + markSetEnv.Close() //nolint:errcheck + return nil, xerrors.Errorf("error resuming compaction: %w", err) + } + } + if ss.pruneCheckpointExists() { + log.Info("found prune checkpoint; resuming prune") + if err := ss.completePrune(); err != nil { + markSetEnv.Close() //nolint:errcheck + return nil, xerrors.Errorf("error resuming prune: %w", err) + } + } + + return ss, nil +} + +// Blockstore interface +func (s *SplitStore) DeleteBlock(_ context.Context, _ cid.Cid) error { + // afaict we don't seem to be using this method, so it's not implemented + return errors.New("DeleteBlock not implemented on SplitStore; don't do this Luke!") //nolint +} + +func (s *SplitStore) DeleteMany(_ context.Context, _ []cid.Cid) error { + // afaict we don't seem to be using this method, so it's not implemented + return errors.New("DeleteMany not implemented on SplitStore; don't do this Luke!") //nolint +} + +func (s *SplitStore) Has(ctx context.Context, cid cid.Cid) (bool, error) { + if isIdentiyCid(cid) { + return true, nil + } + + s.txnLk.RLock() + defer s.txnLk.RUnlock() + + // critical section + if s.txnMarkSet != nil { + has, err := s.txnMarkSet.Has(cid) + if err != nil { + return false, err + } + + if has { + return s.has(cid) + } + switch s.compactType { + case hot: + return s.cold.Has(ctx, cid) + case cold: + return s.hot.Has(ctx, cid) + default: + return false, xerrors.Errorf("invalid compaction type %d, only hot and cold allowed for critical section", s.compactType) + } + } + + has, err := s.hot.Has(ctx, cid) + + if err != nil { + return has, err + } + + if has { + s.trackTxnRef(cid) + return true, nil + } + + has, err = s.cold.Has(ctx, cid) + if has { + s.trackTxnRef(cid) + if bstore.IsHotView(ctx) { + s.reifyColdObject(cid) + } + } + + return has, err + +} + +func (s *SplitStore) Get(ctx context.Context, cid cid.Cid) (blocks.Block, error) { + if isIdentiyCid(cid) { + data, err := decodeIdentityCid(cid) + if err != nil { + return nil, err + } + + return blocks.NewBlockWithCid(data, cid) + } + + s.txnLk.RLock() + defer s.txnLk.RUnlock() + + // critical section + if s.txnMarkSet != nil { + has, err := s.txnMarkSet.Has(cid) + if err != nil { + return nil, err + } + + if has { + return s.get(cid) + } + switch s.compactType { + case hot: + return s.cold.Get(ctx, cid) + case cold: + return s.hot.Get(ctx, cid) + default: + return nil, xerrors.Errorf("invalid compaction type %d, only hot and cold allowed for critical section", s.compactType) + } + } + + blk, err := s.hot.Get(ctx, cid) + + switch { + case err == nil: + s.trackTxnRef(cid) + return blk, nil + + case ipld.IsNotFound(err): + if s.isWarm() { + s.debug.LogReadMiss(cid) + } + + blk, err = s.cold.Get(ctx, cid) + if err == nil { + s.trackTxnRef(cid) + if bstore.IsHotView(ctx) { + s.reifyColdObject(cid) + } + + stats.Record(s.ctx, metrics.SplitstoreMiss.M(1)) + } + return blk, err + + default: + return nil, err + } +} + +func (s *SplitStore) GetSize(ctx context.Context, cid cid.Cid) (int, error) { + if isIdentiyCid(cid) { + data, err := decodeIdentityCid(cid) + if err != nil { + return 0, err + } + + return len(data), nil + } + + s.txnLk.RLock() + defer s.txnLk.RUnlock() + + // critical section + if s.txnMarkSet != nil { + has, err := s.txnMarkSet.Has(cid) + if err != nil { + return 0, err + } + + if has { + return s.getSize(cid) + } + switch s.compactType { + case hot: + return s.cold.GetSize(ctx, cid) + case cold: + return s.hot.GetSize(ctx, cid) + default: + return 0, xerrors.Errorf("invalid compaction type %d, only hot and cold allowed for critical section", s.compactType) + } + } + + size, err := s.hot.GetSize(ctx, cid) + + switch { + case err == nil: + s.trackTxnRef(cid) + return size, nil + + case ipld.IsNotFound(err): + if s.isWarm() { + s.debug.LogReadMiss(cid) + } + + size, err = s.cold.GetSize(ctx, cid) + if err == nil { + s.trackTxnRef(cid) + if bstore.IsHotView(ctx) { + s.reifyColdObject(cid) + } + + stats.Record(s.ctx, metrics.SplitstoreMiss.M(1)) + } + return size, err + + default: + return 0, err + } +} + +func (s *SplitStore) Flush(ctx context.Context) error { + s.txnLk.RLock() + defer s.txnLk.RUnlock() + + if err := s.cold.Flush(ctx); err != nil { + return err + } + if err := s.hot.Flush(ctx); err != nil { + return err + } + if err := s.ds.Sync(ctx, dstore.Key{}); err != nil { + return err + } + + return nil +} + +func (s *SplitStore) Put(ctx context.Context, blk blocks.Block) error { + if isIdentiyCid(blk.Cid()) { + return nil + } + + s.txnLk.RLock() + defer s.txnLk.RUnlock() + + err := s.hot.Put(ctx, blk) + if err != nil { + return err + } + + s.debug.LogWrite(blk) + + // critical section + if s.txnMarkSet != nil && s.compactType == hot { // puts only touch hot store + s.markLiveRefs([]cid.Cid{blk.Cid()}) + return nil + } + s.trackTxnRef(blk.Cid()) + + return nil +} + +func (s *SplitStore) PutMany(ctx context.Context, blks []blocks.Block) error { + // filter identites + idcids := 0 + for _, blk := range blks { + if isIdentiyCid(blk.Cid()) { + idcids++ + } + } + + if idcids > 0 { + if idcids == len(blks) { + // it's all identities + return nil + } + + filtered := make([]blocks.Block, 0, len(blks)-idcids) + for _, blk := range blks { + if isIdentiyCid(blk.Cid()) { + continue + } + filtered = append(filtered, blk) + } + + blks = filtered + } + + batch := make([]cid.Cid, 0, len(blks)) + for _, blk := range blks { + batch = append(batch, blk.Cid()) + } + + s.txnLk.RLock() + defer s.txnLk.RUnlock() + + err := s.hot.PutMany(ctx, blks) + if err != nil { + return err + } + + s.debug.LogWriteMany(blks) + + // critical section + if s.txnMarkSet != nil && s.compactType == hot { // puts only touch hot store + s.markLiveRefs(batch) + return nil + } + s.trackTxnRefMany(batch) + + return nil +} + +func (s *SplitStore) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { + ctx, cancel := context.WithCancel(ctx) + + chHot, err := s.hot.AllKeysChan(ctx) + if err != nil { + cancel() + return nil, err + } + + chCold, err := s.cold.AllKeysChan(ctx) + if err != nil { + cancel() + return nil, err + } + + seen := cid.NewSet() + ch := make(chan cid.Cid, 8) // buffer is arbitrary, just enough to avoid context switches + go func() { + defer cancel() + defer close(ch) + + for _, in := range []<-chan cid.Cid{chHot, chCold} { + for c := range in { + // ensure we only emit each key once + if !seen.Visit(c) { + continue + } + + select { + case ch <- c: + case <-ctx.Done(): + return + } + } + } + }() + + return ch, nil +} + +func (s *SplitStore) HashOnRead(enabled bool) { + s.hot.HashOnRead(enabled) + s.cold.HashOnRead(enabled) +} + +func (s *SplitStore) View(ctx context.Context, cid cid.Cid, cb func([]byte) error) error { + if isIdentiyCid(cid) { + data, err := decodeIdentityCid(cid) + if err != nil { + return err + } + + return cb(data) + } + + // critical section + s.txnLk.RLock() // the lock is released in protectView if we are not in critical section + if s.txnMarkSet != nil { + has, err := s.txnMarkSet.Has(cid) + s.txnLk.RUnlock() + + if err != nil { + return err + } + + if has { + return s.view(cid, cb) + } + switch s.compactType { + case hot: + return s.cold.View(ctx, cid, cb) + case cold: + return s.hot.View(ctx, cid, cb) + default: + return xerrors.Errorf("invalid compaction type %d, only hot and cold allowed for critical section", s.compactType) + } + } + + // views are (optimistically) protected two-fold: + // - if there is an active transaction, then the reference is protected. + // - if there is no active transaction, active views are tracked in a + // wait group and compaction is inhibited from starting until they + // have all completed. this is necessary to ensure that a (very) long-running + // view can't have its data pointer deleted, which would be catastrophic. + // Note that we can't just RLock for the duration of the view, as this could + // lead to deadlock with recursive views. + s.protectView(cid) + defer s.viewDone() + + err := s.hot.View(ctx, cid, cb) + if ipld.IsNotFound(err) { + if s.isWarm() { + s.debug.LogReadMiss(cid) + } + + err = s.cold.View(ctx, cid, cb) + if err == nil { + if bstore.IsHotView(ctx) { + s.reifyColdObject(cid) + } + + stats.Record(s.ctx, metrics.SplitstoreMiss.M(1)) + } + return err + } + return err +} + +func (s *SplitStore) isWarm() bool { + return s.warmupEpoch.Load() > 0 +} + +// State tracking +func (s *SplitStore) Start(chain ChainAccessor, us stmgr.UpgradeSchedule) error { + s.chain = chain + curTs := chain.GetHeaviestTipSet() + + // precompute the upgrade boundaries + s.upgrades = make([]upgradeRange, 0, len(us)) + for _, upgrade := range us { + boundary := upgrade.Height + for _, pre := range upgrade.PreMigrations { + preMigrationBoundary := upgrade.Height - pre.StartWithin + if preMigrationBoundary < boundary { + boundary = preMigrationBoundary + } + } + + upgradeStart := boundary - upgradeBoundary + upgradeEnd := upgrade.Height + upgradeBoundary + + s.upgrades = append(s.upgrades, upgradeRange{start: upgradeStart, end: upgradeEnd}) + } + + // should we warmup + warmup := false + + // load base epoch from metadata ds + // if none, then use current epoch because it's a fresh start + bs, err := s.ds.Get(s.ctx, baseEpochKey) + switch err { + case nil: + s.baseEpoch = bytesToEpoch(bs) + + case dstore.ErrNotFound: + if curTs == nil { + // this can happen in some tests + break + } + + err = s.setBaseEpoch(curTs.Height()) + if err != nil { + return xerrors.Errorf("error saving base epoch: %w", err) + } + + default: + return xerrors.Errorf("error loading base epoch: %w", err) + } + + // load prune epoch from metadata ds + bs, err = s.ds.Get(s.ctx, pruneEpochKey) + switch err { + case nil: + s.pruneEpoch = bytesToEpoch(bs) + case dstore.ErrNotFound: + if curTs == nil { + //this can happen in some tests + break + } + if err := s.setPruneEpoch(curTs.Height()); err != nil { + return xerrors.Errorf("error saving prune epoch: %w", err) + } + default: + return xerrors.Errorf("error loading prune epoch: %w", err) + } + + // load warmup epoch from metadata ds + bs, err = s.ds.Get(s.ctx, warmupEpochKey) + switch err { + case nil: + s.warmupEpoch.Store(bytesToInt64(bs)) + + case dstore.ErrNotFound: + warmup = true + + default: + return xerrors.Errorf("error loading warmup epoch: %w", err) + } + + // load markSetSize from metadata ds to provide a size hint for marksets + bs, err = s.ds.Get(s.ctx, markSetSizeKey) + switch err { + case nil: + s.markSetSize = bytesToInt64(bs) + + case dstore.ErrNotFound: + default: + return xerrors.Errorf("error loading mark set size: %w", err) + } + + // load compactionIndex from metadata ds to provide a hint as to when to perform moving gc + bs, err = s.ds.Get(s.ctx, compactionIndexKey) + switch err { + case nil: + s.compactionIndex = bytesToInt64(bs) + + case dstore.ErrNotFound: + // this is potentially an upgrade from splitstore v0; schedule a warmup as v0 has + // some issues with hot references leaking into the coldstore. + warmup = true + default: + return xerrors.Errorf("error loading compaction index: %w", err) + } + + log.Infow("starting splitstore", "baseEpoch", s.baseEpoch, "warmupEpoch", s.warmupEpoch.Load()) + + if warmup { + err = s.warmup(curTs) + if err != nil { + return xerrors.Errorf("error starting warmup: %w", err) + } + } + + // spawn the reifier + go s.reifyOrchestrator() + + // watch the chain + chain.SubscribeHeadChanges(s.HeadChange) + + return nil +} + +func (s *SplitStore) AddProtector(protector func(func(cid.Cid) error) error) { + s.mx.Lock() + defer s.mx.Unlock() + + s.protectors = append(s.protectors, protector) +} + +func (s *SplitStore) Close() error { + if !atomic.CompareAndSwapInt32(&s.closing, 0, 1) { + // already closing + return nil + } + + if atomic.LoadInt32(&s.compacting) == 1 { + s.txnSyncMx.Lock() + s.txnSync = true + s.txnSyncCond.Broadcast() + s.txnSyncMx.Unlock() + + s.chainSyncMx.Lock() + s.chainSyncFinished = true + s.chainSyncCond.Broadcast() + s.chainSyncMx.Unlock() + + log.Warn("close with ongoing compaction in progress; waiting for it to finish...") + for atomic.LoadInt32(&s.compacting) == 1 { + time.Sleep(time.Second) + } + } + + s.reifyCond.Broadcast() + s.reifyWorkers.Wait() + s.cancel() + return multierr.Combine(s.markSetEnv.Close(), s.debug.Close()) +} + +func (s *SplitStore) checkClosing() error { + if atomic.LoadInt32(&s.closing) == 1 { + return xerrors.Errorf("splitstore is closing") + } + + return nil +} + +func (s *SplitStore) setBaseEpoch(epoch abi.ChainEpoch) error { + s.baseEpoch = epoch + return s.ds.Put(s.ctx, baseEpochKey, epochToBytes(epoch)) +} + +func (s *SplitStore) setPruneEpoch(epoch abi.ChainEpoch) error { + s.pruneEpoch = epoch + return s.ds.Put(s.ctx, pruneEpochKey, epochToBytes(epoch)) +} diff --git a/blockstore/splitstore/splitstore_check.go b/blockstore/splitstore/splitstore_check.go new file mode 100644 index 000000000..bdc706271 --- /dev/null +++ b/blockstore/splitstore/splitstore_check.go @@ -0,0 +1,164 @@ +package splitstore + +import ( + "fmt" + "os" + "path/filepath" + "sync" + "sync/atomic" + "time" + + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + bstore "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/chain/types" +) + +// performs an asynchronous health-check on the splitstore; results are appended to +// /check.txt +func (s *SplitStore) Check() error { + s.headChangeMx.Lock() + defer s.headChangeMx.Unlock() + + // try to take compaction lock and inhibit compaction while the health-check is running + if !atomic.CompareAndSwapInt32(&s.compacting, 0, 1) { + return xerrors.Errorf("can't acquire compaction lock; compacting operation in progress") + } + s.compactType = check + + if s.compactionIndex == 0 { + atomic.StoreInt32(&s.compacting, 0) + return xerrors.Errorf("splitstore hasn't compacted yet; health check is not meaningful") + } + + // check if we are actually closing first + if err := s.checkClosing(); err != nil { + atomic.StoreInt32(&s.compacting, 0) + return err + } + + curTs := s.chain.GetHeaviestTipSet() + go func() { + defer atomic.StoreInt32(&s.compacting, 0) + + log.Info("checking splitstore health") + start := time.Now() + + err := s.doCheck(curTs) + if err != nil { + log.Errorf("error checking splitstore health: %s", err) + return + } + + log.Infow("health check done", "took", time.Since(start)) + }() + + return nil +} + +func (s *SplitStore) doCheck(curTs *types.TipSet) error { + currentEpoch := curTs.Height() + boundaryEpoch := currentEpoch - CompactionBoundary + + outputPath := filepath.Join(s.path, "check.txt") + output, err := os.OpenFile(outputPath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) + if err != nil { + return xerrors.Errorf("error opening check output file %s: %w", outputPath, err) + } + defer output.Close() //nolint:errcheck + + var mx sync.Mutex + write := func(format string, args ...interface{}) { + mx.Lock() + defer mx.Unlock() + _, err := fmt.Fprintf(output, format+"\n", args...) + if err != nil { + log.Warnf("error writing check output: %s", err) + } + } + + ts, _ := time.Now().MarshalText() + write("---------------------------------------------") + write("start check at %s", ts) + write("current epoch: %d", currentEpoch) + write("boundary epoch: %d", boundaryEpoch) + write("compaction index: %d", s.compactionIndex) + write("--") + + coldCnt := new(int64) + missingCnt := new(int64) + + visitor, err := s.markSetEnv.New("check", 0) + if err != nil { + return xerrors.Errorf("error creating visitor: %w", err) + } + defer visitor.Close() //nolint + + size := s.walkChain(curTs, boundaryEpoch, boundaryEpoch, visitor, + func(c cid.Cid) error { + if isUnitaryObject(c) { + return errStopWalk + } + + has, err := s.hot.Has(s.ctx, c) + if err != nil { + return xerrors.Errorf("error checking hotstore: %w", err) + } + + if has { + return nil + } + + has, err = s.cold.Has(s.ctx, c) + if err != nil { + return xerrors.Errorf("error checking coldstore: %w", err) + } + + if has { + atomic.AddInt64(coldCnt, 1) + write("cold object reference: %s", c) + } else { + atomic.AddInt64(missingCnt, 1) + write("missing object reference: %s", c) + return errStopWalk + } + + return nil + }, func(cid.Cid) error { return nil }) + + if err != nil { + err = xerrors.Errorf("error walking chain: %w", err) + write("ERROR: %s", err) + return err + } + + log.Infow("check done", "cold", *coldCnt, "missing", *missingCnt, "walk size", size) + write("--") + write("cold: %d missing: %d", *coldCnt, *missingCnt) + write("DONE") + + return nil +} + +// provides some basic information about the splitstore +func (s *SplitStore) Info() map[string]interface{} { + info := make(map[string]interface{}) + info["base epoch"] = s.baseEpoch + info["warmup epoch"] = s.warmupEpoch.Load() + info["compactions"] = s.compactionIndex + info["prunes"] = s.pruneIndex + info["compacting"] = s.compacting == 1 + + sizer, ok := s.hot.(bstore.BlockstoreSize) + if ok { + size, err := sizer.Size() + if err != nil { + log.Warnf("error getting hotstore size: %s", err) + } else { + info["hotstore size"] = size + } + } + + return info +} diff --git a/blockstore/splitstore/splitstore_compact.go b/blockstore/splitstore/splitstore_compact.go new file mode 100644 index 000000000..534565bf3 --- /dev/null +++ b/blockstore/splitstore/splitstore_compact.go @@ -0,0 +1,1661 @@ +package splitstore + +import ( + "bytes" + "errors" + "os" + "path/filepath" + "runtime" + "sync" + "sync/atomic" + "time" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + ipld "github.com/ipfs/go-ipld-format" + cbg "github.com/whyrusleeping/cbor-gen" + "go.opencensus.io/stats" + "golang.org/x/sync/errgroup" + "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" + "github.com/filecoin-project/lotus/metrics" +) + +var ( + // CompactionThreshold is the number of epochs that need to have elapsed + // from the previously compacted epoch to trigger a new compaction. + // + // |················· CompactionThreshold ··················| + // | | + // =======‖≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡‖------------------------» + // | | chain --> ↑__ current epoch + // | archived epochs ___↑ + // ↑________ CompactionBoundary + // + // === :: cold (already archived) + // ≡≡≡ :: to be archived in this compaction + // --- :: hot + CompactionThreshold = 5 * build.Finality + + // CompactionBoundary is the number of epochs from the current epoch at which + // we will walk the chain for live objects. + CompactionBoundary = 4 * build.Finality + + // SyncGapTime is the time delay from a tipset's min timestamp before we decide + // there is a sync gap + SyncGapTime = time.Minute + + // SyncWaitTime is the time delay from a tipset's min timestamp before we decide + // we have synced. + SyncWaitTime = 30 * time.Second + + // This is a testing flag that should always be true when running a node. itests rely on the rough hack + // of starting genesis so far in the past that they exercise catchup mining to mine + // blocks quickly and so disabling syncgap checking is necessary to test compaction + // without a deep structural improvement of itests. + CheckSyncGap = true +) + +var ( + // used to signal end of walk + errStopWalk = errors.New("stop walk") +) + +const ( + batchSize = 16384 + cidKeySize = 128 +) + +func (s *SplitStore) HeadChange(_, apply []*types.TipSet) error { + s.headChangeMx.Lock() + defer s.headChangeMx.Unlock() + + // Revert only. + if len(apply) == 0 { + return nil + } + + curTs := apply[len(apply)-1] + epoch := curTs.Height() + + // NOTE: there is an implicit invariant assumption that HeadChange is invoked + // synchronously and no other HeadChange can be invoked while one is in + // progress. + // this is guaranteed by the chainstore, and it is pervasive in all lotus + // -- if that ever changes then all hell will break loose in general and + // we will have a rance to protectTipSets here. + // Regardless, we put a mutex in HeadChange just to be safe + + if !atomic.CompareAndSwapInt32(&s.compacting, 0, 1) { + // we are currently compacting + // 1. Signal sync condition to yield compaction when out of sync and resume when in sync + timestamp := time.Unix(int64(curTs.MinTimestamp()), 0) + if CheckSyncGap && time.Since(timestamp) > SyncGapTime { + /* Chain out of sync */ + if atomic.CompareAndSwapInt32(&s.outOfSync, 0, 1) { + // transition from in sync to out of sync + s.chainSyncMx.Lock() + s.chainSyncFinished = false + s.chainSyncMx.Unlock() + } + // already out of sync, no signaling necessary + + } + // TODO: ok to use hysteresis with no transitions between 30s and 1m? + if time.Since(timestamp) < SyncWaitTime { + /* Chain in sync */ + if atomic.CompareAndSwapInt32(&s.outOfSync, 0, 0) { + // already in sync, no signaling necessary + } else { + // transition from out of sync to in sync + s.chainSyncMx.Lock() + s.chainSyncFinished = true + s.chainSyncCond.Broadcast() + s.chainSyncMx.Unlock() + } + + } + // 2. protect the new tipset(s) + s.protectTipSets(apply) + return nil + } + + // check if we are actually closing first + if atomic.LoadInt32(&s.closing) == 1 { + atomic.StoreInt32(&s.compacting, 0) + return nil + } + + timestamp := time.Unix(int64(curTs.MinTimestamp()), 0) + + if CheckSyncGap && time.Since(timestamp) > SyncGapTime { + // don't attempt compaction before we have caught up syncing + atomic.StoreInt32(&s.compacting, 0) + return nil + } + + if s.isNearUpgrade(epoch) { + // we are near an upgrade epoch, suppress compaction + atomic.StoreInt32(&s.compacting, 0) + return nil + } + + if epoch-s.baseEpoch > CompactionThreshold { + // it's time to compact -- prepare the transaction and go! + s.beginTxnProtect() + s.compactType = hot + go func() { + defer atomic.StoreInt32(&s.compacting, 0) + defer s.endTxnProtect() + + log.Info("compacting splitstore") + start := time.Now() + + s.compact(curTs) + + log.Infow("compaction done", "took", time.Since(start)) + }() + // only prune if auto prune is enabled and after at least one compaction + } else { + // no compaction necessary + atomic.StoreInt32(&s.compacting, 0) + } + + return nil +} + +func (s *SplitStore) isNearUpgrade(epoch abi.ChainEpoch) bool { + for _, upgrade := range s.upgrades { + if epoch >= upgrade.start && epoch <= upgrade.end { + return true + } + } + + return false +} + +// transactionally protect incoming tipsets +func (s *SplitStore) protectTipSets(apply []*types.TipSet) { + s.txnLk.RLock() + + if !s.txnActive { + s.txnLk.RUnlock() + return + } + + var cids []cid.Cid + for _, ts := range apply { + cids = append(cids, ts.Cids()...) + } + + if len(cids) == 0 { + s.txnLk.RUnlock() + return + } + + // critical section + if s.txnMarkSet != nil { + curTs := apply[len(apply)-1] + timestamp := time.Unix(int64(curTs.MinTimestamp()), 0) + doSync := time.Since(timestamp) < SyncWaitTime + go func() { + // we are holding the txnLk while marking + // so critical section cannot delete + if doSync { + defer func() { + s.txnSyncMx.Lock() + defer s.txnSyncMx.Unlock() + s.txnSync = true + s.txnSyncCond.Broadcast() + }() + } + defer s.txnLk.RUnlock() + s.markLiveRefs(cids) + + }() + return + } + + s.trackTxnRefMany(cids) + s.txnLk.RUnlock() +} + +func (s *SplitStore) markLiveRefs(cids []cid.Cid) { + log.Debugf("marking %d live refs", len(cids)) + startMark := time.Now() + + szMarked := new(int64) + + count := new(int32) + visitor := newConcurrentVisitor() + walkObject := func(c cid.Cid) (int64, error) { + return s.walkObjectIncomplete(c, visitor, + func(c cid.Cid) error { + if isUnitaryObject(c) { + return errStopWalk + } + + visit, err := s.txnMarkSet.Visit(c) + if err != nil { + return xerrors.Errorf("error visiting object: %w", err) + } + + if !visit { + return errStopWalk + } + + atomic.AddInt32(count, 1) + return nil + }, + func(missing cid.Cid) error { + log.Warnf("missing object reference %s in %s", missing, c) + return errStopWalk + }) + } + + // optimize the common case of single put + if len(cids) == 1 { + sz, err := walkObject(cids[0]) + if err != nil { + log.Errorf("error marking tipset refs: %s", err) + } + log.Debugw("marking live refs done", "took", time.Since(startMark), "marked", *count) + atomic.AddInt64(szMarked, sz) + return + } + + workch := make(chan cid.Cid, len(cids)) + for _, c := range cids { + workch <- c + } + close(workch) + + worker := func() error { + for c := range workch { + sz, err := walkObject(c) + if err != nil { + return err + } + atomic.AddInt64(szMarked, sz) + } + + return nil + } + + workers := runtime.NumCPU() / 2 + if workers < 2 { + workers = 2 + } + if workers > len(cids) { + workers = len(cids) + } + + g := new(errgroup.Group) + for i := 0; i < workers; i++ { + g.Go(worker) + } + + if err := g.Wait(); err != nil { + log.Errorf("error marking tipset refs: %s", err) + } + + log.Debugw("marking live refs done", "took", time.Since(startMark), "marked", *count, "size marked", *szMarked) + s.szMarkedLiveRefs += atomic.LoadInt64(szMarked) +} + +// transactionally protect a view +func (s *SplitStore) protectView(c cid.Cid) { + // the txnLk is held for read + defer s.txnLk.RUnlock() + + if s.txnActive { + s.trackTxnRef(c) + } + + s.txnViewsMx.Lock() + s.txnViews++ + s.txnViewsMx.Unlock() +} + +func (s *SplitStore) viewDone() { + s.txnViewsMx.Lock() + defer s.txnViewsMx.Unlock() + + s.txnViews-- + if s.txnViews == 0 && s.txnViewsWaiting { + s.txnViewsCond.Broadcast() + } +} + +func (s *SplitStore) viewWait() { + s.txnViewsMx.Lock() + defer s.txnViewsMx.Unlock() + + s.txnViewsWaiting = true + for s.txnViews > 0 { + s.txnViewsCond.Wait() + } + s.txnViewsWaiting = false +} + +// transactionally protect a reference to an object +func (s *SplitStore) trackTxnRef(c cid.Cid) { + if !s.txnActive { + // not compacting + return + } + + if isUnitaryObject(c) { + return + } + + s.txnRefsMx.Lock() + s.txnRefs[c] = struct{}{} + s.txnRefsMx.Unlock() +} + +// transactionally protect a batch of references +func (s *SplitStore) trackTxnRefMany(cids []cid.Cid) { + if !s.txnActive { + // not compacting + return + } + + s.txnRefsMx.Lock() + defer s.txnRefsMx.Unlock() + + for _, c := range cids { + if isUnitaryObject(c) { + continue + } + + s.txnRefs[c] = struct{}{} + } + + return +} + +// protect all pending transactional references +func (s *SplitStore) protectTxnRefs(markSet MarkSet) error { + for { + var txnRefs map[cid.Cid]struct{} + + s.txnRefsMx.Lock() + if len(s.txnRefs) > 0 { + txnRefs = s.txnRefs + s.txnRefs = make(map[cid.Cid]struct{}) + } + s.txnRefsMx.Unlock() + + if len(txnRefs) == 0 { + return nil + } + + log.Infow("protecting transactional references", "refs", len(txnRefs)) + count := 0 + sz := new(int64) + workch := make(chan cid.Cid, len(txnRefs)) + startProtect := time.Now() + + for c := range txnRefs { + mark, err := markSet.Has(c) + if err != nil { + return xerrors.Errorf("error checking markset: %w", err) + } + + if mark { + continue + } + + workch <- c + count++ + } + close(workch) + + if count == 0 { + return nil + } + + workers := runtime.NumCPU() / 2 + if workers < 2 { + workers = 2 + } + if workers > count { + workers = count + } + + worker := func() error { + for c := range workch { + szTxn, err := s.doTxnProtect(c, markSet) + if err != nil { + return xerrors.Errorf("error protecting transactional references to %s: %w", c, err) + } + atomic.AddInt64(sz, szTxn) + } + return nil + } + + g := new(errgroup.Group) + for i := 0; i < workers; i++ { + g.Go(worker) + } + + if err := g.Wait(); err != nil { + return err + } + s.szProtectedTxns += atomic.LoadInt64(sz) + log.Infow("protecting transactional refs done", "took", time.Since(startProtect), "protected", count, "protected size", sz) + } +} + +// transactionally protect a reference by walking the object and marking. +// concurrent markings are short circuited by checking the markset. +func (s *SplitStore) doTxnProtect(root cid.Cid, markSet MarkSet) (int64, error) { + if err := s.checkClosing(); err != nil { + return 0, err + } + + // Note: cold objects are deleted heaviest first, so the consituents of an object + // cannot be deleted before the object itself. + return s.walkObjectIncomplete(root, newTmpVisitor(), + func(c cid.Cid) error { + if isUnitaryObject(c) { + return errStopWalk + } + + visit, err := markSet.Visit(c) + if err != nil { + return xerrors.Errorf("error visiting object: %w", err) + } + + if !visit { + return errStopWalk + } + + return nil + }, + func(c cid.Cid) error { + if s.txnMissing != nil { + log.Debugf("missing object reference %s in %s", c, root) + s.txnRefsMx.Lock() + s.txnMissing[c] = struct{}{} + s.txnRefsMx.Unlock() + } + return errStopWalk + }) +} + +func (s *SplitStore) applyProtectors() error { + s.mx.Lock() + defer s.mx.Unlock() + + count := 0 + for _, protect := range s.protectors { + err := protect(func(c cid.Cid) error { + s.trackTxnRef(c) + count++ + return nil + }) + + if err != nil { + return xerrors.Errorf("error applynig protector: %w", err) + } + } + + if count > 0 { + log.Infof("protected %d references through %d protectors", count, len(s.protectors)) + } + + return nil +} + +// --- Compaction --- +// Compaction works transactionally with the following algorithm: +// - We prepare a transaction, whereby all i/o referenced objects through the API are tracked. +// - We walk the chain and mark reachable objects, keeping 4 finalities of state roots and messages and all headers all the way to genesis. +// - Once the chain walk is complete, we begin full transaction protection with concurrent marking; we walk and mark all references created during the chain walk. On the same time, all I/O through the API concurrently marks objects as live references. +// - We collect cold objects by iterating through the hotstore and checking the mark set; if an object is not marked, then it is candidate for purge. +// - When running with a coldstore, we next copy all cold objects to the coldstore. +// - At this point we are ready to begin purging: +// - We sort cold objects heaviest first, so as to never delete the consituents of a DAG before the DAG itself (which would leave dangling references) +// - We delete in small batches taking a lock; each batch is checked again for marks, from the concurrent transactional mark, so as to never delete anything live +// +// - We then end the transaction and compact/gc the hotstore. +func (s *SplitStore) compact(curTs *types.TipSet) { + log.Info("waiting for active views to complete") + start := time.Now() + s.viewWait() + log.Infow("waiting for active views done", "took", time.Since(start)) + + start = time.Now() + err := s.doCompact(curTs) + took := time.Since(start).Milliseconds() + stats.Record(s.ctx, metrics.SplitstoreCompactionTimeSeconds.M(float64(took)/1e3)) + + if err != nil { + log.Errorf("COMPACTION ERROR: %s", err) + } +} + +func (s *SplitStore) doCompact(curTs *types.TipSet) error { + if s.checkpointExists() { + // this really shouldn't happen, but if it somehow does, it means that the hotstore + // might be potentially inconsistent; abort compaction and notify the user to intervene. + return xerrors.Errorf("checkpoint exists; aborting compaction") + } + s.clearSizeMeasurements() + + currentEpoch := curTs.Height() + boundaryEpoch := currentEpoch - CompactionBoundary + + var inclMsgsEpoch abi.ChainEpoch + inclMsgsRange := abi.ChainEpoch(s.cfg.HotStoreMessageRetention) * build.Finality + if inclMsgsRange < boundaryEpoch { + inclMsgsEpoch = boundaryEpoch - inclMsgsRange + } + + log.Infow("running compaction", "currentEpoch", currentEpoch, "baseEpoch", s.baseEpoch, "boundaryEpoch", boundaryEpoch, "inclMsgsEpoch", inclMsgsEpoch, "compactionIndex", s.compactionIndex) + + markSet, err := s.markSetEnv.New("live", s.markSetSize) + if err != nil { + return xerrors.Errorf("error creating mark set: %w", err) + } + defer markSet.Close() //nolint:errcheck + defer s.debug.Flush() + + coldSet, err := s.markSetEnv.New("cold", s.markSetSize) + if err != nil { + return xerrors.Errorf("error creating cold mark set: %w", err) + } + defer coldSet.Close() //nolint:errcheck + + if err := s.checkYield(); err != nil { + return err + } + + // 0. track all protected references at beginning of compaction; anything added later should + // be transactionally protected by the write + log.Info("protecting references with registered protectors") + err = s.applyProtectors() + if err != nil { + return err + } + + // 1. mark reachable objects by walking the chain from the current epoch; we keep state roots + // and messages until the boundary epoch. + log.Info("marking reachable objects") + startMark := time.Now() + + count := new(int64) + + coldCount := new(int64) + fCold := func(c cid.Cid) error { + // Writes to cold set optimized away in universal and discard mode + // + // Nothing gets written to cold store in discard mode so no cold objects to write + // Everything not marked hot gets written to cold store in universal mode so no need to track cold objects separately + if s.cfg.DiscardColdBlocks || s.cfg.UniversalColdBlocks { + return nil + } + + if isUnitaryObject(c) { + return errStopWalk + } + + visit, err := coldSet.Visit(c) + if err != nil { + return xerrors.Errorf("error visiting object: %w", err) + } + + if !visit { + return errStopWalk + } + + atomic.AddInt64(coldCount, 1) + return nil + } + fHot := func(c cid.Cid) error { + if isUnitaryObject(c) { + return errStopWalk + } + + visit, err := markSet.Visit(c) + if err != nil { + return xerrors.Errorf("error visiting object: %w", err) + } + + if !visit { + return errStopWalk + } + + atomic.AddInt64(count, 1) + return nil + } + + err = s.walkChain(curTs, boundaryEpoch, inclMsgsEpoch, &noopVisitor{}, fHot, fCold) + if err != nil { + return xerrors.Errorf("error marking: %w", err) + } + + s.markSetSize = *count + *count>>2 // overestimate a bit + + log.Infow("marking done", "took", time.Since(startMark), "marked", *count) + + if err := s.checkYield(); err != nil { + return err + } + + // 1.1 protect transactional refs + err = s.protectTxnRefs(markSet) + if err != nil { + return xerrors.Errorf("error protecting transactional refs: %w", err) + } + + if err := s.checkYield(); err != nil { + return err + } + + // 2. iterate through the hotstore to collect cold objects + log.Info("collecting cold objects") + startCollect := time.Now() + + coldw, err := NewColdSetWriter(s.coldSetPath()) + if err != nil { + return xerrors.Errorf("error creating coldset: %w", err) + } + defer coldw.Close() //nolint:errcheck + + purgew, err := NewColdSetWriter(s.discardSetPath()) + if err != nil { + return xerrors.Errorf("error creating deadset: %w", err) + } + defer purgew.Close() //nolint:errcheck + + // some stats for logging + var hotCnt, coldCnt, purgeCnt int64 + err = s.hot.ForEachKey(func(c cid.Cid) error { + // was it marked? + mark, err := markSet.Has(c) + if err != nil { + return xerrors.Errorf("error checking mark set for %s: %w", c, err) + } + + if mark { + hotCnt++ + return nil + } + + // it needs to be removed from hot store, mark it as candidate for purge + if err := purgew.Write(c); err != nil { + return xerrors.Errorf("error writing cid to purge set: %w", err) + } + purgeCnt++ + + coldMark, err := coldSet.Has(c) + if err != nil { + return xerrors.Errorf("error checking cold mark set for %s: %w", c, err) + } + + // Discard mode: coldMark == false, s.cfg.UniversalColdBlocks == false, always return here, no writes to cold store + // Universal mode: coldMark == false, s.cfg.UniversalColdBlocks == true, never stop here, all writes to cold store + // Otherwise: s.cfg.UniversalColdBlocks == false, if !coldMark stop here and don't write to cold store, if coldMark continue and write to cold store + if !coldMark && !s.cfg.UniversalColdBlocks { // universal mode means mark everything as cold + return nil + } + + // it's cold, mark as candidate for move + if err := coldw.Write(c); err != nil { + return xerrors.Errorf("error writing cid to cold set") + } + coldCnt++ + + return nil + }) + if err != nil { + return xerrors.Errorf("error collecting cold objects: %w", err) + } + if err := purgew.Close(); err != nil { + return xerrors.Errorf("erroring closing purgeset: %w", err) + } + if err := coldw.Close(); err != nil { + return xerrors.Errorf("error closing coldset: %w", err) + } + + log.Infow("cold collection done", "took", time.Since(startCollect)) + + log.Infow("compaction stats", "hot", hotCnt, "cold", coldCnt, "purge", purgeCnt) + s.szKeys = hotCnt * cidKeySize + stats.Record(s.ctx, metrics.SplitstoreCompactionHot.M(hotCnt)) + stats.Record(s.ctx, metrics.SplitstoreCompactionCold.M(coldCnt)) + + if err := s.checkYield(); err != nil { + return err + } + + // now that we have collected cold objects, check for missing references from transactional i/o + // and disable further collection of such references (they will not be acted upon as we can't + // possibly delete objects we didn't have when we were collecting cold objects) + s.waitForMissingRefs(markSet) + + if err := s.checkYield(); err != nil { + return err + } + + coldr, err := NewColdSetReader(s.coldSetPath()) + if err != nil { + return xerrors.Errorf("error opening coldset: %w", err) + } + defer coldr.Close() //nolint:errcheck + + // 3. copy the cold objects to the coldstore -- if we have one + if !s.cfg.DiscardColdBlocks { + log.Info("moving cold objects to the coldstore") + startMove := time.Now() + err = s.moveColdBlocks(coldr) + if err != nil { + return xerrors.Errorf("error moving cold objects: %w", err) + } + log.Infow("moving done", "took", time.Since(startMove)) + + if err := s.checkYield(); err != nil { + return err + } + + if err := coldr.Reset(); err != nil { + return xerrors.Errorf("error resetting coldset: %w", err) + } + } + + purger, err := NewColdSetReader(s.discardSetPath()) + if err != nil { + return xerrors.Errorf("error opening coldset: %w", err) + } + defer purger.Close() //nolint:errcheck + + // 4. Purge cold objects with checkpointing for recovery. + // This is the critical section of compaction, whereby any cold object not in the markSet is + // considered already deleted. + // We delete cold objects in batches, holding the transaction lock, where we check the markSet + // again for new references created by the VM. + // After each batch, we write a checkpoint to disk; if the process is interrupted before completion, + // the process will continue from the checkpoint in the next recovery. + if err := s.beginCriticalSection(markSet); err != nil { + return xerrors.Errorf("error beginning critical section: %w", err) + } + + if err := s.checkClosing(); err != nil { + return err + } + + // wait for the head to catch up so that the current tipset is marked + s.waitForTxnSync() + + if err := s.checkClosing(); err != nil { + return err + } + + checkpoint, err := NewCheckpoint(s.checkpointPath()) + if err != nil { + return xerrors.Errorf("error creating checkpoint: %w", err) + } + defer checkpoint.Close() //nolint:errcheck + + // 5. purge cold objects from the hotstore, taking protected references into account + log.Info("purging cold objects from the hotstore") + startPurge := time.Now() + err = s.purge(purger, checkpoint, markSet) + if err != nil { + return xerrors.Errorf("error purging cold objects: %w", err) + } + log.Infow("purging cold objects from hotstore done", "took", time.Since(startPurge)) + s.endCriticalSection() + log.Infow("critical section done", "total protected size", s.szProtectedTxns, "total marked live size", s.szMarkedLiveRefs) + + if err := checkpoint.Close(); err != nil { + log.Warnf("error closing checkpoint: %s", err) + } + if err := os.Remove(s.checkpointPath()); err != nil { + log.Warnf("error removing checkpoint: %s", err) + } + if err := coldr.Close(); err != nil { + log.Warnf("error closing coldset: %s", err) + } + if err := os.Remove(s.coldSetPath()); err != nil { + log.Warnf("error removing coldset: %s", err) + } + if err := os.Remove(s.discardSetPath()); err != nil { + log.Warnf("error removing discardset: %s", err) + } + + // we are done; do some housekeeping + s.endTxnProtect() + s.gcHotAfterCompaction() + + err = s.setBaseEpoch(boundaryEpoch) + if err != nil { + return xerrors.Errorf("error saving base epoch: %w", err) + } + + err = s.ds.Put(s.ctx, markSetSizeKey, int64ToBytes(s.markSetSize)) + if err != nil { + return xerrors.Errorf("error saving mark set size: %w", err) + } + + s.compactionIndex++ + err = s.ds.Put(s.ctx, compactionIndexKey, int64ToBytes(s.compactionIndex)) + if err != nil { + return xerrors.Errorf("error saving compaction index: %w", err) + } + + return nil +} + +func (s *SplitStore) beginTxnProtect() { + log.Info("preparing compaction transaction") + + s.txnLk.Lock() + defer s.txnLk.Unlock() + + s.txnActive = true + s.txnSync = false + s.txnRefs = make(map[cid.Cid]struct{}) + s.txnMissing = make(map[cid.Cid]struct{}) +} + +func (s *SplitStore) beginCriticalSection(markSet MarkSet) error { + log.Info("beginning critical section") + + // do that once first to get the bulk before the markset is in critical section + if err := s.protectTxnRefs(markSet); err != nil { + return xerrors.Errorf("error protecting transactional references: %w", err) + } + + if err := markSet.BeginCriticalSection(); err != nil { + return xerrors.Errorf("error beginning critical section for markset: %w", err) + } + + s.txnLk.Lock() + defer s.txnLk.Unlock() + + s.txnMarkSet = markSet + + // and do it again while holding the lock to mark references that might have been created + // in the meantime and avoid races of the type Has->txnRef->enterCS->Get fails because + // it's not in the markset + if err := s.protectTxnRefs(markSet); err != nil { + return xerrors.Errorf("error protecting transactional references: %w", err) + } + + return nil +} + +func (s *SplitStore) waitForTxnSync() { + log.Info("waiting for sync") + if !CheckSyncGap { + log.Warnf("If you see this outside of test it is a serious splitstore issue") + return + } + startWait := time.Now() + defer func() { + log.Infow("waiting for sync done", "took", time.Since(startWait)) + }() + + s.txnSyncMx.Lock() + defer s.txnSyncMx.Unlock() + + for !s.txnSync { + s.txnSyncCond.Wait() + } +} + +// Block compaction operations if chain sync has fallen behind +func (s *SplitStore) waitForSync() { + if atomic.LoadInt32(&s.outOfSync) == 0 { + return + } + s.chainSyncMx.Lock() + defer s.chainSyncMx.Unlock() + + for !s.chainSyncFinished { + s.chainSyncCond.Wait() + } +} + +// Combined sync and closing check +func (s *SplitStore) checkYield() error { + s.waitForSync() + return s.checkClosing() +} + +func (s *SplitStore) endTxnProtect() { + s.txnLk.Lock() + defer s.txnLk.Unlock() + + if !s.txnActive { + return + } + + s.txnActive = false + s.txnSync = false + s.txnRefs = nil + s.txnMissing = nil + s.txnMarkSet = nil +} + +func (s *SplitStore) endCriticalSection() { + log.Info("ending critical section") + + s.txnLk.Lock() + defer s.txnLk.Unlock() + + s.txnMarkSet.EndCriticalSection() + s.txnMarkSet = nil +} + +func (s *SplitStore) walkChain(ts *types.TipSet, inclState, inclMsgs abi.ChainEpoch, + visitor ObjectVisitor, fHot, fCold func(cid.Cid) error) error { + var walked ObjectVisitor + var mx sync.Mutex + // we copy the tipset first into a new slice, which allows us to reuse it in every epoch. + toWalk := make([]cid.Cid, len(ts.Cids())) + copy(toWalk, ts.Cids()) + walkCnt := new(int64) + scanCnt := new(int64) + szWalk := new(int64) + + tsRef := func(blkCids []cid.Cid) (cid.Cid, error) { + return types.NewTipSetKey(blkCids...).Cid() + } + + stopWalk := func(_ cid.Cid) error { return errStopWalk } + + walkBlock := func(c cid.Cid) error { + visit, err := walked.Visit(c) + if err != nil { + return err + } + if !visit { + return nil + } + + atomic.AddInt64(walkCnt, 1) + + if err := fHot(c); err != nil { + return err + } + + var hdr types.BlockHeader + err = s.view(c, func(data []byte) error { + return hdr.UnmarshalCBOR(bytes.NewBuffer(data)) + }) + if err != nil { + return xerrors.Errorf("error unmarshaling block header (cid: %s): %w", c, err) + } + + // tipset CID references are retained + pRef, err := tsRef(hdr.Parents) + if err != nil { + return xerrors.Errorf("error computing cid reference to parent tipset") + } + sz, err := s.walkObjectIncomplete(pRef, visitor, fHot, stopWalk) + if err != nil { + return xerrors.Errorf("error walking parent tipset cid reference") + } + atomic.AddInt64(szWalk, sz) + + // message are retained if within the inclMsgs boundary + if hdr.Height >= inclMsgs && hdr.Height > 0 { + if inclMsgs < inclState { + // we need to use walkObjectIncomplete here, as messages/receipts may be missing early on if we + // synced from snapshot and have a long HotStoreMessageRetentionPolicy. + sz, err := s.walkObjectIncomplete(hdr.Messages, visitor, fHot, stopWalk) + if err != nil { + return xerrors.Errorf("error walking messages (cid: %s): %w", hdr.Messages, err) + } + atomic.AddInt64(szWalk, sz) + + sz, err = s.walkObjectIncomplete(hdr.ParentMessageReceipts, visitor, fHot, stopWalk) + if err != nil { + return xerrors.Errorf("error walking messages receipts (cid: %s): %w", hdr.ParentMessageReceipts, err) + } + atomic.AddInt64(szWalk, sz) + } else { + sz, err = s.walkObject(hdr.Messages, visitor, fHot) + if err != nil { + return xerrors.Errorf("error walking messages (cid: %s): %w", hdr.Messages, err) + } + atomic.AddInt64(szWalk, sz) + + sz, err := s.walkObjectIncomplete(hdr.ParentMessageReceipts, visitor, fHot, stopWalk) + if err != nil { + return xerrors.Errorf("error walking message receipts (cid: %s): %w", hdr.ParentMessageReceipts, err) + } + atomic.AddInt64(szWalk, sz) + } + } + + // messages and receipts outside of inclMsgs are included in the cold store + if hdr.Height < inclMsgs && hdr.Height > 0 { + sz, err := s.walkObjectIncomplete(hdr.Messages, visitor, fCold, stopWalk) + if err != nil { + return xerrors.Errorf("error walking messages (cid: %s): %w", hdr.Messages, err) + } + atomic.AddInt64(szWalk, sz) + sz, err = s.walkObjectIncomplete(hdr.ParentMessageReceipts, visitor, fCold, stopWalk) + if err != nil { + return xerrors.Errorf("error walking messages receipts (cid: %s): %w", hdr.ParentMessageReceipts, err) + } + atomic.AddInt64(szWalk, sz) + } + + // state is only retained if within the inclState boundary, with the exception of genesis + if hdr.Height >= inclState || hdr.Height == 0 { + sz, err := s.walkObject(hdr.ParentStateRoot, visitor, fHot) + if err != nil { + return xerrors.Errorf("error walking state root (cid: %s): %w", hdr.ParentStateRoot, err) + } + atomic.AddInt64(szWalk, sz) + atomic.AddInt64(scanCnt, 1) + } + + if hdr.Height > 0 { + mx.Lock() + toWalk = append(toWalk, hdr.Parents...) + mx.Unlock() + } + + return nil + } + + // retain ref to chain head + hRef, err := tsRef(ts.Cids()) + if err != nil { + return xerrors.Errorf("error computing cid reference to parent tipset") + } + sz, err := s.walkObjectIncomplete(hRef, visitor, fHot, stopWalk) + if err != nil { + return xerrors.Errorf("error walking parent tipset cid reference") + } + atomic.AddInt64(szWalk, sz) + + for len(toWalk) > 0 { + // walking can take a while, so check this with every opportunity + if err := s.checkYield(); err != nil { + return err + } + + workers := len(toWalk) + if workers > runtime.NumCPU()/2 { + workers = runtime.NumCPU() / 2 + } + if workers < 2 { + workers = 2 + } + + // the walk is BFS, so we can reset the walked set in every iteration and avoid building up + // a set that contains all blocks (1M epochs -> 5M blocks -> 200MB worth of memory and growing + // over time) + walked = newConcurrentVisitor() + workch := make(chan cid.Cid, len(toWalk)) + for _, c := range toWalk { + workch <- c + } + close(workch) + toWalk = toWalk[:0] + + g := new(errgroup.Group) + for i := 0; i < workers; i++ { + g.Go(func() error { + for c := range workch { + if err := walkBlock(c); err != nil { + return xerrors.Errorf("error walking block (cid: %s): %w", c, err) + } + + if err := s.checkYield(); err != nil { + return xerrors.Errorf("check yield: %w", err) + } + } + return nil + }) + } + + if err := g.Wait(); err != nil { + return xerrors.Errorf("walkBlock workers errored: %w", err) + } + } + + log.Infow("chain walk done", "walked", *walkCnt, "scanned", *scanCnt, "walk size", szWalk) + s.szWalk = atomic.LoadInt64(szWalk) + return nil +} + +func (s *SplitStore) walkObject(c cid.Cid, visitor ObjectVisitor, f func(cid.Cid) error) (int64, error) { + var sz int64 + visit, err := visitor.Visit(c) + if err != nil { + return 0, xerrors.Errorf("error visiting object: %w", err) + } + + if !visit { + return sz, nil + } + + if err := f(c); err != nil { + if err == errStopWalk { + return sz, nil + } + + return 0, err + } + + if c.Prefix().Codec != cid.DagCBOR { + return sz, nil + } + + // check this before recursing + if err := s.checkClosing(); err != nil { + return 0, xerrors.Errorf("check closing: %w", err) + } + + var links []cid.Cid + err = s.view(c, func(data []byte) error { + sz += int64(len(data)) + return cbg.ScanForLinks(bytes.NewReader(data), func(c cid.Cid) { + links = append(links, c) + }) + }) + + if err != nil { + return 0, xerrors.Errorf("error scanning linked block (cid: %s): %w", c, err) + } + + for _, c := range links { + szLink, err := s.walkObject(c, visitor, f) + if err != nil { + return 0, xerrors.Errorf("error walking link (cid: %s): %w", c, err) + } + sz += szLink + } + + return sz, nil +} + +// like walkObject, but the object may be potentially incomplete (references missing) +func (s *SplitStore) walkObjectIncomplete(c cid.Cid, visitor ObjectVisitor, f, missing func(cid.Cid) error) (int64, error) { + var sz int64 + visit, err := visitor.Visit(c) + if err != nil { + return 0, xerrors.Errorf("error visiting object: %w", err) + } + + if !visit { + return sz, nil + } + + // occurs check -- only for DAGs + if c.Prefix().Codec == cid.DagCBOR { + has, err := s.has(c) + if err != nil { + return 0, xerrors.Errorf("error occur checking %s: %w", c, err) + } + + if !has { + err = missing(c) + if err == errStopWalk { + return sz, nil + } + + return 0, err + } + } + + if err := f(c); err != nil { + if err == errStopWalk { + return sz, nil + } + + return 0, err + } + + if c.Prefix().Codec != cid.DagCBOR { + return sz, nil + } + + // check this before recursing + if err := s.checkClosing(); err != nil { + return sz, xerrors.Errorf("check closing: %w", err) + } + + var links []cid.Cid + err = s.view(c, func(data []byte) error { + sz += int64(len(data)) + return cbg.ScanForLinks(bytes.NewReader(data), func(c cid.Cid) { + links = append(links, c) + }) + }) + + if err != nil { + return 0, xerrors.Errorf("error scanning linked block (cid: %s): %w", c, err) + } + + for _, c := range links { + szLink, err := s.walkObjectIncomplete(c, visitor, f, missing) + if err != nil { + return 0, xerrors.Errorf("error walking link (cid: %s): %w", c, err) + } + sz += szLink + } + + return sz, nil +} + +// internal version used during compaction and related operations +func (s *SplitStore) view(c cid.Cid, cb func([]byte) error) error { + if isIdentiyCid(c) { + data, err := decodeIdentityCid(c) + if err != nil { + return err + } + + return cb(data) + } + + err := s.hot.View(s.ctx, c, cb) + if ipld.IsNotFound(err) { + return s.cold.View(s.ctx, c, cb) + } + return err +} + +func (s *SplitStore) has(c cid.Cid) (bool, error) { + if isIdentiyCid(c) { + return true, nil + } + + has, err := s.hot.Has(s.ctx, c) + + if has || err != nil { + return has, err + } + + return s.cold.Has(s.ctx, c) +} + +func (s *SplitStore) get(c cid.Cid) (blocks.Block, error) { + blk, err := s.hot.Get(s.ctx, c) + switch { + case err == nil: + return blk, nil + case ipld.IsNotFound(err): + return s.cold.Get(s.ctx, c) + default: + return nil, err + } +} + +func (s *SplitStore) getSize(c cid.Cid) (int, error) { + sz, err := s.hot.GetSize(s.ctx, c) + switch { + case err == nil: + return sz, nil + case ipld.IsNotFound(err): + return s.cold.GetSize(s.ctx, c) + default: + return 0, err + } +} + +func (s *SplitStore) moveColdBlocks(coldr *ColdSetReader) error { + batch := make([]blocks.Block, 0, batchSize) + + err := coldr.ForEach(func(c cid.Cid) error { + if err := s.checkYield(); err != nil { + return err + } + blk, err := s.hot.Get(s.ctx, c) + if err != nil { + if ipld.IsNotFound(err) { + log.Warnf("hotstore missing block %s", c) + return nil + } + + return xerrors.Errorf("error retrieving block %s from hotstore: %w", c, err) + } + + batch = append(batch, blk) + if len(batch) == batchSize { + err = s.cold.PutMany(s.ctx, batch) + if err != nil { + return xerrors.Errorf("error putting batch to coldstore: %w", err) + } + batch = batch[:0] + + } + + return nil + }) + + if err != nil { + return xerrors.Errorf("error iterating coldset: %w", err) + } + + if len(batch) > 0 { + err := s.cold.PutMany(s.ctx, batch) + if err != nil { + return xerrors.Errorf("error putting batch to coldstore: %w", err) + } + } + + return nil +} + +func (s *SplitStore) purge(coldr *ColdSetReader, checkpoint *Checkpoint, markSet MarkSet) error { + batch := make([]cid.Cid, 0, batchSize) + deadCids := make([]cid.Cid, 0, batchSize) + + var purgeCnt, liveCnt int + defer func() { + log.Infow("purged cold objects", "purged", purgeCnt, "live", liveCnt) + }() + + deleteBatch := func() error { + pc, lc, err := s.purgeBatch(batch, deadCids, checkpoint, markSet) + + purgeCnt += pc + liveCnt += lc + batch = batch[:0] + + return err + } + + err := coldr.ForEach(func(c cid.Cid) error { + batch = append(batch, c) + if len(batch) == batchSize { + return deleteBatch() + } + + return nil + }) + + if err != nil { + return err + } + + if len(batch) > 0 { + return deleteBatch() + } + + return nil +} + +func (s *SplitStore) purgeBatch(batch, deadCids []cid.Cid, checkpoint *Checkpoint, markSet MarkSet) (purgeCnt int, liveCnt int, err error) { + if err := s.checkClosing(); err != nil { + return 0, 0, err + } + + s.txnLk.Lock() + defer s.txnLk.Unlock() + + for _, c := range batch { + has, err := markSet.Has(c) + if err != nil { + return 0, 0, xerrors.Errorf("error checking markset for liveness: %w", err) + } + + if has { + liveCnt++ + continue + } + + deadCids = append(deadCids, c) + } + + if len(deadCids) == 0 { + if err := checkpoint.Set(batch[len(batch)-1]); err != nil { + return 0, 0, xerrors.Errorf("error setting checkpoint: %w", err) + } + + return 0, liveCnt, nil + } + + switch s.compactType { + case hot: + if err := s.hot.DeleteMany(s.ctx, deadCids); err != nil { + return 0, liveCnt, xerrors.Errorf("error purging cold objects: %w", err) + } + case cold: + if err := s.cold.DeleteMany(s.ctx, deadCids); err != nil { + return 0, liveCnt, xerrors.Errorf("error purging dead objects: %w", err) + } + default: + return 0, liveCnt, xerrors.Errorf("invalid compaction type %d, only hot and cold allowed for critical section", s.compactType) + } + + s.debug.LogDelete(deadCids) + purgeCnt = len(deadCids) + + if err := checkpoint.Set(batch[len(batch)-1]); err != nil { + return purgeCnt, liveCnt, xerrors.Errorf("error setting checkpoint: %w", err) + } + + return purgeCnt, liveCnt, nil +} + +func (s *SplitStore) coldSetPath() string { + return filepath.Join(s.path, "coldset") +} + +func (s *SplitStore) discardSetPath() string { + return filepath.Join(s.path, "deadset") +} + +func (s *SplitStore) checkpointPath() string { + return filepath.Join(s.path, "checkpoint") +} + +func (s *SplitStore) pruneCheckpointPath() string { + return filepath.Join(s.path, "prune-checkpoint") +} + +func (s *SplitStore) checkpointExists() bool { + _, err := os.Stat(s.checkpointPath()) + return err == nil +} + +func (s *SplitStore) pruneCheckpointExists() bool { + _, err := os.Stat(s.pruneCheckpointPath()) + return err == nil +} + +func (s *SplitStore) completeCompaction() error { + checkpoint, last, err := OpenCheckpoint(s.checkpointPath()) + if err != nil { + return xerrors.Errorf("error opening checkpoint: %w", err) + } + defer checkpoint.Close() //nolint:errcheck + + coldr, err := NewColdSetReader(s.coldSetPath()) + if err != nil { + return xerrors.Errorf("error opening coldset: %w", err) + } + defer coldr.Close() //nolint:errcheck + + markSet, err := s.markSetEnv.Recover("live") + if err != nil { + return xerrors.Errorf("error recovering markset: %w", err) + } + defer markSet.Close() //nolint:errcheck + + // PURGE + s.compactType = hot + log.Info("purging cold objects from the hotstore") + startPurge := time.Now() + err = s.completePurge(coldr, checkpoint, last, markSet) + if err != nil { + return xerrors.Errorf("error purging cold objects: %w", err) + } + log.Infow("purging cold objects from hotstore done", "took", time.Since(startPurge)) + + markSet.EndCriticalSection() + + if err := checkpoint.Close(); err != nil { + log.Warnf("error closing checkpoint: %s", err) + } + if err := os.Remove(s.checkpointPath()); err != nil { + log.Warnf("error removing checkpoint: %s", err) + } + if err := coldr.Close(); err != nil { + log.Warnf("error closing coldset: %s", err) + } + if err := os.Remove(s.coldSetPath()); err != nil { + log.Warnf("error removing coldset: %s", err) + } + s.compactType = none + + // Note: at this point we can start the splitstore; base epoch is not + // incremented here so a compaction should run on the first head + // change, which will trigger gc on the hotstore. + // We don't mind the second (back-to-back) compaction as the head will + // have advanced during marking and coldset accumulation. + return nil +} + +func (s *SplitStore) completePurge(coldr *ColdSetReader, checkpoint *Checkpoint, start cid.Cid, markSet MarkSet) error { + if !start.Defined() { + return s.purge(coldr, checkpoint, markSet) + } + + seeking := true + batch := make([]cid.Cid, 0, batchSize) + deadCids := make([]cid.Cid, 0, batchSize) + + var purgeCnt, liveCnt int + defer func() { + log.Infow("purged cold objects", "purged", purgeCnt, "live", liveCnt) + }() + + deleteBatch := func() error { + pc, lc, err := s.purgeBatch(batch, deadCids, checkpoint, markSet) + + purgeCnt += pc + liveCnt += lc + batch = batch[:0] + + return err + } + + err := coldr.ForEach(func(c cid.Cid) error { + if seeking { + if start.Equals(c) { + seeking = false + } + + return nil + } + + batch = append(batch, c) + if len(batch) == batchSize { + return deleteBatch() + } + + return nil + }) + + if err != nil { + return err + } + + if len(batch) > 0 { + return deleteBatch() + } + + return nil +} + +func (s *SplitStore) clearSizeMeasurements() { + s.szKeys = 0 + s.szMarkedLiveRefs = 0 + s.szProtectedTxns = 0 + s.szWalk = 0 +} + +// I really don't like having this code, but we seem to have some occasional DAG references with +// missing constituents. During testing in mainnet *some* of these references *sometimes* appeared +// after a little bit. +// We need to figure out where they are coming from and eliminate that vector, but until then we +// have this gem[TM]. +// My best guess is that they are parent message receipts or yet to be computed state roots; magik +// thinks the cause may be block validation. +func (s *SplitStore) waitForMissingRefs(markSet MarkSet) { + s.txnLk.Lock() + missing := s.txnMissing + s.txnMissing = nil + s.txnLk.Unlock() + + if len(missing) == 0 { + return + } + + log.Info("waiting for missing references") + start := time.Now() + count := 0 + defer func() { + log.Infow("waiting for missing references done", "took", time.Since(start), "marked", count) + }() + + for i := 0; i < 3 && len(missing) > 0; i++ { + if err := s.checkClosing(); err != nil { + return + } + + wait := time.Duration(i) * time.Minute + log.Infof("retrying for %d missing references in %s (attempt: %d)", len(missing), wait, i+1) + if wait > 0 { + time.Sleep(wait) + } + + towalk := missing + visitor := newTmpVisitor() + missing = make(map[cid.Cid]struct{}) + + for c := range towalk { + _, err := s.walkObjectIncomplete(c, visitor, + func(c cid.Cid) error { + if isUnitaryObject(c) { + return errStopWalk + } + + visit, err := markSet.Visit(c) + if err != nil { + return xerrors.Errorf("error visiting object: %w", err) + } + + if !visit { + return errStopWalk + } + + count++ + return nil + }, + func(c cid.Cid) error { + missing[c] = struct{}{} + return errStopWalk + }) + + if err != nil { + log.Warnf("error marking: %s", err) + } + } + } + + if len(missing) > 0 { + log.Warnf("still missing %d references", len(missing)) + for c := range missing { + log.Warnf("unresolved missing reference: %s", c) + } + } +} diff --git a/blockstore/splitstore/splitstore_expose.go b/blockstore/splitstore/splitstore_expose.go new file mode 100644 index 000000000..931836129 --- /dev/null +++ b/blockstore/splitstore/splitstore_expose.go @@ -0,0 +1,114 @@ +package splitstore + +import ( + "context" + "errors" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + ipld "github.com/ipfs/go-ipld-format" + + bstore "github.com/filecoin-project/lotus/blockstore" +) + +type exposedSplitStore struct { + s *SplitStore +} + +var _ bstore.Blockstore = (*exposedSplitStore)(nil) + +func (s *SplitStore) Expose() bstore.Blockstore { + return &exposedSplitStore{s: s} +} + +func (es *exposedSplitStore) DeleteBlock(_ context.Context, _ cid.Cid) error { + return errors.New("DeleteBlock: operation not supported") +} + +func (es *exposedSplitStore) DeleteMany(_ context.Context, _ []cid.Cid) error { + return errors.New("DeleteMany: operation not supported") +} + +func (es *exposedSplitStore) Has(ctx context.Context, c cid.Cid) (bool, error) { + if isIdentiyCid(c) { + return true, nil + } + + has, err := es.s.hot.Has(ctx, c) + if has || err != nil { + return has, err + } + + return es.s.cold.Has(ctx, c) +} + +func (es *exposedSplitStore) Get(ctx context.Context, c cid.Cid) (blocks.Block, error) { + + if isIdentiyCid(c) { + data, err := decodeIdentityCid(c) + if err != nil { + return nil, err + } + + return blocks.NewBlockWithCid(data, c) + } + + blk, err := es.s.hot.Get(ctx, c) + if ipld.IsNotFound(err) { + return es.s.cold.Get(ctx, c) + } + return blk, err +} + +func (es *exposedSplitStore) GetSize(ctx context.Context, c cid.Cid) (int, error) { + if isIdentiyCid(c) { + data, err := decodeIdentityCid(c) + if err != nil { + return 0, err + } + + return len(data), nil + } + + size, err := es.s.hot.GetSize(ctx, c) + if ipld.IsNotFound(err) { + return es.s.cold.GetSize(ctx, c) + } + return size, err +} + +func (es *exposedSplitStore) Flush(ctx context.Context) error { + return es.s.Flush(ctx) +} + +func (es *exposedSplitStore) Put(ctx context.Context, blk blocks.Block) error { + return es.s.Put(ctx, blk) +} + +func (es *exposedSplitStore) PutMany(ctx context.Context, blks []blocks.Block) error { + return es.s.PutMany(ctx, blks) +} + +func (es *exposedSplitStore) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { + return es.s.AllKeysChan(ctx) +} + +func (es *exposedSplitStore) HashOnRead(enabled bool) {} + +func (es *exposedSplitStore) View(ctx context.Context, c cid.Cid, f func([]byte) error) error { + if isIdentiyCid(c) { + data, err := decodeIdentityCid(c) + if err != nil { + return err + } + + return f(data) + } + + err := es.s.hot.View(ctx, c, f) + if ipld.IsNotFound(err) { + return es.s.cold.View(ctx, c, f) + } + + return err +} diff --git a/blockstore/splitstore/splitstore_gc.go b/blockstore/splitstore/splitstore_gc.go new file mode 100644 index 000000000..8b154f574 --- /dev/null +++ b/blockstore/splitstore/splitstore_gc.go @@ -0,0 +1,102 @@ +package splitstore + +import ( + "fmt" + "time" + + bstore "github.com/filecoin-project/lotus/blockstore" +) + +const ( + // Fraction of garbage in badger vlog for online GC traversal to collect garbage + AggressiveOnlineGCThreshold = 0.0001 +) + +func (s *SplitStore) gcHotAfterCompaction() { + // Measure hotstore size, determine if we should do full GC, determine if we can do full GC. + // We should do full GC if + // FullGCFrequency is specified and compaction index matches frequency + // OR HotstoreMaxSpaceTarget is specified and total moving space is within 150 GB of target + // We can do full if + // HotstoreMaxSpaceTarget is not specified + // OR total moving space would not exceed 50 GB below target + // + // a) If we should not do full GC => online GC + // b) If we should do full GC and can => moving GC + // c) If we should do full GC and can't => aggressive online GC + getSize := func() int64 { + sizer, ok := s.hot.(bstore.BlockstoreSize) + if ok { + size, err := sizer.Size() + if err != nil { + log.Warnf("error getting hotstore size: %s, estimating empty hot store for targeting", err) + return 0 + } + return size + } + log.Errorf("Could not measure hotstore size, assuming it is 0 bytes, which it is not") + return 0 + } + hotSize := getSize() + + copySizeApprox := s.szKeys + s.szMarkedLiveRefs + s.szProtectedTxns + s.szWalk + shouldTarget := s.cfg.HotstoreMaxSpaceTarget > 0 && hotSize+copySizeApprox > int64(s.cfg.HotstoreMaxSpaceTarget)-int64(s.cfg.HotstoreMaxSpaceThreshold) + shouldFreq := s.cfg.HotStoreFullGCFrequency > 0 && s.compactionIndex%int64(s.cfg.HotStoreFullGCFrequency) == 0 + shouldDoFull := shouldTarget || shouldFreq + canDoFull := s.cfg.HotstoreMaxSpaceTarget == 0 || hotSize+copySizeApprox < int64(s.cfg.HotstoreMaxSpaceTarget)-int64(s.cfg.HotstoreMaxSpaceSafetyBuffer) + log.Debugw("approximating new hot store size", "key size", s.szKeys, "marked live refs", s.szMarkedLiveRefs, "protected txns", s.szProtectedTxns, "walked DAG", s.szWalk) + log.Infof("measured hot store size: %d, approximate new size: %d, should do full %t, can do full %t", hotSize, copySizeApprox, shouldDoFull, canDoFull) + + var opts []bstore.BlockstoreGCOption + if shouldDoFull && canDoFull { + opts = append(opts, bstore.WithFullGC(true)) + } else if shouldDoFull && !canDoFull { + log.Warnf("Attention! Estimated moving GC size %d is not within safety buffer %d of target max %d, performing aggressive online GC to attempt to bring hotstore size down safely", copySizeApprox, s.cfg.HotstoreMaxSpaceSafetyBuffer, s.cfg.HotstoreMaxSpaceTarget) + log.Warn("If problem continues you can 1) temporarily allocate more disk space to hotstore and 2) reflect in HotstoreMaxSpaceTarget OR trigger manual move with `lotus chain prune hot-moving`") + log.Warn("If problem continues and you do not have any more disk space you can run continue to manually trigger online GC at aggressive thresholds (< 0.01) with `lotus chain prune hot`") + + opts = append(opts, bstore.WithThreshold(AggressiveOnlineGCThreshold)) + } + + if err := s.gcBlockstore(s.hot, opts); err != nil { + log.Warnf("error garbage collecting hostore: %s", err) + } + log.Infof("measured hot store size after GC: %d", getSize()) +} + +func (s *SplitStore) gcBlockstore(b bstore.Blockstore, opts []bstore.BlockstoreGCOption) error { + if err := s.checkYield(); err != nil { + return err + } + if gc, ok := b.(bstore.BlockstoreGC); ok { + log.Info("garbage collecting blockstore") + startGC := time.Now() + + opts = append(opts, bstore.WithCheckFreq(90*time.Second)) + opts = append(opts, bstore.WithCheck(s.checkYield)) + if err := gc.CollectGarbage(s.ctx, opts...); err != nil { + return err + } + + log.Infow("garbage collecting blockstore done", "took", time.Since(startGC)) + return nil + } + + return fmt.Errorf("blockstore doesn't support garbage collection: %T", b) +} + +func (s *SplitStore) gcBlockstoreOnce(b bstore.Blockstore, opts []bstore.BlockstoreGCOption) error { + if gc, ok := b.(bstore.BlockstoreGCOnce); ok { + log.Debug("gc blockstore once") + startGC := time.Now() + + if err := gc.GCOnce(s.ctx, opts...); err != nil { + return err + } + + log.Debugw("gc blockstore once done", "took", time.Since(startGC)) + return nil + } + + return fmt.Errorf("blockstore doesn't support gc once: %T", b) +} diff --git a/blockstore/splitstore/splitstore_prune.go b/blockstore/splitstore/splitstore_prune.go new file mode 100644 index 000000000..08d5b8cca --- /dev/null +++ b/blockstore/splitstore/splitstore_prune.go @@ -0,0 +1,591 @@ +package splitstore + +import ( + "bytes" + "os" + "runtime" + "sync" + "sync/atomic" + "time" + + cid "github.com/ipfs/go-cid" + ipld "github.com/ipfs/go-ipld-format" + cbg "github.com/whyrusleeping/cbor-gen" + "go.opencensus.io/stats" + "golang.org/x/xerrors" + + "github.com/filecoin-project/lotus/api" + bstore "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/metrics" +) + +var ( + // PruneOnline is a prune option that instructs PruneChain to use online gc for reclaiming space; + // there is no value associated with this option. + PruneOnlineGC = "splitstore.PruneOnlineGC" + + // PruneMoving is a prune option that instructs PruneChain to use moving gc for reclaiming space; + // the value associated with this option is the path of the new coldstore. + PruneMovingGC = "splitstore.PruneMovingGC" + + // PruneRetainState is a prune option that instructs PruneChain as to how many finalities worth + // of state to retain in the coldstore. + // The value is an integer: + // - if it is -1 then all state objects reachable from the chain will be retained in the coldstore. + // this is useful for garbage collecting side-chains and other garbage in archival nodes. + // This is the (safe) default. + // - if it is 0 then no state objects that are unreachable within the compaction boundary will + // be retained in the coldstore. + // - if it is a positive integer, then it's the number of finalities past the compaction boundary + // for which chain-reachable state objects are retained. + PruneRetainState = "splitstore.PruneRetainState" + + // PruneThreshold is the number of epochs that need to have elapsed + // from the previously pruned epoch to trigger a new prune + PruneThreshold = 7 * build.Finality +) + +// GCHotstore runs online GC on the chain state in the hotstore according the to options specified +func (s *SplitStore) GCHotStore(opts api.HotGCOpts) error { + if opts.Moving { + gcOpts := []bstore.BlockstoreGCOption{bstore.WithFullGC(true)} + return s.gcBlockstore(s.hot, gcOpts) + } + + gcOpts := []bstore.BlockstoreGCOption{bstore.WithThreshold(opts.Threshold)} + var err error + if opts.Periodic { + err = s.gcBlockstore(s.hot, gcOpts) + } else { + err = s.gcBlockstoreOnce(s.hot, gcOpts) + } + return err +} + +// PruneChain instructs the SplitStore to prune chain state in the coldstore, according to the +// options specified. +func (s *SplitStore) PruneChain(opts api.PruneOpts) error { + retainState := opts.RetainState + + var gcOpts []bstore.BlockstoreGCOption + if opts.MovingGC { + gcOpts = append(gcOpts, bstore.WithFullGC(true)) + } + doGC := func() error { return s.gcBlockstore(s.cold, gcOpts) } + + var retainStateP func(int64) bool + switch { + case retainState > 0: + retainStateP = func(depth int64) bool { + return depth <= int64(CompactionBoundary)+retainState*int64(build.Finality) + } + case retainState < 0: + retainStateP = func(_ int64) bool { return true } + default: + retainStateP = func(depth int64) bool { + return depth <= int64(CompactionBoundary) + } + } + + if _, ok := s.cold.(bstore.BlockstoreIterator); !ok { + return xerrors.Errorf("coldstore does not support efficient iteration") + } + + return s.pruneChain(retainStateP, doGC) +} + +func (s *SplitStore) pruneChain(retainStateP func(int64) bool, doGC func() error) error { + // inhibit compaction while we are setting up + s.headChangeMx.Lock() + defer s.headChangeMx.Unlock() + + // take the compaction lock; fail if there is a compaction in progress + if !atomic.CompareAndSwapInt32(&s.compacting, 0, 1) { + return xerrors.Errorf("compaction, prune or warmup in progress") + } + + // check if we are actually closing first + if atomic.LoadInt32(&s.closing) == 1 { + atomic.StoreInt32(&s.compacting, 0) + return errClosing + } + + // ensure that we have compacted at least once + if s.compactionIndex == 0 { + atomic.StoreInt32(&s.compacting, 0) + return xerrors.Errorf("splitstore has not compacted yet") + } + + // get the current tipset + curTs := s.chain.GetHeaviestTipSet() + + // begin the transaction and go + s.beginTxnProtect() + s.compactType = cold + go func() { + defer atomic.StoreInt32(&s.compacting, 0) + defer s.endTxnProtect() + + log.Info("pruning splitstore") + start := time.Now() + + s.prune(curTs, retainStateP, doGC) + + log.Infow("prune done", "took", time.Since(start)) + }() + + return nil +} + +func (s *SplitStore) prune(curTs *types.TipSet, retainStateP func(int64) bool, doGC func() error) { + log.Debug("waiting for active views to complete") + start := time.Now() + s.viewWait() + log.Debugw("waiting for active views done", "took", time.Since(start)) + + err := s.doPrune(curTs, retainStateP, doGC) + if err != nil { + log.Errorf("PRUNE ERROR: %s", err) + } +} + +func (s *SplitStore) doPrune(curTs *types.TipSet, retainStateP func(int64) bool, doGC func() error) error { + currentEpoch := curTs.Height() + boundaryEpoch := currentEpoch - CompactionBoundary + + log.Infow("running prune", "currentEpoch", currentEpoch, "pruneEpoch", s.pruneEpoch) + + markSet, err := s.markSetEnv.New("live", s.markSetSize) + if err != nil { + return xerrors.Errorf("error creating mark set: %w", err) + } + defer markSet.Close() //nolint:errcheck + defer s.debug.Flush() + + if err := s.checkClosing(); err != nil { + return err + } + + // 0. track all protected references at beginning of compaction; anything added later should + // be transactionally protected by the write + log.Info("protecting references with registered protectors") + err = s.applyProtectors() + if err != nil { + return err + } + + // 1. mark reachable objects by walking the chain from the current epoch; we keep all messages + // and chain headers; state and reciepts are retained only if it is within retention policy scope + log.Info("marking reachable objects") + startMark := time.Now() + + count := new(int64) + err = s.walkChainDeep(curTs, retainStateP, + func(c cid.Cid) error { + if isUnitaryObject(c) { + return errStopWalk + } + + mark, err := markSet.Has(c) + if err != nil { + return xerrors.Errorf("error checking markset: %w", err) + } + + if mark { + return errStopWalk + } + + atomic.AddInt64(count, 1) + return markSet.Mark(c) + }) + + if err != nil { + return xerrors.Errorf("error marking: %w", err) + } + + log.Infow("marking done", "took", time.Since(startMark), "marked", count) + + if err := s.checkClosing(); err != nil { + return err + } + + // 1.1 protect transactional refs + err = s.protectTxnRefs(markSet) + if err != nil { + return xerrors.Errorf("error protecting transactional refs: %w", err) + } + + if err := s.checkClosing(); err != nil { + return err + } + + // 2. iterate through the coldstore to collect dead objects + log.Info("collecting dead objects") + startCollect := time.Now() + + deadw, err := NewColdSetWriter(s.discardSetPath()) + if err != nil { + return xerrors.Errorf("error creating coldset: %w", err) + } + defer deadw.Close() //nolint:errcheck + + // some stats for logging + var liveCnt, deadCnt int + + err = s.cold.(bstore.BlockstoreIterator).ForEachKey(func(c cid.Cid) error { + // was it marked? + mark, err := markSet.Has(c) + if err != nil { + return xerrors.Errorf("error checking mark set for %s: %w", c, err) + } + + if mark { + liveCnt++ + return nil + } + + // Note: a possible optimization here is to also purge objects that are in the hotstore + // but this needs special care not to purge genesis state, so we don't bother (yet) + + // it's dead in the coldstore, mark it as candidate for purge + + if err := deadw.Write(c); err != nil { + return xerrors.Errorf("error writing cid to coldstore: %w", err) + } + deadCnt++ + + return nil + }) + + if err != nil { + return xerrors.Errorf("error dead objects: %w", err) + } + + if err := deadw.Close(); err != nil { + return xerrors.Errorf("error closing deadset: %w", err) + } + + stats.Record(s.ctx, metrics.SplitstoreCompactionDead.M(int64(deadCnt))) + + log.Infow("dead collection done", "took", time.Since(startCollect)) + log.Infow("prune stats", "live", liveCnt, "dead", deadCnt) + + if err := s.checkClosing(); err != nil { + return err + } + + // now that we have collected dead objects, check for missing references from transactional i/o + // this is carried over from hot compaction for completeness + s.waitForMissingRefs(markSet) + + if err := s.checkClosing(); err != nil { + return err + } + + deadr, err := NewColdSetReader(s.discardSetPath()) + if err != nil { + return xerrors.Errorf("error opening deadset: %w", err) + } + defer deadr.Close() //nolint:errcheck + + // 3. Purge dead objects with checkpointing for recovery. + // This is the critical section of prune, whereby any dead object not in the markSet is + // considered already deleted. + // We delete dead objects in batches, holding the transaction lock, where we check the markSet + // again for new references created by the caller. + // After each batch we write a checkpoint to disk; if the process is interrupted before completion + // the process will continue from the checkpoint in the next recovery. + if err := s.beginCriticalSection(markSet); err != nil { + return xerrors.Errorf("error beginning critical section: %w", err) + } + + if err := s.checkClosing(); err != nil { + return err + } + + checkpoint, err := NewCheckpoint(s.pruneCheckpointPath()) + if err != nil { + return xerrors.Errorf("error creating checkpoint: %w", err) + } + defer checkpoint.Close() //nolint:errcheck + + log.Info("purging dead objects from the coldstore") + startPurge := time.Now() + err = s.purge(deadr, checkpoint, markSet) + if err != nil { + return xerrors.Errorf("error purging dead objects: %w", err) + } + log.Infow("purging dead objects from coldstore done", "took", time.Since(startPurge)) + + s.endCriticalSection() + + if err := checkpoint.Close(); err != nil { + log.Warnf("error closing checkpoint: %s", err) + } + if err := os.Remove(s.pruneCheckpointPath()); err != nil { + log.Warnf("error removing checkpoint: %s", err) + } + if err := deadr.Close(); err != nil { + log.Warnf("error closing discard set: %s", err) + } + if err := os.Remove(s.discardSetPath()); err != nil { + log.Warnf("error removing discard set: %s", err) + } + + // we are done; do some housekeeping + s.endTxnProtect() + err = doGC() + if err != nil { + log.Warnf("error garbage collecting cold store: %s", err) + } + + if err := s.setPruneEpoch(boundaryEpoch); err != nil { + return xerrors.Errorf("error saving prune base epoch: %w", err) + } + + s.pruneIndex++ + err = s.ds.Put(s.ctx, pruneIndexKey, int64ToBytes(s.pruneIndex)) + if err != nil { + return xerrors.Errorf("error saving prune index: %w", err) + } + + return nil +} + +func (s *SplitStore) completePrune() error { + checkpoint, last, err := OpenCheckpoint(s.pruneCheckpointPath()) + if err != nil { + return xerrors.Errorf("error opening checkpoint: %w", err) + } + defer checkpoint.Close() //nolint:errcheck + + deadr, err := NewColdSetReader(s.discardSetPath()) + if err != nil { + return xerrors.Errorf("error opening deadset: %w", err) + } + defer deadr.Close() //nolint:errcheck + + markSet, err := s.markSetEnv.Recover("live") + if err != nil { + return xerrors.Errorf("error recovering markset: %w", err) + } + defer markSet.Close() //nolint:errcheck + + // PURGE! + s.compactType = cold + log.Info("purging dead objects from the coldstore") + startPurge := time.Now() + err = s.completePurge(deadr, checkpoint, last, markSet) + if err != nil { + return xerrors.Errorf("error purgin dead objects: %w", err) + } + log.Infow("purging dead objects from the coldstore done", "took", time.Since(startPurge)) + + markSet.EndCriticalSection() + s.compactType = none + + if err := checkpoint.Close(); err != nil { + log.Warnf("error closing checkpoint: %s", err) + } + if err := os.Remove(s.pruneCheckpointPath()); err != nil { + log.Warnf("error removing checkpoint: %s", err) + } + if err := deadr.Close(); err != nil { + log.Warnf("error closing deadset: %s", err) + } + if err := os.Remove(s.discardSetPath()); err != nil { + log.Warnf("error removing deadset: %s", err) + } + + return nil +} + +// like walkChain but peforms a deep walk, using parallel walking with walkObjectLax, +// whereby all extant messages are retained and state roots are retained if they satisfy +// the given predicate. +// missing references are ignored, as we expect to have plenty for snapshot syncs. +func (s *SplitStore) walkChainDeep(ts *types.TipSet, retainStateP func(int64) bool, + f func(cid.Cid) error) error { + visited := cid.NewSet() + toWalk := ts.Cids() + walkCnt := 0 + + workers := runtime.NumCPU() / 2 + if workers < 2 { + workers = 2 + } + + var wg sync.WaitGroup + workch := make(chan cid.Cid, 16*workers) + errch := make(chan error, workers) + + var once sync.Once + defer once.Do(func() { close(workch) }) + + push := func(c cid.Cid) error { + if !visited.Visit(c) { + return nil + } + + select { + case workch <- c: + return nil + case err := <-errch: + return err + } + } + + worker := func() { + defer wg.Done() + for c := range workch { + err := s.walkObjectLax(c, f) + if err != nil { + errch <- xerrors.Errorf("error walking object (cid: %s): %w", c, err) + return + } + } + } + + for i := 0; i < workers; i++ { + wg.Add(1) + go worker() + } + + baseEpoch := ts.Height() + minEpoch := baseEpoch // for progress report + log.Infof("walking at epoch %d", minEpoch) + + walkBlock := func(c cid.Cid) error { + if !visited.Visit(c) { + return nil + } + + walkCnt++ + + if err := f(c); err != nil { + return err + } + + var hdr types.BlockHeader + err := s.view(c, func(data []byte) error { + return hdr.UnmarshalCBOR(bytes.NewBuffer(data)) + }) + + if err != nil { + return xerrors.Errorf("error unmarshaling block header (cid: %s): %w", c, err) + } + + if hdr.Height < minEpoch { + minEpoch = hdr.Height + if minEpoch%10_000 == 0 { + log.Infof("walking at epoch %d (walked: %d)", minEpoch, walkCnt) + } + } + + depth := int64(baseEpoch - hdr.Height) + retainState := retainStateP(depth) + + if hdr.Height > 0 { + if err := push(hdr.Messages); err != nil { + return err + } + if retainState { + if err := push(hdr.ParentMessageReceipts); err != nil { + return err + } + } + } + + if retainState || hdr.Height == 0 { + if err := push(hdr.ParentStateRoot); err != nil { + return err + } + } + + if hdr.Height > 0 { + toWalk = append(toWalk, hdr.Parents...) + } + + return nil + } + + for len(toWalk) > 0 { + // walking can take a while, so check this with every opportunity + if err := s.checkClosing(); err != nil { + return err + } + + select { + case err := <-errch: + return err + default: + } + + walking := toWalk + toWalk = nil + for _, c := range walking { + if err := walkBlock(c); err != nil { + return xerrors.Errorf("error walking block (cid: %s): %w", c, err) + } + } + } + + once.Do(func() { close(workch) }) + wg.Wait() + select { + case err := <-errch: + return err + default: + } + + log.Infow("chain walk done", "walked", walkCnt) + + return nil +} + +// like walkObject but treats missing references laxly; faster version of walkObjectIncomplete +// without an occurs check. +func (s *SplitStore) walkObjectLax(c cid.Cid, f func(cid.Cid) error) error { + if err := f(c); err != nil { + if err == errStopWalk { + return nil + } + + return err + } + + if c.Prefix().Codec != cid.DagCBOR { + return nil + } + + // check this before recursing + if err := s.checkClosing(); err != nil { + return err + } + + var links []cid.Cid + err := s.view(c, func(data []byte) error { + return cbg.ScanForLinks(bytes.NewReader(data), func(c cid.Cid) { + links = append(links, c) + }) + }) + + if err != nil { + if ipld.IsNotFound(err) { // not a problem for deep walks + return nil + } + + return xerrors.Errorf("error scanning linked block (cid: %s): %w", c, err) + } + + for _, c := range links { + err := s.walkObjectLax(c, f) + if err != nil { + return xerrors.Errorf("error walking link (cid: %s): %w", c, err) + } + } + + return nil +} diff --git a/blockstore/splitstore/splitstore_reify.go b/blockstore/splitstore/splitstore_reify.go new file mode 100644 index 000000000..7f54de55f --- /dev/null +++ b/blockstore/splitstore/splitstore_reify.go @@ -0,0 +1,181 @@ +package splitstore + +import ( + "errors" + "runtime" + "sync/atomic" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" +) + +var ( + errReifyLimit = errors.New("reification limit reached") + ReifyLimit = 16384 +) + +func (s *SplitStore) reifyColdObject(c cid.Cid) { + if !s.isWarm() { + return + } + + if isUnitaryObject(c) { + return + } + + s.reifyMx.Lock() + defer s.reifyMx.Unlock() + + _, ok := s.reifyInProgress[c] + if ok { + return + } + + s.reifyPend[c] = struct{}{} + s.reifyCond.Broadcast() +} + +func (s *SplitStore) reifyOrchestrator() { + workers := runtime.NumCPU() / 4 + if workers < 2 { + workers = 2 + } + + workch := make(chan cid.Cid, workers) + defer close(workch) + + for i := 0; i < workers; i++ { + s.reifyWorkers.Add(1) + go s.reifyWorker(workch) + } + + for { + s.reifyMx.Lock() + for len(s.reifyPend) == 0 && atomic.LoadInt32(&s.closing) == 0 { + s.reifyCond.Wait() + } + + if atomic.LoadInt32(&s.closing) != 0 { + s.reifyMx.Unlock() + return + } + + reifyPend := s.reifyPend + s.reifyPend = make(map[cid.Cid]struct{}) + s.reifyMx.Unlock() + + for c := range reifyPend { + select { + case workch <- c: + case <-s.ctx.Done(): + return + } + } + } +} + +func (s *SplitStore) reifyWorker(workch chan cid.Cid) { + defer s.reifyWorkers.Done() + for c := range workch { + s.doReify(c) + } +} + +func (s *SplitStore) doReify(c cid.Cid) { + var toreify, toforget []cid.Cid + + defer func() { + s.reifyMx.Lock() + defer s.reifyMx.Unlock() + + for _, c := range toreify { + delete(s.reifyInProgress, c) + } + for _, c := range toforget { + delete(s.reifyInProgress, c) + } + }() + + s.txnLk.RLock() + defer s.txnLk.RUnlock() + + count := 0 + _, err := s.walkObjectIncomplete(c, newTmpVisitor(), + func(c cid.Cid) error { + if isUnitaryObject(c) { + return errStopWalk + } + + count++ + if count > ReifyLimit { + return errReifyLimit + } + + s.reifyMx.Lock() + _, inProgress := s.reifyInProgress[c] + if !inProgress { + s.reifyInProgress[c] = struct{}{} + } + s.reifyMx.Unlock() + + if inProgress { + return errStopWalk + } + + has, err := s.hot.Has(s.ctx, c) + if err != nil { + return xerrors.Errorf("error checking hotstore: %w", err) + } + + // All reified blocks are tracked at reification start + if has { + toforget = append(toforget, c) + return errStopWalk + } + + toreify = append(toreify, c) + return nil + }, + func(missing cid.Cid) error { + log.Warnf("missing reference while reifying %s: %s", c, missing) + return errStopWalk + }) + + if err != nil { + if errors.Is(err, errReifyLimit) { + log.Debug("reification aborted; reify limit reached") + return + } + + log.Warnf("error walking cold object for reification (cid: %s): %s", c, err) + return + } + + log.Debugf("reifying %d objects rooted at %s", len(toreify), c) + + // this should not get too big, maybe some 100s of objects. + batch := make([]blocks.Block, 0, len(toreify)) + for _, c := range toreify { + blk, err := s.cold.Get(s.ctx, c) + if err != nil { + log.Warnf("error retrieving cold object for reification (cid: %s): %s", c, err) + continue + } + + if err := s.checkClosing(); err != nil { + return + } + + batch = append(batch, blk) + } + + if len(batch) > 0 { + err = s.hot.PutMany(s.ctx, batch) + if err != nil { + log.Warnf("error reifying cold object (cid: %s): %s", c, err) + return + } + } + +} diff --git a/blockstore/splitstore/splitstore_test.go b/blockstore/splitstore/splitstore_test.go new file mode 100644 index 000000000..63e77b47e --- /dev/null +++ b/blockstore/splitstore/splitstore_test.go @@ -0,0 +1,781 @@ +// stm: #unit +package splitstore + +import ( + "context" + "errors" + "fmt" + "math/rand" + "sync" + "sync/atomic" + "testing" + "time" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + "github.com/ipfs/go-datastore" + dssync "github.com/ipfs/go-datastore/sync" + ipld "github.com/ipfs/go-ipld-format" + logging "github.com/ipfs/go-log/v2" + mh "github.com/multiformats/go-multihash" + + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/mock" +) + +func init() { + CompactionThreshold = 5 + CompactionBoundary = 2 + WarmupBoundary = 0 + SyncWaitTime = time.Millisecond + logging.SetLogLevel("splitstore", "DEBUG") +} + +func testSplitStore(t *testing.T, cfg *Config) { + ctx := context.Background() + chain := &mockChain{t: t} + fmt.Printf("Config: %v\n", cfg) + + // the myriads of stores + ds := dssync.MutexWrap(datastore.NewMapDatastore()) + hot := newMockStore() + cold := newMockStore() + + // this is necessary to avoid the garbage mock puts in the blocks + garbage := blocks.NewBlock([]byte{1, 2, 3}) + err := cold.Put(ctx, garbage) + if err != nil { + t.Fatal(err) + } + + // genesis + genBlock := mock.MkBlock(nil, 0, 0) + genBlock.Messages = garbage.Cid() + genBlock.ParentMessageReceipts = garbage.Cid() + genBlock.ParentStateRoot = garbage.Cid() + genBlock.Timestamp = uint64(time.Now().Unix()) + + genTs := mock.TipSet(genBlock) + chain.push(genTs) + + // put the genesis block to cold store + blk, err := genBlock.ToStorageBlock() + if err != nil { + t.Fatal(err) + } + + err = cold.Put(ctx, blk) + if err != nil { + t.Fatal(err) + } + + // create a garbage block that is protected with a rgistered protector + protected := blocks.NewBlock([]byte("protected!")) + err = hot.Put(ctx, protected) + if err != nil { + t.Fatal(err) + } + + // and another one that is not protected + unprotected := blocks.NewBlock([]byte("unprotected!")) + err = hot.Put(ctx, unprotected) + if err != nil { + t.Fatal(err) + } + + path := t.TempDir() + + // open the splitstore + ss, err := Open(path, ds, hot, cold, cfg) + if err != nil { + t.Fatal(err) + } + defer ss.Close() //nolint + + // register our protector + ss.AddProtector(func(protect func(cid.Cid) error) error { + return protect(protected.Cid()) + }) + + err = ss.Start(chain, nil) + if err != nil { + t.Fatal(err) + } + + // make some tipsets, but not enough to cause compaction + mkBlock := func(curTs *types.TipSet, i int, stateRoot blocks.Block) *types.TipSet { + blk := mock.MkBlock(curTs, uint64(i), uint64(i)) + + blk.Messages = garbage.Cid() + blk.ParentMessageReceipts = garbage.Cid() + blk.ParentStateRoot = stateRoot.Cid() + blk.Timestamp = uint64(time.Now().Unix()) + + sblk, err := blk.ToStorageBlock() + if err != nil { + t.Fatal(err) + } + err = ss.Put(ctx, stateRoot) + if err != nil { + t.Fatal(err) + } + err = ss.Put(ctx, sblk) + if err != nil { + t.Fatal(err) + } + ts := mock.TipSet(blk) + chain.push(ts) + + return ts + } + + waitForCompaction := func() { + ss.txnSyncMx.Lock() + ss.txnSync = true + ss.txnSyncCond.Broadcast() + ss.txnSyncMx.Unlock() + for atomic.LoadInt32(&ss.compacting) == 1 { + time.Sleep(100 * time.Millisecond) + } + } + + curTs := genTs + for i := 1; i < 5; i++ { + stateRoot := blocks.NewBlock([]byte{byte(i), 3, 3, 7}) + curTs = mkBlock(curTs, i, stateRoot) + waitForCompaction() + } + + // count objects in the cold and hot stores + countBlocks := func(bs blockstore.Blockstore) int { + count := 0 + _ = bs.(blockstore.BlockstoreIterator).ForEachKey(func(_ cid.Cid) error { + count++ + return nil + }) + return count + } + + coldCnt := countBlocks(cold) + hotCnt := countBlocks(hot) + + if coldCnt != 2 { + t.Errorf("expected %d blocks, but got %d", 2, coldCnt) + } + + if hotCnt != 12 { + t.Errorf("expected %d blocks, but got %d", 12, hotCnt) + } + + // trigger a compaction + for i := 5; i < 10; i++ { + stateRoot := blocks.NewBlock([]byte{byte(i), 3, 3, 7}) + curTs = mkBlock(curTs, i, stateRoot) + waitForCompaction() + } + + coldCnt = countBlocks(cold) + hotCnt = countBlocks(hot) + + if coldCnt != 6 { + t.Errorf("expected %d cold blocks, but got %d", 6, coldCnt) + } + + if hotCnt != 18 { + t.Errorf("expected %d hot blocks, but got %d", 18, hotCnt) + } + + // ensure our protected block is still there + has, err := hot.Has(ctx, protected.Cid()) + if err != nil { + t.Fatal(err) + } + + if !has { + t.Fatal("protected block is missing from hotstore") + } + + // ensure our unprotected block is in the coldstore now + has, err = hot.Has(ctx, unprotected.Cid()) + if err != nil { + t.Fatal(err) + } + + if has { + t.Fatal("unprotected block is still in hotstore") + } + + has, err = cold.Has(ctx, unprotected.Cid()) + if err != nil { + t.Fatal(err) + } + + if !has { + t.Fatal("unprotected block is missing from coldstore") + } + + // Make sure we can revert without panicking. + chain.revert(2) +} + +func TestSplitStoreCompaction(t *testing.T) { + //stm: @SPLITSTORE_SPLITSTORE_OPEN_001, @SPLITSTORE_SPLITSTORE_CLOSE_001 + //stm: @SPLITSTORE_SPLITSTORE_PUT_001, @SPLITSTORE_SPLITSTORE_ADD_PROTECTOR_001 + //stm: @SPLITSTORE_SPLITSTORE_CLOSE_001 + testSplitStore(t, &Config{MarkSetType: "map", UniversalColdBlocks: true}) +} + +func TestSplitStoreCompactionWithBadger(t *testing.T) { + //stm: @SPLITSTORE_SPLITSTORE_OPEN_001, @SPLITSTORE_SPLITSTORE_CLOSE_001 + //stm: @SPLITSTORE_SPLITSTORE_PUT_001, @SPLITSTORE_SPLITSTORE_ADD_PROTECTOR_001 + //stm: @SPLITSTORE_SPLITSTORE_CLOSE_001 + bs := badgerMarkSetBatchSize + badgerMarkSetBatchSize = 1 + t.Cleanup(func() { + badgerMarkSetBatchSize = bs + }) + testSplitStore(t, &Config{MarkSetType: "badger", UniversalColdBlocks: true}) +} + +func TestSplitStoreSuppressCompactionNearUpgrade(t *testing.T) { + //stm: @SPLITSTORE_SPLITSTORE_OPEN_001, @SPLITSTORE_SPLITSTORE_CLOSE_001 + //stm: @SPLITSTORE_SPLITSTORE_PUT_001, @SPLITSTORE_SPLITSTORE_ADD_PROTECTOR_001 + //stm: @SPLITSTORE_SPLITSTORE_CLOSE_001 + ctx := context.Background() + chain := &mockChain{t: t} + + // the myriads of stores + ds := dssync.MutexWrap(datastore.NewMapDatastore()) + hot := newMockStore() + cold := newMockStore() + + // this is necessary to avoid the garbage mock puts in the blocks + garbage := blocks.NewBlock([]byte{1, 2, 3}) + err := cold.Put(ctx, garbage) + if err != nil { + t.Fatal(err) + } + + // genesis + genBlock := mock.MkBlock(nil, 0, 0) + genBlock.Messages = garbage.Cid() + genBlock.ParentMessageReceipts = garbage.Cid() + genBlock.ParentStateRoot = garbage.Cid() + genBlock.Timestamp = uint64(time.Now().Unix()) + + genTs := mock.TipSet(genBlock) + chain.push(genTs) + + // put the genesis block to cold store + blk, err := genBlock.ToStorageBlock() + if err != nil { + t.Fatal(err) + } + + err = cold.Put(ctx, blk) + if err != nil { + t.Fatal(err) + } + + path := t.TempDir() + + // open the splitstore + ss, err := Open(path, ds, hot, cold, &Config{MarkSetType: "map", UniversalColdBlocks: true}) + if err != nil { + t.Fatal(err) + } + defer ss.Close() //nolint + + // create an upgrade schedule that will suppress compaction during the test + upgradeBoundary = 0 + upgrade := stmgr.Upgrade{ + Height: 10, + PreMigrations: []stmgr.PreMigration{{StartWithin: 10}}, + } + + err = ss.Start(chain, []stmgr.Upgrade{upgrade}) + if err != nil { + t.Fatal(err) + } + + mkBlock := func(curTs *types.TipSet, i int, stateRoot blocks.Block) *types.TipSet { + blk := mock.MkBlock(curTs, uint64(i), uint64(i)) + + blk.Messages = garbage.Cid() + blk.ParentMessageReceipts = garbage.Cid() + blk.ParentStateRoot = stateRoot.Cid() + blk.Timestamp = uint64(time.Now().Unix()) + + sblk, err := blk.ToStorageBlock() + if err != nil { + t.Fatal(err) + } + err = ss.Put(ctx, stateRoot) + if err != nil { + t.Fatal(err) + } + err = ss.Put(ctx, sblk) + if err != nil { + t.Fatal(err) + } + ts := mock.TipSet(blk) + chain.push(ts) + + return ts + } + + waitForCompaction := func() { + ss.txnSyncMx.Lock() + ss.txnSync = true + ss.txnSyncCond.Broadcast() + ss.txnSyncMx.Unlock() + for atomic.LoadInt32(&ss.compacting) == 1 { + time.Sleep(100 * time.Millisecond) + } + } + + curTs := genTs + for i := 1; i < 10; i++ { + stateRoot := blocks.NewBlock([]byte{byte(i), 3, 3, 7}) + curTs = mkBlock(curTs, i, stateRoot) + waitForCompaction() + } + + countBlocks := func(bs blockstore.Blockstore) int { + count := 0 + _ = bs.(blockstore.BlockstoreIterator).ForEachKey(func(_ cid.Cid) error { + count++ + return nil + }) + return count + } + + // we should not have compacted due to suppression and everything should still be hot + hotCnt := countBlocks(hot) + coldCnt := countBlocks(cold) + + if hotCnt != 20 { + t.Errorf("expected %d blocks, but got %d", 20, hotCnt) + } + + if coldCnt != 2 { + t.Errorf("expected %d blocks, but got %d", 2, coldCnt) + } + + // put some more blocks, now we should compact + for i := 10; i < 20; i++ { + stateRoot := blocks.NewBlock([]byte{byte(i), 3, 3, 7}) + curTs = mkBlock(curTs, i, stateRoot) + waitForCompaction() + } + + hotCnt = countBlocks(hot) + coldCnt = countBlocks(cold) + + if hotCnt != 24 { + t.Errorf("expected %d blocks, but got %d", 24, hotCnt) + } + + if coldCnt != 18 { + t.Errorf("expected %d blocks, but got %d", 18, coldCnt) + } +} + +func testSplitStoreReification(t *testing.T, f func(context.Context, blockstore.Blockstore, cid.Cid) error) { + ds := dssync.MutexWrap(datastore.NewMapDatastore()) + hot := newMockStore() + cold := newMockStore() + + mkRandomBlock := func() blocks.Block { + data := make([]byte, 128) + _, err := rand.Read(data) + if err != nil { + t.Fatal(err) + } + + return blocks.NewBlock(data) + } + + block1 := mkRandomBlock() + block2 := mkRandomBlock() + block3 := mkRandomBlock() + + hdr := mock.MkBlock(nil, 0, 0) + hdr.Messages = block1.Cid() + hdr.ParentMessageReceipts = block2.Cid() + hdr.ParentStateRoot = block3.Cid() + block4, err := hdr.ToStorageBlock() + if err != nil { + t.Fatal(err) + } + + allBlocks := []blocks.Block{block1, block2, block3, block4} + for _, blk := range allBlocks { + err := cold.Put(context.Background(), blk) + if err != nil { + t.Fatal(err) + } + } + + path := t.TempDir() + + ss, err := Open(path, ds, hot, cold, &Config{MarkSetType: "map", UniversalColdBlocks: true}) + if err != nil { + t.Fatal(err) + } + defer ss.Close() //nolint + + ss.warmupEpoch.Store(1) + go ss.reifyOrchestrator() + + waitForReification := func() { + for { + ss.reifyMx.Lock() + ready := len(ss.reifyPend) == 0 && len(ss.reifyInProgress) == 0 + ss.reifyMx.Unlock() + + if ready { + return + } + + time.Sleep(time.Millisecond) + } + } + + // first access using the standard view + err = f(context.Background(), ss, block4.Cid()) + if err != nil { + t.Fatal(err) + } + + // nothing should be reified + waitForReification() + for _, blk := range allBlocks { + has, err := hot.Has(context.Background(), blk.Cid()) + if err != nil { + t.Fatal(err) + } + + if has { + t.Fatal("block unexpectedly reified") + } + } + + // now make the hot/reifying view and ensure access reifies + err = f(blockstore.WithHotView(context.Background()), ss, block4.Cid()) + if err != nil { + t.Fatal(err) + } + + // everything should be reified + waitForReification() + for i, blk := range allBlocks { + has, err := hot.Has(context.Background(), blk.Cid()) + if err != nil { + t.Fatal(err) + } + + if !has { + t.Fatalf("block%d was not reified", i+1) + } + } +} + +func testSplitStoreReificationLimit(t *testing.T, f func(context.Context, blockstore.Blockstore, cid.Cid) error) { + ds := dssync.MutexWrap(datastore.NewMapDatastore()) + hot := newMockStore() + cold := newMockStore() + + mkRandomBlock := func() blocks.Block { + data := make([]byte, 128) + _, err := rand.Read(data) + if err != nil { + t.Fatal(err) + } + + return blocks.NewBlock(data) + } + + block1 := mkRandomBlock() + block2 := mkRandomBlock() + block3 := mkRandomBlock() + + hdr := mock.MkBlock(nil, 0, 0) + hdr.Messages = block1.Cid() + hdr.ParentMessageReceipts = block2.Cid() + hdr.ParentStateRoot = block3.Cid() + block4, err := hdr.ToStorageBlock() + if err != nil { + t.Fatal(err) + } + + allBlocks := []blocks.Block{block1, block2, block3, block4} + for _, blk := range allBlocks { + err := cold.Put(context.Background(), blk) + if err != nil { + t.Fatal(err) + } + } + + path := t.TempDir() + + ss, err := Open(path, ds, hot, cold, &Config{MarkSetType: "map", UniversalColdBlocks: true}) + if err != nil { + t.Fatal(err) + } + defer ss.Close() //nolint + + ss.warmupEpoch.Store(1) + go ss.reifyOrchestrator() + + waitForReification := func() { + for { + ss.reifyMx.Lock() + ready := len(ss.reifyPend) == 0 && len(ss.reifyInProgress) == 0 + ss.reifyMx.Unlock() + + if ready { + return + } + + time.Sleep(time.Millisecond) + } + } + + // do a hot access -- nothing should be reified as the limit should be exceeded + oldReifyLimit := ReifyLimit + ReifyLimit = 2 + t.Cleanup(func() { + ReifyLimit = oldReifyLimit + }) + + err = f(blockstore.WithHotView(context.Background()), ss, block4.Cid()) + if err != nil { + t.Fatal(err) + } + + waitForReification() + + for _, blk := range allBlocks { + has, err := hot.Has(context.Background(), blk.Cid()) + if err != nil { + t.Fatal(err) + } + + if has { + t.Fatal("block unexpectedly reified") + } + } + +} + +func TestSplitStoreReification(t *testing.T) { + t.Log("test reification with Has") + testSplitStoreReification(t, func(ctx context.Context, s blockstore.Blockstore, c cid.Cid) error { + _, err := s.Has(ctx, c) + return err + }) + t.Log("test reification with Get") + testSplitStoreReification(t, func(ctx context.Context, s blockstore.Blockstore, c cid.Cid) error { + _, err := s.Get(ctx, c) + return err + }) + t.Log("test reification with GetSize") + testSplitStoreReification(t, func(ctx context.Context, s blockstore.Blockstore, c cid.Cid) error { + _, err := s.GetSize(ctx, c) + return err + }) + t.Log("test reification with View") + testSplitStoreReification(t, func(ctx context.Context, s blockstore.Blockstore, c cid.Cid) error { + return s.View(ctx, c, func(_ []byte) error { return nil }) + }) + t.Log("test reification limit") + testSplitStoreReificationLimit(t, func(ctx context.Context, s blockstore.Blockstore, c cid.Cid) error { + _, err := s.Has(ctx, c) + return err + }) +} + +type mockChain struct { + t testing.TB + + sync.Mutex + genesis *types.BlockHeader + tipsets []*types.TipSet + listener func(revert []*types.TipSet, apply []*types.TipSet) error +} + +func (c *mockChain) push(ts *types.TipSet) { + c.Lock() + c.tipsets = append(c.tipsets, ts) + if c.genesis == nil { + c.genesis = ts.Blocks()[0] + } + c.Unlock() + + if c.listener != nil { + err := c.listener(nil, []*types.TipSet{ts}) + if err != nil { + c.t.Errorf("mockchain: error dispatching listener: %s", err) + } + } +} + +func (c *mockChain) revert(count int) { + c.Lock() + revert := make([]*types.TipSet, count) + if count > len(c.tipsets) { + c.Unlock() + c.t.Fatalf("not enough tipsets to revert") + } + copy(revert, c.tipsets[len(c.tipsets)-count:]) + c.tipsets = c.tipsets[:len(c.tipsets)-count] + c.Unlock() + + if c.listener != nil { + err := c.listener(revert, nil) + if err != nil { + c.t.Errorf("mockchain: error dispatching listener: %s", err) + } + } +} + +func (c *mockChain) GetTipsetByHeight(_ context.Context, epoch abi.ChainEpoch, _ *types.TipSet, _ bool) (*types.TipSet, error) { + c.Lock() + defer c.Unlock() + + iEpoch := int(epoch) + if iEpoch > len(c.tipsets) { + return nil, fmt.Errorf("bad epoch %d", epoch) + } + + return c.tipsets[iEpoch], nil +} + +func (c *mockChain) GetHeaviestTipSet() *types.TipSet { + c.Lock() + defer c.Unlock() + + return c.tipsets[len(c.tipsets)-1] +} + +func (c *mockChain) SubscribeHeadChanges(change func(revert []*types.TipSet, apply []*types.TipSet) error) { + c.listener = change +} + +type mockStore struct { + mx sync.Mutex + set map[string]blocks.Block +} + +func newMockStore() *mockStore { + return &mockStore{set: make(map[string]blocks.Block)} +} + +func (b *mockStore) keyOf(c cid.Cid) string { + return string(c.Hash()) +} + +func (b *mockStore) cidOf(k string) cid.Cid { + return cid.NewCidV1(cid.Raw, mh.Multihash([]byte(k))) +} + +func (b *mockStore) Has(_ context.Context, cid cid.Cid) (bool, error) { + b.mx.Lock() + defer b.mx.Unlock() + _, ok := b.set[b.keyOf(cid)] + return ok, nil +} + +func (b *mockStore) HashOnRead(hor bool) {} + +func (b *mockStore) Get(_ context.Context, cid cid.Cid) (blocks.Block, error) { + b.mx.Lock() + defer b.mx.Unlock() + + blk, ok := b.set[b.keyOf(cid)] + if !ok { + return nil, ipld.ErrNotFound{Cid: cid} + } + return blk, nil +} + +func (b *mockStore) GetSize(ctx context.Context, cid cid.Cid) (int, error) { + blk, err := b.Get(ctx, cid) + if err != nil { + return 0, err + } + + return len(blk.RawData()), nil +} + +func (b *mockStore) View(ctx context.Context, cid cid.Cid, f func([]byte) error) error { + blk, err := b.Get(ctx, cid) + if err != nil { + return err + } + return f(blk.RawData()) +} + +func (b *mockStore) Put(_ context.Context, blk blocks.Block) error { + b.mx.Lock() + defer b.mx.Unlock() + + b.set[b.keyOf(blk.Cid())] = blk + return nil +} + +func (b *mockStore) PutMany(_ context.Context, blks []blocks.Block) error { + b.mx.Lock() + defer b.mx.Unlock() + + for _, blk := range blks { + b.set[b.keyOf(blk.Cid())] = blk + } + return nil +} + +func (b *mockStore) DeleteBlock(_ context.Context, cid cid.Cid) error { + b.mx.Lock() + defer b.mx.Unlock() + + delete(b.set, b.keyOf(cid)) + return nil +} + +func (b *mockStore) DeleteMany(_ context.Context, cids []cid.Cid) error { + b.mx.Lock() + defer b.mx.Unlock() + + for _, c := range cids { + delete(b.set, b.keyOf(c)) + } + return nil +} + +func (b *mockStore) Flush(context.Context) error { return nil } + +func (b *mockStore) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { + return nil, errors.New("not implemented") +} + +func (b *mockStore) ForEachKey(f func(cid.Cid) error) error { + b.mx.Lock() + defer b.mx.Unlock() + + for c := range b.set { + err := f(b.cidOf(c)) + if err != nil { + return err + } + } + return nil +} + +func (b *mockStore) Close() error { + return nil +} diff --git a/blockstore/splitstore/splitstore_util.go b/blockstore/splitstore/splitstore_util.go new file mode 100644 index 000000000..6e317f4d4 --- /dev/null +++ b/blockstore/splitstore/splitstore_util.go @@ -0,0 +1,66 @@ +package splitstore + +import ( + "encoding/binary" + + "github.com/ipfs/go-cid" + mh "github.com/multiformats/go-multihash" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-state-types/abi" +) + +func epochToBytes(epoch abi.ChainEpoch) []byte { + return uint64ToBytes(uint64(epoch)) +} + +func bytesToEpoch(buf []byte) abi.ChainEpoch { + return abi.ChainEpoch(bytesToUint64(buf)) +} + +func int64ToBytes(i int64) []byte { + return uint64ToBytes(uint64(i)) +} + +func bytesToInt64(buf []byte) int64 { + return int64(bytesToUint64(buf)) +} + +func uint64ToBytes(i uint64) []byte { + buf := make([]byte, 16) + n := binary.PutUvarint(buf, i) + return buf[:n] +} + +func bytesToUint64(buf []byte) uint64 { + i, _ := binary.Uvarint(buf) + return i +} + +func isUnitaryObject(c cid.Cid) bool { + pre := c.Prefix() + switch pre.Codec { + case cid.FilCommitmentSealed, cid.FilCommitmentUnsealed: + return true + default: + return pre.MhType == mh.IDENTITY + } +} + +func isIdentiyCid(c cid.Cid) bool { + return c.Prefix().MhType == mh.IDENTITY +} + +func decodeIdentityCid(c cid.Cid) ([]byte, error) { + dmh, err := mh.Decode(c.Hash()) + if err != nil { + return nil, xerrors.Errorf("error decoding identity cid %s: %w", c, err) + } + + // sanity check + if dmh.Code != mh.IDENTITY { + return nil, xerrors.Errorf("error decoding identity cid %s: hash type is not identity", c) + } + + return dmh.Digest, nil +} diff --git a/blockstore/splitstore/splitstore_warmup.go b/blockstore/splitstore/splitstore_warmup.go new file mode 100644 index 000000000..7fb6f3b9d --- /dev/null +++ b/blockstore/splitstore/splitstore_warmup.go @@ -0,0 +1,149 @@ +package splitstore + +import ( + "sync" + "sync/atomic" + "time" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + ipld "github.com/ipfs/go-ipld-format" + "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" +) + +var ( + // WarmupBoundary is the number of epochs to load state during warmup. + WarmupBoundary = build.Finality +) + +// warmup acquires the compaction lock and spawns a goroutine to warm up the hotstore; +// this is necessary when we sync from a snapshot or when we enable the splitstore +// on top of an existing blockstore (which becomes the coldstore). +func (s *SplitStore) warmup(curTs *types.TipSet) error { + if !atomic.CompareAndSwapInt32(&s.compacting, 0, 1) { + return xerrors.Errorf("error locking compaction") + } + s.compactType = warmup + go func() { + defer atomic.StoreInt32(&s.compacting, 0) + + log.Info("warming up hotstore") + start := time.Now() + + err := s.doWarmup(curTs) + if err != nil { + log.Errorf("error warming up hotstore: %s", err) + return + } + + log.Infow("warm up done", "took", time.Since(start)) + }() + + return nil +} + +// the actual warmup procedure; it walks the chain loading all state roots at the boundary +// and headers all the way up to genesis. +// objects are written in batches so as to minimize overhead. +func (s *SplitStore) doWarmup(curTs *types.TipSet) error { + var boundaryEpoch abi.ChainEpoch + epoch := curTs.Height() + if WarmupBoundary < epoch { + boundaryEpoch = epoch - WarmupBoundary + } + var mx sync.Mutex + batchHot := make([]blocks.Block, 0, batchSize) + count := new(int64) + xcount := new(int64) + missing := new(int64) + + visitor, err := s.markSetEnv.New("warmup", 0) + if err != nil { + return xerrors.Errorf("error creating visitor: %w", err) + } + defer visitor.Close() //nolint + + err = s.walkChain(curTs, boundaryEpoch, epoch+1, // we don't load messages/receipts in warmup + visitor, + func(c cid.Cid) error { + if isUnitaryObject(c) { + return errStopWalk + } + + atomic.AddInt64(count, 1) + + has, err := s.hot.Has(s.ctx, c) + if err != nil { + return err + } + + if has { + return nil + } + + blk, err := s.cold.Get(s.ctx, c) + if err != nil { + if ipld.IsNotFound(err) { + atomic.AddInt64(missing, 1) + return errStopWalk + } + return err + } + + atomic.AddInt64(xcount, 1) + + mx.Lock() + batchHot = append(batchHot, blk) + if len(batchHot) == batchSize { + err = s.hot.PutMany(s.ctx, batchHot) + if err != nil { + mx.Unlock() + return err + } + batchHot = batchHot[:0] + } + mx.Unlock() + + return nil + }, func(cid.Cid) error { return nil }) + + if err != nil { + return err + } + + if len(batchHot) > 0 { + err = s.hot.PutMany(s.ctx, batchHot) + if err != nil { + return err + } + } + + log.Infow("warmup stats", "visited", *count, "warm", *xcount, "missing", *missing) + + s.markSetSize = *count + *count>>2 // overestimate a bit + err = s.ds.Put(s.ctx, markSetSizeKey, int64ToBytes(s.markSetSize)) + if err != nil { + log.Warnf("error saving mark set size: %s", err) + } + + // save the warmup epoch + err = s.ds.Put(s.ctx, warmupEpochKey, epochToBytes(epoch)) + if err != nil { + return xerrors.Errorf("error saving warm up epoch: %w", err) + } + + s.warmupEpoch.Store(int64(epoch)) + + // also save the compactionIndex, as this is used as an indicator of warmup for upgraded nodes + err = s.ds.Put(s.ctx, compactionIndexKey, int64ToBytes(s.compactionIndex)) + if err != nil { + return xerrors.Errorf("error saving compaction index: %w", err) + } + + return nil +} diff --git a/blockstore/splitstore/visitor.go b/blockstore/splitstore/visitor.go new file mode 100644 index 000000000..f43bef675 --- /dev/null +++ b/blockstore/splitstore/visitor.go @@ -0,0 +1,60 @@ +package splitstore + +import ( + "sync" + + "github.com/ipfs/go-cid" +) + +// ObjectVisitor is an interface for deduplicating objects during walks +type ObjectVisitor interface { + Visit(cid.Cid) (bool, error) +} + +type noopVisitor struct{} + +var _ ObjectVisitor = (*noopVisitor)(nil) + +func (v *noopVisitor) Visit(_ cid.Cid) (bool, error) { + return true, nil +} + +type tmpVisitor struct { + set *cid.Set +} + +var _ ObjectVisitor = (*tmpVisitor)(nil) + +func (v *tmpVisitor) Visit(c cid.Cid) (bool, error) { + if isUnitaryObject(c) { + return false, nil + } + + return v.set.Visit(c), nil +} + +func newTmpVisitor() ObjectVisitor { + return &tmpVisitor{set: cid.NewSet()} +} + +type concurrentVisitor struct { + mx sync.Mutex + set *cid.Set +} + +var _ ObjectVisitor = (*concurrentVisitor)(nil) + +func newConcurrentVisitor() *concurrentVisitor { + return &concurrentVisitor{set: cid.NewSet()} +} + +func (v *concurrentVisitor) Visit(c cid.Cid) (bool, error) { + if isUnitaryObject(c) { + return false, nil + } + + v.mx.Lock() + defer v.mx.Unlock() + + return v.set.Visit(c), nil +} diff --git a/blockstore/sync.go b/blockstore/sync.go new file mode 100644 index 000000000..4f0cf830e --- /dev/null +++ b/blockstore/sync.go @@ -0,0 +1,83 @@ +package blockstore + +import ( + "context" + "sync" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" +) + +// NewMemorySync returns a thread-safe in-memory blockstore. +func NewMemorySync() *SyncBlockstore { + return &SyncBlockstore{bs: make(MemBlockstore)} +} + +// SyncBlockstore is a terminal blockstore that is a synchronized version +// of MemBlockstore. +type SyncBlockstore struct { + mu sync.RWMutex + bs MemBlockstore // specifically use a memStore to save indirection overhead. +} + +func (*SyncBlockstore) Flush(context.Context) error { return nil } + +func (m *SyncBlockstore) DeleteBlock(ctx context.Context, k cid.Cid) error { + m.mu.Lock() + defer m.mu.Unlock() + return m.bs.DeleteBlock(ctx, k) +} + +func (m *SyncBlockstore) DeleteMany(ctx context.Context, ks []cid.Cid) error { + m.mu.Lock() + defer m.mu.Unlock() + return m.bs.DeleteMany(ctx, ks) +} + +func (m *SyncBlockstore) Has(ctx context.Context, k cid.Cid) (bool, error) { + m.mu.RLock() + defer m.mu.RUnlock() + return m.bs.Has(ctx, k) +} + +func (m *SyncBlockstore) View(ctx context.Context, k cid.Cid, callback func([]byte) error) error { + m.mu.RLock() + defer m.mu.RUnlock() + + return m.bs.View(ctx, k, callback) +} + +func (m *SyncBlockstore) Get(ctx context.Context, k cid.Cid) (blocks.Block, error) { + m.mu.RLock() + defer m.mu.RUnlock() + return m.bs.Get(ctx, k) +} + +func (m *SyncBlockstore) GetSize(ctx context.Context, k cid.Cid) (int, error) { + m.mu.RLock() + defer m.mu.RUnlock() + return m.bs.GetSize(ctx, k) +} + +func (m *SyncBlockstore) Put(ctx context.Context, b blocks.Block) error { + m.mu.Lock() + defer m.mu.Unlock() + return m.bs.Put(ctx, b) +} + +func (m *SyncBlockstore) PutMany(ctx context.Context, bs []blocks.Block) error { + m.mu.Lock() + defer m.mu.Unlock() + return m.bs.PutMany(ctx, bs) +} + +func (m *SyncBlockstore) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { + m.mu.RLock() + defer m.mu.RUnlock() + // this blockstore implementation doesn't do any async work. + return m.bs.AllKeysChan(ctx) +} + +func (m *SyncBlockstore) HashOnRead(enabled bool) { + // noop +} diff --git a/blockstore/timed.go b/blockstore/timed.go new file mode 100644 index 000000000..01e089a9f --- /dev/null +++ b/blockstore/timed.go @@ -0,0 +1,201 @@ +package blockstore + +import ( + "context" + "fmt" + "sync" + "time" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + ipld "github.com/ipfs/go-ipld-format" + "github.com/raulk/clock" + "go.uber.org/multierr" +) + +// TimedCacheBlockstore is a blockstore that keeps blocks for at least the +// specified caching interval before discarding them. Garbage collection must +// be started and stopped by calling Start/Stop. +// +// Under the covers, it's implemented with an active and an inactive blockstore +// that are rotated every cache time interval. This means all blocks will be +// stored at most 2x the cache interval. +// +// Create a new instance by calling the NewTimedCacheBlockstore constructor. +type TimedCacheBlockstore struct { + mu sync.RWMutex + active, inactive MemBlockstore + clock clock.Clock + interval time.Duration + closeCh chan struct{} + doneRotatingCh chan struct{} +} + +func NewTimedCacheBlockstore(interval time.Duration) *TimedCacheBlockstore { + b := &TimedCacheBlockstore{ + active: NewMemory(), + inactive: NewMemory(), + interval: interval, + clock: clock.New(), + } + return b +} + +func (t *TimedCacheBlockstore) Start(_ context.Context) error { + t.mu.Lock() + defer t.mu.Unlock() + if t.closeCh != nil { + return fmt.Errorf("already started") + } + t.closeCh = make(chan struct{}) + + // Create this timer before starting the goroutine. Otherwise, creating the timer will race + // with adding time to the mock clock, and we could add time _first_, then stall waiting for + // a timer that'll never fire. + ticker := t.clock.Ticker(t.interval) + go func() { + defer ticker.Stop() + for { + select { + case <-ticker.C: + t.rotate() + if t.doneRotatingCh != nil { + t.doneRotatingCh <- struct{}{} + } + case <-t.closeCh: + return + } + } + }() + return nil +} + +func (t *TimedCacheBlockstore) Stop(_ context.Context) error { + t.mu.Lock() + defer t.mu.Unlock() + if t.closeCh == nil { + return fmt.Errorf("not started") + } + select { + case <-t.closeCh: + // already closed + default: + close(t.closeCh) + } + return nil +} + +func (t *TimedCacheBlockstore) rotate() { + newBs := NewMemory() + + t.mu.Lock() + t.inactive, t.active = t.active, newBs + t.mu.Unlock() +} + +func (t *TimedCacheBlockstore) Flush(ctx context.Context) error { + t.mu.Lock() + defer t.mu.Unlock() + + if err := t.active.Flush(ctx); err != nil { + return err + } + return t.inactive.Flush(ctx) +} + +func (t *TimedCacheBlockstore) Put(ctx context.Context, b blocks.Block) error { + // Don't check the inactive set here. We want to keep this block for at + // least one interval. + t.mu.Lock() + defer t.mu.Unlock() + return t.active.Put(ctx, b) +} + +func (t *TimedCacheBlockstore) PutMany(ctx context.Context, bs []blocks.Block) error { + t.mu.Lock() + defer t.mu.Unlock() + return t.active.PutMany(ctx, bs) +} + +func (t *TimedCacheBlockstore) View(ctx context.Context, k cid.Cid, callback func([]byte) error) error { + // The underlying blockstore is always a "mem" blockstore so there's no difference, + // from a performance perspective, between view & get. So we call Get to avoid + // calling an arbitrary callback while holding a lock. + t.mu.RLock() + block, err := t.active.Get(ctx, k) + if ipld.IsNotFound(err) { + block, err = t.inactive.Get(ctx, k) + } + t.mu.RUnlock() + + if err != nil { + return err + } + return callback(block.RawData()) +} + +func (t *TimedCacheBlockstore) Get(ctx context.Context, k cid.Cid) (blocks.Block, error) { + t.mu.RLock() + defer t.mu.RUnlock() + b, err := t.active.Get(ctx, k) + if ipld.IsNotFound(err) { + b, err = t.inactive.Get(ctx, k) + } + return b, err +} + +func (t *TimedCacheBlockstore) GetSize(ctx context.Context, k cid.Cid) (int, error) { + t.mu.RLock() + defer t.mu.RUnlock() + size, err := t.active.GetSize(ctx, k) + if ipld.IsNotFound(err) { + size, err = t.inactive.GetSize(ctx, k) + } + return size, err +} + +func (t *TimedCacheBlockstore) Has(ctx context.Context, k cid.Cid) (bool, error) { + t.mu.RLock() + defer t.mu.RUnlock() + if has, err := t.active.Has(ctx, k); err != nil { + return false, err + } else if has { + return true, nil + } + return t.inactive.Has(ctx, k) +} + +func (t *TimedCacheBlockstore) HashOnRead(_ bool) { + // no-op +} + +func (t *TimedCacheBlockstore) DeleteBlock(ctx context.Context, k cid.Cid) error { + t.mu.Lock() + defer t.mu.Unlock() + return multierr.Combine(t.active.DeleteBlock(ctx, k), t.inactive.DeleteBlock(ctx, k)) +} + +func (t *TimedCacheBlockstore) DeleteMany(ctx context.Context, ks []cid.Cid) error { + t.mu.Lock() + defer t.mu.Unlock() + return multierr.Combine(t.active.DeleteMany(ctx, ks), t.inactive.DeleteMany(ctx, ks)) +} + +func (t *TimedCacheBlockstore) AllKeysChan(_ context.Context) (<-chan cid.Cid, error) { + t.mu.RLock() + defer t.mu.RUnlock() + + ch := make(chan cid.Cid, len(t.active)+len(t.inactive)) + for _, b := range t.active { + ch <- b.Cid() + } + for _, b := range t.inactive { + c := b.Cid() + if _, ok := t.active[string(c.Hash())]; ok { + continue + } + ch <- c + } + close(ch) + return ch, nil +} diff --git a/blockstore/timed_test.go b/blockstore/timed_test.go new file mode 100644 index 000000000..931f14507 --- /dev/null +++ b/blockstore/timed_test.go @@ -0,0 +1,90 @@ +// stm: #unit +package blockstore + +import ( + "context" + "testing" + "time" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + "github.com/raulk/clock" + "github.com/stretchr/testify/require" +) + +func TestTimedCacheBlockstoreSimple(t *testing.T) { + //stm: @SPLITSTORE_TIMED_BLOCKSTORE_START_001 + //stm: @SPLITSTORE_TIMED_BLOCKSTORE_PUT_001, @SPLITSTORE_TIMED_BLOCKSTORE_HAS_001, @SPLITSTORE_TIMED_BLOCKSTORE_GET_001 + //stm: @SPLITSTORE_TIMED_BLOCKSTORE_ALL_KEYS_CHAN_001 + tc := NewTimedCacheBlockstore(10 * time.Millisecond) + mClock := clock.NewMock() + mClock.Set(time.Now()) + tc.clock = mClock + tc.doneRotatingCh = make(chan struct{}) + + ctx := context.Background() + + _ = tc.Start(context.Background()) + mClock.Add(1) // IDK why it is needed but it makes it work + + defer func() { + _ = tc.Stop(context.Background()) + }() + + b1 := blocks.NewBlock([]byte("foo")) + require.NoError(t, tc.Put(ctx, b1)) + + b2 := blocks.NewBlock([]byte("bar")) + require.NoError(t, tc.Put(ctx, b2)) + + b3 := blocks.NewBlock([]byte("baz")) + + b1out, err := tc.Get(ctx, b1.Cid()) + require.NoError(t, err) + require.Equal(t, b1.RawData(), b1out.RawData()) + + has, err := tc.Has(ctx, b1.Cid()) + require.NoError(t, err) + require.True(t, has) + + mClock.Add(10 * time.Millisecond) + <-tc.doneRotatingCh + + // We should still have everything. + has, err = tc.Has(ctx, b1.Cid()) + require.NoError(t, err) + require.True(t, has) + + has, err = tc.Has(ctx, b2.Cid()) + require.NoError(t, err) + require.True(t, has) + + // extend b2, add b3. + require.NoError(t, tc.Put(ctx, b2)) + require.NoError(t, tc.Put(ctx, b3)) + + // all keys once. + allKeys, err := tc.AllKeysChan(context.Background()) + var ks []cid.Cid + for k := range allKeys { + ks = append(ks, k) + } + require.NoError(t, err) + require.ElementsMatch(t, ks, []cid.Cid{b1.Cid(), b2.Cid(), b3.Cid()}) + + mClock.Add(10 * time.Millisecond) + <-tc.doneRotatingCh + // should still have b2, and b3, but not b1 + + has, err = tc.Has(ctx, b1.Cid()) + require.NoError(t, err) + require.False(t, has) + + has, err = tc.Has(ctx, b2.Cid()) + require.NoError(t, err) + require.True(t, has) + + has, err = tc.Has(ctx, b3.Cid()) + require.NoError(t, err) + require.True(t, has) +} diff --git a/blockstore/union.go b/blockstore/union.go new file mode 100644 index 000000000..e7fafbbbd --- /dev/null +++ b/blockstore/union.go @@ -0,0 +1,128 @@ +package blockstore + +import ( + "context" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + ipld "github.com/ipfs/go-ipld-format" +) + +type unionBlockstore []Blockstore + +// Union returns an unioned blockstore. +// +// - Reads return from the first blockstore that has the value, querying in the +// supplied order. +// - Writes (puts and deletes) are broadcast to all stores. +func Union(stores ...Blockstore) Blockstore { + return unionBlockstore(stores) +} + +func (m unionBlockstore) Has(ctx context.Context, cid cid.Cid) (has bool, err error) { + for _, bs := range m { + if has, err = bs.Has(ctx, cid); has || err != nil { + break + } + } + return has, err +} + +func (m unionBlockstore) Get(ctx context.Context, cid cid.Cid) (blk blocks.Block, err error) { + for _, bs := range m { + if blk, err = bs.Get(ctx, cid); err == nil || !ipld.IsNotFound(err) { + break + } + } + return blk, err +} + +func (m unionBlockstore) View(ctx context.Context, cid cid.Cid, callback func([]byte) error) (err error) { + for _, bs := range m { + if err = bs.View(ctx, cid, callback); err == nil || !ipld.IsNotFound(err) { + break + } + } + return err +} + +func (m unionBlockstore) GetSize(ctx context.Context, cid cid.Cid) (size int, err error) { + for _, bs := range m { + if size, err = bs.GetSize(ctx, cid); err == nil || !ipld.IsNotFound(err) { + break + } + } + return size, err +} + +func (m unionBlockstore) Flush(ctx context.Context) (err error) { + for _, bs := range m { + if err = bs.Flush(ctx); err != nil { + break + } + } + return err +} + +func (m unionBlockstore) Put(ctx context.Context, block blocks.Block) (err error) { + for _, bs := range m { + if err = bs.Put(ctx, block); err != nil { + break + } + } + return err +} + +func (m unionBlockstore) PutMany(ctx context.Context, blks []blocks.Block) (err error) { + for _, bs := range m { + if err = bs.PutMany(ctx, blks); err != nil { + break + } + } + return err +} + +func (m unionBlockstore) DeleteBlock(ctx context.Context, cid cid.Cid) (err error) { + for _, bs := range m { + if err = bs.DeleteBlock(ctx, cid); err != nil { + break + } + } + return err +} + +func (m unionBlockstore) DeleteMany(ctx context.Context, cids []cid.Cid) (err error) { + for _, bs := range m { + if err = bs.DeleteMany(ctx, cids); err != nil { + break + } + } + return err +} + +func (m unionBlockstore) AllKeysChan(ctx context.Context) (<-chan cid.Cid, error) { + // this does not deduplicate; this interface needs to be revisited. + outCh := make(chan cid.Cid) + + go func() { + defer close(outCh) + + for _, bs := range m { + ch, err := bs.AllKeysChan(ctx) + if err != nil { + return + } + for cid := range ch { + outCh <- cid + } + } + }() + + return outCh, nil +} + +func (m unionBlockstore) HashOnRead(enabled bool) { + for _, bs := range m { + bs.HashOnRead(enabled) + } +} diff --git a/blockstore/union_test.go b/blockstore/union_test.go new file mode 100644 index 000000000..579489947 --- /dev/null +++ b/blockstore/union_test.go @@ -0,0 +1,110 @@ +// stm: #unit +package blockstore + +import ( + "context" + "testing" + + blocks "github.com/ipfs/go-block-format" + "github.com/stretchr/testify/require" +) + +var ( + b0 = blocks.NewBlock([]byte("abc")) + b1 = blocks.NewBlock([]byte("foo")) + b2 = blocks.NewBlock([]byte("bar")) + b3 = blocks.NewBlock([]byte("baz")) +) + +func TestUnionBlockstore_Get(t *testing.T) { + //stm: @SPLITSTORE_UNION_BLOCKSTORE_GET_001 + ctx := context.Background() + m1 := NewMemory() + m2 := NewMemory() + + _ = m1.Put(ctx, b1) + _ = m2.Put(ctx, b2) + + u := Union(m1, m2) + + v1, err := u.Get(ctx, b1.Cid()) + require.NoError(t, err) + require.Equal(t, b1.RawData(), v1.RawData()) + + v2, err := u.Get(ctx, b2.Cid()) + require.NoError(t, err) + require.Equal(t, b2.RawData(), v2.RawData()) +} + +func TestUnionBlockstore_Put_PutMany_Delete_AllKeysChan(t *testing.T) { + //stm: @SPLITSTORE_UNION_BLOCKSTORE_PUT_001, @SPLITSTORE_UNION_BLOCKSTORE_HAS_001 + //stm: @SPLITSTORE_UNION_BLOCKSTORE_PUT_MANY_001, @SPLITSTORE_UNION_BLOCKSTORE_DELETE_001 + //stm: @SPLITSTORE_UNION_BLOCKSTORE_ALL_KEYS_CHAN_001 + ctx := context.Background() + m1 := NewMemory() + m2 := NewMemory() + + u := Union(m1, m2) + + err := u.Put(ctx, b0) + require.NoError(t, err) + + var has bool + + // write was broadcasted to all stores. + has, _ = m1.Has(ctx, b0.Cid()) + require.True(t, has) + + has, _ = m2.Has(ctx, b0.Cid()) + require.True(t, has) + + has, _ = u.Has(ctx, b0.Cid()) + require.True(t, has) + + // put many. + err = u.PutMany(ctx, []blocks.Block{b1, b2}) + require.NoError(t, err) + + // write was broadcasted to all stores. + has, _ = m1.Has(ctx, b1.Cid()) + require.True(t, has) + + has, _ = m1.Has(ctx, b2.Cid()) + require.True(t, has) + + has, _ = m2.Has(ctx, b1.Cid()) + require.True(t, has) + + has, _ = m2.Has(ctx, b2.Cid()) + require.True(t, has) + + // also in the union store. + has, _ = u.Has(ctx, b1.Cid()) + require.True(t, has) + + has, _ = u.Has(ctx, b2.Cid()) + require.True(t, has) + + // deleted from all stores. + err = u.DeleteBlock(ctx, b1.Cid()) + require.NoError(t, err) + + has, _ = u.Has(ctx, b1.Cid()) + require.False(t, has) + + has, _ = m1.Has(ctx, b1.Cid()) + require.False(t, has) + + has, _ = m2.Has(ctx, b1.Cid()) + require.False(t, has) + + // check that AllKeysChan returns b0 and b2, twice (once per backing store) + ch, err := u.AllKeysChan(context.Background()) + require.NoError(t, err) + + var i int + for range ch { + i++ + } + require.Equal(t, 4, i) +} diff --git a/build/actors/README.md b/build/actors/README.md new file mode 100644 index 000000000..981ad726a --- /dev/null +++ b/build/actors/README.md @@ -0,0 +1,26 @@ +# Bundles + +This directory includes the actors bundles for each release. Each actor bundle is a zstd compressed +tarfile containing one bundle per network type. These tarfiles are subsequently embedded in the +lotus binary. + +## Updating + +To update, run the `./pack.sh` script. For example, the following will pack the [builtin actors release](https://github.com/filecoin-project/builtin-actors/releases) `dev/20220602` into the `v8` tarfile. + +```bash +./pack.sh v8 dev/20220602 +``` + +This will: + +1. Download the actors bundles and pack them into the appropriate tarfile (`$VERSION.tar.zst`). +2. Run `make bundle-gen` in the top-level directory to regenerate the bundle metadata file for _all_ network versions (all `*.tar.zst` files in this directory). + +## Overriding + +To build a bundle, but specify a different release/tag for a specific network, append `$network=$alternative_release` on the command line. For example: + +```bash +./pack.sh v8 dev/20220602 mainnet=v8.0.0 calibrationnet=v8.0.0-rc.1 +``` diff --git a/build/actors/pack.sh b/build/actors/pack.sh new file mode 100755 index 000000000..e594bb2da --- /dev/null +++ b/build/actors/pack.sh @@ -0,0 +1,55 @@ +#!/bin/bash + +NETWORKS=(devnet mainnet caterpillarnet butterflynet testing testing-fake-proofs calibrationnet) + +set -e + +if [[ $# -lt 2 ]]; then + echo "Usage: $0 VERSION RELEASE [NETWORK=RELEASE_OVERRIDE]..." >&2 + echo "expected at least two arguments, an actors version (e.g., v8), an actors release, and any number of release overrides." >&2 + exit 1 +fi + +VERSION="$1" # actors version +RELEASE="$2" # actors release name +RELEASE_OVERRIDES=("${@:3}") + +echo "Downloading bundles for actors version ${VERSION} release ${RELEASE}" +echo "With release overrides ${RELEASE_OVERRIDES[*]}" + +TARGET_FILE="$(pwd)/${VERSION}.tar.zst" +WORKDIR=$(mktemp -d -t "actor-bundles-${VERSION}.XXXXXXXXXX") +trap 'rm -rf -- "$WORKDIR"' EXIT + +encode_release() { + jq -rn --arg release "$1" '$release | @uri' +} + +pushd "${WORKDIR}" +for network in "${NETWORKS[@]}"; do + release="$RELEASE" + # Ideally, we'd use an associative array (map). But that's not supported on macos. + for override in "${RELEASE_OVERRIDES[@]}"; do + if [[ "${network}" = "${override%%=*}" ]]; then + release="${override#*=}" + break + fi + done + encoded_release="$(encode_release "$release")" + echo "Downloading $release for network $network." + wget "https://github.com/filecoin-project/builtin-actors/releases/download/${encoded_release}/builtin-actors-${network}"{.car,.sha256} +done + +echo "Checking the checksums..." + +sha256sum -c -- *.sha256 + +echo "Packing..." + +rm -f -- "$TARGET_FILE" +tar -cf "$TARGET_FILE" --use-compress-program "zstd -19" -- *.car +popd + +echo "Generating metadata..." + +make -C ../../ VERSION="$VERSION" RELEASE="$RELEASE" RELEASE_OVERRIDES="${RELEASE_OVERRIDES[*]}" bundle-gen diff --git a/build/actors/v10.tar.zst b/build/actors/v10.tar.zst new file mode 100644 index 000000000..9ac6453f8 Binary files /dev/null and b/build/actors/v10.tar.zst differ diff --git a/build/actors/v11.tar.zst b/build/actors/v11.tar.zst new file mode 100644 index 000000000..069ce23c8 Binary files /dev/null and b/build/actors/v11.tar.zst differ diff --git a/build/actors/v8.tar.zst b/build/actors/v8.tar.zst new file mode 100644 index 000000000..88e0ac800 Binary files /dev/null and b/build/actors/v8.tar.zst differ diff --git a/build/actors/v9.tar.zst b/build/actors/v9.tar.zst new file mode 100644 index 000000000..f19c7db4c Binary files /dev/null and b/build/actors/v9.tar.zst differ diff --git a/build/bootstrap.go b/build/bootstrap.go index ca6cc26ce..d86115398 100644 --- a/build/bootstrap.go +++ b/build/bootstrap.go @@ -2,35 +2,33 @@ package build import ( "context" - "os" + "embed" + "path" "strings" - "github.com/filecoin-project/lotus/lib/addrutil" - "golang.org/x/xerrors" + "github.com/libp2p/go-libp2p/core/peer" - rice "github.com/GeertJohan/go.rice" - "github.com/libp2p/go-libp2p-core/peer" + "github.com/filecoin-project/lotus/lib/addrutil" ) +//go:embed bootstrap +var bootstrapfs embed.FS + func BuiltinBootstrap() ([]peer.AddrInfo, error) { - var out []peer.AddrInfo - - b := rice.MustFindBox("bootstrap") - err := b.Walk("", func(path string, info os.FileInfo, err error) error { + if DisableBuiltinAssets { + return nil, nil + } + if BootstrappersFile != "" { + spi, err := bootstrapfs.ReadFile(path.Join("bootstrap", BootstrappersFile)) if err != nil { - return xerrors.Errorf("failed to walk box: %w", err) + return nil, err + } + if len(spi) == 0 { + return nil, nil } - if !strings.HasSuffix(path, ".pi") { - return nil - } - spi := b.MustString(path) - if spi == "" { - return nil - } - pi, err := addrutil.ParseAddresses(context.TODO(), strings.Split(strings.TrimSpace(spi), "\n")) - out = append(out, pi...) - return err - }) - return out, err + return addrutil.ParseAddresses(context.TODO(), strings.Split(strings.TrimSpace(string(spi)), "\n")) + } + + return nil, nil } diff --git a/build/bootstrap/bootstrappers.pi b/build/bootstrap/bootstrappers.pi deleted file mode 100644 index 61b7dede3..000000000 --- a/build/bootstrap/bootstrappers.pi +++ /dev/null @@ -1,12 +0,0 @@ -/dns4/bootstrap-0-sin.fil-test.net/tcp/1347/p2p/12D3KooWKNF7vNFEhnvB45E9mw2B5z6t419W3ziZPLdUDVnLLKGs -/ip4/86.109.15.57/tcp/1347/p2p/12D3KooWKNF7vNFEhnvB45E9mw2B5z6t419W3ziZPLdUDVnLLKGs -/dns4/bootstrap-0-dfw.fil-test.net/tcp/1347/p2p/12D3KooWECJTm7RUPyGfNbRwm6y2fK4wA7EB8rDJtWsq5AKi7iDr -/ip4/139.178.84.45/tcp/1347/p2p/12D3KooWECJTm7RUPyGfNbRwm6y2fK4wA7EB8rDJtWsq5AKi7iDr -/dns4/bootstrap-0-fra.fil-test.net/tcp/1347/p2p/12D3KooWC7MD6m7iNCuDsYtNr7xVtazihyVUizBbhmhEiyMAm9ym -/ip4/136.144.49.17/tcp/1347/p2p/12D3KooWC7MD6m7iNCuDsYtNr7xVtazihyVUizBbhmhEiyMAm9ym -/dns4/bootstrap-1-sin.fil-test.net/tcp/1347/p2p/12D3KooWD8eYqsKcEMFax6EbWN3rjA7qFsxCez2rmN8dWqkzgNaN -/ip4/86.109.15.55/tcp/1347/p2p/12D3KooWD8eYqsKcEMFax6EbWN3rjA7qFsxCez2rmN8dWqkzgNaN -/dns4/bootstrap-1-dfw.fil-test.net/tcp/1347/p2p/12D3KooWLB3RR8frLAmaK4ntHC2dwrAjyGzQgyUzWxAum1FxyyqD -/ip4/139.178.84.41/tcp/1347/p2p/12D3KooWLB3RR8frLAmaK4ntHC2dwrAjyGzQgyUzWxAum1FxyyqD -/dns4/bootstrap-1-fra.fil-test.net/tcp/1347/p2p/12D3KooWGPDJAw3HW4uVU3JEQBfFaZ1kdpg4HvvwRMVpUYbzhsLQ -/ip4/136.144.49.131/tcp/1347/p2p/12D3KooWGPDJAw3HW4uVU3JEQBfFaZ1kdpg4HvvwRMVpUYbzhsLQ diff --git a/build/bootstrap/butterflynet.pi b/build/bootstrap/butterflynet.pi new file mode 100644 index 000000000..c7e9b2e92 --- /dev/null +++ b/build/bootstrap/butterflynet.pi @@ -0,0 +1,2 @@ +/dns4/bootstrap-0.butterfly.fildev.network/tcp/1347/p2p/12D3KooWD5mtdmjHQ1Puj9Md7SEfoa7kWMpwqUhAKsyYsBP56LQC +/dns4/bootstrap-1.butterfly.fildev.network/tcp/1347/p2p/12D3KooWEoYPkm6o87ES6AppFY7d7WHJUQg7XVPRAyQZjEU31efQ diff --git a/build/bootstrap/calibnet.pi b/build/bootstrap/calibnet.pi new file mode 100644 index 000000000..4ac1d1e3a --- /dev/null +++ b/build/bootstrap/calibnet.pi @@ -0,0 +1,4 @@ +/dns4/bootstrap-0.calibration.fildev.network/tcp/1347/p2p/12D3KooWCi2w8U4DDB9xqrejb5KYHaQv2iA2AJJ6uzG3iQxNLBMy +/dns4/bootstrap-1.calibration.fildev.network/tcp/1347/p2p/12D3KooWDTayrBojBn9jWNNUih4nNQQBGJD7Zo3gQCKgBkUsS6dp +/dns4/bootstrap-2.calibration.fildev.network/tcp/1347/p2p/12D3KooWNRxTHUn8bf7jz1KEUPMc2dMgGfa4f8ZJTsquVSn3vHCG +/dns4/bootstrap-3.calibration.fildev.network/tcp/1347/p2p/12D3KooWFWUqE9jgXvcKHWieYs9nhyp6NF4ftwLGAHm4sCv73jjK diff --git a/build/bootstrap/interopnet.pi b/build/bootstrap/interopnet.pi new file mode 100644 index 000000000..6e8b78790 --- /dev/null +++ b/build/bootstrap/interopnet.pi @@ -0,0 +1,2 @@ +/dns4/bootstrap-0.interop.fildev.network/tcp/1347/p2p/12D3KooWDpppr8csCNvEPnD2Z83KTPdBTM7iJhL66qK8LK3bB5NU +/dns4/bootstrap-1.interop.fildev.network/tcp/1347/p2p/12D3KooWR3K1sXWoDYcXWqDF26mFEM1o1g7e7fcVR3NYE7rn24Gs diff --git a/build/bootstrap/mainnet.pi b/build/bootstrap/mainnet.pi new file mode 100644 index 000000000..3e09b3d14 --- /dev/null +++ b/build/bootstrap/mainnet.pi @@ -0,0 +1,16 @@ +/dns4/bootstrap-0.mainnet.filops.net/tcp/1347/p2p/12D3KooWCVe8MmsEMes2FzgTpt9fXtmCY7wrq91GRiaC8PHSCCBj +/dns4/bootstrap-1.mainnet.filops.net/tcp/1347/p2p/12D3KooWCwevHg1yLCvktf2nvLu7L9894mcrJR4MsBCcm4syShVc +/dns4/bootstrap-2.mainnet.filops.net/tcp/1347/p2p/12D3KooWEWVwHGn2yR36gKLozmb4YjDJGerotAPGxmdWZx2nxMC4 +/dns4/bootstrap-3.mainnet.filops.net/tcp/1347/p2p/12D3KooWKhgq8c7NQ9iGjbyK7v7phXvG6492HQfiDaGHLHLQjk7R +/dns4/bootstrap-4.mainnet.filops.net/tcp/1347/p2p/12D3KooWL6PsFNPhYftrJzGgF5U18hFoaVhfGk7xwzD8yVrHJ3Uc +/dns4/bootstrap-5.mainnet.filops.net/tcp/1347/p2p/12D3KooWLFynvDQiUpXoHroV1YxKHhPJgysQGH2k3ZGwtWzR4dFH +/dns4/bootstrap-6.mainnet.filops.net/tcp/1347/p2p/12D3KooWP5MwCiqdMETF9ub1P3MbCvQCcfconnYHbWg6sUJcDRQQ +/dns4/bootstrap-7.mainnet.filops.net/tcp/1347/p2p/12D3KooWRs3aY1p3juFjPy8gPN95PEQChm2QKGUCAdcDCC4EBMKf +/dns4/bootstrap-8.mainnet.filops.net/tcp/1347/p2p/12D3KooWScFR7385LTyR4zU1bYdzSiiAb5rnNABfVahPvVSzyTkR +/dns4/lotus-bootstrap.ipfsforce.com/tcp/41778/p2p/12D3KooWGhufNmZHF3sv48aQeS13ng5XVJZ9E6qy2Ms4VzqeUsHk +/dns4/bootstrap-0.starpool.in/tcp/12757/p2p/12D3KooWGHpBMeZbestVEWkfdnC9u7p6uFHXL1n7m1ZBqsEmiUzz +/dns4/bootstrap-1.starpool.in/tcp/12757/p2p/12D3KooWQZrGH1PxSNZPum99M1zNvjNFM33d1AAu5DcvdHptuU7u +/dns4/node.glif.io/tcp/1235/p2p/12D3KooWBF8cpp65hp2u9LK5mh19x67ftAam84z9LsfaquTDSBpt +/dns4/bootstrap-0.ipfsmain.cn/tcp/34721/p2p/12D3KooWQnwEGNqcM2nAcPtRR9rAX8Hrg4k9kJLCHoTR5chJfz6d +/dns4/bootstrap-1.ipfsmain.cn/tcp/34723/p2p/12D3KooWMKxMkD5DMpSWsW7dBddKxKT7L2GgbNuckz9otxvkvByP +/dns4/bootstarp-0.1475.io/tcp/61256/p2p/12D3KooWRzCVDwHUkgdK7eRgnoXbjDAELhxPErjHzbRLguSV1aRt \ No newline at end of file diff --git a/build/builtin_actors.go b/build/builtin_actors.go new file mode 100644 index 000000000..50aecde40 --- /dev/null +++ b/build/builtin_actors.go @@ -0,0 +1,266 @@ +package build + +import ( + "archive/tar" + "context" + "embed" + "fmt" + "io" + "os" + "path" + "sort" + "strconv" + "strings" + + "github.com/DataDog/zstd" + "github.com/ipfs/go-cid" + cbor "github.com/ipfs/go-ipld-cbor" + "github.com/ipld/go-car" + "golang.org/x/xerrors" + + actorstypes "github.com/filecoin-project/go-state-types/actors" + + "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +//go:embed actors/*.tar.zst +var embeddedBuiltinActorReleases embed.FS + +func init() { + if BundleOverrides == nil { + BundleOverrides = make(map[actorstypes.Version]string) + } + for _, av := range actors.Versions { + path := os.Getenv(fmt.Sprintf("LOTUS_BUILTIN_ACTORS_V%d_BUNDLE", av)) + if path == "" { + continue + } + BundleOverrides[actorstypes.Version(av)] = path + } + if err := loadManifests(NetworkBundle); err != nil { + panic(err) + } +} + +// UseNetworkBundle switches to a different network bundle, by name. +func UseNetworkBundle(netw string) error { + if NetworkBundle == netw { + return nil + } + if err := loadManifests(netw); err != nil { + return err + } + NetworkBundle = netw + return nil +} + +func loadManifests(netw string) error { + overridden := make(map[actorstypes.Version]struct{}) + var newMetadata []*BuiltinActorsMetadata + // First, prefer overrides. + for av, path := range BundleOverrides { + root, actorCids, err := readBundleManifestFromFile(path) + if err != nil { + return err + } + newMetadata = append(newMetadata, &BuiltinActorsMetadata{ + Network: netw, + Version: av, + ManifestCid: root, + Actors: actorCids, + }) + overridden[av] = struct{}{} + } + + // Then load embedded bundle metadata. + for _, meta := range EmbeddedBuiltinActorsMetadata { + if meta.Network != netw { + continue + } + if _, ok := overridden[meta.Version]; ok { + continue + } + newMetadata = append(newMetadata, meta) + } + + actors.ClearManifests() + + for _, meta := range newMetadata { + actors.RegisterManifest(meta.Version, meta.ManifestCid, meta.Actors) + } + + return nil +} + +type BuiltinActorsMetadata struct { + Network string + Version actorstypes.Version + ManifestCid cid.Cid + Actors map[string]cid.Cid + BundleGitTag string +} + +// ReadEmbeddedBuiltinActorsMetadata reads the metadata from the embedded built-in actor bundles. +// There should be no need to call this method as the result is cached in the +// `EmbeddedBuiltinActorsMetadata` variable on `make gen`. +func ReadEmbeddedBuiltinActorsMetadata() ([]*BuiltinActorsMetadata, error) { + files, err := embeddedBuiltinActorReleases.ReadDir("actors") + if err != nil { + return nil, xerrors.Errorf("failed to read embedded bundle directory: %s", err) + } + var bundles []*BuiltinActorsMetadata + for _, dirent := range files { + name := dirent.Name() + b, err := readEmbeddedBuiltinActorsMetadata(name) + if err != nil { + return nil, err + } + bundles = append(bundles, b...) + } + // Sort by network, then by bundle. + sort.Slice(bundles, func(i, j int) bool { + if bundles[i].Network == bundles[j].Network { + return bundles[i].Version < bundles[j].Version + } + return bundles[i].Network < bundles[j].Network + }) + return bundles, nil +} + +func readEmbeddedBuiltinActorsMetadata(bundle string) ([]*BuiltinActorsMetadata, error) { + const ( + archiveExt = ".tar.zst" + bundleExt = ".car" + bundlePrefix = "builtin-actors-" + ) + + if !strings.HasPrefix(bundle, "v") { + return nil, xerrors.Errorf("bundle bundle '%q' doesn't start with a 'v'", bundle) + } + if !strings.HasSuffix(bundle, archiveExt) { + return nil, xerrors.Errorf("bundle bundle '%q' doesn't end with '%s'", bundle, archiveExt) + } + version, err := strconv.ParseInt(bundle[1:len(bundle)-len(archiveExt)], 10, 0) + if err != nil { + return nil, xerrors.Errorf("failed to parse actors version from bundle '%q': %s", bundle, err) + } + fi, err := embeddedBuiltinActorReleases.Open(fmt.Sprintf("actors/%s", bundle)) + if err != nil { + return nil, err + } + defer fi.Close() //nolint + + uncompressed := zstd.NewReader(fi) + defer uncompressed.Close() //nolint + + var bundles []*BuiltinActorsMetadata + + tarReader := tar.NewReader(uncompressed) + for { + header, err := tarReader.Next() + switch err { + case io.EOF: + return bundles, nil + case nil: + default: + return nil, err + } + + // Read the network name from the bundle name. + name := path.Base(header.Name) + if !strings.HasSuffix(name, bundleExt) { + return nil, xerrors.Errorf("expected bundle to end with .car: %s", name) + } + if !strings.HasPrefix(name, bundlePrefix) { + return nil, xerrors.Errorf("expected bundle to end with .car: %s", name) + } + name = name[len(bundlePrefix) : len(name)-len(bundleExt)] + + // Load the bundle. + root, actorCids, err := readBundleManifest(tarReader) + if err != nil { + return nil, xerrors.Errorf("error loading builtin actors bundle: %w", err) + } + bundles = append(bundles, &BuiltinActorsMetadata{ + Network: name, + Version: actorstypes.Version(version), + ManifestCid: root, + Actors: actorCids, + }) + } +} + +func readBundleManifestFromFile(path string) (cid.Cid, map[string]cid.Cid, error) { + fi, err := os.Open(path) + if err != nil { + return cid.Undef, nil, err + } + defer fi.Close() //nolint + + return readBundleManifest(fi) +} + +func readBundleManifest(r io.Reader) (cid.Cid, map[string]cid.Cid, error) { + // Load the bundle. + bs := blockstore.NewMemory() + hdr, err := car.LoadCar(context.Background(), bs, r) + if err != nil { + return cid.Undef, nil, xerrors.Errorf("error loading builtin actors bundle: %w", err) + } + + if len(hdr.Roots) != 1 { + return cid.Undef, nil, xerrors.Errorf("expected one root when loading actors bundle, got %d", len(hdr.Roots)) + } + root := hdr.Roots[0] + actorCids, err := actors.ReadManifest(context.Background(), adt.WrapStore(context.Background(), cbor.NewCborStore(bs)), root) + if err != nil { + return cid.Undef, nil, err + } + + // Make sure we have all the actors. + for name, c := range actorCids { + if has, err := bs.Has(context.Background(), c); err != nil { + return cid.Undef, nil, xerrors.Errorf("got an error when checking that the bundle has the actor %q: %w", name, err) + } else if !has { + return cid.Undef, nil, xerrors.Errorf("actor %q missing from bundle", name) + } + } + + return root, actorCids, nil +} + +// GetEmbeddedBuiltinActorsBundle returns the builtin-actors bundle for the given actors version. +func GetEmbeddedBuiltinActorsBundle(version actorstypes.Version) ([]byte, bool) { + fi, err := embeddedBuiltinActorReleases.Open(fmt.Sprintf("actors/v%d.tar.zst", version)) + if err != nil { + return nil, false + } + defer fi.Close() //nolint + + uncompressed := zstd.NewReader(fi) + defer uncompressed.Close() //nolint + + tarReader := tar.NewReader(uncompressed) + targetFileName := fmt.Sprintf("builtin-actors-%s.car", NetworkBundle) + for { + header, err := tarReader.Next() + switch err { + case io.EOF: + return nil, false + case nil: + default: + panic(err) + } + if header.Name != targetFileName { + continue + } + + car, err := io.ReadAll(tarReader) + if err != nil { + panic(err) + } + return car, true + } +} diff --git a/build/builtin_actors_gen.go b/build/builtin_actors_gen.go new file mode 100644 index 000000000..13f595b6e --- /dev/null +++ b/build/builtin_actors_gen.go @@ -0,0 +1,627 @@ +// WARNING: This file has automatically been generated + +package build + +import ( + "github.com/ipfs/go-cid" +) + +var EmbeddedBuiltinActorsMetadata []*BuiltinActorsMetadata = []*BuiltinActorsMetadata{{ + Network: "butterflynet", + Version: 8, + + ManifestCid: MustParseCid("bafy2bzaceba5qgs4z3imhlxwds5vamahngatvuuglbv5yl3ftfiosj6ud5chs"), + Actors: map[string]cid.Cid{ + "account": MustParseCid("bafk2bzacebd5zetyjtragjwrv2nqktct6u2pmsi4eifbanovxohx3a7lszjxi"), + "cron": MustParseCid("bafk2bzacecrszortqkc7har77ssgajglymv6ftrqvmdko5h2yqqh5k2qospl2"), + "datacap": MustParseCid("bafk2bzacecapjnxnyw4talwqv5ajbtbkzmzqiosztj5cb3sortyp73ndjl76e"), + "eam": MustParseCid("bafk2bzacecflry2dyjqj6fhpovkbcbei377zabectznuxsf6bxggsve7bsxga"), + "ethaccount": MustParseCid("bafk2bzacedl4pmkfxkzoqajs6im3ranmopozsmxjcxsnk3kwvd3vv7mfwwrf4"), + "evm": MustParseCid("bafk2bzacebgzvmvwv7rsnnhp3zhqbiqkumvyrc7pazfovpptgpgtqkalrli74"), + "init": MustParseCid("bafk2bzacecbxp66q3ytjkg37nyv4rmzezbfaigvx4i5yhvqbm5gg4amjeaias"), + "multisig": MustParseCid("bafk2bzacecjltag3mn75dsnmrmopjow27buxqhabissowayqlmavrcfetqswc"), + "paymentchannel": MustParseCid("bafk2bzacednzxg263eqbl2imwz3uhujov63tjkffieyl4hl3dhrgxyhwep6hc"), + "placeholder": MustParseCid("bafk2bzaceaamp2a35vpfml4skap4dffklzae2urcm34mtwwce2lvhaons3a5y"), + "reward": MustParseCid("bafk2bzacectp23cxsbbdrr3uggnw7f263qll5wkkfzqhn5yq37ae2ehdjdzri"), + "storagemarket": MustParseCid("bafk2bzacea45ko3ezkpeujsniovncwnizc4wsxd7kyckskhs7gvzwthzb2mqe"), + "storageminer": MustParseCid("bafk2bzaced74qthwrl3gahcf7o3vrdrodbcqhlplh6fykbgy5sd2iyouhq44c"), + "storagepower": MustParseCid("bafk2bzaceduksv6wqthr5fgp7mx5prv6gzul2oozf3svrjbuggc4bgokdxgfy"), + "system": MustParseCid("bafk2bzacebe6j2ius6clbbr7dypsg54jzmn5xablzunph7ebedw6yhwla4cj2"), + "verifiedregistry": MustParseCid("bafk2bzacebu4joy25gneu2qv3qfm3ktakzalndjrbhekeqrqk3zhotv6nyy2g"), + }, +}, { + Network: "butterflynet", + Version: 9, + + ManifestCid: MustParseCid("bafy2bzacec35by4erhcdgcsgzp7yb3j57utydlxxfc73m3k5pep67ehvvyv6i"), + Actors: map[string]cid.Cid{ + "account": MustParseCid("bafk2bzaceajsdln7v4chxqoukiw7lxw6aexg5qdsaex2hgelz2sbu24iblhzg"), + "cron": MustParseCid("bafk2bzacecgrwmgnqhybn3l23uvwf2n2vrcfjrprfzgd44uxers2pgr5mhsue"), + "datacap": MustParseCid("bafk2bzacebyier2ceh27acbrq2ccv4efvzotl6qntnlrxdsrik6i4tembz6qw"), + "init": MustParseCid("bafk2bzaceberhto43wnf4pklkd4c7d36kzslngyzyms4op7shxuswv3dtvfxu"), + "multisig": MustParseCid("bafk2bzaceaclpbrhoqdruvsuqqgknvy2k5dywzmjoehk4uarce3uvt3w2rewu"), + "paymentchannel": MustParseCid("bafk2bzacedzp56g5cg73oilloak3kf7u667rdkd5pgnhe2cljmr3o7ykcrzuk"), + "reward": MustParseCid("bafk2bzacebczbwfbbi6mvppbjcozatasjiaohvjjiqcy65ccuuyyw3xiixhk2"), + "storagemarket": MustParseCid("bafk2bzaceawqexy6t2ybzh3jjwhbs7icbg5vqnedbbge4e4r4pfp7spkcadsu"), + "storageminer": MustParseCid("bafk2bzacearemd7pn2jj26fdtqd4di27lfhpng3vp5chepm7qnmdzgiqr6wfi"), + "storagepower": MustParseCid("bafk2bzaceddc7fiaxfobfegqaobf5xinjgmhsa5iu4yi6klvc3jmjimcdvgyg"), + "system": MustParseCid("bafk2bzacedylltr57b2n6zpadh4i2c2kis4fzzvhao3kgvfaggrrbqyacew7q"), + "verifiedregistry": MustParseCid("bafk2bzacecjkesz766626ab4svnzpq3jfs26a75vfktlfaku5fjdao2eyiqyq"), + }, +}, { + Network: "butterflynet", + Version: 10, + + ManifestCid: MustParseCid("bafy2bzaceckjhsggacixv2d377zfdcnuio4hzkveprio3xnhm3gohi3zy3zco"), + Actors: map[string]cid.Cid{ + "account": MustParseCid("bafk2bzacedkt3uzgugcsdrcsyfvizcpyr5eshltmienbyhjne2t7t3ktkihny"), + "cron": MustParseCid("bafk2bzacecrehknegmfnhmhwy2g43cw52mvl7ptfpp44syus4iph7az7uveuq"), + "datacap": MustParseCid("bafk2bzaced4krgbpj4sywcc453l3pygqr4qocc6nxylhztsm4duvkgfwd7vws"), + "eam": MustParseCid("bafk2bzacebn5lyg5pfhjpdlf3r7lnah4x33bhp5afftdgbr4kbpuioytr4bhe"), + "ethaccount": MustParseCid("bafk2bzaceaxyu24a2tbiacfr4p367xjtptrbang4qrh3fx65cojyrzolwyi4u"), + "evm": MustParseCid("bafk2bzacea5bqaubqeuqmpguxrem2pgocjr43wcfi5e3jpw2e3b4o6tcvs746"), + "init": MustParseCid("bafk2bzaceaufptkdg2gc4eq4ijqxtqp7wxwifusxb6kxay3vdz3wr5epqjbho"), + "multisig": MustParseCid("bafk2bzacedp3c26ccw3l7fci4xhedxhqeqevkubuf5okuslq7o7rcqwqfahci"), + "paymentchannel": MustParseCid("bafk2bzacedlmiqvbutz4ebx2mezy3pqj72x2yt4gwea7sf4dv4a4s7xidelok"), + "placeholder": MustParseCid("bafk2bzacedfvut2myeleyq67fljcrw4kkmn5pb5dpyozovj7jpoez5irnc3ro"), + "reward": MustParseCid("bafk2bzacecrzxiowkhzpgz4rl2pdldzwmmnctuq5zzntqjkgyhyfllo3afb5s"), + "storagemarket": MustParseCid("bafk2bzacebh2q3ofolirt5q2jpx367dfv22aecevsmybba3yhnxfs3foe6c5q"), + "storageminer": MustParseCid("bafk2bzaceavop4j7iwneew6h7p667gvx37baloxilxetwkhsrr26jme6yye5o"), + "storagepower": MustParseCid("bafk2bzacecfblbat4w7jkxx7kjst33lowyb7s6apdnl7fsnpmy5c3jfq5kvye"), + "system": MustParseCid("bafk2bzacebojf25kc5yo7gskdbdgg5f52oppej2jp6nknzlvrww4ue5vkddd2"), + "verifiedregistry": MustParseCid("bafk2bzaceavue3zekq4wmvttck2vgxlcensrsgh5niu5qhna2owejycorftcc"), + }, +}, { + Network: "butterflynet", + Version: 11, + BundleGitTag: "v11.0.0", + ManifestCid: MustParseCid("bafy2bzaceaiy4dsxxus5xp5n5i4tjzkb7sc54mjz7qnk2efhgmsrobjesxnza"), + Actors: map[string]cid.Cid{ + "account": MustParseCid("bafk2bzacecfdqb7p3jakhaa3cqnzpt7hxmhghrbxvafsylqno3febx55fnidw"), + "cron": MustParseCid("bafk2bzaceavmqu2qihgbe3xdaotgypuzvdpiifnm7ll6rolks2u4lac6voosk"), + "datacap": MustParseCid("bafk2bzacealtvh65rzb34fmyzw4m2np2htnio4w3pn4alzqovwxkdbf23dvpo"), + "eam": MustParseCid("bafk2bzacedko6hcjmwpuwgma5pb4gr2wgyvregk3nqqjxit7dv4es6vh5cjoc"), + "ethaccount": MustParseCid("bafk2bzacedhcei2xnr34poxr4xziypm2obqlibke4cs2cjfnr3sz6nf6h7fyy"), + "evm": MustParseCid("bafk2bzacebn5lwxboiikhz67ajwa34v2lc4qevnhpwdnipbmrnutkvrrqkb46"), + "init": MustParseCid("bafk2bzacea6vw4esh5tg7mprv5jkbx5xcyilcy4vvf64lss32mjyuvv2mh5ng"), + "multisig": MustParseCid("bafk2bzacedq2afnwcfipay5twv5mgzjoio5bbjvyo4yqchdwqcr7wrareyx54"), + "paymentchannel": MustParseCid("bafk2bzacebbsvr7i7mqmaadyjibe5wxnv7bwvvec2wlgknuwda6ep45amnd5w"), + "placeholder": MustParseCid("bafk2bzacedfvut2myeleyq67fljcrw4kkmn5pb5dpyozovj7jpoez5irnc3ro"), + "reward": MustParseCid("bafk2bzaceafuh6idvaqqkj353vs4qdl42tcmvnymewu5zf4rq2nruxdyunses"), + "storagemarket": MustParseCid("bafk2bzaceb7bx4honi3byjllpdk6fea32dpu3vqvil3okodybdk5m3erlnwjw"), + "storageminer": MustParseCid("bafk2bzacebxjhofdr3sb2uhy2ky2vcijh4nhmwkh5xijtbgk6dzkknji2kn7a"), + "storagepower": MustParseCid("bafk2bzaceabskmmkas6njbowols7t4ib3bipa5abpomk3jtgfwojtzd7mjzfm"), + "system": MustParseCid("bafk2bzacedtuh7cht3fud7fb4avl4g2zbz57lc4ohiaufpaex6dkmdokn5rgo"), + "verifiedregistry": MustParseCid("bafk2bzaceb37hxeuoo5rgf6ansrdl2ykm5v5zp6kireubn4orcopr67jbxv6k"), + }, +}, { + Network: "calibrationnet", + Version: 8, + + ManifestCid: MustParseCid("bafy2bzacedrdn6z3z7xz7lx4wll3tlgktirhllzqxb766dxpaqp3ukxsjfsba"), + Actors: map[string]cid.Cid{ + "account": MustParseCid("bafk2bzacecruossn66xqbeutqx5r4k2kjzgd43frmwd4qkw6haez44ubvvpxo"), + "cron": MustParseCid("bafk2bzaceaxlezmclw5ugldhhtfgvn7yztux45scqik3ez4yhwiqhg5ssib44"), + "init": MustParseCid("bafk2bzaceadyfilb22bcvzvnpzbg2lyg6npmperyq6es2brvzjdh5rmywc4ry"), + "multisig": MustParseCid("bafk2bzacec66wmb4kohuzvuxsulhcgiwju7sqkldwfpmmgw7dbbwgm5l2574q"), + "paymentchannel": MustParseCid("bafk2bzaceblot4pemhfgwb3lceellwrpgxaqkpselzbpqu32maffpopdunlha"), + "reward": MustParseCid("bafk2bzaceayah37uvj7brl5no4gmvmqbmtndh5raywuts7h6tqbgbq2ge7dhu"), + "storagemarket": MustParseCid("bafk2bzacebotg5coqnglzsdrqxtkqk2eq4krxt6zvds3i3vb2yejgxhexl2n6"), + "storageminer": MustParseCid("bafk2bzacea6rabflc7kpwr6y4lzcqsnuahr4zblyq3rhzrrsfceeiw2lufrb4"), + "storagepower": MustParseCid("bafk2bzacecpwr4mynn55bg5hrlns3osvg7sty3rca6zlai3vl52vbbjk7ulfa"), + "system": MustParseCid("bafk2bzaceaqrkllksxv2jsfgjvmuewx5vbzrammw5mdscod6gkdr3ijih2q64"), + "verifiedregistry": MustParseCid("bafk2bzaceaihibfu625lbtzdp3tcftscshrmbgghgrc7kzqhxn4455pycpdkm"), + }, +}, { + Network: "calibrationnet", + Version: 9, + + ManifestCid: MustParseCid("bafy2bzacedbedgynklc4dgpyxippkxmba2mgtw7ecntoneclsvvl4klqwuyyy"), + Actors: map[string]cid.Cid{ + "account": MustParseCid("bafk2bzaceavfgpiw6whqigmskk74z4blm22nwjfnzxb4unlqz2e4wg3c5ujpw"), + "cron": MustParseCid("bafk2bzaceb7hxmudhvkizszbmmf2ur2qfnfxfkok3xmbrlifylx6huw4bb3s4"), + "datacap": MustParseCid("bafk2bzaceanmwcfjfj65xy275rrfqqgoblnuqirdg6zwhc6qhbfhpphomvceu"), + "init": MustParseCid("bafk2bzaceczqxpivlxifdo5ohr2rx5ny4uyvssm6tkf7am357xm47x472yxu2"), + "multisig": MustParseCid("bafk2bzacec6gmi7ucukr3bk67akaxwngohw3lsg3obvdazhmfhdzflkszk3tg"), + "paymentchannel": MustParseCid("bafk2bzacec4kg3bfjtssvv2b4wizlbdk3pdtrg5aknzgeb3a6rmksgurpynca"), + "reward": MustParseCid("bafk2bzacebpptqhcw6mcwdj576dgpryapdd2zfexxvqzlh3aoc24mabwgmcss"), + "storagemarket": MustParseCid("bafk2bzacebkfcnc27d3agm2bhzzbvvtbqahmvy2b2nf5xyj4aoxehow3bules"), + "storageminer": MustParseCid("bafk2bzacebz4na3nq4gmumghegtkaofrv4nffiihd7sxntrryfneusqkuqodm"), + "storagepower": MustParseCid("bafk2bzaceburxajojmywawjudovqvigmos4dlu4ifdikogumhso2ca2ccaleo"), + "system": MustParseCid("bafk2bzaceaue3nzucbom3tcclgyaahy3iwvbqejsxrohiquakvvsjgbw3shac"), + "verifiedregistry": MustParseCid("bafk2bzacebh7dj6j7yi5vadh7lgqjtq42qi2uq4n6zy2g5vjeathacwn2tscu"), + }, +}, { + Network: "calibrationnet", + Version: 10, + + ManifestCid: MustParseCid("bafy2bzaced25ta3j6ygs34roprilbtb3f6mxifyfnm7z7ndquaruxzdq3y7lo"), + Actors: map[string]cid.Cid{ + "account": MustParseCid("bafk2bzacebhfuz3sv7duvk653544xsxhdn4lsmy7ol7k6gdgancyctvmd7lnq"), + "cron": MustParseCid("bafk2bzacecw2yjb6ysieffa7lk7xd32b3n4ssowvafolt7eq52lp6lk4lkhji"), + "datacap": MustParseCid("bafk2bzaceaot6tv6p4cat3cg5fknq22htosw3p5rwyijmdsraatwqyc4qyero"), + "eam": MustParseCid("bafk2bzacec5untyj6cefdsfm47wckozw6wt6svqqh5dzh63nu4f6dvf26fkco"), + "ethaccount": MustParseCid("bafk2bzacebiyrhz32xwxi6xql67aaq5nrzeelzas472kuwjqmdmgwotpkj35e"), + "evm": MustParseCid("bafk2bzaceblpgzid4qjfavuiht6uwvq2lznshklk2qmf5akm3dzx2fczdqdxc"), + "init": MustParseCid("bafk2bzacedhxbcglnonzruxf2jpczara73eh735wf2kznatx2u4gsuhgqwffq"), + "multisig": MustParseCid("bafk2bzacebv5gdlte2pyovmz6s37me6x2rixaa6a33w6lgqdohmycl23snvwm"), + "paymentchannel": MustParseCid("bafk2bzacea7ngq44gedftjlar3j3ql3dmd7e7xkkb6squgxinfncybfmppmlc"), + "placeholder": MustParseCid("bafk2bzacedfvut2myeleyq67fljcrw4kkmn5pb5dpyozovj7jpoez5irnc3ro"), + "reward": MustParseCid("bafk2bzacea3yo22x4dsh4axioshrdp42eoeugef3tqtmtwz5untyvth7uc73o"), + "storagemarket": MustParseCid("bafk2bzacecclsfboql3iraf3e66pzuh3h7qp3vgmfurqz26qh5g5nrexjgknc"), + "storageminer": MustParseCid("bafk2bzacedu4chbl36rilas45py4vhqtuj6o7aa5stlvnwef3kshgwcsmha6y"), + "storagepower": MustParseCid("bafk2bzacedu3c67spbf2dmwo77ymkjel6i2o5gpzyksgu2iuwu2xvcnxgfdjg"), + "system": MustParseCid("bafk2bzacea4mtukm5zazygkdbgdf26cpnwwif5n2no7s6tknpxlwy6fpq3mug"), + "verifiedregistry": MustParseCid("bafk2bzacec67wuchq64k7kgrujguukjvdlsl24pgighqdx5vgjhyk6bycrwnc"), + }, +}, { + Network: "calibrationnet", + Version: 11, + BundleGitTag: "v11.0.0-rc2", + ManifestCid: MustParseCid("bafy2bzacedhuowetjy2h4cxnijz2l64h4mzpk5m256oywp4evarpono3cjhco"), + Actors: map[string]cid.Cid{ + "account": MustParseCid("bafk2bzacebor5mnjnsav34cmm5pcd3dy4wubbv4wtcrvba7depy3sct7ie4sy"), + "cron": MustParseCid("bafk2bzacebetehhedh55alfn4rcx2mhjhvuiustxlhtxc3drkemnpttws5eqw"), + "datacap": MustParseCid("bafk2bzaced6uhmrh5jjexhw4lco4ipesi2iutl7uupnyspgmnbydyo3amtu4i"), + "eam": MustParseCid("bafk2bzacea6wzcnflfnaxqnwydoghh7ezg5au32ew3bnzljzpiw6fimhlpoiu"), + "ethaccount": MustParseCid("bafk2bzacedrbpvjvyzif2cjxosm4pliyq2m6wzndvrg7r6hzdhixplzvgubbw"), + "evm": MustParseCid("bafk2bzaceabftmhejmvjvpzmbsv4cvaew6v5juj5sqtq7cfijugwsnahnsy5w"), + "init": MustParseCid("bafk2bzaceduyjd35y7o2lhvevtysqf45rp5ot7x5f36q6iond6dyiz6773g5q"), + "multisig": MustParseCid("bafk2bzacebcb72fmbpocetnzgni2wnbrduamlqx6fl3yelrlzu7id6bu5ib5g"), + "paymentchannel": MustParseCid("bafk2bzaceazwhm63kyp47pste5i5acnuhosrgythyagf3kc5clogiqqx6vkzk"), + "placeholder": MustParseCid("bafk2bzacedfvut2myeleyq67fljcrw4kkmn5pb5dpyozovj7jpoez5irnc3ro"), + "reward": MustParseCid("bafk2bzacecp7xo5ev46y64zr5osnn5fxo7itpoqw235tcfv6eo4rymzdemet2"), + "storagemarket": MustParseCid("bafk2bzacedjt5mueomasx7dijooxnwxsbtzu2dj2ppp45rtle4kiinkmgzeei"), + "storageminer": MustParseCid("bafk2bzacebkjnjp5okqjhjxzft5qkuv36u4tz7inawseiwi2kw4j43xpxvhpm"), + "storagepower": MustParseCid("bafk2bzaced2qsypqwore3jrdtaesh4itst2fyeepdsozvtffc2pianzmphdum"), + "system": MustParseCid("bafk2bzacedqvik2n3phnj3cni3h2k5mtvz43nyq7mdmv7k7euejysvajywdug"), + "verifiedregistry": MustParseCid("bafk2bzaceceoo5jlom2zweh7kpye2vkj33wgqnkjshlsw2neemqkfg5g2rmvg"), + }, +}, { + Network: "caterpillarnet", + Version: 8, + + ManifestCid: MustParseCid("bafy2bzacebsdvrxmdajiyxq2mxxxppvg2zwvqjzz3pgbsxwh6pvdcjofpmnxw"), + Actors: map[string]cid.Cid{ + "account": MustParseCid("bafk2bzacedfms6w3ghqtljpgsfuiqa6ztjx7kcuin6myjezj6rypj3zjbqms6"), + "cron": MustParseCid("bafk2bzaceaganmlpozvy4jywigs46pfrtdmhjjey6uyhpurplqbasojsislba"), + "datacap": MustParseCid("bafk2bzacebafqqe3wv5ytkfwmqzbmchgem66pw6yq6rl7w6vlhqsbkxnisswq"), + "eam": MustParseCid("bafk2bzaceaeayeksiivw4y3gdqtigbgfntyvwc3q7v2ivb5kx7u55pn4q5lt6"), + "ethaccount": MustParseCid("bafk2bzaceburkmtd63nmzxpux5rcxsbqr6x5didl2ce7al32g4tqrvo4pjz2i"), + "evm": MustParseCid("bafk2bzacea7tp4lop7ivhay3ozitkmxxurk74v4zse42ant47rh2uw5z3tq5e"), + "init": MustParseCid("bafk2bzaced23r54kwuebl7t6mdantbby5qpfduxwxfryeliof2enyqzhokix6"), + "multisig": MustParseCid("bafk2bzacebcn3rib6j6jvclys7dkf62hco45ssgamczkrtzt6xyewd6gt3mtu"), + "paymentchannel": MustParseCid("bafk2bzacecvas4leo44pqdguj22nnwqoqdgwajzrpm5d6ltkehc37ni6p6doq"), + "placeholder": MustParseCid("bafk2bzaceaamp2a35vpfml4skap4dffklzae2urcm34mtwwce2lvhaons3a5y"), + "reward": MustParseCid("bafk2bzacebiizh4ohvv6p4uxjusoygex4wxcgvudqmdl2fsh6ft6s2zt4tz6q"), + "storagemarket": MustParseCid("bafk2bzacedhkidshm7w2sqlw7izvaieyhkvmyhfsem6t6qfnkh7dnwqe56po2"), + "storageminer": MustParseCid("bafk2bzacedcmsibwfwhkp3sabmbyjmhqibyhjf3wwst7u5bkb2k6xpun3xevg"), + "storagepower": MustParseCid("bafk2bzacecrgnpypxnxzgglhlitaallfee3dl4ejy3y63knl7llnwba4ycf7i"), + "system": MustParseCid("bafk2bzacecl7gizbe52xj6sfm5glubkhrdblmzuwlid6lxrwr5zhcmv4dl2ew"), + "verifiedregistry": MustParseCid("bafk2bzacebzndvdqtdck2y35smcxezldgh6nm6rbkj3g3fmiknsgg2uah235y"), + }, +}, { + Network: "caterpillarnet", + Version: 9, + + ManifestCid: MustParseCid("bafy2bzacebsdvrxmdajiyxq2mxxxppvg2zwvqjzz3pgbsxwh6pvdcjofpmnxw"), + Actors: map[string]cid.Cid{ + "account": MustParseCid("bafk2bzacedfms6w3ghqtljpgsfuiqa6ztjx7kcuin6myjezj6rypj3zjbqms6"), + "cron": MustParseCid("bafk2bzaceaganmlpozvy4jywigs46pfrtdmhjjey6uyhpurplqbasojsislba"), + "datacap": MustParseCid("bafk2bzacebafqqe3wv5ytkfwmqzbmchgem66pw6yq6rl7w6vlhqsbkxnisswq"), + "eam": MustParseCid("bafk2bzaceaeayeksiivw4y3gdqtigbgfntyvwc3q7v2ivb5kx7u55pn4q5lt6"), + "ethaccount": MustParseCid("bafk2bzaceburkmtd63nmzxpux5rcxsbqr6x5didl2ce7al32g4tqrvo4pjz2i"), + "evm": MustParseCid("bafk2bzacea7tp4lop7ivhay3ozitkmxxurk74v4zse42ant47rh2uw5z3tq5e"), + "init": MustParseCid("bafk2bzaced23r54kwuebl7t6mdantbby5qpfduxwxfryeliof2enyqzhokix6"), + "multisig": MustParseCid("bafk2bzacebcn3rib6j6jvclys7dkf62hco45ssgamczkrtzt6xyewd6gt3mtu"), + "paymentchannel": MustParseCid("bafk2bzacecvas4leo44pqdguj22nnwqoqdgwajzrpm5d6ltkehc37ni6p6doq"), + "placeholder": MustParseCid("bafk2bzaceaamp2a35vpfml4skap4dffklzae2urcm34mtwwce2lvhaons3a5y"), + "reward": MustParseCid("bafk2bzacebiizh4ohvv6p4uxjusoygex4wxcgvudqmdl2fsh6ft6s2zt4tz6q"), + "storagemarket": MustParseCid("bafk2bzacedhkidshm7w2sqlw7izvaieyhkvmyhfsem6t6qfnkh7dnwqe56po2"), + "storageminer": MustParseCid("bafk2bzacedcmsibwfwhkp3sabmbyjmhqibyhjf3wwst7u5bkb2k6xpun3xevg"), + "storagepower": MustParseCid("bafk2bzacecrgnpypxnxzgglhlitaallfee3dl4ejy3y63knl7llnwba4ycf7i"), + "system": MustParseCid("bafk2bzacecl7gizbe52xj6sfm5glubkhrdblmzuwlid6lxrwr5zhcmv4dl2ew"), + "verifiedregistry": MustParseCid("bafk2bzacebzndvdqtdck2y35smcxezldgh6nm6rbkj3g3fmiknsgg2uah235y"), + }, +}, { + Network: "caterpillarnet", + Version: 10, + + ManifestCid: MustParseCid("bafy2bzaceajftd7jawqnwf4kzkotksrwy6ag7mu2apkvypzrrmxboheuum5oi"), + Actors: map[string]cid.Cid{ + "account": MustParseCid("bafk2bzacecsbx4tovnr5x2ifcpqbpx33oht74mgtvmaauzrqcq2wnm7prr7ak"), + "cron": MustParseCid("bafk2bzacecpzfajba6m4v4ty342jw6lcu6n63bwtldmzko733wpd2q5jzfdvu"), + "datacap": MustParseCid("bafk2bzaceaa5zplkxvguwvnecfen62buhli5rraa3ga74b33a3sbscanzx4ok"), + "eam": MustParseCid("bafk2bzaceaffoa3eqmj7h53lwjatfqrjw63l3czk3vthyjz6oyhgwka3xwp6g"), + "ethaccount": MustParseCid("bafk2bzaceb7suh5m4xagoq6ap5v5x7vrhex2coq6gu6d54jteblm36cxhk5b2"), + "evm": MustParseCid("bafk2bzaceccmwmnb42pn7y7skbjwjur7b2eqxuw4lvm3he2xpvudjzluss4os"), + "init": MustParseCid("bafk2bzaceai72h4hxbgbp6gwm3m24uujscrj4bmbh6pxoerqtduijxt6dchfq"), + "multisig": MustParseCid("bafk2bzacebycdokda2gysqpnl3dwksgidujgsksf4n6qotjq4erj5zd7clkzy"), + "paymentchannel": MustParseCid("bafk2bzaceb5ucvftftiim6cxjusdpsmbht4x33kgexxgv5447gevk47h7jjqk"), + "placeholder": MustParseCid("bafk2bzacedfvut2myeleyq67fljcrw4kkmn5pb5dpyozovj7jpoez5irnc3ro"), + "reward": MustParseCid("bafk2bzaceajqygfkhamlzfsquqjgoy4p7pc2fruouqajapfucf22rbmtt5yf6"), + "storagemarket": MustParseCid("bafk2bzacednmzko2o5iv5kc6qxvpqfx5rq72krxzvna6cqoqem6flbfukglby"), + "storageminer": MustParseCid("bafk2bzacedayzz5qw7t7ykycf3a2hp666j5hb23a3mnmgp4xbbpvrx3h3ags4"), + "storagepower": MustParseCid("bafk2bzacedd3eiejzp35xuwjf3cvgd43b5ukqhelqmtgzqzqnt2wcy56pb744"), + "system": MustParseCid("bafk2bzacecfivztuulqqv4o5oyvvvrkblwix4hqt24pqru6ivnpioefhuhria"), + "verifiedregistry": MustParseCid("bafk2bzacecdhw6x7dfrxfysmn6tdbn2ny464omgqppxhjuawxauscidppd7pc"), + }, +}, { + Network: "caterpillarnet", + Version: 11, + BundleGitTag: "v11.0.0", + ManifestCid: MustParseCid("bafy2bzacebexc2jgzwr5ngn6jdnkwdqwwmcapajuypdgvopoe6bnvp4yxm4o2"), + Actors: map[string]cid.Cid{ + "account": MustParseCid("bafk2bzaceanjiq5m3feytue5m7hhxfkob2ofg2greoct5tr77reuhrjglo66g"), + "cron": MustParseCid("bafk2bzaceavgd5qj6n744tukhdrvxejygzs3jnlizmcvjsdnxkgiimrd5jrys"), + "datacap": MustParseCid("bafk2bzacedmdywxwrzop2gmf4ys5stydlmvbe35j3nyr2efmf273briksuvse"), + "eam": MustParseCid("bafk2bzacec7qo7s72li7tqysllstlrxxm2dhfqv2w32pytel2e775cki4ozqm"), + "ethaccount": MustParseCid("bafk2bzaceaygtkliu26ubb7ivljrvaeesp5sbjlis5okzl35ishxioa2tlx4w"), + "evm": MustParseCid("bafk2bzacebo7iqzy2ophz4f3civzwlltec7q5fut7kmtfckr6vy33r6ic5eqe"), + "init": MustParseCid("bafk2bzaceb7uzzlsquqwrqhb2vpbvk3jgr4wp5i3smu2splnag2v5sppdehns"), + "multisig": MustParseCid("bafk2bzacebwibfqrytobl4pjtny244zkmfoomazbap3r5gddjryckx5js4csi"), + "paymentchannel": MustParseCid("bafk2bzacecuaa5esuxpouigxoamyl5gire2qqqhvyhewsig2x2j73f6ksh7go"), + "placeholder": MustParseCid("bafk2bzacedfvut2myeleyq67fljcrw4kkmn5pb5dpyozovj7jpoez5irnc3ro"), + "reward": MustParseCid("bafk2bzaced4xxqhv63njf2ibvsqshlwikafctxev7aho5lgsfxyt2javjwvtw"), + "storagemarket": MustParseCid("bafk2bzacedwtx3xokqmbgkgkoqkdt6lam4ymdjb3eznlbtec5wcrtx74l2bpc"), + "storageminer": MustParseCid("bafk2bzacebbbe4sdo3xxkez7x7lkl6j46w34vx7eg7xswmdzhp7moa44p3wjg"), + "storagepower": MustParseCid("bafk2bzacedfgz6n24tjsor4pcayomim2f5f3a3fgyatmjgwxxeejna7okndda"), + "system": MustParseCid("bafk2bzacebxfzeom3d7ahcz2n2nlwp7ncv767bdbbrisugks4l6v7lcu2tmyg"), + "verifiedregistry": MustParseCid("bafk2bzacedaws3or3twy45ltcxucgvqijsje4x675ph6vup2w35smlfneamno"), + }, +}, { + Network: "devnet", + Version: 8, + + ManifestCid: MustParseCid("bafy2bzacedq7tuibavyqxzkq4uybjj7ly22eu42mjkoehwn5d47xfunmtjm4k"), + Actors: map[string]cid.Cid{ + "account": MustParseCid("bafk2bzacea4tlgnp7m6tlldpz3termlwxlnyq24nwd4zdzv4r6nsjuaktuuzc"), + "cron": MustParseCid("bafk2bzacecgrlf3vg3mufwovddlbgclhpnpp3jftr46stssh3crd3pyljc37w"), + "init": MustParseCid("bafk2bzacedarbnovmucppbjkcwsxopludrj5ttmtm7mzfqsugmxdnqevqso7o"), + "multisig": MustParseCid("bafk2bzaced4gcxjwy6garxwfw6y5a2k4jewj4t5nzopjy4qwnimhjtnsgo3ss"), + "paymentchannel": MustParseCid("bafk2bzaceb3isfguytt6cs4xecyoonbhhekmngfbap2msggbwyde7zch3a6w4"), + "reward": MustParseCid("bafk2bzacedn3fkp27ys5dxn4pwqdq2atj2x6cyezxuekdorvjwi7zazirgvgy"), + "storagemarket": MustParseCid("bafk2bzacecw57fpkqesfhi5g3nr4csy4oy7oc42wmwjuis6l7ijniolo4rt2k"), + "storageminer": MustParseCid("bafk2bzacebze3elvppssc6v5457ukszzy6ndrg6xgaojfsqfbbtg3xfwo4rbs"), + "storagepower": MustParseCid("bafk2bzaceb45l6zhgc34n6clz7xnvd7ek55bhw46q25umuje34t6kroix6hh6"), + "system": MustParseCid("bafk2bzacecf7eta2stfd3cnuxzervd33imbvlaqq6b5tsho7pxmhifrybreru"), + "verifiedregistry": MustParseCid("bafk2bzaceaajgtglewgitshgdi2nzrvq7eihjtyqj5yiamesqun2hujl3xev2"), + }, +}, { + Network: "devnet", + Version: 9, + + ManifestCid: MustParseCid("bafy2bzacedozk3jh2j4nobqotkbofodq4chbrabioxbfrygpldgoxs3zwgggk"), + Actors: map[string]cid.Cid{ + "account": MustParseCid("bafk2bzaced5llqnqqhypolyuogz3h2wjomugqkrhyhocvly3aoib4c5xiush6"), + "cron": MustParseCid("bafk2bzaceahwdt32ji53mo5yz6imvztz3s3g2ra5uz3jdfa77j7hqcnq6r4l2"), + "datacap": MustParseCid("bafk2bzaceabcxoy5iscdierasorjoj6xzqgnnb5pmrr7prkuibw4yggx3v2d2"), + "init": MustParseCid("bafk2bzaceastwn42kqyztz7uzej7l4lemp5nakqqsfvksry7k75q5ombhprme"), + "multisig": MustParseCid("bafk2bzacebeiygkjupkpfxcrsidci4bvn6afkvx4lsj3ut3ywhsj654pzfgk4"), + "paymentchannel": MustParseCid("bafk2bzacedhsdoo4ww47rm44pizu5qqpho753cizzbbvnd5yz3nm3347su5cy"), + "reward": MustParseCid("bafk2bzacebzqvisqe3iaodtxq7l2lgzwfkxznrnp676ddpllqcpvuae5i33le"), + "storagemarket": MustParseCid("bafk2bzaceduauegz4nniegh667btjhg2anipwpxeb664s4ossq2ifvuqwqlso"), + "storageminer": MustParseCid("bafk2bzacec23wjdmbm5pt6pqsbjb3w6j7vyrolijz2mysvp6clllfgpmhb6ge"), + "storagepower": MustParseCid("bafk2bzacebnyywv46n2ghg62inllwpmnyuwtoz57fn5lpgpf436mahajg4qrg"), + "system": MustParseCid("bafk2bzacebgafb6h2o2g5whrujc2uvsttrussyc5t56rvhrjqkqhzdu4jopwa"), + "verifiedregistry": MustParseCid("bafk2bzacednorhcy446agy7ecpmfms2u4aoa3mj2eqomffuoerbik5yavrxyi"), + }, +}, { + Network: "devnet", + Version: 10, + + ManifestCid: MustParseCid("bafy2bzacebzz376j5kizfck56366kdz5aut6ktqrvqbi3efa2d4l2o2m653ts"), + Actors: map[string]cid.Cid{ + "account": MustParseCid("bafk2bzacedkj5dqs5xxamnlug2d5dyjl6askf7wlmvwzhmsrzcvogv7acqfe6"), + "cron": MustParseCid("bafk2bzaceabslrigld2vshng6sppbp3bsptjtttvbxctwqe5lkyl2efom2wu4"), + "datacap": MustParseCid("bafk2bzaceagg4qklzhhg5oj4shwqpoeykeyxus7xhj2abuot2tycdwsf2oaaa"), + "eam": MustParseCid("bafk2bzaceafttsbglcetxwtzqtdniittwczogkefgnxztgsp7mymcpvdlhdik"), + "ethaccount": MustParseCid("bafk2bzacedypn6tf3yrj4bavmscddygeima3puih37fbkxuhjhlrzbjh3dbo4"), + "evm": MustParseCid("bafk2bzacec5ywczgg73fnwi36nlxso3zduop3fwj3pq6ynn5zltrs4dpcwglg"), + "init": MustParseCid("bafk2bzacebkanlbkwwtniyz4fawevnkoyje67l5nflltmciplqiutekxzzfh4"), + "multisig": MustParseCid("bafk2bzacectxa2izvpaybmmpvearekrybxtglctwnexzzneyn6xrnrmectmpa"), + "paymentchannel": MustParseCid("bafk2bzacectov7vawkhsvq7aobyjq3oppamytq425wpkxejmq65vvcdm4bt2e"), + "placeholder": MustParseCid("bafk2bzacedfvut2myeleyq67fljcrw4kkmn5pb5dpyozovj7jpoez5irnc3ro"), + "reward": MustParseCid("bafk2bzacec3xpbrxw2rnpuve4mxfhny44lxbpbwmduy4ula4ohj2bp6wplpvc"), + "storagemarket": MustParseCid("bafk2bzacec5nexsejraoqraywka7zcacjoxgpdbopehdkhiwqwcyghtof4s3w"), + "storageminer": MustParseCid("bafk2bzacecw5xzj6z5b7qxx5xca5py4aoecmqj2pxb6nw673alufy22zckkyo"), + "storagepower": MustParseCid("bafk2bzaceckhnpxoaanjf474wxzkntlnzdofoy75ehyuydfjkuw4swhotws4y"), + "system": MustParseCid("bafk2bzaceairk5qz5hyzt4yyaxa356aszyifswiust5ilxizwxujcmtzvjzoa"), + "verifiedregistry": MustParseCid("bafk2bzaced2mkyqobpgna5jevosym3adv2bvraggigyz2jgn5cxymirxj4x3i"), + }, +}, { + Network: "devnet", + Version: 11, + BundleGitTag: "v11.0.0", + ManifestCid: MustParseCid("bafy2bzaceay35go4xbjb45km6o46e5bib3bi46panhovcbedrynzwmm3drr4i"), + Actors: map[string]cid.Cid{ + "account": MustParseCid("bafk2bzacecf2pprkbdlpm4e2xz3ufunxtgrgyh2ie3stuqiyhibsvdze7kvri"), + "cron": MustParseCid("bafk2bzaceasr5d2skowvzv5mzsyak6waqrgc46ewj6rzbapkfi5woom6n6bwa"), + "datacap": MustParseCid("bafk2bzaceaqd77gptubupda7rp7daxkxbkzwc253dxhiyoezxvj2tljmkgpny"), + "eam": MustParseCid("bafk2bzacedve6p4ye6zxydjbfs4ode5r2equ7rqzpyltujsq2lu6wyxnijfx4"), + "ethaccount": MustParseCid("bafk2bzacea25xfsxwew3h2crer6jlb4c5vwu2gtch2jh73ocuxjhupenyrugy"), + "evm": MustParseCid("bafk2bzacece5hivtkmi757lyfahgti7xuqgofodb2u65pxgf6oizfwiiwlcsi"), + "init": MustParseCid("bafk2bzacecxnr5y7qifzdqqiwfbjxv2yr7lbkcyu3e2mf5zjdncteupxdlquu"), + "multisig": MustParseCid("bafk2bzaceayap4k4u3lbysaeeixct5fvhmafy3fa5eagvdpk3i4a7ubfdpobe"), + "paymentchannel": MustParseCid("bafk2bzaceafgrz5wepbein35gie7rnsu7zttxvgllgdneuefmmy4j5izydtza"), + "placeholder": MustParseCid("bafk2bzacedfvut2myeleyq67fljcrw4kkmn5pb5dpyozovj7jpoez5irnc3ro"), + "reward": MustParseCid("bafk2bzacedwbtfqlx47fdkxjrb5mwiatheci44x3zkpx33smybc2cme23ymuo"), + "storagemarket": MustParseCid("bafk2bzaceaj74fmooaf3gj3ebwon64ky7hhdh7kytdr3agclqfrqzmpzykh7g"), + "storageminer": MustParseCid("bafk2bzacedb7bokkzzs7hnbhivp74pgcpermuy7j6b3ncodylksukkxtnn7ze"), + "storagepower": MustParseCid("bafk2bzacedilnkegizkxz3nuutib4d4wwlk4bkla22loepia2h53yf4hysmq6"), + "system": MustParseCid("bafk2bzacedpyoncjbl4oxkjm5e77ngvpy2xfajjc4myfsv2vltvzxioattlu2"), + "verifiedregistry": MustParseCid("bafk2bzacebdqi5tr5pjnem5nylg2zbqcugvi7oxi35bhnrfudx4y4ufhlit2k"), + }, +}, { + Network: "hyperspace", + Version: 8, + + ManifestCid: MustParseCid("bafy2bzacedvffumcvf72f2btjqvece3kpcdorxq5tq76iwcmqbzvsiu526cqm"), + Actors: map[string]cid.Cid{ + "account": MustParseCid("bafk2bzacecim7uybic2qprbkjhowg7qkniv4zywj5h5g4u4ss72urco2akzuo"), + "cron": MustParseCid("bafk2bzaceahgq64awp4f7li3hdgimc4upqvdvltpmeywckvens33umcxt424a"), + "datacap": MustParseCid("bafk2bzacebkxn52ttooaslkwncijk3bgd3tm2zw7vijdhwvg2cxnxbrzmmq5e"), + "eam": MustParseCid("bafk2bzaceczhgub5anrnaf7ol65mu54gsgwcj6c6m3yhet7rhxm2l6kz4s4ru"), + "ethaccount": MustParseCid("bafk2bzacealn5enbxyxbfs7gbsjbyma2zk3bcr7okvflxhpr753d4eh6ixooa"), + "evm": MustParseCid("bafk2bzacedljkrmazyewawpnddrkzrt55556374dw2pm2hokgkompgzw4vx5y"), + "init": MustParseCid("bafk2bzacec55gyyaqjrw7zughywocgwcjvv6k5fijjpjw4xgckuqz6pjtff5a"), + "multisig": MustParseCid("bafk2bzaceblozbdzybdivvjdiid4jwm2jc6x5a66sunh2vvwsqba6wzqmr7i6"), + "paymentchannel": MustParseCid("bafk2bzacealcyke5a6n24efs6qe4iikynpk2twqssyugy7jcyf6p6shgw2iwa"), + "placeholder": MustParseCid("bafk2bzaceaamp2a35vpfml4skap4dffklzae2urcm34mtwwce2lvhaons3a5y"), + "reward": MustParseCid("bafk2bzacebafzaqhwsm3nmsfwcd6ngvx6ev6zlcpyfljqh4kb77vok6opban6"), + "storagemarket": MustParseCid("bafk2bzacecrjfg4p7fxznsdkoobs4po2ve3ywixrirrk6netgxh63qqaefamg"), + "storageminer": MustParseCid("bafk2bzaceb3ctd4atxwhdkmlg4i63zxo5aopknlj7l5kaiqr22xpcmico6vg4"), + "storagepower": MustParseCid("bafk2bzacecvcix3ugopvby2vah5wwiu5cqjedwzwkanmr34kdoc4f3o6p7nsq"), + "system": MustParseCid("bafk2bzacedo2hfopt6gy52goj7fot5qwzhtnysmgo7h25crq4clpugkerjabk"), + "verifiedregistry": MustParseCid("bafk2bzacea7rfkjrixaidksnmjehglmavyt56nyeu3sfxu2e3dcpf62oab6tw"), + }, +}, { + Network: "mainnet", + Version: 8, + + ManifestCid: MustParseCid("bafy2bzacebogjbpiemi7npzxchgcjjki3tfxon4ims55obfyfleqntteljsea"), + Actors: map[string]cid.Cid{ + "account": MustParseCid("bafk2bzacedudbf7fc5va57t3tmo63snmt3en4iaidv4vo3qlyacbxaa6hlx6y"), + "cron": MustParseCid("bafk2bzacecqb3eolfurehny6yp7tgmapib4ocazo5ilkopjce2c7wc2bcec62"), + "init": MustParseCid("bafk2bzaceaipvjhoxmtofsnv3aj6gj5ida4afdrxa4ewku2hfipdlxpaektlw"), + "multisig": MustParseCid("bafk2bzacebhldfjuy4o5v7amrhp5p2gzv2qo5275jut4adnbyp56fxkwy5fag"), + "paymentchannel": MustParseCid("bafk2bzacebalad3f72wyk7qyilvfjijcwubdspytnyzlrhvn73254gqis44rq"), + "reward": MustParseCid("bafk2bzacecwzzxlgjiavnc3545cqqil3cmq4hgpvfp2crguxy2pl5ybusfsbe"), + "storagemarket": MustParseCid("bafk2bzacediohrxkp2fbsl4yj4jlupjdkgsiwqb4zuezvinhdo2j5hrxco62q"), + "storageminer": MustParseCid("bafk2bzacecgnynvd3tene3bvqoknuspit56canij5bpra6wl4mrq2mxxwriyu"), + "storagepower": MustParseCid("bafk2bzacebjvqva6ppvysn5xpmiqcdfelwbbcxmghx5ww6hr37cgred6dyrpm"), + "system": MustParseCid("bafk2bzacedwq5uppsw7vp55zpj7jdieizirmldceehu6wvombw3ixq2tcq57w"), + "verifiedregistry": MustParseCid("bafk2bzaceb3zbkjz3auizmoln2unmxep7dyfcmsre64vnqfhdyh7rkqfoxlw4"), + }, +}, { + Network: "mainnet", + Version: 9, + + ManifestCid: MustParseCid("bafy2bzaceb6j6666h36xnhksu3ww4kxb6e25niayfgkdnifaqi6m6ooc66i6i"), + Actors: map[string]cid.Cid{ + "account": MustParseCid("bafk2bzacect2p7urje3pylrrrjy3tngn6yaih4gtzauuatf2jllk3ksgfiw2y"), + "cron": MustParseCid("bafk2bzacebcec3lffmos3nawm5cvwehssxeqwxixoyyfvejy7viszzsxzyu26"), + "datacap": MustParseCid("bafk2bzacebb6uy2ys7tapekmtj7apnjg7oyj4ia5t7tlkvbmwtxwv74lb2pug"), + "init": MustParseCid("bafk2bzacebtdq4zyuxk2fzbdkva6kc4mx75mkbfmldplfntayhbl5wkqou33i"), + "multisig": MustParseCid("bafk2bzacec4va3nmugyqjqrs3lqyr2ij67jhjia5frvx7omnh2isha6abxzya"), + "paymentchannel": MustParseCid("bafk2bzacebhdvjbjcgupklddfavzef4e4gnkt3xk3rbmgfmk7xhecszhfxeds"), + "reward": MustParseCid("bafk2bzacebezgbbmcm2gbcqwisus5fjvpj7hhmu5ubd37phuku3hmkfulxm2o"), + "storagemarket": MustParseCid("bafk2bzacec3j7p6gklk64stax5px3xxd7hdtejaepnd4nw7s2adihde6emkcu"), + "storageminer": MustParseCid("bafk2bzacedyux5hlrildwutvvjdcsvjtwsoc5xnqdjl73ouiukgklekeuyfl4"), + "storagepower": MustParseCid("bafk2bzacedsetphfajgne4qy3vdrpyd6ekcmtfs2zkjut4r34cvnuoqemdrtw"), + "system": MustParseCid("bafk2bzaceagvlo2jtahj7dloshrmwfulrd6e2izqev32qm46eumf754weec6c"), + "verifiedregistry": MustParseCid("bafk2bzacecf3yodlyudzukumehbuabgqljyhjt5ifiv4vetcfohnvsxzynwga"), + }, +}, { + Network: "mainnet", + Version: 10, + + ManifestCid: MustParseCid("bafy2bzacecsuyf7mmvrhkx2evng5gnz5canlnz2fdlzu2lvcgptiq2pzuovos"), + Actors: map[string]cid.Cid{ + "account": MustParseCid("bafk2bzaceampw4romta75hyz5p4cqriypmpbgnkxncgxgqn6zptv5lsp2w2bo"), + "cron": MustParseCid("bafk2bzacedcbtsifegiu432m5tysjzkxkmoczxscb6hqpmrr6img7xzdbbs2g"), + "datacap": MustParseCid("bafk2bzacealj5uk7wixhvk7l5tnredtelralwnceafqq34nb2lbylhtuyo64u"), + "eam": MustParseCid("bafk2bzacedrpm5gbleh4xkyo2jvs7p5g6f34soa6dpv7ashcdgy676snsum6g"), + "ethaccount": MustParseCid("bafk2bzaceaqoc5zakbhjxn3jljc4lxnthllzunhdor7sxhwgmskvc6drqc3fa"), + "evm": MustParseCid("bafk2bzaceahmzdxhqsm7cu2mexusjp6frm7r4kdesvti3etv5evfqboos2j4g"), + "init": MustParseCid("bafk2bzaced2f5rhir3hbpqbz5ght7ohv2kgj42g5ykxrypuo2opxsup3ykwl6"), + "multisig": MustParseCid("bafk2bzaceduf3hayh63jnl4z2knxv7cnrdenoubni22fxersc4octlwpxpmy4"), + "paymentchannel": MustParseCid("bafk2bzaceartlg4mrbwgzcwric6mtvyawpbgx2xclo2vj27nna57nxynf3pgc"), + "placeholder": MustParseCid("bafk2bzacedfvut2myeleyq67fljcrw4kkmn5pb5dpyozovj7jpoez5irnc3ro"), + "reward": MustParseCid("bafk2bzacebnhtaejfjtzymyfmbdrfmo7vgj3zsof6zlucbmkhrvcuotw5dxpq"), + "storagemarket": MustParseCid("bafk2bzaceclejwjtpu2dhw3qbx6ow7b4pmhwa7ocrbbiqwp36sq5yeg6jz2bc"), + "storageminer": MustParseCid("bafk2bzaced4h7noksockro7glnssz2jnmo2rpzd7dvnmfs4p24zx3h6gtx47s"), + "storagepower": MustParseCid("bafk2bzacec4ay4crzo73ypmh7o3fjendhbqrxake46bprabw67fvwjz5q6ixq"), + "system": MustParseCid("bafk2bzacedakk5nofebyup4m7nvx6djksfwhnxzrfuq4oyemhpl4lllaikr64"), + "verifiedregistry": MustParseCid("bafk2bzacedfel6edzqpe5oujno7fog4i526go4dtcs6vwrdtbpy2xq6htvcg6"), + }, +}, { + Network: "mainnet", + Version: 11, + BundleGitTag: "v11.0.0", + ManifestCid: MustParseCid("bafy2bzacecnhaiwcrpyjvzl4uv4q3jzoif26okl3m66q3cijp3dfwlcxwztwo"), + Actors: map[string]cid.Cid{ + "account": MustParseCid("bafk2bzacealnlr7st6lkwoh6wxpf2hnrlex5sknaopgmkr2tuhg7vmbfy45so"), + "cron": MustParseCid("bafk2bzacebpewdvvgt6tk2o2u4rcovdgym67tadiis5usemlbejg7k3kt567o"), + "datacap": MustParseCid("bafk2bzacebslykoyrb2hm7aacjngqgd5n2wmeii2goadrs5zaya3pvdf6pdnq"), + "eam": MustParseCid("bafk2bzaceaelwt4yfsfvsu3pa3miwalsvy3cfkcjvmt4sqoeopsppnrmj2mf2"), + "ethaccount": MustParseCid("bafk2bzaceclkmc4yidxc6lgcjpfypbde2eddnevcveo4j5kmh4ek6inqysz2k"), + "evm": MustParseCid("bafk2bzacediwh6etwzwmb5pivtclpdplewdjzphouwqpppce6opisjv2fjqfe"), + "init": MustParseCid("bafk2bzaceckwf3w6n2nw6eh77ktmsxqgsvshonvgnyk5q5syyngtetxvasfxg"), + "multisig": MustParseCid("bafk2bzaceafajceqwg5ybiz7xw6rxammuirkgtuv625gzaehsqfprm4bazjmk"), + "paymentchannel": MustParseCid("bafk2bzaceb4e6cnsnviegmqvsmoxzncruvhra54piq7bwiqfqevle6oob2gvo"), + "placeholder": MustParseCid("bafk2bzacedfvut2myeleyq67fljcrw4kkmn5pb5dpyozovj7jpoez5irnc3ro"), + "reward": MustParseCid("bafk2bzacebwjw2vxkobs7r2kwjdqqb42h2kucyuk6flbnyzw4odg5s4mogamo"), + "storagemarket": MustParseCid("bafk2bzaceazu2j2zu4p24tr22btnqzkhzjvyjltlvsagaj6w3syevikeb5d7m"), + "storageminer": MustParseCid("bafk2bzacec24okjqrp7c7rj3hbrs5ez5apvwah2ruka6haesgfngf37mhk6us"), + "storagepower": MustParseCid("bafk2bzaceaxgloxuzg35vu7l7tohdgaq2frsfp4ejmuo7tkoxjp5zqrze6sf4"), + "system": MustParseCid("bafk2bzaced7npe5mt5nh72jxr2igi2sofoa7gedt4w6kueeke7i3xxugqpjfm"), + "verifiedregistry": MustParseCid("bafk2bzacedej3dnr62g2je2abmyjg3xqv4otvh6e26du5fcrhvw7zgcaaez3a"), + }, +}, { + Network: "testing", + Version: 8, + + ManifestCid: MustParseCid("bafy2bzacedkjpqx27wgsvfxzuxfvixuxtbpt2y6yo6igcasez6gqiowron776"), + Actors: map[string]cid.Cid{ + "account": MustParseCid("bafk2bzacebmfbtdj5vruje5auacrhhprcjdd6uclhukb7je7t2f6ozfcgqlu2"), + "cron": MustParseCid("bafk2bzacea4gwsbeux7z4yxvpkxpco77iyxijoyqaoikofrxdewunwh3unjem"), + "init": MustParseCid("bafk2bzacecqk6zlwein7tzy7yrrhtj4pzavrkofgpyxvvw5ktr3w4x4ml4lis"), + "multisig": MustParseCid("bafk2bzacea5zp2g6ag5qfuro7zw6kyku2swxs57wjxncaaxbih5iqflqy4ghm"), + "paymentchannel": MustParseCid("bafk2bzaced47dbtbygmfwnyfsp5iihzhhdmnkpuyc5nlnfgc4mkkvlsgvj2do"), + "reward": MustParseCid("bafk2bzacecmcagk32pzdzfg7piobzqhlgla37x3g7jjzyndlz7mqdno2zulfi"), + "storagemarket": MustParseCid("bafk2bzaceballmgd7puoixfwm65f5shi3kzreqdisowtsoufbvduwytydqotw"), + "storageminer": MustParseCid("bafk2bzacebucngwdhxtod2gvv52adtdssafyg43znsoy4omtfkkqe2hbhvxeu"), + "storagepower": MustParseCid("bafk2bzaceakxw5wx3rtqoarrdbzhmxkufg2kx7n34xotzxzacvvbe5iqggmsa"), + "system": MustParseCid("bafk2bzaced6kjkbv7lrb2qwq5we2hqaxc6ztch5p52g27qtjy45zdemsk4b7m"), + "verifiedregistry": MustParseCid("bafk2bzacectzxvtoselhnzsair5nv6k5vokvegnht6z2lfee4p3xexo4kg4m6"), + }, +}, { + Network: "testing", + Version: 9, + + ManifestCid: MustParseCid("bafy2bzacecnnrmekqw2xvud46g3vo6x26cogh3ydgljqajlxqxzzbuxsjlwjm"), + Actors: map[string]cid.Cid{ + "account": MustParseCid("bafk2bzaceaiebfiuu76zoywzltelio2zuvsavirka27ur6kspn7scvcl5cuiy"), + "cron": MustParseCid("bafk2bzacecla36w3tbwap5jgdtooxsud25mdpc75kgtjs34mi4xhwygph2gki"), + "datacap": MustParseCid("bafk2bzaced5h3ct6i7oqpyimkj3hwdywmux5tslu5vs2ywbzruqmxjtqczygs"), + "init": MustParseCid("bafk2bzaceauxqpspnvui7dryuvfgzoogatbkbahp4ovaih734blwi4bassnlm"), + "multisig": MustParseCid("bafk2bzaceddfagxfpsihjxq7yt4ditv2tcoou5w4hzbsapadlw3v44cxfcqpi"), + "paymentchannel": MustParseCid("bafk2bzaced4nc4ofrbqevpwrt7fnf3beshi5ccrecq3zojt2sxgrkz7ebnbh4"), + "reward": MustParseCid("bafk2bzacedxleepeg4ei3jnayzcfz6shi25rrvoyhr6fxmkdezq4owrazi7rq"), + "storagemarket": MustParseCid("bafk2bzaceakqcjpppg3exrr7dru7jglvno2xyw4hsuebxay4lvrzvmwmv5kvu"), + "storageminer": MustParseCid("bafk2bzacealfvphicwnysmmyyerseppyvydy2reisvbft46vdprp2lnfvlgqc"), + "storagepower": MustParseCid("bafk2bzaceageil5b5mr5uwo6vqs4nnnmpiwe3fkjffzyngcicuu7gruuwapjm"), + "system": MustParseCid("bafk2bzacedo4pu3iwx2gu72hinsstpiokhl5iicnb3rumzffsnhy7zhmnxhyy"), + "verifiedregistry": MustParseCid("bafk2bzaceatmqip2o3ausbntvdhj7yemu6hb3b5yqv6hm42gylbbmz7geocpm"), + }, +}, { + Network: "testing", + Version: 10, + + ManifestCid: MustParseCid("bafy2bzacebsp3bkxwsijenqeimhvhtg52d6o76hn6qhzxveqfq7d5hdd5l2ee"), + Actors: map[string]cid.Cid{ + "account": MustParseCid("bafk2bzaceazxb6p2xg6caivmie6k2bvutyesngwyvhwv4eemwu7ia4vnqkcuy"), + "cron": MustParseCid("bafk2bzaceax6ym73boyl5zdpbcr6zmbajzylmcdvlapz5zcqgzcshakz44jbq"), + "datacap": MustParseCid("bafk2bzacea63x3v6lvtb4ast5uq3nhrpokvylymvezyr5xyjl6vtlfwkuw6qo"), + "eam": MustParseCid("bafk2bzacebhualcn7fofyqr6lhrel32ud23hcwzeenfqu3rrn5nmt6gugqgo6"), + "ethaccount": MustParseCid("bafk2bzacecgft7e3v4kbpb3tlt5s6hng74ptu3ggcdi4wmt5p4vr6qkmkw2zc"), + "evm": MustParseCid("bafk2bzaceaoqvbqetgicqpvwvcnpjx5aa74kwlhq3u7mwv4yseszxkimwz5pk"), + "init": MustParseCid("bafk2bzaceapmoyg2qppzle24t25ncyycn2uwhnw6crqkqlokkbc7w4mn74wko"), + "multisig": MustParseCid("bafk2bzacecn3dlepgaps3h6iwlq65dx6zyrbfi4pmgdqxphb5idubb6ibflwe"), + "paymentchannel": MustParseCid("bafk2bzaceaanxurr2k3ueolwcnminmdfp3tyxtntqg5fou37smeulb5dxqjzk"), + "placeholder": MustParseCid("bafk2bzacedfvut2myeleyq67fljcrw4kkmn5pb5dpyozovj7jpoez5irnc3ro"), + "reward": MustParseCid("bafk2bzacedujdvwk4omjexdnmh2qrkqbw27v4c2g3krajhtzyfzart36bimum"), + "storagemarket": MustParseCid("bafk2bzacecdbjjxvdtltobiu7thwyyr2puunoz3q4vyfnhhxl2sbp4ovwq37s"), + "storageminer": MustParseCid("bafk2bzacebo5q7jrf4qjrhtotwt5ouzlygvml4bzofs2egdnbxyfmuo7tro6c"), + "storagepower": MustParseCid("bafk2bzacebt2ipqnorxbzncwjadkulip6blzksmwd4mmyrfjsmjyf55itra2k"), + "system": MustParseCid("bafk2bzacecf2jimdz7knhngs64ximfz3eaud6s3kiunmkybgrkupdjyo2dw7o"), + "verifiedregistry": MustParseCid("bafk2bzacecdmek2htsgcyoyl35glakyab66cojqo2y335njnm7krleb6yfbps"), + }, +}, { + Network: "testing", + Version: 11, + BundleGitTag: "v11.0.0", + ManifestCid: MustParseCid("bafy2bzacea2vxre32tg3xhpejrktiuzx4d3pcoe7yyazgscfibmegmchr6n42"), + Actors: map[string]cid.Cid{ + "account": MustParseCid("bafk2bzaceccerssb3tgel6ukdghlwvs7dxsolj4fpkgn7dh7owzwapqb6ejpw"), + "cron": MustParseCid("bafk2bzacebtfl6fczxnitrqqjhyefskf3asyn3gzuvqcddieoqfsaddk5fd4q"), + "datacap": MustParseCid("bafk2bzacediikc55y6uzmnhucf4mik6rqpjulwvgp5gdibogxjhgbvusmzff2"), + "eam": MustParseCid("bafk2bzaceazqi5ezossp6kvqogaaba6hxlfarqgniktmb7iy5qonha3eflz6m"), + "ethaccount": MustParseCid("bafk2bzaceb77ospgfqqmf67v23wkyeg7lr2mu53ybaacu3bslx7s7nhttdueo"), + "evm": MustParseCid("bafk2bzacedvgt7mv22hux4vrnklylq7qmw43kfrqwam6wdsfzkdnaewr33qbu"), + "init": MustParseCid("bafk2bzacealzb3nk2oypway5ubz3hs5py5ok5tuw545454vg4d3mwbslef4js"), + "multisig": MustParseCid("bafk2bzacec45ppn4hrwizmopp2v2atkxw35tb6yem6uqhqilrv7aiaknnnxmu"), + "paymentchannel": MustParseCid("bafk2bzaceajbr3t6cngzh3katqteflbcrtwtdgbthnlfemon5tg6rytf2uonw"), + "placeholder": MustParseCid("bafk2bzacedfvut2myeleyq67fljcrw4kkmn5pb5dpyozovj7jpoez5irnc3ro"), + "reward": MustParseCid("bafk2bzacea7ycf53kbq4robcuh3ziy7qwwhaqamc5krn3lugypgpxhlewdaiq"), + "storagemarket": MustParseCid("bafk2bzacedskmbcpaeb6bezs32szh52jrukvihobluadauayroo5gzrt32tkm"), + "storageminer": MustParseCid("bafk2bzaced3yg5lctmswnbkxyd6cleg3llyux7fu2vbddyd2ho36fpym423mq"), + "storagepower": MustParseCid("bafk2bzacebvpdf372fzxgixztbz2r7ayxyvx7jmdxwlfuqt2cq7tnqgie3klw"), + "system": MustParseCid("bafk2bzaceaatvscbnkv36ixhtt2zel4er5oskxevgumh5gegqkv7uzah36f24"), + "verifiedregistry": MustParseCid("bafk2bzacebp2r56wxadvfzpfbmqwfi3dlnwpmoc5u4tau2hfftbkuafkhye64"), + }, +}, { + Network: "testing-fake-proofs", + Version: 8, + + ManifestCid: MustParseCid("bafy2bzacecd3lb5v6tzjylnhnrhexslssyaozy6hogzgpkhztoe76exbrgrug"), + Actors: map[string]cid.Cid{ + "account": MustParseCid("bafk2bzacebmfbtdj5vruje5auacrhhprcjdd6uclhukb7je7t2f6ozfcgqlu2"), + "cron": MustParseCid("bafk2bzacea4gwsbeux7z4yxvpkxpco77iyxijoyqaoikofrxdewunwh3unjem"), + "init": MustParseCid("bafk2bzacebwkqd6e7gdphfzw2kdmbokdh2bly6fvzgfopxzy7quq4l67gmkks"), + "multisig": MustParseCid("bafk2bzacea5zp2g6ag5qfuro7zw6kyku2swxs57wjxncaaxbih5iqflqy4ghm"), + "paymentchannel": MustParseCid("bafk2bzaced47dbtbygmfwnyfsp5iihzhhdmnkpuyc5nlnfgc4mkkvlsgvj2do"), + "reward": MustParseCid("bafk2bzacecmcagk32pzdzfg7piobzqhlgla37x3g7jjzyndlz7mqdno2zulfi"), + "storagemarket": MustParseCid("bafk2bzacecxqgajcaednamgolc6wc3lzbjc6tz5alfrbwqez2y3c372vts6dg"), + "storageminer": MustParseCid("bafk2bzaceaqwxllfycpq6decpsnkqjdeycpysh5acubonjae7u3wciydlkvki"), + "storagepower": MustParseCid("bafk2bzaceddmeolsokbxgcr25cuf2skrobtmmoof3dmqfpcfp33lmw63oikvm"), + "system": MustParseCid("bafk2bzaced6kjkbv7lrb2qwq5we2hqaxc6ztch5p52g27qtjy45zdemsk4b7m"), + "verifiedregistry": MustParseCid("bafk2bzacectzxvtoselhnzsair5nv6k5vokvegnht6z2lfee4p3xexo4kg4m6"), + }, +}, { + Network: "testing-fake-proofs", + Version: 9, + + ManifestCid: MustParseCid("bafy2bzacecql2gj2tri4fnbznmldue73qzt6zszvugw4exd64mwb52zrhv7k2"), + Actors: map[string]cid.Cid{ + "account": MustParseCid("bafk2bzaceaiebfiuu76zoywzltelio2zuvsavirka27ur6kspn7scvcl5cuiy"), + "cron": MustParseCid("bafk2bzacecla36w3tbwap5jgdtooxsud25mdpc75kgtjs34mi4xhwygph2gki"), + "datacap": MustParseCid("bafk2bzaced5h3ct6i7oqpyimkj3hwdywmux5tslu5vs2ywbzruqmxjtqczygs"), + "init": MustParseCid("bafk2bzaceauxqpspnvui7dryuvfgzoogatbkbahp4ovaih734blwi4bassnlm"), + "multisig": MustParseCid("bafk2bzaceddfagxfpsihjxq7yt4ditv2tcoou5w4hzbsapadlw3v44cxfcqpi"), + "paymentchannel": MustParseCid("bafk2bzaced4nc4ofrbqevpwrt7fnf3beshi5ccrecq3zojt2sxgrkz7ebnbh4"), + "reward": MustParseCid("bafk2bzacedxleepeg4ei3jnayzcfz6shi25rrvoyhr6fxmkdezq4owrazi7rq"), + "storagemarket": MustParseCid("bafk2bzaceakqcjpppg3exrr7dru7jglvno2xyw4hsuebxay4lvrzvmwmv5kvu"), + "storageminer": MustParseCid("bafk2bzaceab3cjrwwwfemyc5lw73w6tibpgxtx3wuzjhami6tvhcvetygdm7m"), + "storagepower": MustParseCid("bafk2bzaceafemwhsy3e7ueqsrn3f7n53vdqkvfbig3hgbw7eohsefnfvgq7yc"), + "system": MustParseCid("bafk2bzacedo4pu3iwx2gu72hinsstpiokhl5iicnb3rumzffsnhy7zhmnxhyy"), + "verifiedregistry": MustParseCid("bafk2bzaceatmqip2o3ausbntvdhj7yemu6hb3b5yqv6hm42gylbbmz7geocpm"), + }, +}, { + Network: "testing-fake-proofs", + Version: 10, + + ManifestCid: MustParseCid("bafy2bzacedwap2uuii4luljckrnb4vkur2unb6fyinn7xjie6xlva2wmlygj2"), + Actors: map[string]cid.Cid{ + "account": MustParseCid("bafk2bzaceazxb6p2xg6caivmie6k2bvutyesngwyvhwv4eemwu7ia4vnqkcuy"), + "cron": MustParseCid("bafk2bzaceax6ym73boyl5zdpbcr6zmbajzylmcdvlapz5zcqgzcshakz44jbq"), + "datacap": MustParseCid("bafk2bzacea63x3v6lvtb4ast5uq3nhrpokvylymvezyr5xyjl6vtlfwkuw6qo"), + "eam": MustParseCid("bafk2bzacebhualcn7fofyqr6lhrel32ud23hcwzeenfqu3rrn5nmt6gugqgo6"), + "ethaccount": MustParseCid("bafk2bzacecgft7e3v4kbpb3tlt5s6hng74ptu3ggcdi4wmt5p4vr6qkmkw2zc"), + "evm": MustParseCid("bafk2bzaceaoqvbqetgicqpvwvcnpjx5aa74kwlhq3u7mwv4yseszxkimwz5pk"), + "init": MustParseCid("bafk2bzaceapmoyg2qppzle24t25ncyycn2uwhnw6crqkqlokkbc7w4mn74wko"), + "multisig": MustParseCid("bafk2bzacecn3dlepgaps3h6iwlq65dx6zyrbfi4pmgdqxphb5idubb6ibflwe"), + "paymentchannel": MustParseCid("bafk2bzaceaanxurr2k3ueolwcnminmdfp3tyxtntqg5fou37smeulb5dxqjzk"), + "placeholder": MustParseCid("bafk2bzacedfvut2myeleyq67fljcrw4kkmn5pb5dpyozovj7jpoez5irnc3ro"), + "reward": MustParseCid("bafk2bzacedujdvwk4omjexdnmh2qrkqbw27v4c2g3krajhtzyfzart36bimum"), + "storagemarket": MustParseCid("bafk2bzacecdbjjxvdtltobiu7thwyyr2puunoz3q4vyfnhhxl2sbp4ovwq37s"), + "storageminer": MustParseCid("bafk2bzacedc5klueery4fn2voso4u76rgo54uctsculesdbxxbeh6rgp2q4te"), + "storagepower": MustParseCid("bafk2bzacecuz2h2renlfio4xkyrvvro7nwidf7utpjy3oizk2xuszoz3gmea6"), + "system": MustParseCid("bafk2bzacecf2jimdz7knhngs64ximfz3eaud6s3kiunmkybgrkupdjyo2dw7o"), + "verifiedregistry": MustParseCid("bafk2bzacecdmek2htsgcyoyl35glakyab66cojqo2y335njnm7krleb6yfbps"), + }, +}, { + Network: "testing-fake-proofs", + Version: 11, + BundleGitTag: "v11.0.0", + ManifestCid: MustParseCid("bafy2bzacecojemqglhzzhjnhgtrcbsgkyv67ziytvtbhwlr4ym4oxqofv7zui"), + Actors: map[string]cid.Cid{ + "account": MustParseCid("bafk2bzaceccerssb3tgel6ukdghlwvs7dxsolj4fpkgn7dh7owzwapqb6ejpw"), + "cron": MustParseCid("bafk2bzacebtfl6fczxnitrqqjhyefskf3asyn3gzuvqcddieoqfsaddk5fd4q"), + "datacap": MustParseCid("bafk2bzacediikc55y6uzmnhucf4mik6rqpjulwvgp5gdibogxjhgbvusmzff2"), + "eam": MustParseCid("bafk2bzaceazqi5ezossp6kvqogaaba6hxlfarqgniktmb7iy5qonha3eflz6m"), + "ethaccount": MustParseCid("bafk2bzaceb77ospgfqqmf67v23wkyeg7lr2mu53ybaacu3bslx7s7nhttdueo"), + "evm": MustParseCid("bafk2bzacedvgt7mv22hux4vrnklylq7qmw43kfrqwam6wdsfzkdnaewr33qbu"), + "init": MustParseCid("bafk2bzacealzb3nk2oypway5ubz3hs5py5ok5tuw545454vg4d3mwbslef4js"), + "multisig": MustParseCid("bafk2bzacec45ppn4hrwizmopp2v2atkxw35tb6yem6uqhqilrv7aiaknnnxmu"), + "paymentchannel": MustParseCid("bafk2bzaceajbr3t6cngzh3katqteflbcrtwtdgbthnlfemon5tg6rytf2uonw"), + "placeholder": MustParseCid("bafk2bzacedfvut2myeleyq67fljcrw4kkmn5pb5dpyozovj7jpoez5irnc3ro"), + "reward": MustParseCid("bafk2bzacea7ycf53kbq4robcuh3ziy7qwwhaqamc5krn3lugypgpxhlewdaiq"), + "storagemarket": MustParseCid("bafk2bzacedskmbcpaeb6bezs32szh52jrukvihobluadauayroo5gzrt32tkm"), + "storageminer": MustParseCid("bafk2bzacebqeztpa5exztccqjwqhan5droiy7ga6zekm6f2gzxoe655vneczm"), + "storagepower": MustParseCid("bafk2bzaceb2tlyuwxncdxsh3hc4fwcjnpxaijkiv54ustwdjbrqabxdsc27km"), + "system": MustParseCid("bafk2bzaceaatvscbnkv36ixhtt2zel4er5oskxevgumh5gegqkv7uzah36f24"), + "verifiedregistry": MustParseCid("bafk2bzacebp2r56wxadvfzpfbmqwfi3dlnwpmoc5u4tau2hfftbkuafkhye64"), + }, +}} diff --git a/build/builtin_actors_test.go b/build/builtin_actors_test.go new file mode 100644 index 000000000..bb133bdab --- /dev/null +++ b/build/builtin_actors_test.go @@ -0,0 +1,45 @@ +package build_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors" +) + +// Test that the embedded metadata is correct. +func TestEmbeddedMetadata(t *testing.T) { + metadata, err := build.ReadEmbeddedBuiltinActorsMetadata() + require.NoError(t, err) + + for i, v1 := range metadata { + v2 := build.EmbeddedBuiltinActorsMetadata[i] + require.Equal(t, v1.Network, v2.Network) + require.Equal(t, v1.Version, v2.Version) + require.Equal(t, v1.ManifestCid, v2.ManifestCid) + require.Equal(t, v1.Actors, v2.Actors) + } +} + +// Test that we're registering the manifest correctly. +func TestRegistration(t *testing.T) { + for _, av := range []actorstypes.Version{actorstypes.Version8, actorstypes.Version9} { + manifestCid, found := actors.GetManifest(av) + require.True(t, found) + require.True(t, manifestCid.Defined()) + + for _, key := range manifest.GetBuiltinActorsKeys(av) { + actorCid, found := actors.GetActorCodeID(av, key) + require.True(t, found) + name, version, found := actors.GetActorMetaByCode(actorCid) + require.True(t, found) + require.Equal(t, av, version) + require.Equal(t, key, name) + } + } +} diff --git a/build/clock.go b/build/clock.go new file mode 100644 index 000000000..a3943897d --- /dev/null +++ b/build/clock.go @@ -0,0 +1,10 @@ +package build + +import "github.com/raulk/clock" + +// Clock is the global clock for the system. In standard builds, +// we use a real-time clock, which maps to the `time` package. +// +// Tests that need control of time can replace this variable with +// clock.NewMock(). Always use real time for socket/stream deadlines. +var Clock = clock.New() diff --git a/build/drand.go b/build/drand.go new file mode 100644 index 000000000..3b976ac92 --- /dev/null +++ b/build/drand.go @@ -0,0 +1,74 @@ +package build + +import ( + "sort" + + "github.com/filecoin-project/lotus/node/modules/dtypes" +) + +type DrandEnum int + +func DrandConfigSchedule() dtypes.DrandSchedule { + out := dtypes.DrandSchedule{} + for start, config := range DrandSchedule { + out = append(out, dtypes.DrandPoint{Start: start, Config: DrandConfigs[config]}) + } + + sort.Slice(out, func(i, j int) bool { + return out[i].Start < out[j].Start + }) + + return out +} + +const ( + DrandMainnet DrandEnum = iota + 1 + DrandTestnet + DrandDevnet + DrandLocalnet + DrandIncentinet +) + +var DrandConfigs = map[DrandEnum]dtypes.DrandConfig{ + DrandMainnet: { + Servers: []string{ + "https://api.drand.sh", + "https://api2.drand.sh", + "https://api3.drand.sh", + "https://drand.cloudflare.com", + }, + Relays: []string{ + "/dnsaddr/api.drand.sh/", + "/dnsaddr/api2.drand.sh/", + "/dnsaddr/api3.drand.sh/", + }, + ChainInfoJSON: `{"public_key":"868f005eb8e6e4ca0a47c8a77ceaa5309a47978a7c71bc5cce96366b5d7a569937c529eeda66c7293784a9402801af31","period":30,"genesis_time":1595431050,"hash":"8990e7a9aaed2ffed73dbd7092123d6f289930540d7651336225dc172e51b2ce","groupHash":"176f93498eac9ca337150b46d21dd58673ea4e3581185f869672e59fa4cb390a"}`, + }, + DrandTestnet: { + Servers: []string{ + "https://pl-eu.testnet.drand.sh", + "https://pl-us.testnet.drand.sh", + "https://pl-sin.testnet.drand.sh", + }, + Relays: []string{ + "/dnsaddr/pl-eu.testnet.drand.sh/", + "/dnsaddr/pl-us.testnet.drand.sh/", + "/dnsaddr/pl-sin.testnet.drand.sh/", + }, + ChainInfoJSON: `{"public_key":"922a2e93828ff83345bae533f5172669a26c02dc76d6bf59c80892e12ab1455c229211886f35bb56af6d5bea981024df","period":25,"genesis_time":1590445175,"hash":"84b2234fb34e835dccd048255d7ad3194b81af7d978c3bf157e3469592ae4e02","groupHash":"4dd408e5fdff9323c76a9b6f087ba8fdc5a6da907bd9217d9d10f2287d081957"}`, + }, + DrandDevnet: { + Servers: []string{ + "https://dev1.drand.sh", + "https://dev2.drand.sh", + }, + Relays: []string{ + "/dnsaddr/dev1.drand.sh/", + "/dnsaddr/dev2.drand.sh/", + }, + ChainInfoJSON: `{"public_key":"8cda589f88914aa728fd183f383980b35789ce81b274e5daee1f338b77d02566ef4d3fb0098af1f844f10f9c803c1827","period":25,"genesis_time":1595348225,"hash":"e73b7dc3c4f6a236378220c0dd6aa110eb16eed26c11259606e07ee122838d4f","groupHash":"567d4785122a5a3e75a9bc9911d7ea807dd85ff76b78dc4ff06b075712898607"}`, + }, + DrandIncentinet: { + ChainInfoJSON: `{"public_key":"8cad0c72c606ab27d36ee06de1d5b2db1faf92e447025ca37575ab3a8aac2eaae83192f846fc9e158bc738423753d000","period":30,"genesis_time":1595873820,"hash":"80c8b872c714f4c00fdd3daa465d5514049f457f01f85a4caf68cdcd394ba039","groupHash":"d9406aaed487f7af71851b4399448e311f2328923d454e971536c05398ce2d9b"}`, + }, +} diff --git a/build/flags.go b/build/flags.go new file mode 100644 index 000000000..33e9f6ede --- /dev/null +++ b/build/flags.go @@ -0,0 +1,15 @@ +package build + +// DisableBuiltinAssets disables the resolution of go.rice boxes that store +// built-in assets, such as proof parameters, bootstrap peers, genesis blocks, +// etc. +// +// When this value is set to true, it is expected that the user will +// provide any such configurations through the Lotus API itself. +// +// This is useful when you're using Lotus as a library, such as to orchestrate +// test scenarios, or for other purposes where you don't need to use the +// defaults shipped with the binary. +// +// For this flag to be effective, it must be enabled _before_ instantiating Lotus. +var DisableBuiltinAssets = false diff --git a/build/genesis.go b/build/genesis.go index dc4ded273..6d94b38cf 100644 --- a/build/genesis.go +++ b/build/genesis.go @@ -1,23 +1,23 @@ package build import ( - rice "github.com/GeertJohan/go.rice" + "embed" + "path" + logging "github.com/ipfs/go-log/v2" ) // moved from now-defunct build/paramfetch.go var log = logging.Logger("build") +//go:embed genesis +var genesisfs embed.FS + func MaybeGenesis() []byte { - builtinGen, err := rice.FindBox("genesis") + genBytes, err := genesisfs.ReadFile(path.Join("genesis", GenesisFile)) if err != nil { log.Warnf("loading built-in genesis: %s", err) return nil } - genBytes, err := builtinGen.Bytes("devnet.car") - if err != nil { - log.Warnf("loading built-in genesis: %s", err) - } - return genBytes } diff --git a/build/genesis/butterflynet.car b/build/genesis/butterflynet.car new file mode 100644 index 000000000..30ec609ec Binary files /dev/null and b/build/genesis/butterflynet.car differ diff --git a/build/genesis/calibnet.car b/build/genesis/calibnet.car new file mode 100644 index 000000000..775cdf790 Binary files /dev/null and b/build/genesis/calibnet.car differ diff --git a/build/genesis/devnet.car b/build/genesis/devnet.car deleted file mode 100644 index 5f13410a6..000000000 Binary files a/build/genesis/devnet.car and /dev/null differ diff --git a/build/genesis/interopnet.car b/build/genesis/interopnet.car new file mode 100644 index 000000000..2dadae61e Binary files /dev/null and b/build/genesis/interopnet.car differ diff --git a/build/genesis/mainnet.car b/build/genesis/mainnet.car new file mode 100644 index 000000000..f1b3f342a Binary files /dev/null and b/build/genesis/mainnet.car differ diff --git a/build/isnearupgrade.go b/build/isnearupgrade.go new file mode 100644 index 000000000..4273f0e9e --- /dev/null +++ b/build/isnearupgrade.go @@ -0,0 +1,9 @@ +package build + +import ( + "github.com/filecoin-project/go-state-types/abi" +) + +func IsNearUpgrade(epoch, upgradeEpoch abi.ChainEpoch) bool { + return epoch > upgradeEpoch-Finality && epoch < upgradeEpoch+Finality +} diff --git a/build/limits.go b/build/limits.go new file mode 100644 index 000000000..93d56577c --- /dev/null +++ b/build/limits.go @@ -0,0 +1,6 @@ +package build + +var ( + DefaultFDLimit uint64 = 16 << 10 + MinerFDLimit uint64 = 100_000 +) diff --git a/build/openrpc.go b/build/openrpc.go new file mode 100644 index 000000000..a50d6f51e --- /dev/null +++ b/build/openrpc.go @@ -0,0 +1,62 @@ +package build + +import ( + "bytes" + "compress/gzip" + "embed" + "encoding/json" + + apitypes "github.com/filecoin-project/lotus/api/types" +) + +//go:embed openrpc +var openrpcfs embed.FS + +func mustReadGzippedOpenRPCDocument(data []byte) apitypes.OpenRPCDocument { + zr, err := gzip.NewReader(bytes.NewBuffer(data)) + if err != nil { + log.Fatal(err) + } + m := apitypes.OpenRPCDocument{} + err = json.NewDecoder(zr).Decode(&m) + if err != nil { + log.Fatal(err) + } + err = zr.Close() + if err != nil { + log.Fatal(err) + } + return m +} + +func OpenRPCDiscoverJSON_Full() apitypes.OpenRPCDocument { + data, err := openrpcfs.ReadFile("openrpc/full.json.gz") + if err != nil { + panic(err) + } + return mustReadGzippedOpenRPCDocument(data) +} + +func OpenRPCDiscoverJSON_Miner() apitypes.OpenRPCDocument { + data, err := openrpcfs.ReadFile("openrpc/miner.json.gz") + if err != nil { + panic(err) + } + return mustReadGzippedOpenRPCDocument(data) +} + +func OpenRPCDiscoverJSON_Worker() apitypes.OpenRPCDocument { + data, err := openrpcfs.ReadFile("openrpc/worker.json.gz") + if err != nil { + panic(err) + } + return mustReadGzippedOpenRPCDocument(data) +} + +func OpenRPCDiscoverJSON_Gateway() apitypes.OpenRPCDocument { + data, err := openrpcfs.ReadFile("openrpc/gateway.json.gz") + if err != nil { + panic(err) + } + return mustReadGzippedOpenRPCDocument(data) +} diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz new file mode 100644 index 000000000..8e12e7ed3 Binary files /dev/null and b/build/openrpc/full.json.gz differ diff --git a/build/openrpc/gateway.json.gz b/build/openrpc/gateway.json.gz new file mode 100644 index 000000000..2fa38e648 Binary files /dev/null and b/build/openrpc/gateway.json.gz differ diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz new file mode 100644 index 000000000..164434f3b Binary files /dev/null and b/build/openrpc/miner.json.gz differ diff --git a/build/openrpc/worker.json.gz b/build/openrpc/worker.json.gz new file mode 100644 index 000000000..f56329b05 Binary files /dev/null and b/build/openrpc/worker.json.gz differ diff --git a/build/openrpc_test.go b/build/openrpc_test.go new file mode 100644 index 000000000..05119fd5d --- /dev/null +++ b/build/openrpc_test.go @@ -0,0 +1,26 @@ +// stm: #unit +package build + +import ( + "testing" + + apitypes "github.com/filecoin-project/lotus/api/types" +) + +func TestOpenRPCDiscoverJSON_Version(t *testing.T) { + //stm: @OTHER_IMPLEMENTATION_OPENRPC_VERSION_001 + // openRPCDocVersion is the current OpenRPC version of the API docs. + openRPCDocVersion := "1.2.6" + + for i, docFn := range []func() apitypes.OpenRPCDocument{ + OpenRPCDiscoverJSON_Full, + OpenRPCDiscoverJSON_Miner, + OpenRPCDiscoverJSON_Worker, + OpenRPCDiscoverJSON_Gateway, + } { + doc := docFn() + if got, ok := doc["openrpc"]; !ok || got != openRPCDocVersion { + t.Fatalf("case: %d, want: %s, got: %v, doc: %v", i, openRPCDocVersion, got, doc) + } + } +} diff --git a/build/panic_reporter.go b/build/panic_reporter.go new file mode 100644 index 000000000..617d619eb --- /dev/null +++ b/build/panic_reporter.go @@ -0,0 +1,183 @@ +package build + +import ( + "fmt" + "io" + "os" + "path/filepath" + "runtime/debug" + "runtime/pprof" + "strconv" + "strings" + "time" + + "github.com/icza/backscanner" + logging "github.com/ipfs/go-log/v2" +) + +var ( + panicLog = logging.Logger("panic-reporter") + defaultJournalTail = 500 +) + +// PanicReportingPath is the name of the subdir created within the repoPath +// path provided to GeneratePanicReport +var PanicReportingPath = "panic-reports" + +// PanicReportJournalTail is the number of lines captured from the end of +// the lotus journal to be included in the panic report. +var PanicReportJournalTail = defaultJournalTail + +// GeneratePanicReport produces a timestamped dump of the application state +// for inspection and debugging purposes. Call this function from any place +// where a panic or severe error needs to be examined. `persistPath` is the +// path where the reports should be saved. `repoPath` is the path where the +// journal should be read from. `label` is an optional string to include +// next to the report timestamp. +func GeneratePanicReport(persistPath, repoPath, label string) { + // make sure we always dump the latest logs on the way out + // especially since we're probably panicking + defer panicLog.Sync() //nolint:errcheck + + if persistPath == "" && repoPath == "" { + panicLog.Warn("missing persist and repo paths, aborting panic report creation") + return + } + + reportPath := filepath.Join(repoPath, PanicReportingPath, generateReportName(label)) + if persistPath != "" { + reportPath = filepath.Join(persistPath, generateReportName(label)) + } + panicLog.Warnf("generating panic report at %s", reportPath) + + tl := os.Getenv("LOTUS_PANIC_JOURNAL_LOOKBACK") + if tl != "" && PanicReportJournalTail == defaultJournalTail { + i, err := strconv.Atoi(tl) + if err == nil { + PanicReportJournalTail = i + } + } + + err := os.MkdirAll(reportPath, 0755) + if err != nil { + panicLog.Error(err.Error()) + return + } + + writeAppVersion(filepath.Join(reportPath, "version")) + writeStackTrace(filepath.Join(reportPath, "stacktrace.dump")) + writeProfile("goroutines", filepath.Join(reportPath, "goroutines.pprof.gz")) + writeProfile("heap", filepath.Join(reportPath, "heap.pprof.gz")) + writeJournalTail(PanicReportJournalTail, repoPath, filepath.Join(reportPath, "journal.ndjson")) +} + +func writeAppVersion(file string) { + f, err := os.Create(file) + if err != nil { + panicLog.Error(err.Error()) + } + defer f.Close() //nolint:errcheck + + versionString := []byte(BuildVersion + BuildTypeString() + CurrentCommit + "\n") + if _, err := f.Write(versionString); err != nil { + panicLog.Error(err.Error()) + } +} + +func writeStackTrace(file string) { + f, err := os.Create(file) + if err != nil { + panicLog.Error(err.Error()) + } + defer f.Close() //nolint:errcheck + + if _, err := f.Write(debug.Stack()); err != nil { + panicLog.Error(err.Error()) + } + +} + +func writeProfile(profileType string, file string) { + p := pprof.Lookup(profileType) + if p == nil { + panicLog.Warnf("%s profile not available", profileType) + return + } + f, err := os.Create(file) + if err != nil { + panicLog.Error(err.Error()) + return + } + defer f.Close() //nolint:errcheck + + if err := p.WriteTo(f, 0); err != nil { + panicLog.Error(err.Error()) + } +} + +func writeJournalTail(tailLen int, repoPath, file string) { + if repoPath == "" { + panicLog.Warn("repo path is empty, aborting copy of journal log") + return + } + + f, err := os.Create(file) + if err != nil { + panicLog.Error(err.Error()) + return + } + defer f.Close() //nolint:errcheck + + jPath, err := getLatestJournalFilePath(repoPath) + if err != nil { + panicLog.Warnf("failed getting latest journal: %s", err.Error()) + return + } + j, err := os.OpenFile(jPath, os.O_RDONLY, 0400) + if err != nil { + panicLog.Error(err.Error()) + return + } + js, err := j.Stat() + if err != nil { + panicLog.Error(err.Error()) + return + } + jScan := backscanner.New(j, int(js.Size())) + linesWritten := 0 + for { + if linesWritten > tailLen { + break + } + line, _, err := jScan.LineBytes() + if err != nil { + if err != io.EOF { + panicLog.Error(err.Error()) + } + break + } + if _, err := f.Write(line); err != nil { + panicLog.Error(err.Error()) + break + } + if _, err := f.Write([]byte("\n")); err != nil { + panicLog.Error(err.Error()) + break + } + linesWritten++ + } +} + +func getLatestJournalFilePath(repoPath string) (string, error) { + journalPath := filepath.Join(repoPath, "journal") + entries, err := os.ReadDir(journalPath) + if err != nil { + return "", err + } + return filepath.Join(journalPath, entries[len(entries)-1].Name()), nil +} + +func generateReportName(label string) string { + label = strings.ReplaceAll(label, " ", "") + return fmt.Sprintf("report_%s_%s", label, time.Now().Format("2006-01-02T150405")) +} diff --git a/build/parameters.go b/build/parameters.go index b7fac93d1..9e60f12a6 100644 --- a/build/parameters.go +++ b/build/parameters.go @@ -1,7 +1,19 @@ package build -import rice "github.com/GeertJohan/go.rice" +import ( + _ "embed" +) -func ParametersJson() []byte { - return rice.MustFindBox("proof-params").MustBytes("parameters.json") +//go:embed proof-params/parameters.json +var params []byte + +//go:embed proof-params/srs-inner-product.json +var srs []byte + +func ParametersJSON() []byte { + return params +} + +func SrsJSON() []byte { + return srs } diff --git a/build/params_2k.go b/build/params_2k.go index ef1f2ac31..c3199e2d6 100644 --- a/build/params_2k.go +++ b/build/params_2k.go @@ -1,27 +1,133 @@ +//go:build debug || 2k // +build debug 2k package build import ( - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/abi/big" - "github.com/filecoin-project/specs-actors/actors/builtin/miner" - "github.com/filecoin-project/specs-actors/actors/builtin/power" - "github.com/filecoin-project/specs-actors/actors/builtin/verifreg" + "os" + "strconv" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/network" + + "github.com/filecoin-project/lotus/chain/actors/policy" ) -func init() { - power.ConsensusMinerMinPower = big.NewInt(2048) - miner.SupportedProofTypes = map[abi.RegisteredProof]struct{}{ - abi.RegisteredProof_StackedDRG2KiBSeal: {}, - } - verifreg.MinVerifiedDealSize = big.NewInt(256) +const BootstrappersFile = "" +const GenesisFile = "" + +var NetworkBundle = "devnet" +var BundleOverrides map[actorstypes.Version]string +var ActorDebugging = true + +const GenesisNetworkVersion = network.Version18 + +var UpgradeBreezeHeight = abi.ChainEpoch(-1) + +const BreezeGasTampingDuration = 0 + +var UpgradeSmokeHeight = abi.ChainEpoch(-1) +var UpgradeIgnitionHeight = abi.ChainEpoch(-2) +var UpgradeRefuelHeight = abi.ChainEpoch(-3) +var UpgradeTapeHeight = abi.ChainEpoch(-4) + +var UpgradeAssemblyHeight = abi.ChainEpoch(-5) +var UpgradeLiftoffHeight = abi.ChainEpoch(-6) + +var UpgradeKumquatHeight = abi.ChainEpoch(-7) +var UpgradeCalicoHeight = abi.ChainEpoch(-9) +var UpgradePersianHeight = abi.ChainEpoch(-10) +var UpgradeOrangeHeight = abi.ChainEpoch(-11) +var UpgradeClausHeight = abi.ChainEpoch(-12) + +var UpgradeTrustHeight = abi.ChainEpoch(-13) + +var UpgradeNorwegianHeight = abi.ChainEpoch(-14) + +var UpgradeTurboHeight = abi.ChainEpoch(-15) + +var UpgradeHyperdriveHeight = abi.ChainEpoch(-16) + +var UpgradeChocolateHeight = abi.ChainEpoch(-17) + +var UpgradeOhSnapHeight = abi.ChainEpoch(-18) + +var UpgradeSkyrHeight = abi.ChainEpoch(-19) + +var UpgradeSharkHeight = abi.ChainEpoch(-20) + +var UpgradeHyggeHeight = abi.ChainEpoch(-21) + +var UpgradeLightningHeight = abi.ChainEpoch(30) + +var UpgradeThunderHeight = abi.ChainEpoch(1000) + +var DrandSchedule = map[abi.ChainEpoch]DrandEnum{ + 0: DrandMainnet, } -// Seconds -const BlockDelay = 2 +var SupportedProofTypes = []abi.RegisteredSealProof{ + abi.RegisteredSealProof_StackedDrg2KiBV1, + abi.RegisteredSealProof_StackedDrg8MiBV1, +} +var ConsensusMinerMinPower = abi.NewStoragePower(2048) +var MinVerifiedDealSize = abi.NewStoragePower(256) +var PreCommitChallengeDelay = abi.ChainEpoch(10) -const PropagationDelay = 3 +func init() { + policy.SetSupportedProofTypes(SupportedProofTypes...) + policy.SetConsensusMinerMinPower(ConsensusMinerMinPower) + policy.SetMinVerifiedDealSize(MinVerifiedDealSize) + policy.SetPreCommitChallengeDelay(PreCommitChallengeDelay) + + getUpgradeHeight := func(ev string, def abi.ChainEpoch) abi.ChainEpoch { + hs, found := os.LookupEnv(ev) + if found { + h, err := strconv.Atoi(hs) + if err != nil { + log.Panicf("failed to parse %s env var", ev) + } + + return abi.ChainEpoch(h) + } + + return def + } + + UpgradeBreezeHeight = getUpgradeHeight("LOTUS_BREEZE_HEIGHT", UpgradeBreezeHeight) + UpgradeSmokeHeight = getUpgradeHeight("LOTUS_SMOKE_HEIGHT", UpgradeSmokeHeight) + UpgradeIgnitionHeight = getUpgradeHeight("LOTUS_IGNITION_HEIGHT", UpgradeIgnitionHeight) + UpgradeRefuelHeight = getUpgradeHeight("LOTUS_REFUEL_HEIGHT", UpgradeRefuelHeight) + UpgradeTapeHeight = getUpgradeHeight("LOTUS_TAPE_HEIGHT", UpgradeTapeHeight) + UpgradeAssemblyHeight = getUpgradeHeight("LOTUS_ACTORSV2_HEIGHT", UpgradeAssemblyHeight) + UpgradeLiftoffHeight = getUpgradeHeight("LOTUS_LIFTOFF_HEIGHT", UpgradeLiftoffHeight) + UpgradeKumquatHeight = getUpgradeHeight("LOTUS_KUMQUAT_HEIGHT", UpgradeKumquatHeight) + UpgradeCalicoHeight = getUpgradeHeight("LOTUS_CALICO_HEIGHT", UpgradeCalicoHeight) + UpgradePersianHeight = getUpgradeHeight("LOTUS_PERSIAN_HEIGHT", UpgradePersianHeight) + UpgradeOrangeHeight = getUpgradeHeight("LOTUS_ORANGE_HEIGHT", UpgradeOrangeHeight) + UpgradeClausHeight = getUpgradeHeight("LOTUS_CLAUS_HEIGHT", UpgradeClausHeight) + UpgradeTrustHeight = getUpgradeHeight("LOTUS_ACTORSV3_HEIGHT", UpgradeTrustHeight) + UpgradeNorwegianHeight = getUpgradeHeight("LOTUS_NORWEGIAN_HEIGHT", UpgradeNorwegianHeight) + UpgradeTurboHeight = getUpgradeHeight("LOTUS_ACTORSV4_HEIGHT", UpgradeTurboHeight) + UpgradeHyperdriveHeight = getUpgradeHeight("LOTUS_HYPERDRIVE_HEIGHT", UpgradeHyperdriveHeight) + UpgradeChocolateHeight = getUpgradeHeight("LOTUS_CHOCOLATE_HEIGHT", UpgradeChocolateHeight) + UpgradeOhSnapHeight = getUpgradeHeight("LOTUS_OHSNAP_HEIGHT", UpgradeOhSnapHeight) + UpgradeSkyrHeight = getUpgradeHeight("LOTUS_SKYR_HEIGHT", UpgradeSkyrHeight) + UpgradeSharkHeight = getUpgradeHeight("LOTUS_SHARK_HEIGHT", UpgradeSharkHeight) + UpgradeHyggeHeight = getUpgradeHeight("LOTUS_HYGGE_HEIGHT", UpgradeHyggeHeight) + UpgradeLightningHeight = getUpgradeHeight("LOTUS_LIGHTNING_HEIGHT", UpgradeLightningHeight) + UpgradeThunderHeight = getUpgradeHeight("LOTUS_THUNDER_HEIGHT", UpgradeThunderHeight) + + BuildType |= Build2k + +} + +const BlockDelaySecs = uint64(4) + +const PropagationDelaySecs = uint64(1) // SlashablePowerDelay is the number of epochs after ElectionPeriodStart, after // which the miner is slashed @@ -31,3 +137,11 @@ const SlashablePowerDelay = 20 // Epochs const InteractivePoRepConfidence = 6 + +const BootstrapPeerThreshold = 1 + +// ChainId defines the chain ID used in the Ethereum JSON-RPC endpoint. +// As per https://github.com/ethereum-lists/chains +const Eip155ChainId = 31415926 + +var WhitelistedBlock = cid.Undef diff --git a/build/params_butterfly.go b/build/params_butterfly.go new file mode 100644 index 000000000..e26fb4ad1 --- /dev/null +++ b/build/params_butterfly.go @@ -0,0 +1,93 @@ +//go:build butterflynet +// +build butterflynet + +package build + +import ( + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/network" + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + + "github.com/filecoin-project/lotus/chain/actors/policy" +) + +var DrandSchedule = map[abi.ChainEpoch]DrandEnum{ + 0: DrandMainnet, +} + +const GenesisNetworkVersion = network.Version18 + +var NetworkBundle = "butterflynet" +var BundleOverrides map[actorstypes.Version]string +var ActorDebugging = false + +const BootstrappersFile = "butterflynet.pi" +const GenesisFile = "butterflynet.car" + +const UpgradeBreezeHeight = -1 +const BreezeGasTampingDuration = 120 +const UpgradeSmokeHeight = -2 +const UpgradeIgnitionHeight = -3 +const UpgradeRefuelHeight = -4 + +var UpgradeAssemblyHeight = abi.ChainEpoch(-5) + +const UpgradeTapeHeight = -6 +const UpgradeLiftoffHeight = -7 +const UpgradeKumquatHeight = -8 +const UpgradeCalicoHeight = -9 +const UpgradePersianHeight = -10 +const UpgradeClausHeight = -11 +const UpgradeOrangeHeight = -12 +const UpgradeTrustHeight = -13 +const UpgradeNorwegianHeight = -14 +const UpgradeTurboHeight = -15 +const UpgradeHyperdriveHeight = -16 +const UpgradeChocolateHeight = -17 +const UpgradeOhSnapHeight = -18 +const UpgradeSkyrHeight = -19 +const UpgradeSharkHeight = -20 +const UpgradeHyggeHeight = -21 + +const UpgradeLightningHeight = 50 + +const UpgradeThunderHeight = UpgradeLightningHeight + 360 + +var SupportedProofTypes = []abi.RegisteredSealProof{ + abi.RegisteredSealProof_StackedDrg512MiBV1, + abi.RegisteredSealProof_StackedDrg32GiBV1, + abi.RegisteredSealProof_StackedDrg64GiBV1, +} +var ConsensusMinerMinPower = abi.NewStoragePower(2 << 30) +var MinVerifiedDealSize = abi.NewStoragePower(1 << 20) +var PreCommitChallengeDelay = abi.ChainEpoch(150) + +func init() { + policy.SetSupportedProofTypes(SupportedProofTypes...) + policy.SetConsensusMinerMinPower(ConsensusMinerMinPower) + policy.SetMinVerifiedDealSize(MinVerifiedDealSize) + policy.SetPreCommitChallengeDelay(PreCommitChallengeDelay) + + SetAddressNetwork(address.Testnet) + + Devnet = true + + BuildType = BuildButterflynet +} + +const BlockDelaySecs = uint64(builtin2.EpochDurationSeconds) + +const PropagationDelaySecs = uint64(6) + +// BootstrapPeerThreshold is the minimum number peers we need to track for a sync worker to start +const BootstrapPeerThreshold = 2 + +// ChainId defines the chain ID used in the Ethereum JSON-RPC endpoint. +// As per https://github.com/ethereum-lists/chains +const Eip155ChainId = 3141592 + +var WhitelistedBlock = cid.Undef diff --git a/build/params_calibnet.go b/build/params_calibnet.go new file mode 100644 index 000000000..e8b1c9d7e --- /dev/null +++ b/build/params_calibnet.go @@ -0,0 +1,130 @@ +//go:build calibnet +// +build calibnet + +package build + +import ( + "os" + "strconv" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/network" + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + + "github.com/filecoin-project/lotus/chain/actors/policy" +) + +var DrandSchedule = map[abi.ChainEpoch]DrandEnum{ + 0: DrandMainnet, +} + +const GenesisNetworkVersion = network.Version0 + +var NetworkBundle = "calibrationnet" +var BundleOverrides map[actorstypes.Version]string +var ActorDebugging = false + +const BootstrappersFile = "calibnet.pi" +const GenesisFile = "calibnet.car" + +const UpgradeBreezeHeight = -1 +const BreezeGasTampingDuration = 120 + +const UpgradeSmokeHeight = -2 + +const UpgradeIgnitionHeight = -3 +const UpgradeRefuelHeight = -4 + +var UpgradeAssemblyHeight = abi.ChainEpoch(30) + +const UpgradeTapeHeight = 60 + +const UpgradeLiftoffHeight = -5 + +const UpgradeKumquatHeight = 90 + +const UpgradeCalicoHeight = 120 +const UpgradePersianHeight = UpgradeCalicoHeight + (builtin2.EpochsInHour * 1) + +const UpgradeClausHeight = 270 + +const UpgradeOrangeHeight = 300 + +const UpgradeTrustHeight = 330 + +const UpgradeNorwegianHeight = 360 + +const UpgradeTurboHeight = 390 + +const UpgradeHyperdriveHeight = 420 + +const UpgradeChocolateHeight = 450 + +const UpgradeOhSnapHeight = 480 + +const UpgradeSkyrHeight = 510 + +const UpgradeSharkHeight = 16800 // 6 days after genesis + +// 2023-02-21T16:30:00Z +const UpgradeHyggeHeight = 322354 + +// 2023-04-20T14:00:00Z +const UpgradeLightningHeight = 489094 + +// 2023-04-21T16:00:00Z +const UpgradeThunderHeight = UpgradeLightningHeight + 3120 + +var SupportedProofTypes = []abi.RegisteredSealProof{ + abi.RegisteredSealProof_StackedDrg32GiBV1, + abi.RegisteredSealProof_StackedDrg64GiBV1, +} +var ConsensusMinerMinPower = abi.NewStoragePower(32 << 30) +var MinVerifiedDealSize = abi.NewStoragePower(1 << 20) +var PreCommitChallengeDelay = abi.ChainEpoch(150) + +func init() { + policy.SetSupportedProofTypes(SupportedProofTypes...) + policy.SetConsensusMinerMinPower(ConsensusMinerMinPower) + policy.SetMinVerifiedDealSize(MinVerifiedDealSize) + policy.SetPreCommitChallengeDelay(PreCommitChallengeDelay) + + SetAddressNetwork(address.Testnet) + + Devnet = true + + // NOTE: DO NOT change this unless you REALLY know what you're doing. This is not consensus critical, however, + //set this value too high may impacts your block submission; set this value too low may cause you miss + //parent tipsets for blocking forming and mining. + if len(os.Getenv("PROPAGATION_DELAY_SECS")) != 0 { + pds, err := strconv.ParseUint(os.Getenv("PROPAGATION_DELAY_SECS"), 10, 64) + if err != nil { + log.Warnw("Error setting PROPAGATION_DELAY_SECS, %v, proceed with default value %s", err, + PropagationDelaySecs) + } else { + PropagationDelaySecs = pds + log.Warnw(" !!WARNING!! propagation delay is set to be %s second, "+ + "this value impacts your message republish interval and block forming - monitor with caution!!", PropagationDelaySecs) + } + } + + BuildType = BuildCalibnet + +} + +const BlockDelaySecs = uint64(builtin2.EpochDurationSeconds) + +var PropagationDelaySecs = uint64(10) + +// BootstrapPeerThreshold is the minimum number peers we need to track for a sync worker to start +const BootstrapPeerThreshold = 4 + +// ChainId defines the chain ID used in the Ethereum JSON-RPC endpoint. +// As per https://github.com/ethereum-lists/chains +const Eip155ChainId = 314159 + +var WhitelistedBlock = cid.Undef diff --git a/build/params_debug.go b/build/params_debug.go index e7c6d2e9e..e977cda05 100644 --- a/build/params_debug.go +++ b/build/params_debug.go @@ -1,9 +1,11 @@ +//go:build debug // +build debug package build func init() { InsecurePoStValidation = true + BuildType |= BuildDebug } // NOTE: Also includes settings from params_2k diff --git a/build/params_interop.go b/build/params_interop.go new file mode 100644 index 000000000..04fc777f5 --- /dev/null +++ b/build/params_interop.go @@ -0,0 +1,131 @@ +//go:build interopnet +// +build interopnet + +package build + +import ( + "os" + "strconv" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/network" + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + + "github.com/filecoin-project/lotus/chain/actors/policy" +) + +var NetworkBundle = "caterpillarnet" +var BundleOverrides map[actorstypes.Version]string +var ActorDebugging = false + +const BootstrappersFile = "interopnet.pi" +const GenesisFile = "interopnet.car" + +const GenesisNetworkVersion = network.Version16 + +var UpgradeBreezeHeight = abi.ChainEpoch(-1) + +const BreezeGasTampingDuration = 0 + +var UpgradeSmokeHeight = abi.ChainEpoch(-1) +var UpgradeIgnitionHeight = abi.ChainEpoch(-2) +var UpgradeRefuelHeight = abi.ChainEpoch(-3) +var UpgradeTapeHeight = abi.ChainEpoch(-4) +var UpgradeAssemblyHeight = abi.ChainEpoch(-5) +var UpgradeLiftoffHeight = abi.ChainEpoch(-6) +var UpgradeKumquatHeight = abi.ChainEpoch(-7) +var UpgradeCalicoHeight = abi.ChainEpoch(-9) +var UpgradePersianHeight = abi.ChainEpoch(-10) +var UpgradeOrangeHeight = abi.ChainEpoch(-11) +var UpgradeClausHeight = abi.ChainEpoch(-12) +var UpgradeTrustHeight = abi.ChainEpoch(-13) +var UpgradeNorwegianHeight = abi.ChainEpoch(-14) +var UpgradeTurboHeight = abi.ChainEpoch(-15) +var UpgradeHyperdriveHeight = abi.ChainEpoch(-16) +var UpgradeChocolateHeight = abi.ChainEpoch(-17) +var UpgradeOhSnapHeight = abi.ChainEpoch(-18) +var UpgradeSkyrHeight = abi.ChainEpoch(-19) +var UpgradeSharkHeight = abi.ChainEpoch(-20) +var UpgradeHyggeHeight = abi.ChainEpoch(-21) +var UpgradeLightningHeight = abi.ChainEpoch(-22) + +const UpgradeThunderHeight = 50 + +var DrandSchedule = map[abi.ChainEpoch]DrandEnum{ + 0: DrandMainnet, +} + +var SupportedProofTypes = []abi.RegisteredSealProof{ + abi.RegisteredSealProof_StackedDrg2KiBV1, + abi.RegisteredSealProof_StackedDrg8MiBV1, + abi.RegisteredSealProof_StackedDrg512MiBV1, +} +var ConsensusMinerMinPower = abi.NewStoragePower(2048) +var MinVerifiedDealSize = abi.NewStoragePower(256) +var PreCommitChallengeDelay = abi.ChainEpoch(10) + +func init() { + policy.SetSupportedProofTypes(SupportedProofTypes...) + policy.SetConsensusMinerMinPower(ConsensusMinerMinPower) + policy.SetMinVerifiedDealSize(MinVerifiedDealSize) + policy.SetPreCommitChallengeDelay(PreCommitChallengeDelay) + + getUpgradeHeight := func(ev string, def abi.ChainEpoch) abi.ChainEpoch { + hs, found := os.LookupEnv(ev) + if found { + h, err := strconv.Atoi(hs) + if err != nil { + log.Panicf("failed to parse %s env var", ev) + } + + return abi.ChainEpoch(h) + } + + return def + } + + UpgradeBreezeHeight = getUpgradeHeight("LOTUS_BREEZE_HEIGHT", UpgradeBreezeHeight) + UpgradeSmokeHeight = getUpgradeHeight("LOTUS_SMOKE_HEIGHT", UpgradeSmokeHeight) + UpgradeIgnitionHeight = getUpgradeHeight("LOTUS_IGNITION_HEIGHT", UpgradeIgnitionHeight) + UpgradeRefuelHeight = getUpgradeHeight("LOTUS_REFUEL_HEIGHT", UpgradeRefuelHeight) + UpgradeTapeHeight = getUpgradeHeight("LOTUS_TAPE_HEIGHT", UpgradeTapeHeight) + UpgradeAssemblyHeight = getUpgradeHeight("LOTUS_ACTORSV2_HEIGHT", UpgradeAssemblyHeight) + UpgradeLiftoffHeight = getUpgradeHeight("LOTUS_LIFTOFF_HEIGHT", UpgradeLiftoffHeight) + UpgradeKumquatHeight = getUpgradeHeight("LOTUS_KUMQUAT_HEIGHT", UpgradeKumquatHeight) + UpgradeCalicoHeight = getUpgradeHeight("LOTUS_CALICO_HEIGHT", UpgradeCalicoHeight) + UpgradePersianHeight = getUpgradeHeight("LOTUS_PERSIAN_HEIGHT", UpgradePersianHeight) + UpgradeOrangeHeight = getUpgradeHeight("LOTUS_ORANGE_HEIGHT", UpgradeOrangeHeight) + UpgradeClausHeight = getUpgradeHeight("LOTUS_CLAUS_HEIGHT", UpgradeClausHeight) + UpgradeTrustHeight = getUpgradeHeight("LOTUS_ACTORSV3_HEIGHT", UpgradeTrustHeight) + UpgradeNorwegianHeight = getUpgradeHeight("LOTUS_NORWEGIAN_HEIGHT", UpgradeNorwegianHeight) + UpgradeTurboHeight = getUpgradeHeight("LOTUS_ACTORSV4_HEIGHT", UpgradeTurboHeight) + UpgradeHyperdriveHeight = getUpgradeHeight("LOTUS_HYPERDRIVE_HEIGHT", UpgradeHyperdriveHeight) + UpgradeChocolateHeight = getUpgradeHeight("LOTUS_CHOCOLATE_HEIGHT", UpgradeChocolateHeight) + UpgradeOhSnapHeight = getUpgradeHeight("LOTUS_OHSNAP_HEIGHT", UpgradeOhSnapHeight) + UpgradeSkyrHeight = getUpgradeHeight("LOTUS_SKYR_HEIGHT", UpgradeSkyrHeight) + UpgradeSharkHeight = getUpgradeHeight("LOTUS_SHARK_HEIGHT", UpgradeSharkHeight) + UpgradeHyggeHeight = getUpgradeHeight("LOTUS_HYGGE_HEIGHT", UpgradeHyggeHeight) + + BuildType |= BuildInteropnet + SetAddressNetwork(address.Testnet) + Devnet = true + +} + +const BlockDelaySecs = uint64(builtin2.EpochDurationSeconds) + +const PropagationDelaySecs = uint64(6) + +// BootstrapPeerThreshold is the minimum number peers we need to track for a sync worker to start +const BootstrapPeerThreshold = 2 + +// ChainId defines the chain ID used in the Ethereum JSON-RPC endpoint. +// As per https://github.com/ethereum-lists/chains +// TODO same as butterfly for now, as we didn't submit an assignment for interopnet. +const Eip155ChainId = 3141592 + +var WhitelistedBlock = cid.Undef diff --git a/build/params_mainnet.go b/build/params_mainnet.go new file mode 100644 index 000000000..53eeb2091 --- /dev/null +++ b/build/params_mainnet.go @@ -0,0 +1,149 @@ +//go:build !debug && !2k && !testground && !calibnet && !butterflynet && !interopnet +// +build !debug,!2k,!testground,!calibnet,!butterflynet,!interopnet + +package build + +import ( + "math" + "os" + "strconv" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/network" + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" +) + +var DrandSchedule = map[abi.ChainEpoch]DrandEnum{ + 0: DrandIncentinet, + UpgradeSmokeHeight: DrandMainnet, +} + +var NetworkBundle = "mainnet" + +// NOTE: DO NOT change this unless you REALLY know what you're doing. This is consensus critical. +var BundleOverrides map[actorstypes.Version]string + +// NOTE: DO NOT change this unless you REALLY know what you're doing. This is consensus critical. +const ActorDebugging = false + +const GenesisNetworkVersion = network.Version0 + +const BootstrappersFile = "mainnet.pi" +const GenesisFile = "mainnet.car" + +const UpgradeBreezeHeight = 41280 + +const BreezeGasTampingDuration = 120 + +const UpgradeSmokeHeight = 51000 + +const UpgradeIgnitionHeight = 94000 +const UpgradeRefuelHeight = 130800 + +const UpgradeAssemblyHeight = 138720 + +const UpgradeTapeHeight = 140760 + +// This signals our tentative epoch for mainnet launch. Can make it later, but not earlier. +// Miners, clients, developers, custodians all need time to prepare. +// We still have upgrades and state changes to do, but can happen after signaling timing here. +const UpgradeLiftoffHeight = 148888 + +const UpgradeKumquatHeight = 170000 + +const UpgradeCalicoHeight = 265200 +const UpgradePersianHeight = UpgradeCalicoHeight + (builtin2.EpochsInHour * 60) + +const UpgradeOrangeHeight = 336458 + +// 2020-12-22T02:00:00Z +// var because of wdpost_test.go +var UpgradeClausHeight = abi.ChainEpoch(343200) + +// 2021-03-04T00:00:30Z +const UpgradeTrustHeight = 550321 + +// 2021-04-12T22:00:00Z +const UpgradeNorwegianHeight = 665280 + +// 2021-04-29T06:00:00Z +const UpgradeTurboHeight = 712320 + +// 2021-06-30T22:00:00Z +const UpgradeHyperdriveHeight = 892800 + +// 2021-10-26T13:30:00Z +const UpgradeChocolateHeight = 1231620 + +// 2022-03-01T15:00:00Z +const UpgradeOhSnapHeight = 1594680 + +// 2022-07-06T14:00:00Z +const UpgradeSkyrHeight = 1960320 + +// 2022-11-30T14:00:00Z +const UpgradeSharkHeight = 2383680 + +// 2023-03-14T15:14:00Z +const UpgradeHyggeHeight = 2683348 + +// 2023-04-27T13:00:00Z +var UpgradeLightningHeight = abi.ChainEpoch(2809800) + +// 2023-05-18T13:00:00Z +var UpgradeThunderHeight = UpgradeLightningHeight + 2880*21 + +var SupportedProofTypes = []abi.RegisteredSealProof{ + abi.RegisteredSealProof_StackedDrg32GiBV1, + abi.RegisteredSealProof_StackedDrg64GiBV1, +} +var ConsensusMinerMinPower = abi.NewStoragePower(10 << 40) +var PreCommitChallengeDelay = abi.ChainEpoch(150) +var PropagationDelaySecs = uint64(10) + +func init() { + if os.Getenv("LOTUS_USE_TEST_ADDRESSES") != "1" { + SetAddressNetwork(address.Mainnet) + } + + if os.Getenv("LOTUS_DISABLE_LIGHTNING") == "1" { + UpgradeLightningHeight = math.MaxInt64 + } + + if os.Getenv("LOTUS_DISABLE_THUNDER") == "1" { + UpgradeThunderHeight = math.MaxInt64 + } + + // NOTE: DO NOT change this unless you REALLY know what you're doing. This is not consensus critical, however, + //set this value too high may impacts your block submission; set this value too low may cause you miss + //parent tipsets for blocking forming and mining. + if len(os.Getenv("PROPAGATION_DELAY_SECS")) != 0 { + pds, err := strconv.ParseUint(os.Getenv("PROPAGATION_DELAY_SECS"), 10, 64) + if err != nil { + log.Warnw("Error setting PROPAGATION_DELAY_SECS, %v, proceed with default value %s", err, + PropagationDelaySecs) + } else { + PropagationDelaySecs = pds + log.Warnw(" !!WARNING!! propagation delay is set to be %s second, "+ + "this value impacts your message republish interval and block forming - monitor with caution!!", PropagationDelaySecs) + } + } + + Devnet = false + + BuildType = BuildMainnet +} + +const BlockDelaySecs = uint64(builtin2.EpochDurationSeconds) + +// BootstrapPeerThreshold is the minimum number peers we need to track for a sync worker to start +const BootstrapPeerThreshold = 4 + +// ChainId defines the chain ID used in the Ethereum JSON-RPC endpoint. +// As per https://github.com/ethereum-lists/chains +const Eip155ChainId = 314 + +// we skip checks on message validity in this block to sidestep the zero-bls signature +var WhitelistedBlock = MustParseCid("bafy2bzaceapyg2uyzk7vueh3xccxkuwbz3nxewjyguoxvhx77malc2lzn2ybi") diff --git a/build/params_shared.go b/build/params_shared.go deleted file mode 100644 index 2b2f3e985..000000000 --- a/build/params_shared.go +++ /dev/null @@ -1,128 +0,0 @@ -package build - -import ( - "math/big" - "sort" - - "github.com/libp2p/go-libp2p-core/protocol" - - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/builtin" - "github.com/filecoin-project/specs-actors/actors/builtin/miner" - - "github.com/filecoin-project/lotus/node/modules/dtypes" -) - -func DefaultSectorSize() abi.SectorSize { - szs := make([]abi.SectorSize, 0, len(miner.SupportedProofTypes)) - for spt := range miner.SupportedProofTypes { - ss, err := spt.SectorSize() - if err != nil { - panic(err) - } - - szs = append(szs, ss) - } - - sort.Slice(szs, func(i, j int) bool { - return szs[i] < szs[j] - }) - - return szs[0] -} - -// Core network constants - -func BlocksTopic(netName dtypes.NetworkName) string { return "/fil/blocks/" + string(netName) } -func MessagesTopic(netName dtypes.NetworkName) string { return "/fil/msgs/" + string(netName) } -func DhtProtocolName(netName dtypes.NetworkName) protocol.ID { - return protocol.ID("/fil/kad/" + string(netName)) -} - -// ///// -// Storage - -const UnixfsChunkSize uint64 = 1 << 20 -const UnixfsLinksPerLevel = 1024 - -// ///// -// Consensus / Network - -// Seconds -const AllowableClockDrift = 1 - -// Epochs -const ForkLengthThreshold = Finality - -// Blocks (e) -var BlocksPerEpoch = uint64(builtin.ExpectedLeadersPerEpoch) - -// Epochs -const Finality = miner.ChainFinalityish - -// constants for Weight calculation -// The ratio of weight contributed by short-term vs long-term factors in a given round -const WRatioNum = int64(1) -const WRatioDen = 2 - -// ///// -// Proofs - -// Epochs -const SealRandomnessLookback = Finality - -// Epochs -const SealRandomnessLookbackLimit = SealRandomnessLookback + 2000 // TODO: Get from spec specs-actors - -// Maximum lookback that randomness can be sourced from for a seal proof submission -const MaxSealLookback = SealRandomnessLookbackLimit + 2000 // TODO: Get from specs-actors - -// ///// -// Mining - -// Epochs -const TicketRandomnessLookback = 1 - -const WinningPoStSectorSetLookback = 10 - -// ///// -// Devnet settings - -const TotalFilecoin = 2_000_000_000 -const MiningRewardTotal = 1_400_000_000 - -const FilecoinPrecision = 1_000_000_000_000_000_000 - -var InitialRewardBalance *big.Int - -// TODO: Move other important consts here - -func init() { - InitialRewardBalance = big.NewInt(MiningRewardTotal) - InitialRewardBalance = InitialRewardBalance.Mul(InitialRewardBalance, big.NewInt(FilecoinPrecision)) -} - -// Sync -const BadBlockCacheSize = 1 << 15 - -// assuming 4000 messages per round, this lets us not lose any messages across a -// 10 block reorg. -const BlsSignatureCacheSize = 40000 - -// Size of signature verification cache -// 32k keeps the cache around 10MB in size, max -const VerifSigCacheSize = 32000 - -// /////// -// Limits - -// TODO: If this is gonna stay, it should move to specs-actors -const BlockMessageLimit = 512 -const BlockGasLimit = 100_000_000 - -var DrandCoeffs = []string{ - "82c279cce744450e68de98ee08f9698a01dd38f8e3be3c53f2b840fb9d09ad62a0b6b87981e179e1b14bc9a2d284c985", - "82d51308ad346c686f81b8094551597d7b963295cbf313401a93df9baf52d5ae98a87745bee70839a4d6e65c342bd15b", - "94eebfd53f4ba6a3b8304236400a12e73885e5a781509a5c8d41d2e8b476923d8ea6052649b3c17282f596217f96c5de", - "8dc4231e42b4edf39e86ef1579401692480647918275da767d3e558c520d6375ad953530610fd27daf110187877a65d0", -} diff --git a/build/params_shared_funcs.go b/build/params_shared_funcs.go new file mode 100644 index 000000000..d117264ab --- /dev/null +++ b/build/params_shared_funcs.go @@ -0,0 +1,51 @@ +package build + +import ( + "github.com/ipfs/go-cid" + "github.com/libp2p/go-libp2p/core/protocol" + + "github.com/filecoin-project/go-address" + + "github.com/filecoin-project/lotus/node/modules/dtypes" +) + +// Core network constants + +func BlocksTopic(netName dtypes.NetworkName) string { return "/fil/blocks/" + string(netName) } +func MessagesTopic(netName dtypes.NetworkName) string { return "/fil/msgs/" + string(netName) } +func IndexerIngestTopic(netName dtypes.NetworkName) string { + + nn := string(netName) + // The network name testnetnet is here for historical reasons. + // Going forward we aim to use the name `mainnet` where possible. + if nn == "testnetnet" { + nn = "mainnet" + } + + return "/indexer/ingest/" + nn +} +func DhtProtocolName(netName dtypes.NetworkName) protocol.ID { + return protocol.ID("/fil/kad/" + string(netName)) +} + +func SetAddressNetwork(n address.Network) { + address.CurrentNetwork = n +} + +func MustParseAddress(addr string) address.Address { + ret, err := address.NewFromString(addr) + if err != nil { + panic(err) + } + + return ret +} + +func MustParseCid(c string) cid.Cid { + ret, err := cid.Decode(c) + if err != nil { + panic(err) + } + + return ret +} diff --git a/build/params_shared_vals.go b/build/params_shared_vals.go new file mode 100644 index 000000000..dd7386863 --- /dev/null +++ b/build/params_shared_vals.go @@ -0,0 +1,129 @@ +//go:build !testground +// +build !testground + +package build + +import ( + "math/big" + "os" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/network" + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + + "github.com/filecoin-project/lotus/chain/actors/policy" +) + +// ///// +// Storage + +const UnixfsChunkSize uint64 = 1 << 20 +const UnixfsLinksPerLevel = 1024 + +// ///// +// Consensus / Network + +const AllowableClockDriftSecs = uint64(1) + +// Used by tests and some obscure tooling +/* inline-gen template +const TestNetworkVersion = network.Version{{.latestNetworkVersion}} +/* inline-gen start */ +const TestNetworkVersion = network.Version20 + +/* inline-gen end */ + +// Epochs +const ForkLengthThreshold = Finality + +// Blocks (e) +var BlocksPerEpoch = uint64(builtin2.ExpectedLeadersPerEpoch) + +// Epochs +const Finality = policy.ChainFinality +const MessageConfidence = uint64(5) + +// constants for Weight calculation +// The ratio of weight contributed by short-term vs long-term factors in a given round +const WRatioNum = int64(1) +const WRatioDen = uint64(2) + +// ///// +// Proofs + +// Epochs +// TODO: unused +const SealRandomnessLookback = policy.SealRandomnessLookback + +// ///// +// Mining + +// Epochs +const TicketRandomnessLookback = abi.ChainEpoch(1) + +// ///// +// Address + +const AddressMainnetEnvVar = "_mainnet_" + +// the 'f' prefix doesn't matter +var ZeroAddress = MustParseAddress("f3yaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaby2smx7a") + +// ///// +// Devnet settings + +var Devnet = true + +const FilBase = uint64(2_000_000_000) +const FilAllocStorageMining = uint64(1_100_000_000) + +const FilecoinPrecision = uint64(1_000_000_000_000_000_000) +const FilReserved = uint64(300_000_000) + +var InitialRewardBalance *big.Int +var InitialFilReserved *big.Int + +// TODO: Move other important consts here + +func init() { + InitialRewardBalance = big.NewInt(int64(FilAllocStorageMining)) + InitialRewardBalance = InitialRewardBalance.Mul(InitialRewardBalance, big.NewInt(int64(FilecoinPrecision))) + + InitialFilReserved = big.NewInt(int64(FilReserved)) + InitialFilReserved = InitialFilReserved.Mul(InitialFilReserved, big.NewInt(int64(FilecoinPrecision))) + + if os.Getenv("LOTUS_ADDRESS_TYPE") == AddressMainnetEnvVar { + SetAddressNetwork(address.Mainnet) + } +} + +// Sync +const BadBlockCacheSize = 1 << 15 + +// assuming 4000 messages per round, this lets us not lose any messages across a +// 10 block reorg. +const BlsSignatureCacheSize = 40000 + +// Size of signature verification cache +// 32k keeps the cache around 10MB in size, max +const VerifSigCacheSize = 32000 + +// /////// +// Limits + +// TODO: If this is gonna stay, it should move to specs-actors +const BlockMessageLimit = 10000 + +var BlockGasLimit = int64(10_000_000_000) +var BlockGasTarget = BlockGasLimit / 2 + +const BaseFeeMaxChangeDenom = 8 // 12.5% +const InitialBaseFee = 100e6 +const MinimumBaseFee = 100 +const PackingEfficiencyNum = 4 +const PackingEfficiencyDenom = 5 + +// Actor consts +// TODO: pieceSize unused from actors +var MinDealDuration, MaxDealDuration = policy.DealDurationBounds(0) diff --git a/build/params_testground.go b/build/params_testground.go new file mode 100644 index 000000000..278edd40b --- /dev/null +++ b/build/params_testground.go @@ -0,0 +1,144 @@ +//go:build testground +// +build testground + +// This file makes hardcoded parameters (const) configurable as vars. +// +// Its purpose is to unlock various degrees of flexibility and parametrization +// when writing Testground plans for Lotus. +package build + +import ( + "math/big" + "time" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/network" + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + + "github.com/filecoin-project/lotus/chain/actors/policy" +) + +var ( + UnixfsChunkSize = uint64(1 << 20) + UnixfsLinksPerLevel = 1024 + + BlocksPerEpoch = uint64(builtin2.ExpectedLeadersPerEpoch) + BlockMessageLimit = 512 + BlockGasLimit = int64(100_000_000_000) + BlockGasTarget = int64(BlockGasLimit / 2) + BaseFeeMaxChangeDenom = int64(8) // 12.5% + InitialBaseFee = int64(100e6) + MinimumBaseFee = int64(100) + BlockDelaySecs = uint64(builtin2.EpochDurationSeconds) + PropagationDelaySecs = uint64(6) + SupportedProofTypes = []abi.RegisteredSealProof{ + abi.RegisteredSealProof_StackedDrg32GiBV1, + abi.RegisteredSealProof_StackedDrg64GiBV1, + } + ConsensusMinerMinPower = abi.NewStoragePower(10 << 40) + PreCommitChallengeDelay = abi.ChainEpoch(150) + + AllowableClockDriftSecs = uint64(1) + + SlashablePowerDelay = 20 + InteractivePoRepConfidence = 6 + + MessageConfidence uint64 = 5 + + WRatioNum = int64(1) + WRatioDen = uint64(2) + + BadBlockCacheSize = 1 << 15 + BlsSignatureCacheSize = 40000 + VerifSigCacheSize = 32000 + + SealRandomnessLookback = policy.SealRandomnessLookback + + TicketRandomnessLookback = abi.ChainEpoch(1) + + FilBase uint64 = 2_000_000_000 + FilAllocStorageMining uint64 = 1_400_000_000 + FilReserved uint64 = 300_000_000 + + FilecoinPrecision uint64 = 1_000_000_000_000_000_000 + + InitialRewardBalance = func() *big.Int { + v := big.NewInt(int64(FilAllocStorageMining)) + v = v.Mul(v, big.NewInt(int64(FilecoinPrecision))) + return v + }() + + InitialFilReserved = func() *big.Int { + v := big.NewInt(int64(FilReserved)) + v = v.Mul(v, big.NewInt(int64(FilecoinPrecision))) + return v + }() + + // Actor consts + // TODO: pieceSize unused from actors + MinDealDuration, MaxDealDuration = policy.DealDurationBounds(0) + + PackingEfficiencyNum int64 = 4 + PackingEfficiencyDenom int64 = 5 + + UpgradeBreezeHeight abi.ChainEpoch = -1 + BreezeGasTampingDuration abi.ChainEpoch = 0 + + UpgradeSmokeHeight abi.ChainEpoch = -1 + UpgradeIgnitionHeight abi.ChainEpoch = -2 + UpgradeRefuelHeight abi.ChainEpoch = -3 + UpgradeTapeHeight abi.ChainEpoch = -4 + UpgradeAssemblyHeight abi.ChainEpoch = 10 + UpgradeLiftoffHeight abi.ChainEpoch = -5 + UpgradeKumquatHeight abi.ChainEpoch = -6 + UpgradeCalicoHeight abi.ChainEpoch = -8 + UpgradePersianHeight abi.ChainEpoch = -9 + UpgradeOrangeHeight abi.ChainEpoch = -10 + UpgradeClausHeight abi.ChainEpoch = -11 + UpgradeTrustHeight abi.ChainEpoch = -12 + UpgradeNorwegianHeight abi.ChainEpoch = -13 + UpgradeTurboHeight abi.ChainEpoch = -14 + UpgradeHyperdriveHeight abi.ChainEpoch = -15 + UpgradeChocolateHeight abi.ChainEpoch = -16 + UpgradeOhSnapHeight abi.ChainEpoch = -17 + UpgradeSkyrHeight abi.ChainEpoch = -18 + UpgradeSharkHeight abi.ChainEpoch = -19 + UpgradeHyggeHeight abi.ChainEpoch = -20 + UpgradeLightningHeight abi.ChainEpoch = -21 + UpgradeThunderHeight abi.ChainEpoch = -22 + + DrandSchedule = map[abi.ChainEpoch]DrandEnum{ + 0: DrandMainnet, + } + + GenesisNetworkVersion = network.Version0 + NetworkBundle = "devnet" + BundleOverrides map[actorstypes.Version]string + ActorDebugging = true + + NewestNetworkVersion = network.Version16 + ActorUpgradeNetworkVersion = network.Version16 + + Devnet = true + ZeroAddress = MustParseAddress("f3yaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaby2smx7a") + + WhitelistedBlock = cid.Undef + BootstrappersFile = "" + GenesisFile = "" +) + +const Finality = policy.ChainFinality +const ForkLengthThreshold = Finality + +const BootstrapPeerThreshold = 1 + +// ChainId defines the chain ID used in the Ethereum JSON-RPC endpoint. +// As per https://github.com/ethereum-lists/chains +const Eip155ChainId = 31415926 + +// Reducing the delivery delay for equivocation of +// consistent broadcast to just half a second. +var CBDeliveryDelay = 500 * time.Millisecond diff --git a/build/params_testnet.go b/build/params_testnet.go deleted file mode 100644 index 5d5e6b81f..000000000 --- a/build/params_testnet.go +++ /dev/null @@ -1,25 +0,0 @@ -// +build !debug -// +build !2k - -package build - -import ( - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/abi/big" - "github.com/filecoin-project/specs-actors/actors/builtin" - "github.com/filecoin-project/specs-actors/actors/builtin/miner" - "github.com/filecoin-project/specs-actors/actors/builtin/power" -) - -func init() { - power.ConsensusMinerMinPower = big.NewInt(1024 << 30) - miner.SupportedProofTypes = map[abi.RegisteredProof]struct{}{ - abi.RegisteredProof_StackedDRG32GiBSeal: {}, - abi.RegisteredProof_StackedDRG64GiBSeal: {}, - } -} - -// Seconds -const BlockDelay = builtin.EpochDurationSeconds - -const PropagationDelay = 6 diff --git a/build/proof-params/parameters.json b/build/proof-params/parameters.json index 4ca3e6d2d..88bb0bfa3 100644 --- a/build/proof-params/parameters.json +++ b/build/proof-params/parameters.json @@ -1,152 +1,202 @@ { - "v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-0170db1f394b35d995252228ee359194b13199d259380541dc529fb0099096b0.params": { - "cid": "QmYkygifkXnrnsN4MJsjBFHTQJHx294CyikDgDK8nYxdGh", - "digest": "df3f30442a6d6b4192f5071fb17e820c", - "sector_size": 2048 - }, - "v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-0170db1f394b35d995252228ee359194b13199d259380541dc529fb0099096b0.vk": { - "cid": "QmdXyqbmy2bkJA9Kyhh6z25GrTCq48LwX6c1mxPsm54wi7", - "digest": "0bea3951abf9557a3569f68e52a30c6c", - "sector_size": 2048 - }, - "v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-0cfb4f178bbb71cf2ecfcd42accce558b27199ab4fb59cb78f2483fe21ef36d9.params": { - "cid": "Qmf5XZZtP5VcYTf65MbKjLVabcS6cYMbr2rFShmfJzh5e5", - "digest": "655e6277638edc8c658094f6f0b33d54", + "v28-empty-sector-update-merkletree-poseidon_hasher-8-0-0-61fa69f38b9cc771ba27b670124714b4ea77fbeae05e377fb859c4a43b73a30c.params": { + "cid": "Qma5WL6abSqYg9uUQAZ3EHS286bsNsha7oAGsJBD48Bq2q", + "digest": "c3ad7bb549470b82ad52ed070aebb4f4", "sector_size": 536870912 }, - "v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-0cfb4f178bbb71cf2ecfcd42accce558b27199ab4fb59cb78f2483fe21ef36d9.vk": { - "cid": "QmPuhdWnAXBks43emnkqi9FQzyU1gASKyz23zrD27BPGs8", - "digest": "57690e3a6a94c3f704802a674b34f36b", + "v28-empty-sector-update-merkletree-poseidon_hasher-8-0-0-61fa69f38b9cc771ba27b670124714b4ea77fbeae05e377fb859c4a43b73a30c.vk": { + "cid": "QmUa7f9JtJMsqJJ3s3ZXk6WyF4xJLE8FiqYskZGgk8GCDv", + "digest": "994c5b7d450ca9da348c910689f2dc7f", "sector_size": 536870912 }, - "v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-3ea05428c9d11689f23529cde32fd30aabd50f7d2c93657c1d3650bca3e8ea9e.params": { - "cid": "QmPNVgTN7N5vDtD5u7ERMTLcvUtrKRBfYVUDr6uW3pKhX7", - "digest": "3d390654f58e603b896ac70c653f5676", + "v28-empty-sector-update-merkletree-poseidon_hasher-8-0-0-92180959e1918d26350b8e6cfe217bbdd0a2d8de51ebec269078b364b715ad63.params": { + "cid": "QmQiT4qBGodrVNEgVTDXxBNDdPbaD8Ag7Sx3ZTq1zHX79S", + "digest": "5aedd2cf3e5c0a15623d56a1b43110ad", + "sector_size": 8388608 + }, + "v28-empty-sector-update-merkletree-poseidon_hasher-8-0-0-92180959e1918d26350b8e6cfe217bbdd0a2d8de51ebec269078b364b715ad63.vk": { + "cid": "QmdcpKUQvHM8RFRVKbk1yHfEqMcBzhtFWKRp9SNEmWq37i", + "digest": "abd80269054d391a734febdac0d2e687", + "sector_size": 8388608 + }, + "v28-empty-sector-update-merkletree-poseidon_hasher-8-0-0-fb9e095bebdd77511c0269b967b4d87ba8b8a525edaa0e165de23ba454510194.params": { + "cid": "QmYM6Hg7mjmvA3ZHTsqkss1fkdyDju5dDmLiBZGJ5pz9y9", + "digest": "311f92a3e75036ced01b1c0025f1fa0c", "sector_size": 2048 }, - "v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-3ea05428c9d11689f23529cde32fd30aabd50f7d2c93657c1d3650bca3e8ea9e.vk": { - "cid": "Qmbj61Zez7v5xA7nSCnmWbyLYznWJDWeusz7Yg8EcgVdoN", - "digest": "8c170a164743c39576a7f47a1b51e6f3", + "v28-empty-sector-update-merkletree-poseidon_hasher-8-0-0-fb9e095bebdd77511c0269b967b4d87ba8b8a525edaa0e165de23ba454510194.vk": { + "cid": "QmaQsTLL3nc5dw6wAvaioJSBfd1jhQrA2o6ucFf7XeV74P", + "digest": "eadad9784969890d30f2749708c79771", "sector_size": 2048 }, - "v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-50c7368dea9593ed0989e70974d28024efa9d156d585b7eea1be22b2e753f331.params": { - "cid": "QmRApb8RZoBK3cqicT7V3ydXg8yVvqPFMPrQNXP33aBihp", - "digest": "b1b58ff9a297b82885e8a7dfb035f83c", - "sector_size": 8388608 - }, - "v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-50c7368dea9593ed0989e70974d28024efa9d156d585b7eea1be22b2e753f331.vk": { - "cid": "QmcytF1dTdqMFoyXi931j1RgmGtLfR9LLLaBznRt1tPQyD", - "digest": "1a09e00c641f192f55af3433a028f050", - "sector_size": 8388608 - }, - "v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-5294475db5237a2e83c3e52fd6c2b03859a1831d45ed08c4f35dbf9a803165a9.params": { - "cid": "QmPvr54tWaVeP4WnekivzUAJitTqsQfvikBvAHNEaDNQSw", - "digest": "9380e41368ed4083dbc922b290d3b786", - "sector_size": 8388608 - }, - "v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-5294475db5237a2e83c3e52fd6c2b03859a1831d45ed08c4f35dbf9a803165a9.vk": { - "cid": "QmXyVLVDRCcxA9SjT7PeK8HFtyxZ2ZH3SHa8KoGLw8VGJt", - "digest": "f0731a7e20f90704bd38fc5d27882f6d", - "sector_size": 8388608 - }, - "v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-7d739b8cf60f1b0709eeebee7730e297683552e4b69cab6984ec0285663c5781.params": { - "cid": "Qmf5f6ko3dqj7qauzXpZqxM9B2x2sL977K6gE2ppNwuJPv", - "digest": "273ebb8c896326b7c292bee8b775fd38", - "sector_size": 536870912 - }, - "v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-7d739b8cf60f1b0709eeebee7730e297683552e4b69cab6984ec0285663c5781.vk": { - "cid": "QmfP3MQe8koW63n5MkDENENVHxib78MJYYyZvbneCsuze8", - "digest": "3dd94da9da64e51b3445bc528d84e76d", - "sector_size": 536870912 - }, - "v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-0-0377ded656c6f524f1618760bffe4e0a1c51d5a70c4509eedae8a27555733edc.params": { - "cid": "QmYEeeCE8uT2bsVkxcqqUYeMmMEbe6rfmo8wQCv7jFHqqm", - "digest": "c947f2021304ed43b7216f7a8436e294", + "v28-empty-sector-update-merkletree-poseidon_hasher-8-8-0-3b7f44a9362e3985369454947bc94022e118211e49fd672d52bec1cbfd599d18.params": { + "cid": "QmNPc75iEfcahCwNKdqnWLtxnjspUGGR4iscjiz3wP3RtS", + "digest": "1b3cfd761a961543f9eb273e435a06a2", "sector_size": 34359738368 }, - "v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-0-0377ded656c6f524f1618760bffe4e0a1c51d5a70c4509eedae8a27555733edc.vk": { - "cid": "QmXB63ExriFjB4ywWnXTnFwCcLFfCeEP3h15qtL5i7F4aX", - "digest": "ab20d7b253e7e9a0d2ccdf7599ec8ec3", + "v28-empty-sector-update-merkletree-poseidon_hasher-8-8-0-3b7f44a9362e3985369454947bc94022e118211e49fd672d52bec1cbfd599d18.vk": { + "cid": "QmdFFUe1gcz9MMHc6YW8aoV48w4ckvcERjt7PkydQAMfCN", + "digest": "3a6941983754737fde880d29c7094905", "sector_size": 34359738368 }, - "v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-0-559e581f022bb4e4ec6e719e563bf0e026ad6de42e56c18714a2c692b1b88d7e.params": { - "cid": "QmW5Yxg3L1NSzuQVcRMHMbG3uvVoi4dTLzVaDpnEUPQpnA", - "digest": "079ba19645828ae42b22b0e3f4866e8d", - "sector_size": 34359738368 - }, - "v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-0-559e581f022bb4e4ec6e719e563bf0e026ad6de42e56c18714a2c692b1b88d7e.vk": { - "cid": "QmQzZ5dJ11tcSBees38WX41tZLXS9BqpEti253m5QcnTNs", - "digest": "c76125a50a7de315165de359b5174ae4", - "sector_size": 34359738368 - }, - "v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-2-2627e4006b67f99cef990c0a47d5426cb7ab0a0ad58fc1061547bf2d28b09def.params": { - "cid": "QmNk3wga1tS53FUu1QnkK8ehWA2cqpCnSEAPv3KLxdJxNa", - "digest": "421e4790c0b80e0107a7ff67acf14084", + "v28-empty-sector-update-merkletree-poseidon_hasher-8-8-2-102e1444a7e9a97ebf1e3d6855dcc77e66c011ea66f936d9b2c508f87f2f83a7.params": { + "cid": "QmUB6xTVjzBQGuDNeyJMrrJ1byk58vhPm8eY2Lv9pgwanp", + "digest": "1a392e7b759fb18e036c7559b5ece816", "sector_size": 68719476736 }, - "v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-2-2627e4006b67f99cef990c0a47d5426cb7ab0a0ad58fc1061547bf2d28b09def.vk": { - "cid": "QmVQCHGsrUtbn9RjHs1e6GXfeXDW5m9w4ge48PSX3Z2as2", - "digest": "8b60e9cc1470a6729c687d6cf0a1f79c", + "v28-empty-sector-update-merkletree-poseidon_hasher-8-8-2-102e1444a7e9a97ebf1e3d6855dcc77e66c011ea66f936d9b2c508f87f2f83a7.vk": { + "cid": "Qmd794Jty7k26XJ8Eg4NDEks65Qk8G4GVfGkwqvymv8HAg", + "digest": "80e366df2f1011953c2d01c7b7c9ee8e", "sector_size": 68719476736 }, - "v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-2-b62098629d07946e9028127e70295ed996fe3ed25b0f9f88eb610a0ab4385a3c.params": { - "cid": "QmTL3VvydaMFWKvE5VzxjgKsJYgL9JMM4JVYNtQxdj9JK1", - "digest": "2685f31124b22ea6b2857e5a5e87ffa3", - "sector_size": 68719476736 - }, - "v26-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-2-b62098629d07946e9028127e70295ed996fe3ed25b0f9f88eb610a0ab4385a3c.vk": { - "cid": "QmSVWbLqQYbUbbJyfsRMzEib2rfSqMtnPks1Nw22omcBQm", - "digest": "efe703cd2839597c7ca5c2a906b74296", - "sector_size": 68719476736 - }, - "v26-stacked-proof-of-replication-merkletree-poseidon_hasher-8-0-0-sha256_hasher-032d3138d22506ec0082ed72b2dcba18df18477904e35bafee82b3793b06832f.params": { - "cid": "QmU9dH31nZZUJnsogR4Ld4ySUcH6wm2RgmGiujwnqtbU6k", - "digest": "fcef8e87ae2afd7a28aae44347b804cf", + "v28-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-0170db1f394b35d995252228ee359194b13199d259380541dc529fb0099096b0.params": { + "cid": "QmVxjFRyhmyQaZEtCh7nk2abc7LhFkzhnRX4rcHqCCpikR", + "digest": "7610b9f82bfc88405b7a832b651ce2f6", "sector_size": 2048 }, - "v26-stacked-proof-of-replication-merkletree-poseidon_hasher-8-0-0-sha256_hasher-032d3138d22506ec0082ed72b2dcba18df18477904e35bafee82b3793b06832f.vk": { - "cid": "QmdJ15DMGPooye5NaPcRfXUdHUDibcN7hKjbmTGuu1K4AQ", - "digest": "2ee2b3518229680db15161d4f582af37", + "v28-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-0170db1f394b35d995252228ee359194b13199d259380541dc529fb0099096b0.vk": { + "cid": "QmcS5JZs8X3TdtkEBpHAdUYjdNDqcL7fWQFtQz69mpnu2X", + "digest": "0e0958009936b9d5e515ec97b8cb792d", "sector_size": 2048 }, - "v26-stacked-proof-of-replication-merkletree-poseidon_hasher-8-0-0-sha256_hasher-6babf46ce344ae495d558e7770a585b2382d54f225af8ed0397b8be7c3fcd472.params": { - "cid": "QmZgtxcY3tMXXQxZTA7ZTUDXLVUnfxNcerXgeW4gG2NnfP", - "digest": "3273c7135cb75684248b475781b738ee", + "v28-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-0cfb4f178bbb71cf2ecfcd42accce558b27199ab4fb59cb78f2483fe21ef36d9.params": { + "cid": "QmUiRx71uxfmUE8V3H9sWAsAXoM88KR4eo1ByvvcFNeTLR", + "digest": "1a7d4a9c8a502a497ed92a54366af33f", "sector_size": 536870912 }, - "v26-stacked-proof-of-replication-merkletree-poseidon_hasher-8-0-0-sha256_hasher-6babf46ce344ae495d558e7770a585b2382d54f225af8ed0397b8be7c3fcd472.vk": { - "cid": "QmSS6ZkAV2aGZcgKgdPpEEgihXF1ryZX8PSAZDWSoeL1d4", - "digest": "1519b5f61d9044a59f2bdc57537c094b", + "v28-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-0cfb4f178bbb71cf2ecfcd42accce558b27199ab4fb59cb78f2483fe21ef36d9.vk": { + "cid": "QmfCeddjFpWtavzfEzZpJfzSajGNwfL4RjFXWAvA9TSnTV", + "digest": "4dae975de4f011f101f5a2f86d1daaba", "sector_size": 536870912 }, - "v26-stacked-proof-of-replication-merkletree-poseidon_hasher-8-0-0-sha256_hasher-ecd683648512ab1765faa2a5f14bab48f676e633467f0aa8aad4b55dcb0652bb.params": { - "cid": "QmQBGXeiNn6hVwbR6qFarQqiNGDdKk4h9ucfyvcXyfYz2N", - "digest": "7d5f896f435c38e93bcda6dd168d860b", + "v28-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-3ea05428c9d11689f23529cde32fd30aabd50f7d2c93657c1d3650bca3e8ea9e.params": { + "cid": "QmcSTqDcFVLGGVYz1njhUZ7B6fkKtBumsLUwx4nkh22TzS", + "digest": "82c88066be968bb550a05e30ff6c2413", + "sector_size": 2048 + }, + "v28-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-3ea05428c9d11689f23529cde32fd30aabd50f7d2c93657c1d3650bca3e8ea9e.vk": { + "cid": "QmSTCXF2ipGA3f6muVo6kHc2URSx6PzZxGUqu7uykaH5KU", + "digest": "ffd79788d614d27919ae5bd2d94eacb6", + "sector_size": 2048 + }, + "v28-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-50c7368dea9593ed0989e70974d28024efa9d156d585b7eea1be22b2e753f331.params": { + "cid": "QmU9SBzJNrcjRFDiFc4GcApqdApN6z9X7MpUr66mJ2kAJP", + "digest": "700171ecf7334e3199437c930676af82", "sector_size": 8388608 }, - "v26-stacked-proof-of-replication-merkletree-poseidon_hasher-8-0-0-sha256_hasher-ecd683648512ab1765faa2a5f14bab48f676e633467f0aa8aad4b55dcb0652bb.vk": { - "cid": "QmPrZgBVGMckEAeu5eSJnLmiAwcPQjKjZe5ir6VaQ5AxKs", - "digest": "fe6d2de44580a0db5a4934688899b92f", + "v28-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-50c7368dea9593ed0989e70974d28024efa9d156d585b7eea1be22b2e753f331.vk": { + "cid": "QmbmUMa3TbbW3X5kFhExs6WgC4KeWT18YivaVmXDkB6ANG", + "digest": "79ebb55f56fda427743e35053edad8fc", "sector_size": 8388608 }, - "v26-stacked-proof-of-replication-merkletree-poseidon_hasher-8-8-0-sha256_hasher-82a357d2f2ca81dc61bb45f4a762807aedee1b0a53fd6c4e77b46a01bfef7820.params": { - "cid": "QmZL2cq45XJn5BFzagAZwgFmLrcM1W6CXoiEF9C5j5tjEF", - "digest": "acdfed9f0512bc85a01a9fb871d475d5", + "v28-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-5294475db5237a2e83c3e52fd6c2b03859a1831d45ed08c4f35dbf9a803165a9.params": { + "cid": "QmdNEL2RtqL52GQNuj8uz6mVj5Z34NVnbaJ1yMyh1oXtBx", + "digest": "c49499bb76a0762884896f9683403f55", + "sector_size": 8388608 + }, + "v28-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-5294475db5237a2e83c3e52fd6c2b03859a1831d45ed08c4f35dbf9a803165a9.vk": { + "cid": "QmUiVYCQUgr6Y13pZFr8acWpSM4xvTXUdcvGmxyuHbKhsc", + "digest": "34d4feeacd9abf788d69ef1bb4d8fd00", + "sector_size": 8388608 + }, + "v28-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-7d739b8cf60f1b0709eeebee7730e297683552e4b69cab6984ec0285663c5781.params": { + "cid": "QmVgCsJFRXKLuuUhT3aMYwKVGNA9rDeR6DCrs7cAe8riBT", + "digest": "827359440349fe8f5a016e7598993b79", + "sector_size": 536870912 + }, + "v28-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-0-0-7d739b8cf60f1b0709eeebee7730e297683552e4b69cab6984ec0285663c5781.vk": { + "cid": "QmfA31fbCWojSmhSGvvfxmxaYCpMoXP95zEQ9sLvBGHNaN", + "digest": "bd2cd62f65c1ab84f19ca27e97b7c731", + "sector_size": 536870912 + }, + "v28-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-0-0377ded656c6f524f1618760bffe4e0a1c51d5a70c4509eedae8a27555733edc.params": { + "cid": "QmaUmfcJt6pozn8ndq1JVBzLRjRJdHMTPd4foa8iw5sjBZ", + "digest": "2cf49eb26f1fee94c85781a390ddb4c8", "sector_size": 34359738368 }, - "v26-stacked-proof-of-replication-merkletree-poseidon_hasher-8-8-0-sha256_hasher-82a357d2f2ca81dc61bb45f4a762807aedee1b0a53fd6c4e77b46a01bfef7820.vk": { - "cid": "QmQ4zB7nNa1tDYNifBkExRnZtwtxZw775iaqvVsZyRi6Q2", - "digest": "524a2f3e9d6826593caebc41bb545c40", + "v28-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-0-0377ded656c6f524f1618760bffe4e0a1c51d5a70c4509eedae8a27555733edc.vk": { + "cid": "QmR9i9KL3vhhAqTBGj1bPPC7LvkptxrH9RvxJxLN1vvsBE", + "digest": "0f8ec542485568fa3468c066e9fed82b", "sector_size": 34359738368 }, - "v26-stacked-proof-of-replication-merkletree-poseidon_hasher-8-8-2-sha256_hasher-96f1b4a04c5c51e4759bbf224bbc2ef5a42c7100f16ec0637123f16a845ddfb2.params": { - "cid": "QmY7DitNKXFeLQt9QoVQkfjM1EvRnprqUVxjmkTXkHDNka", - "digest": "f27271c0537ba65ade2ec045f8fbd069", + "v28-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-0-559e581f022bb4e4ec6e719e563bf0e026ad6de42e56c18714a2c692b1b88d7e.params": { + "cid": "Qmdtczp7p4wrbDofmHdGhiixn9irAcN77mV9AEHZBaTt1i", + "digest": "d84f79a16fe40e9e25a36e2107bb1ba0", + "sector_size": 34359738368 + }, + "v28-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-0-559e581f022bb4e4ec6e719e563bf0e026ad6de42e56c18714a2c692b1b88d7e.vk": { + "cid": "QmZCvxKcKP97vDAk8Nxs9R1fWtqpjQrAhhfXPoCi1nkDoF", + "digest": "fc02943678dd119e69e7fab8420e8819", + "sector_size": 34359738368 + }, + "v28-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-2-2627e4006b67f99cef990c0a47d5426cb7ab0a0ad58fc1061547bf2d28b09def.params": { + "cid": "QmeAN4vuANhXsF8xP2Lx5j2L6yMSdogLzpcvqCJThRGK1V", + "digest": "3810b7780ac0e299b22ae70f1f94c9bc", "sector_size": 68719476736 }, - "v26-stacked-proof-of-replication-merkletree-poseidon_hasher-8-8-2-sha256_hasher-96f1b4a04c5c51e4759bbf224bbc2ef5a42c7100f16ec0637123f16a845ddfb2.vk": { - "cid": "QmUJsvoCuQ4LszPmeRVAkMYb5qY95ctz3UXKhu8xLzyFKo", - "digest": "576b292938c6c9d0a0e721bd867a543b", + "v28-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-2-2627e4006b67f99cef990c0a47d5426cb7ab0a0ad58fc1061547bf2d28b09def.vk": { + "cid": "QmWV8rqZLxs1oQN9jxNWmnT1YdgLwCcscv94VARrhHf1T7", + "digest": "59d2bf1857adc59a4f08fcf2afaa916b", + "sector_size": 68719476736 + }, + "v28-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-2-b62098629d07946e9028127e70295ed996fe3ed25b0f9f88eb610a0ab4385a3c.params": { + "cid": "QmVkrXc1SLcpgcudK5J25HH93QvR9tNsVhVTYHm5UymXAz", + "digest": "2170a91ad5bae22ea61f2ea766630322", + "sector_size": 68719476736 + }, + "v28-proof-of-spacetime-fallback-merkletree-poseidon_hasher-8-8-2-b62098629d07946e9028127e70295ed996fe3ed25b0f9f88eb610a0ab4385a3c.vk": { + "cid": "QmbfQjPD7EpzjhWGmvWAsyN2mAZ4PcYhsf3ujuhU9CSuBm", + "digest": "6d3789148fb6466d07ee1e24d6292fd6", + "sector_size": 68719476736 + }, + "v28-stacked-proof-of-replication-merkletree-poseidon_hasher-8-0-0-sha256_hasher-032d3138d22506ec0082ed72b2dcba18df18477904e35bafee82b3793b06832f.params": { + "cid": "QmWceMgnWYLopMuM4AoGMvGEau7tNe5UK83XFjH5V9B17h", + "digest": "434fb1338ecfaf0f59256f30dde4968f", + "sector_size": 2048 + }, + "v28-stacked-proof-of-replication-merkletree-poseidon_hasher-8-0-0-sha256_hasher-032d3138d22506ec0082ed72b2dcba18df18477904e35bafee82b3793b06832f.vk": { + "cid": "QmamahpFCstMUqHi2qGtVoDnRrsXhid86qsfvoyCTKJqHr", + "digest": "dc1ade9929ade1708238f155343044ac", + "sector_size": 2048 + }, + "v28-stacked-proof-of-replication-merkletree-poseidon_hasher-8-0-0-sha256_hasher-6babf46ce344ae495d558e7770a585b2382d54f225af8ed0397b8be7c3fcd472.params": { + "cid": "QmYBpTt7LWNAWr1JXThV5VxX7wsQFLd1PHrGYVbrU1EZjC", + "digest": "6c77597eb91ab936c1cef4cf19eba1b3", + "sector_size": 536870912 + }, + "v28-stacked-proof-of-replication-merkletree-poseidon_hasher-8-0-0-sha256_hasher-6babf46ce344ae495d558e7770a585b2382d54f225af8ed0397b8be7c3fcd472.vk": { + "cid": "QmWionkqH2B6TXivzBSQeSyBxojaiAFbzhjtwYRrfwd8nH", + "digest": "065179da19fbe515507267677f02823e", + "sector_size": 536870912 + }, + "v28-stacked-proof-of-replication-merkletree-poseidon_hasher-8-0-0-sha256_hasher-ecd683648512ab1765faa2a5f14bab48f676e633467f0aa8aad4b55dcb0652bb.params": { + "cid": "QmPXAPPuQtuQz7Zz3MHMAMEtsYwqM1o9H1csPLeiMUQwZH", + "digest": "09e612e4eeb7a0eb95679a88404f960c", + "sector_size": 8388608 + }, + "v28-stacked-proof-of-replication-merkletree-poseidon_hasher-8-0-0-sha256_hasher-ecd683648512ab1765faa2a5f14bab48f676e633467f0aa8aad4b55dcb0652bb.vk": { + "cid": "QmYCuipFyvVW1GojdMrjK1JnMobXtT4zRCZs1CGxjizs99", + "digest": "b687beb9adbd9dabe265a7e3620813e4", + "sector_size": 8388608 + }, + "v28-stacked-proof-of-replication-merkletree-poseidon_hasher-8-8-0-sha256_hasher-82a357d2f2ca81dc61bb45f4a762807aedee1b0a53fd6c4e77b46a01bfef7820.params": { + "cid": "QmengpM684XLQfG8754ToonszgEg2bQeAGUan5uXTHUQzJ", + "digest": "6a388072a518cf46ebd661f5cc46900a", + "sector_size": 34359738368 + }, + "v28-stacked-proof-of-replication-merkletree-poseidon_hasher-8-8-0-sha256_hasher-82a357d2f2ca81dc61bb45f4a762807aedee1b0a53fd6c4e77b46a01bfef7820.vk": { + "cid": "Qmf93EMrADXAK6CyiSfE8xx45fkMfR3uzKEPCvZC1n2kzb", + "digest": "0c7b4aac1c40fdb7eb82bc355b41addf", + "sector_size": 34359738368 + }, + "v28-stacked-proof-of-replication-merkletree-poseidon_hasher-8-8-2-sha256_hasher-96f1b4a04c5c51e4759bbf224bbc2ef5a42c7100f16ec0637123f16a845ddfb2.params": { + "cid": "QmS7ye6Ri2MfFzCkcUJ7FQ6zxDKuJ6J6B8k5PN7wzSR9sX", + "digest": "1801f8a6e1b00bceb00cc27314bb5ce3", + "sector_size": 68719476736 + }, + "v28-stacked-proof-of-replication-merkletree-poseidon_hasher-8-8-2-sha256_hasher-96f1b4a04c5c51e4759bbf224bbc2ef5a42c7100f16ec0637123f16a845ddfb2.vk": { + "cid": "QmehSmC6BhrgRZakPDta2ewoH9nosNzdjCqQRXsNFNUkLN", + "digest": "a89884252c04c298d0b3c81bfd884164", "sector_size": 68719476736 } -} \ No newline at end of file +} diff --git a/build/proof-params/srs-inner-product.json b/build/proof-params/srs-inner-product.json new file mode 100644 index 000000000..8566bf5fd --- /dev/null +++ b/build/proof-params/srs-inner-product.json @@ -0,0 +1,7 @@ +{ + "v28-fil-inner-product-v1.srs": { + "cid": "Qmdq44DjcQnFfU3PJcdX7J49GCqcUYszr1TxMbHtAkvQ3g", + "digest": "ae20310138f5ba81451d723f858e3797", + "sector_size": 0 + } +} diff --git a/build/tools.go b/build/tools.go index 638c335a6..adebe34dd 100644 --- a/build/tools.go +++ b/build/tools.go @@ -1,7 +1,11 @@ -//+build tools +//go:build tools +// +build tools package build import ( + _ "github.com/GeertJohan/go.rice/rice" + _ "github.com/golang/mock/mockgen" _ "github.com/whyrusleeping/bencher" + _ "golang.org/x/tools/cmd/stringer" ) diff --git a/build/version.go b/build/version.go index b704f2e80..2b7cbeda2 100644 --- a/build/version.go +++ b/build/version.go @@ -1,44 +1,48 @@ package build -import "fmt" +import "os" var CurrentCommit string - -// BuildVersion is the local build version, set by build system -const BuildVersion = "0.3.0" - -var UserVersion = BuildVersion + CurrentCommit - -type Version uint32 - -func newVer(major, minor, patch uint8) Version { - return Version(uint32(major)<<16 | uint32(minor)<<8 | uint32(patch)) -} - -// Ints returns (major, minor, patch) versions -func (ve Version) Ints() (uint32, uint32, uint32) { - v := uint32(ve) - return (v & majorOnlyMask) >> 16, (v & minorOnlyMask) >> 8, v & patchOnlyMask -} - -func (ve Version) String() string { - vmj, vmi, vp := ve.Ints() - return fmt.Sprintf("%d.%d.%d", vmj, vmi, vp) -} - -func (ve Version) EqMajorMinor(v2 Version) bool { - return ve&minorMask == v2&minorMask -} - -// APIVersion is a semver version of the rpc api exposed -var APIVersion Version = newVer(0, 3, 0) +var BuildType int const ( - majorMask = 0xff0000 - minorMask = 0xffff00 - patchMask = 0xffffff - - majorOnlyMask = 0xff0000 - minorOnlyMask = 0x00ff00 - patchOnlyMask = 0x0000ff + BuildDefault = 0 + BuildMainnet = 0x1 + Build2k = 0x2 + BuildDebug = 0x3 + BuildCalibnet = 0x4 + BuildInteropnet = 0x5 + BuildButterflynet = 0x7 ) + +func BuildTypeString() string { + switch BuildType { + case BuildDefault: + return "" + case BuildMainnet: + return "+mainnet" + case Build2k: + return "+2k" + case BuildDebug: + return "+debug" + case BuildCalibnet: + return "+calibnet" + case BuildInteropnet: + return "+interopnet" + case BuildButterflynet: + return "+butterflynet" + default: + return "+huh?" + } +} + +// BuildVersion is the local build version +const BuildVersion = "1.23.2-dev" + +func UserVersion() string { + if os.Getenv("LOTUS_VERSION_IGNORE_COMMIT") == "1" { + return BuildVersion + } + + return BuildVersion + BuildTypeString() + CurrentCommit +} diff --git a/chain/actors/actor_cids.go b/chain/actors/actor_cids.go new file mode 100644 index 000000000..ad9ae4909 --- /dev/null +++ b/chain/actors/actor_cids.go @@ -0,0 +1,330 @@ +package actors + +import ( + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + builtin3 "github.com/filecoin-project/specs-actors/v3/actors/builtin" + builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" + builtin6 "github.com/filecoin-project/specs-actors/v6/actors/builtin" + builtin7 "github.com/filecoin-project/specs-actors/v7/actors/builtin" +) + +// GetActorCodeID looks up a builtin actor's code CID by actor version and canonical actor name. +func GetActorCodeID(av actorstypes.Version, name string) (cid.Cid, bool) { + + // Actors V8 and above + if av >= actorstypes.Version8 { + if cids, ok := GetActorCodeIDsFromManifest(av); ok { + c, ok := cids[name] + return c, ok + } + } + + // Actors V7 and lower + switch name { + + case manifest.AccountKey: + switch av { + + case actorstypes.Version0: + return builtin0.AccountActorCodeID, true + + case actorstypes.Version2: + return builtin2.AccountActorCodeID, true + + case actorstypes.Version3: + return builtin3.AccountActorCodeID, true + + case actorstypes.Version4: + return builtin4.AccountActorCodeID, true + + case actorstypes.Version5: + return builtin5.AccountActorCodeID, true + + case actorstypes.Version6: + return builtin6.AccountActorCodeID, true + + case actorstypes.Version7: + return builtin7.AccountActorCodeID, true + } + + case manifest.CronKey: + switch av { + + case actorstypes.Version0: + return builtin0.CronActorCodeID, true + + case actorstypes.Version2: + return builtin2.CronActorCodeID, true + + case actorstypes.Version3: + return builtin3.CronActorCodeID, true + + case actorstypes.Version4: + return builtin4.CronActorCodeID, true + + case actorstypes.Version5: + return builtin5.CronActorCodeID, true + + case actorstypes.Version6: + return builtin6.CronActorCodeID, true + + case actorstypes.Version7: + return builtin7.CronActorCodeID, true + } + + case manifest.InitKey: + switch av { + + case actorstypes.Version0: + return builtin0.InitActorCodeID, true + + case actorstypes.Version2: + return builtin2.InitActorCodeID, true + + case actorstypes.Version3: + return builtin3.InitActorCodeID, true + + case actorstypes.Version4: + return builtin4.InitActorCodeID, true + + case actorstypes.Version5: + return builtin5.InitActorCodeID, true + + case actorstypes.Version6: + return builtin6.InitActorCodeID, true + + case actorstypes.Version7: + return builtin7.InitActorCodeID, true + } + + case manifest.MarketKey: + switch av { + + case actorstypes.Version0: + return builtin0.StorageMarketActorCodeID, true + + case actorstypes.Version2: + return builtin2.StorageMarketActorCodeID, true + + case actorstypes.Version3: + return builtin3.StorageMarketActorCodeID, true + + case actorstypes.Version4: + return builtin4.StorageMarketActorCodeID, true + + case actorstypes.Version5: + return builtin5.StorageMarketActorCodeID, true + + case actorstypes.Version6: + return builtin6.StorageMarketActorCodeID, true + + case actorstypes.Version7: + return builtin7.StorageMarketActorCodeID, true + } + + case manifest.MinerKey: + switch av { + + case actorstypes.Version0: + return builtin0.StorageMinerActorCodeID, true + + case actorstypes.Version2: + return builtin2.StorageMinerActorCodeID, true + + case actorstypes.Version3: + return builtin3.StorageMinerActorCodeID, true + + case actorstypes.Version4: + return builtin4.StorageMinerActorCodeID, true + + case actorstypes.Version5: + return builtin5.StorageMinerActorCodeID, true + + case actorstypes.Version6: + return builtin6.StorageMinerActorCodeID, true + + case actorstypes.Version7: + return builtin7.StorageMinerActorCodeID, true + } + + case manifest.MultisigKey: + switch av { + + case actorstypes.Version0: + return builtin0.MultisigActorCodeID, true + + case actorstypes.Version2: + return builtin2.MultisigActorCodeID, true + + case actorstypes.Version3: + return builtin3.MultisigActorCodeID, true + + case actorstypes.Version4: + return builtin4.MultisigActorCodeID, true + + case actorstypes.Version5: + return builtin5.MultisigActorCodeID, true + + case actorstypes.Version6: + return builtin6.MultisigActorCodeID, true + + case actorstypes.Version7: + return builtin7.MultisigActorCodeID, true + } + + case manifest.PaychKey: + switch av { + + case actorstypes.Version0: + return builtin0.PaymentChannelActorCodeID, true + + case actorstypes.Version2: + return builtin2.PaymentChannelActorCodeID, true + + case actorstypes.Version3: + return builtin3.PaymentChannelActorCodeID, true + + case actorstypes.Version4: + return builtin4.PaymentChannelActorCodeID, true + + case actorstypes.Version5: + return builtin5.PaymentChannelActorCodeID, true + + case actorstypes.Version6: + return builtin6.PaymentChannelActorCodeID, true + + case actorstypes.Version7: + return builtin7.PaymentChannelActorCodeID, true + } + + case manifest.PowerKey: + switch av { + + case actorstypes.Version0: + return builtin0.StoragePowerActorCodeID, true + + case actorstypes.Version2: + return builtin2.StoragePowerActorCodeID, true + + case actorstypes.Version3: + return builtin3.StoragePowerActorCodeID, true + + case actorstypes.Version4: + return builtin4.StoragePowerActorCodeID, true + + case actorstypes.Version5: + return builtin5.StoragePowerActorCodeID, true + + case actorstypes.Version6: + return builtin6.StoragePowerActorCodeID, true + + case actorstypes.Version7: + return builtin7.StoragePowerActorCodeID, true + } + + case manifest.RewardKey: + switch av { + + case actorstypes.Version0: + return builtin0.RewardActorCodeID, true + + case actorstypes.Version2: + return builtin2.RewardActorCodeID, true + + case actorstypes.Version3: + return builtin3.RewardActorCodeID, true + + case actorstypes.Version4: + return builtin4.RewardActorCodeID, true + + case actorstypes.Version5: + return builtin5.RewardActorCodeID, true + + case actorstypes.Version6: + return builtin6.RewardActorCodeID, true + + case actorstypes.Version7: + return builtin7.RewardActorCodeID, true + } + + case manifest.SystemKey: + switch av { + + case actorstypes.Version0: + return builtin0.SystemActorCodeID, true + + case actorstypes.Version2: + return builtin2.SystemActorCodeID, true + + case actorstypes.Version3: + return builtin3.SystemActorCodeID, true + + case actorstypes.Version4: + return builtin4.SystemActorCodeID, true + + case actorstypes.Version5: + return builtin5.SystemActorCodeID, true + + case actorstypes.Version6: + return builtin6.SystemActorCodeID, true + + case actorstypes.Version7: + return builtin7.SystemActorCodeID, true + } + + case manifest.VerifregKey: + switch av { + + case actorstypes.Version0: + return builtin0.VerifiedRegistryActorCodeID, true + + case actorstypes.Version2: + return builtin2.VerifiedRegistryActorCodeID, true + + case actorstypes.Version3: + return builtin3.VerifiedRegistryActorCodeID, true + + case actorstypes.Version4: + return builtin4.VerifiedRegistryActorCodeID, true + + case actorstypes.Version5: + return builtin5.VerifiedRegistryActorCodeID, true + + case actorstypes.Version6: + return builtin6.VerifiedRegistryActorCodeID, true + + case actorstypes.Version7: + return builtin7.VerifiedRegistryActorCodeID, true + } + } + + return cid.Undef, false +} + +// GetActorCodeIDs looks up all builtin actor's code CIDs by actor version. +func GetActorCodeIDs(av actorstypes.Version) (map[string]cid.Cid, error) { + cids, ok := GetActorCodeIDsFromManifest(av) + if ok { + return cids, nil + } + + actorsKeys := manifest.GetBuiltinActorsKeys(av) + synthCids := make(map[string]cid.Cid) + + for _, key := range actorsKeys { + c, ok := GetActorCodeID(av, key) + if !ok { + return nil, xerrors.Errorf("could not find builtin actor cids for Actors version %d", av) + } + synthCids[key] = c + } + + return synthCids, nil +} diff --git a/chain/actors/adt/adt.go b/chain/actors/adt/adt.go new file mode 100644 index 000000000..084471bb8 --- /dev/null +++ b/chain/actors/adt/adt.go @@ -0,0 +1,29 @@ +package adt + +import ( + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/cbor" +) + +type Map interface { + Root() (cid.Cid, error) + + Put(k abi.Keyer, v cbor.Marshaler) error + Get(k abi.Keyer, v cbor.Unmarshaler) (bool, error) + Delete(k abi.Keyer) error + + ForEach(v cbor.Unmarshaler, fn func(key string) error) error +} + +type Array interface { + Root() (cid.Cid, error) + + Set(idx uint64, v cbor.Marshaler) error + Get(idx uint64, v cbor.Unmarshaler) (bool, error) + Delete(idx uint64) error + Length() uint64 + + ForEach(v cbor.Unmarshaler, fn func(idx int64) error) error +} diff --git a/chain/actors/adt/diff_adt.go b/chain/actors/adt/diff_adt.go new file mode 100644 index 000000000..1d1e02ea1 --- /dev/null +++ b/chain/actors/adt/diff_adt.go @@ -0,0 +1,123 @@ +package adt + +import ( + "bytes" + + typegen "github.com/whyrusleeping/cbor-gen" + + "github.com/filecoin-project/go-state-types/abi" +) + +// AdtArrayDiff generalizes adt.Array diffing by accepting a Deferred type that can unmarshalled to its corresponding struct +// in an interface implantation. +// Add should be called when a new k,v is added to the array +// Modify should be called when a value is modified in the array +// Remove should be called when a value is removed from the array +type AdtArrayDiff interface { + Add(key uint64, val *typegen.Deferred) error + Modify(key uint64, from, to *typegen.Deferred) error + Remove(key uint64, val *typegen.Deferred) error +} + +// TODO Performance can be improved by diffing the underlying IPLD graph, e.g. https://github.com/ipfs/go-merkledag/blob/749fd8717d46b4f34c9ce08253070079c89bc56d/dagutils/diff.go#L104 +// CBOR Marshaling will likely be the largest performance bottleneck here. + +// DiffAdtArray accepts two *adt.Array's and an AdtArrayDiff implementation. It does the following: +// - All values that exist in preArr and not in curArr are passed to AdtArrayDiff.Remove() +// - All values that exist in curArr nnd not in prevArr are passed to adtArrayDiff.Add() +// - All values that exist in preArr and in curArr are passed to AdtArrayDiff.Modify() +// - It is the responsibility of AdtArrayDiff.Modify() to determine if the values it was passed have been modified. +func DiffAdtArray(preArr, curArr Array, out AdtArrayDiff) error { + notNew := make(map[int64]struct{}, curArr.Length()) + prevVal := new(typegen.Deferred) + if err := preArr.ForEach(prevVal, func(i int64) error { + curVal := new(typegen.Deferred) + found, err := curArr.Get(uint64(i), curVal) + if err != nil { + return err + } + if !found { + if err := out.Remove(uint64(i), prevVal); err != nil { + return err + } + return nil + } + + // no modification + if !bytes.Equal(prevVal.Raw, curVal.Raw) { + if err := out.Modify(uint64(i), prevVal, curVal); err != nil { + return err + } + } + notNew[i] = struct{}{} + return nil + }); err != nil { + return err + } + + curVal := new(typegen.Deferred) + return curArr.ForEach(curVal, func(i int64) error { + if _, ok := notNew[i]; ok { + return nil + } + return out.Add(uint64(i), curVal) + }) +} + +// TODO Performance can be improved by diffing the underlying IPLD graph, e.g. https://github.com/ipfs/go-merkledag/blob/749fd8717d46b4f34c9ce08253070079c89bc56d/dagutils/diff.go#L104 +// CBOR Marshaling will likely be the largest performance bottleneck here. + +// AdtMapDiff generalizes adt.Map diffing by accepting a Deferred type that can unmarshalled to its corresponding struct +// in an interface implantation. +// AsKey should return the Keyer implementation specific to the map +// Add should be called when a new k,v is added to the map +// Modify should be called when a value is modified in the map +// Remove should be called when a value is removed from the map +type AdtMapDiff interface { + AsKey(key string) (abi.Keyer, error) + Add(key string, val *typegen.Deferred) error + Modify(key string, from, to *typegen.Deferred) error + Remove(key string, val *typegen.Deferred) error +} + +func DiffAdtMap(preMap, curMap Map, out AdtMapDiff) error { + notNew := make(map[string]struct{}) + prevVal := new(typegen.Deferred) + if err := preMap.ForEach(prevVal, func(key string) error { + curVal := new(typegen.Deferred) + k, err := out.AsKey(key) + if err != nil { + return err + } + + found, err := curMap.Get(k, curVal) + if err != nil { + return err + } + if !found { + if err := out.Remove(key, prevVal); err != nil { + return err + } + return nil + } + + // no modification + if !bytes.Equal(prevVal.Raw, curVal.Raw) { + if err := out.Modify(key, prevVal, curVal); err != nil { + return err + } + } + notNew[key] = struct{}{} + return nil + }); err != nil { + return err + } + + curVal := new(typegen.Deferred) + return curMap.ForEach(curVal, func(key string) error { + if _, ok := notNew[key]; ok { + return nil + } + return out.Add(key, curVal) + }) +} diff --git a/chain/actors/adt/diff_adt_test.go b/chain/actors/adt/diff_adt_test.go new file mode 100644 index 000000000..7ea3c53e5 --- /dev/null +++ b/chain/actors/adt/diff_adt_test.go @@ -0,0 +1,302 @@ +// stm: #unit +package adt + +import ( + "bytes" + "context" + "testing" + + cbornode "github.com/ipfs/go-ipld-cbor" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + typegen "github.com/whyrusleeping/cbor-gen" + + "github.com/filecoin-project/go-state-types/abi" + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + adt2 "github.com/filecoin-project/specs-actors/v2/actors/util/adt" + + bstore "github.com/filecoin-project/lotus/blockstore" +) + +func TestDiffAdtArray(t *testing.T) { + ctxstoreA := newContextStore() + ctxstoreB := newContextStore() + + arrA := adt2.MakeEmptyArray(ctxstoreA) + arrB := adt2.MakeEmptyArray(ctxstoreB) + + require.NoError(t, arrA.Set(0, builtin2.CBORBytes([]byte{0}))) // delete + + require.NoError(t, arrA.Set(1, builtin2.CBORBytes([]byte{0}))) // modify + require.NoError(t, arrB.Set(1, builtin2.CBORBytes([]byte{1}))) + + require.NoError(t, arrA.Set(2, builtin2.CBORBytes([]byte{1}))) // delete + + require.NoError(t, arrA.Set(3, builtin2.CBORBytes([]byte{0}))) // noop + require.NoError(t, arrB.Set(3, builtin2.CBORBytes([]byte{0}))) + + require.NoError(t, arrA.Set(4, builtin2.CBORBytes([]byte{0}))) // modify + require.NoError(t, arrB.Set(4, builtin2.CBORBytes([]byte{6}))) + + require.NoError(t, arrB.Set(5, builtin2.CBORBytes{8})) // add + require.NoError(t, arrB.Set(6, builtin2.CBORBytes{9})) // add + + changes := new(TestDiffArray) + + //stm: @CHAIN_ADT_ARRAY_DIFF_001 + assert.NoError(t, DiffAdtArray(arrA, arrB, changes)) + assert.NotNil(t, changes) + + assert.Equal(t, 2, len(changes.Added)) + // keys 5 and 6 were added + assert.EqualValues(t, uint64(5), changes.Added[0].key) + assert.EqualValues(t, []byte{8}, changes.Added[0].val) + assert.EqualValues(t, uint64(6), changes.Added[1].key) + assert.EqualValues(t, []byte{9}, changes.Added[1].val) + + assert.Equal(t, 2, len(changes.Modified)) + // keys 1 and 4 were modified + assert.EqualValues(t, uint64(1), changes.Modified[0].From.key) + assert.EqualValues(t, []byte{0}, changes.Modified[0].From.val) + assert.EqualValues(t, uint64(1), changes.Modified[0].To.key) + assert.EqualValues(t, []byte{1}, changes.Modified[0].To.val) + assert.EqualValues(t, uint64(4), changes.Modified[1].From.key) + assert.EqualValues(t, []byte{0}, changes.Modified[1].From.val) + assert.EqualValues(t, uint64(4), changes.Modified[1].To.key) + assert.EqualValues(t, []byte{6}, changes.Modified[1].To.val) + + assert.Equal(t, 2, len(changes.Removed)) + // keys 0 and 2 were deleted + assert.EqualValues(t, uint64(0), changes.Removed[0].key) + assert.EqualValues(t, []byte{0}, changes.Removed[0].val) + assert.EqualValues(t, uint64(2), changes.Removed[1].key) + assert.EqualValues(t, []byte{1}, changes.Removed[1].val) +} + +func TestDiffAdtMap(t *testing.T) { + ctxstoreA := newContextStore() + ctxstoreB := newContextStore() + + mapA := adt2.MakeEmptyMap(ctxstoreA) + mapB := adt2.MakeEmptyMap(ctxstoreB) + + require.NoError(t, mapA.Put(abi.UIntKey(0), builtin2.CBORBytes([]byte{0}))) // delete + + require.NoError(t, mapA.Put(abi.UIntKey(1), builtin2.CBORBytes([]byte{0}))) // modify + require.NoError(t, mapB.Put(abi.UIntKey(1), builtin2.CBORBytes([]byte{1}))) + + require.NoError(t, mapA.Put(abi.UIntKey(2), builtin2.CBORBytes([]byte{1}))) // delete + + require.NoError(t, mapA.Put(abi.UIntKey(3), builtin2.CBORBytes([]byte{0}))) // noop + require.NoError(t, mapB.Put(abi.UIntKey(3), builtin2.CBORBytes([]byte{0}))) + + require.NoError(t, mapA.Put(abi.UIntKey(4), builtin2.CBORBytes([]byte{0}))) // modify + require.NoError(t, mapB.Put(abi.UIntKey(4), builtin2.CBORBytes([]byte{6}))) + + require.NoError(t, mapB.Put(abi.UIntKey(5), builtin2.CBORBytes{8})) // add + require.NoError(t, mapB.Put(abi.UIntKey(6), builtin2.CBORBytes{9})) // add + + changes := new(TestDiffMap) + + //stm: @CHAIN_ADT_MAP_DIFF_001 + assert.NoError(t, DiffAdtMap(mapA, mapB, changes)) + assert.NotNil(t, changes) + + assert.Equal(t, 2, len(changes.Added)) + // keys 5 and 6 were added + assert.EqualValues(t, uint64(6), changes.Added[0].key) + assert.EqualValues(t, []byte{9}, changes.Added[0].val) + assert.EqualValues(t, uint64(5), changes.Added[1].key) + assert.EqualValues(t, []byte{8}, changes.Added[1].val) + + assert.Equal(t, 2, len(changes.Modified)) + // keys 1 and 4 were modified + assert.EqualValues(t, uint64(1), changes.Modified[0].From.key) + assert.EqualValues(t, []byte{0}, changes.Modified[0].From.val) + assert.EqualValues(t, uint64(1), changes.Modified[0].To.key) + assert.EqualValues(t, []byte{1}, changes.Modified[0].To.val) + assert.EqualValues(t, uint64(4), changes.Modified[1].From.key) + assert.EqualValues(t, []byte{0}, changes.Modified[1].From.val) + assert.EqualValues(t, uint64(4), changes.Modified[1].To.key) + assert.EqualValues(t, []byte{6}, changes.Modified[1].To.val) + + assert.Equal(t, 2, len(changes.Removed)) + // keys 0 and 2 were deleted + assert.EqualValues(t, uint64(0), changes.Removed[0].key) + assert.EqualValues(t, []byte{0}, changes.Removed[0].val) + assert.EqualValues(t, uint64(2), changes.Removed[1].key) + assert.EqualValues(t, []byte{1}, changes.Removed[1].val) + +} + +type TestDiffMap struct { + Added []adtMapDiffResult + Modified []TestAdtMapDiffModified + Removed []adtMapDiffResult +} + +var _ AdtMapDiff = &TestDiffMap{} + +func (t *TestDiffMap) AsKey(key string) (abi.Keyer, error) { + k, err := abi.ParseUIntKey(key) + if err != nil { + return nil, err + } + return abi.UIntKey(k), nil +} + +func (t *TestDiffMap) Add(key string, val *typegen.Deferred) error { + v := new(builtin2.CBORBytes) + err := v.UnmarshalCBOR(bytes.NewReader(val.Raw)) + if err != nil { + return err + } + k, err := abi.ParseUIntKey(key) + if err != nil { + return err + } + t.Added = append(t.Added, adtMapDiffResult{ + key: k, + val: *v, + }) + return nil +} + +func (t *TestDiffMap) Modify(key string, from, to *typegen.Deferred) error { + vFrom := new(builtin2.CBORBytes) + err := vFrom.UnmarshalCBOR(bytes.NewReader(from.Raw)) + if err != nil { + return err + } + + vTo := new(builtin2.CBORBytes) + err = vTo.UnmarshalCBOR(bytes.NewReader(to.Raw)) + if err != nil { + return err + } + + k, err := abi.ParseUIntKey(key) + if err != nil { + return err + } + + if !bytes.Equal(*vFrom, *vTo) { + t.Modified = append(t.Modified, TestAdtMapDiffModified{ + From: adtMapDiffResult{ + key: k, + val: *vFrom, + }, + To: adtMapDiffResult{ + key: k, + val: *vTo, + }, + }) + } + return nil +} + +func (t *TestDiffMap) Remove(key string, val *typegen.Deferred) error { + v := new(builtin2.CBORBytes) + err := v.UnmarshalCBOR(bytes.NewReader(val.Raw)) + if err != nil { + return err + } + k, err := abi.ParseUIntKey(key) + if err != nil { + return err + } + t.Removed = append(t.Removed, adtMapDiffResult{ + key: k, + val: *v, + }) + return nil +} + +type adtMapDiffResult struct { + key uint64 + val builtin2.CBORBytes +} + +type TestAdtMapDiffModified struct { + From adtMapDiffResult + To adtMapDiffResult +} + +type adtArrayDiffResult struct { + key uint64 + val builtin2.CBORBytes +} + +type TestDiffArray struct { + Added []adtArrayDiffResult + Modified []TestAdtArrayDiffModified + Removed []adtArrayDiffResult +} + +var _ AdtArrayDiff = &TestDiffArray{} + +type TestAdtArrayDiffModified struct { + From adtArrayDiffResult + To adtArrayDiffResult +} + +func (t *TestDiffArray) Add(key uint64, val *typegen.Deferred) error { + v := new(builtin2.CBORBytes) + err := v.UnmarshalCBOR(bytes.NewReader(val.Raw)) + if err != nil { + return err + } + t.Added = append(t.Added, adtArrayDiffResult{ + key: key, + val: *v, + }) + return nil +} + +func (t *TestDiffArray) Modify(key uint64, from, to *typegen.Deferred) error { + vFrom := new(builtin2.CBORBytes) + err := vFrom.UnmarshalCBOR(bytes.NewReader(from.Raw)) + if err != nil { + return err + } + + vTo := new(builtin2.CBORBytes) + err = vTo.UnmarshalCBOR(bytes.NewReader(to.Raw)) + if err != nil { + return err + } + + if !bytes.Equal(*vFrom, *vTo) { + t.Modified = append(t.Modified, TestAdtArrayDiffModified{ + From: adtArrayDiffResult{ + key: key, + val: *vFrom, + }, + To: adtArrayDiffResult{ + key: key, + val: *vTo, + }, + }) + } + return nil +} + +func (t *TestDiffArray) Remove(key uint64, val *typegen.Deferred) error { + v := new(builtin2.CBORBytes) + err := v.UnmarshalCBOR(bytes.NewReader(val.Raw)) + if err != nil { + return err + } + t.Removed = append(t.Removed, adtArrayDiffResult{ + key: key, + val: *v, + }) + return nil +} + +func newContextStore() Store { + ctx := context.Background() + bs := bstore.NewMemorySync() + store := cbornode.NewCborStore(bs) + return WrapStore(ctx, store) +} diff --git a/chain/actors/adt/store.go b/chain/actors/adt/store.go new file mode 100644 index 000000000..71a7df7cb --- /dev/null +++ b/chain/actors/adt/store.go @@ -0,0 +1,18 @@ +package adt + +import ( + "context" + + cbor "github.com/ipfs/go-ipld-cbor" + + "github.com/filecoin-project/specs-actors/actors/util/adt" +) + +type Store interface { + Context() context.Context + cbor.IpldStore +} + +func WrapStore(ctx context.Context, store cbor.IpldStore) Store { + return adt.WrapStore(ctx, store) +} diff --git a/chain/actors/aerrors/error.go b/chain/actors/aerrors/error.go index e687982c8..132cb3baf 100644 --- a/chain/actors/aerrors/error.go +++ b/chain/actors/aerrors/error.go @@ -3,8 +3,9 @@ package aerrors import ( "fmt" - "github.com/filecoin-project/specs-actors/actors/runtime/exitcode" "golang.org/x/xerrors" + + "github.com/filecoin-project/go-state-types/exitcode" ) func IsFatal(err ActorError) bool { diff --git a/chain/actors/aerrors/error_test.go b/chain/actors/aerrors/error_test.go index 4d87ac396..8c3738c88 100644 --- a/chain/actors/aerrors/error_test.go +++ b/chain/actors/aerrors/error_test.go @@ -1,16 +1,19 @@ +// stm: #unit package aerrors_test import ( "testing" - . "github.com/filecoin-project/lotus/chain/actors/aerrors" - "github.com/filecoin-project/specs-actors/actors/runtime/exitcode" - "github.com/stretchr/testify/assert" "golang.org/x/xerrors" + + "github.com/filecoin-project/go-state-types/exitcode" + + . "github.com/filecoin-project/lotus/chain/actors/aerrors" ) func TestFatalError(t *testing.T) { + //stm: @OTHER_IMPLEMENTATION_ACTOR_ERRORS_001 e1 := xerrors.New("out of disk space") e2 := xerrors.Errorf("could not put node: %w", e1) e3 := xerrors.Errorf("could not save head: %w", e2) @@ -24,6 +27,7 @@ func TestFatalError(t *testing.T) { assert.True(t, IsFatal(aw4), "should be fatal") } func TestAbsorbeError(t *testing.T) { + //stm: @OTHER_IMPLEMENTATION_ACTOR_ERRORS_001 e1 := xerrors.New("EOF") e2 := xerrors.Errorf("could not decode: %w", e1) ae := Absorb(e2, 35, "failed to decode CBOR") diff --git a/chain/actors/aerrors/wrap.go b/chain/actors/aerrors/wrap.go index 2e7444101..6bf8a3009 100644 --- a/chain/actors/aerrors/wrap.go +++ b/chain/actors/aerrors/wrap.go @@ -4,9 +4,10 @@ import ( "errors" "fmt" - "github.com/filecoin-project/specs-actors/actors/runtime/exitcode" cbor "github.com/ipfs/go-ipld-cbor" "golang.org/x/xerrors" + + "github.com/filecoin-project/go-state-types/exitcode" ) // New creates a new non-fatal error @@ -50,6 +51,7 @@ func Newf(retCode exitcode.ExitCode, format string, args ...interface{}) ActorEr } // todo: bit hacky + func NewfSkip(skip int, retCode exitcode.ExitCode, format string, args ...interface{}) ActorError { if retCode == 0 { return &actorError{ diff --git a/chain/actors/agen/main.go b/chain/actors/agen/main.go new file mode 100644 index 000000000..811ea27e9 --- /dev/null +++ b/chain/actors/agen/main.go @@ -0,0 +1,268 @@ +package main + +import ( + "bytes" + "fmt" + "go/format" + "os" + "path/filepath" + "strconv" + "text/template" + + "golang.org/x/xerrors" + + lotusactors "github.com/filecoin-project/lotus/chain/actors" +) + +var actors = map[string][]int{ + "account": lotusactors.Versions, + "cron": lotusactors.Versions, + "init": lotusactors.Versions, + "market": lotusactors.Versions, + "miner": lotusactors.Versions, + "multisig": lotusactors.Versions, + "paych": lotusactors.Versions, + "power": lotusactors.Versions, + "system": lotusactors.Versions, + "reward": lotusactors.Versions, + "verifreg": lotusactors.Versions, + "datacap": lotusactors.Versions[8:], + "evm": lotusactors.Versions[9:], +} + +func main() { + if err := generateAdapters(); err != nil { + fmt.Println(err) + return + } + + if err := generatePolicy("chain/actors/policy/policy.go"); err != nil { + fmt.Println(err) + return + } + + if err := generateBuiltin("chain/actors/builtin/builtin.go"); err != nil { + fmt.Println(err) + return + } + + if err := generateRegistry("chain/actors/builtin/registry.go"); err != nil { + fmt.Println(err) + return + } +} + +func generateAdapters() error { + for act, versions := range actors { + actDir := filepath.Join("chain/actors/builtin", act) + + if err := generateState(actDir, versions); err != nil { + return err + } + + if err := generateMessages(actDir); err != nil { + return err + } + + { + af, err := os.ReadFile(filepath.Join(actDir, "actor.go.template")) + if err != nil { + return xerrors.Errorf("loading actor template: %w", err) + } + + tpl := template.Must(template.New("").Funcs(template.FuncMap{ + "import": func(v int) string { return getVersionImports()[v] }, + }).Parse(string(af))) + + var b bytes.Buffer + + err = tpl.Execute(&b, map[string]interface{}{ + "versions": versions, + "latestVersion": lotusactors.LatestVersion, + }) + if err != nil { + return err + } + + fmted, err := format.Source(b.Bytes()) + if err != nil { + return err + } + + if err := os.WriteFile(filepath.Join(actDir, fmt.Sprintf("%s.go", act)), fmted, 0666); err != nil { + return err + } + } + } + + return nil +} + +func generateState(actDir string, versions []int) error { + af, err := os.ReadFile(filepath.Join(actDir, "state.go.template")) + if err != nil { + if os.IsNotExist(err) { + return nil // skip + } + + return xerrors.Errorf("loading state adapter template: %w", err) + } + + for _, version := range versions { + tpl := template.Must(template.New("").Funcs(template.FuncMap{}).Parse(string(af))) + + var b bytes.Buffer + + err := tpl.Execute(&b, map[string]interface{}{ + "v": version, + "import": getVersionImports()[version], + "latestVersion": lotusactors.LatestVersion, + }) + if err != nil { + return err + } + + if err := os.WriteFile(filepath.Join(actDir, fmt.Sprintf("v%d.go", version)), b.Bytes(), 0666); err != nil { + return err + } + } + + return nil +} + +func generateMessages(actDir string) error { + af, err := os.ReadFile(filepath.Join(actDir, "message.go.template")) + if err != nil { + if os.IsNotExist(err) { + return nil // skip + } + + return xerrors.Errorf("loading message adapter template: %w", err) + } + + for _, version := range lotusactors.Versions { + tpl := template.Must(template.New("").Funcs(template.FuncMap{}).Parse(string(af))) + + var b bytes.Buffer + + err := tpl.Execute(&b, map[string]interface{}{ + "v": version, + "import": getVersionImports()[version], + "latestVersion": lotusactors.LatestVersion, + }) + if err != nil { + return err + } + + if err := os.WriteFile(filepath.Join(actDir, fmt.Sprintf("message%d.go", version)), b.Bytes(), 0666); err != nil { + return err + } + } + + return nil +} + +func generatePolicy(policyPath string) error { + + pf, err := os.ReadFile(policyPath + ".template") + if err != nil { + if os.IsNotExist(err) { + return nil // skip + } + + return xerrors.Errorf("loading policy template file: %w", err) + } + + tpl := template.Must(template.New("").Funcs(template.FuncMap{ + "import": func(v int) string { return getVersionImports()[v] }, + }).Parse(string(pf))) + var b bytes.Buffer + + err = tpl.Execute(&b, map[string]interface{}{ + "versions": lotusactors.Versions, + "latestVersion": lotusactors.LatestVersion, + }) + if err != nil { + return err + } + + if err := os.WriteFile(policyPath, b.Bytes(), 0666); err != nil { + return err + } + + return nil +} + +func generateBuiltin(builtinPath string) error { + + bf, err := os.ReadFile(builtinPath + ".template") + if err != nil { + if os.IsNotExist(err) { + return nil // skip + } + + return xerrors.Errorf("loading builtin template file: %w", err) + } + + tpl := template.Must(template.New("").Funcs(template.FuncMap{ + "import": func(v int) string { return getVersionImports()[v] }, + }).Parse(string(bf))) + var b bytes.Buffer + + err = tpl.Execute(&b, map[string]interface{}{ + "versions": lotusactors.Versions, + "latestVersion": lotusactors.LatestVersion, + }) + if err != nil { + return err + } + + if err := os.WriteFile(builtinPath, b.Bytes(), 0666); err != nil { + return err + } + + return nil +} + +func generateRegistry(registryPath string) error { + + bf, err := os.ReadFile(registryPath + ".template") + if err != nil { + if os.IsNotExist(err) { + return nil // skip + } + + return xerrors.Errorf("loading registry template file: %w", err) + } + + tpl := template.Must(template.New("").Funcs(template.FuncMap{ + "import": func(v int) string { return getVersionImports()[v] }, + }).Parse(string(bf))) + var b bytes.Buffer + + err = tpl.Execute(&b, map[string]interface{}{ + "versions": lotusactors.Versions, + }) + if err != nil { + return err + } + + if err := os.WriteFile(registryPath, b.Bytes(), 0666); err != nil { + return err + } + + return nil +} + +func getVersionImports() map[int]string { + versionImports := make(map[int]string, lotusactors.LatestVersion) + for _, v := range lotusactors.Versions { + if v == 0 { + versionImports[v] = "/" + } else { + versionImports[v] = "/v" + strconv.Itoa(v) + "/" + } + } + + return versionImports +} diff --git a/chain/actors/builtin/README.md b/chain/actors/builtin/README.md new file mode 100644 index 000000000..21b3fd38f --- /dev/null +++ b/chain/actors/builtin/README.md @@ -0,0 +1,29 @@ +# Actors + +This package contains shims for abstracting over different actor versions. + +## Design + +Shims in this package follow a few common design principles. + +### Structure Agnostic + +Shims interfaces defined in this package should (ideally) not change even if the +structure of the underlying data changes. For example: + +* All shims store an internal "store" object. That way, state can be moved into + a separate object without needing to add a store to the function signature. +* All functions must return an error, even if unused for now. + +### Minimal + +These interfaces should be expanded only as necessary to reduce maintenance burden. + +### Queries, not field assessors. + +When possible, functions should query the state instead of simply acting as +field assessors. These queries are more likely to remain stable across +specs-actor upgrades than specific state fields. + +Note: there is a trade-off here. Avoid implementing _complicated_ query logic +inside these shims, as it will need to be replicated in every shim. diff --git a/chain/actors/builtin/account/account.go b/chain/actors/builtin/account/account.go new file mode 100644 index 000000000..a29248d56 --- /dev/null +++ b/chain/actors/builtin/account/account.go @@ -0,0 +1,143 @@ +package account + +import ( + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtin11 "github.com/filecoin-project/go-state-types/builtin" + "github.com/filecoin-project/go-state-types/cbor" + "github.com/filecoin-project/go-state-types/manifest" + builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + builtin3 "github.com/filecoin-project/specs-actors/v3/actors/builtin" + builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" + builtin6 "github.com/filecoin-project/specs-actors/v6/actors/builtin" + builtin7 "github.com/filecoin-project/specs-actors/v7/actors/builtin" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/types" +) + +var Methods = builtin11.MethodsAccount + +func Load(store adt.Store, act *types.Actor) (State, error) { + if name, av, ok := actors.GetActorMetaByCode(act.Code); ok { + if name != manifest.AccountKey { + return nil, xerrors.Errorf("actor code is not account: %s", name) + } + + switch av { + + case actorstypes.Version8: + return load8(store, act.Head) + + case actorstypes.Version9: + return load9(store, act.Head) + + case actorstypes.Version10: + return load10(store, act.Head) + + case actorstypes.Version11: + return load11(store, act.Head) + + } + } + + switch act.Code { + + case builtin0.AccountActorCodeID: + return load0(store, act.Head) + + case builtin2.AccountActorCodeID: + return load2(store, act.Head) + + case builtin3.AccountActorCodeID: + return load3(store, act.Head) + + case builtin4.AccountActorCodeID: + return load4(store, act.Head) + + case builtin5.AccountActorCodeID: + return load5(store, act.Head) + + case builtin6.AccountActorCodeID: + return load6(store, act.Head) + + case builtin7.AccountActorCodeID: + return load7(store, act.Head) + + } + + return nil, xerrors.Errorf("unknown actor code %s", act.Code) +} + +func MakeState(store adt.Store, av actorstypes.Version, addr address.Address) (State, error) { + switch av { + + case actorstypes.Version0: + return make0(store, addr) + + case actorstypes.Version2: + return make2(store, addr) + + case actorstypes.Version3: + return make3(store, addr) + + case actorstypes.Version4: + return make4(store, addr) + + case actorstypes.Version5: + return make5(store, addr) + + case actorstypes.Version6: + return make6(store, addr) + + case actorstypes.Version7: + return make7(store, addr) + + case actorstypes.Version8: + return make8(store, addr) + + case actorstypes.Version9: + return make9(store, addr) + + case actorstypes.Version10: + return make10(store, addr) + + case actorstypes.Version11: + return make11(store, addr) + + } + return nil, xerrors.Errorf("unknown actor version %d", av) +} + +type State interface { + cbor.Marshaler + + Code() cid.Cid + ActorKey() string + ActorVersion() actorstypes.Version + + PubkeyAddress() (address.Address, error) + GetState() interface{} +} + +func AllCodes() []cid.Cid { + return []cid.Cid{ + (&state0{}).Code(), + (&state2{}).Code(), + (&state3{}).Code(), + (&state4{}).Code(), + (&state5{}).Code(), + (&state6{}).Code(), + (&state7{}).Code(), + (&state8{}).Code(), + (&state9{}).Code(), + (&state10{}).Code(), + (&state11{}).Code(), + } +} diff --git a/chain/actors/builtin/account/actor.go.template b/chain/actors/builtin/account/actor.go.template new file mode 100644 index 000000000..2db38eff6 --- /dev/null +++ b/chain/actors/builtin/account/actor.go.template @@ -0,0 +1,80 @@ +package account + +import ( + "github.com/ipfs/go-cid" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/lotus/chain/actors" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/cbor" + + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/go-state-types/manifest" + +{{range .versions}} + {{if (le . 7)}} + builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin" + {{end}} +{{end}} + builtin{{.latestVersion}} "github.com/filecoin-project/go-state-types/builtin" +) + +var Methods = builtin{{.latestVersion}}.MethodsAccount + +func Load(store adt.Store, act *types.Actor) (State, error) { + if name, av, ok := actors.GetActorMetaByCode(act.Code); ok { + if name != manifest.AccountKey { + return nil, xerrors.Errorf("actor code is not account: %s", name) + } + + switch av { + {{range .versions}} + {{if (ge . 8)}} + case actorstypes.Version{{.}}: + return load{{.}}(store, act.Head) + {{end}} + {{end}} + } + } + + switch act.Code { +{{range .versions}} + {{if (le . 7)}} + case builtin{{.}}.AccountActorCodeID: + return load{{.}}(store, act.Head) + {{end}} +{{end}} + } + + return nil, xerrors.Errorf("unknown actor code %s", act.Code) +} + +func MakeState(store adt.Store, av actorstypes.Version, addr address.Address) (State, error) { + switch av { +{{range .versions}} + case actorstypes.Version{{.}}: + return make{{.}}(store, addr) +{{end}} +} + return nil, xerrors.Errorf("unknown actor version %d", av) +} + +type State interface { + cbor.Marshaler + + Code() cid.Cid + ActorKey() string + ActorVersion() actorstypes.Version + + PubkeyAddress() (address.Address, error) + GetState() interface{} +} + +func AllCodes() []cid.Cid { + return []cid.Cid{ {{range .versions}} + (&state{{.}}{}).Code(), + {{- end}} + } +} diff --git a/chain/actors/builtin/account/state.go.template b/chain/actors/builtin/account/state.go.template new file mode 100644 index 000000000..55a56de8c --- /dev/null +++ b/chain/actors/builtin/account/state.go.template @@ -0,0 +1,66 @@ +package account + +import ( + "fmt" + actorstypes "github.com/filecoin-project/go-state-types/actors" + + "github.com/filecoin-project/go-address" + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/go-state-types/manifest" + +{{if (le .v 7)}} + account{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/builtin/account" +{{else}} + account{{.v}} "github.com/filecoin-project/go-state-types/builtin{{.import}}account" +{{end}} +) + +var _ State = (*state{{.v}})(nil) + +func load{{.v}}(store adt.Store, root cid.Cid) (State, error) { + out := state{{.v}}{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make{{.v}}(store adt.Store, addr address.Address) (State, error) { + out := state{{.v}}{store: store} + out.State = account{{.v}}.State{Address:addr} + return &out, nil +} + +type state{{.v}} struct { + account{{.v}}.State + store adt.Store +} + +func (s *state{{.v}}) PubkeyAddress() (address.Address, error) { + return s.Address, nil +} + +func (s *state{{.v}}) GetState() interface{} { + return &s.State +} + +func (s *state{{.v}}) ActorKey() string { + return manifest.AccountKey +} + +func (s *state{{.v}}) ActorVersion() actorstypes.Version { + return actorstypes.Version{{.v}} +} + +func (s *state{{.v}}) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} \ No newline at end of file diff --git a/chain/actors/builtin/account/v0.go b/chain/actors/builtin/account/v0.go new file mode 100644 index 000000000..a41ee3879 --- /dev/null +++ b/chain/actors/builtin/account/v0.go @@ -0,0 +1,62 @@ +package account + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-address" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + account0 "github.com/filecoin-project/specs-actors/actors/builtin/account" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state0)(nil) + +func load0(store adt.Store, root cid.Cid) (State, error) { + out := state0{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make0(store adt.Store, addr address.Address) (State, error) { + out := state0{store: store} + out.State = account0.State{Address: addr} + return &out, nil +} + +type state0 struct { + account0.State + store adt.Store +} + +func (s *state0) PubkeyAddress() (address.Address, error) { + return s.Address, nil +} + +func (s *state0) GetState() interface{} { + return &s.State +} + +func (s *state0) ActorKey() string { + return manifest.AccountKey +} + +func (s *state0) ActorVersion() actorstypes.Version { + return actorstypes.Version0 +} + +func (s *state0) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/account/v10.go b/chain/actors/builtin/account/v10.go new file mode 100644 index 000000000..ff87c4212 --- /dev/null +++ b/chain/actors/builtin/account/v10.go @@ -0,0 +1,62 @@ +package account + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-address" + actorstypes "github.com/filecoin-project/go-state-types/actors" + account10 "github.com/filecoin-project/go-state-types/builtin/v10/account" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state10)(nil) + +func load10(store adt.Store, root cid.Cid) (State, error) { + out := state10{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make10(store adt.Store, addr address.Address) (State, error) { + out := state10{store: store} + out.State = account10.State{Address: addr} + return &out, nil +} + +type state10 struct { + account10.State + store adt.Store +} + +func (s *state10) PubkeyAddress() (address.Address, error) { + return s.Address, nil +} + +func (s *state10) GetState() interface{} { + return &s.State +} + +func (s *state10) ActorKey() string { + return manifest.AccountKey +} + +func (s *state10) ActorVersion() actorstypes.Version { + return actorstypes.Version10 +} + +func (s *state10) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/account/v11.go b/chain/actors/builtin/account/v11.go new file mode 100644 index 000000000..7a0c5f556 --- /dev/null +++ b/chain/actors/builtin/account/v11.go @@ -0,0 +1,62 @@ +package account + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-address" + actorstypes "github.com/filecoin-project/go-state-types/actors" + account11 "github.com/filecoin-project/go-state-types/builtin/v11/account" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state11)(nil) + +func load11(store adt.Store, root cid.Cid) (State, error) { + out := state11{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make11(store adt.Store, addr address.Address) (State, error) { + out := state11{store: store} + out.State = account11.State{Address: addr} + return &out, nil +} + +type state11 struct { + account11.State + store adt.Store +} + +func (s *state11) PubkeyAddress() (address.Address, error) { + return s.Address, nil +} + +func (s *state11) GetState() interface{} { + return &s.State +} + +func (s *state11) ActorKey() string { + return manifest.AccountKey +} + +func (s *state11) ActorVersion() actorstypes.Version { + return actorstypes.Version11 +} + +func (s *state11) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/account/v2.go b/chain/actors/builtin/account/v2.go new file mode 100644 index 000000000..db0af77e2 --- /dev/null +++ b/chain/actors/builtin/account/v2.go @@ -0,0 +1,62 @@ +package account + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-address" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + account2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/account" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state2)(nil) + +func load2(store adt.Store, root cid.Cid) (State, error) { + out := state2{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make2(store adt.Store, addr address.Address) (State, error) { + out := state2{store: store} + out.State = account2.State{Address: addr} + return &out, nil +} + +type state2 struct { + account2.State + store adt.Store +} + +func (s *state2) PubkeyAddress() (address.Address, error) { + return s.Address, nil +} + +func (s *state2) GetState() interface{} { + return &s.State +} + +func (s *state2) ActorKey() string { + return manifest.AccountKey +} + +func (s *state2) ActorVersion() actorstypes.Version { + return actorstypes.Version2 +} + +func (s *state2) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/account/v3.go b/chain/actors/builtin/account/v3.go new file mode 100644 index 000000000..9e6c71ad0 --- /dev/null +++ b/chain/actors/builtin/account/v3.go @@ -0,0 +1,62 @@ +package account + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-address" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + account3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/account" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state3)(nil) + +func load3(store adt.Store, root cid.Cid) (State, error) { + out := state3{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make3(store adt.Store, addr address.Address) (State, error) { + out := state3{store: store} + out.State = account3.State{Address: addr} + return &out, nil +} + +type state3 struct { + account3.State + store adt.Store +} + +func (s *state3) PubkeyAddress() (address.Address, error) { + return s.Address, nil +} + +func (s *state3) GetState() interface{} { + return &s.State +} + +func (s *state3) ActorKey() string { + return manifest.AccountKey +} + +func (s *state3) ActorVersion() actorstypes.Version { + return actorstypes.Version3 +} + +func (s *state3) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/account/v4.go b/chain/actors/builtin/account/v4.go new file mode 100644 index 000000000..907896312 --- /dev/null +++ b/chain/actors/builtin/account/v4.go @@ -0,0 +1,62 @@ +package account + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-address" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + account4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/account" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state4)(nil) + +func load4(store adt.Store, root cid.Cid) (State, error) { + out := state4{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make4(store adt.Store, addr address.Address) (State, error) { + out := state4{store: store} + out.State = account4.State{Address: addr} + return &out, nil +} + +type state4 struct { + account4.State + store adt.Store +} + +func (s *state4) PubkeyAddress() (address.Address, error) { + return s.Address, nil +} + +func (s *state4) GetState() interface{} { + return &s.State +} + +func (s *state4) ActorKey() string { + return manifest.AccountKey +} + +func (s *state4) ActorVersion() actorstypes.Version { + return actorstypes.Version4 +} + +func (s *state4) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/account/v5.go b/chain/actors/builtin/account/v5.go new file mode 100644 index 000000000..8514ab325 --- /dev/null +++ b/chain/actors/builtin/account/v5.go @@ -0,0 +1,62 @@ +package account + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-address" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + account5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/account" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state5)(nil) + +func load5(store adt.Store, root cid.Cid) (State, error) { + out := state5{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make5(store adt.Store, addr address.Address) (State, error) { + out := state5{store: store} + out.State = account5.State{Address: addr} + return &out, nil +} + +type state5 struct { + account5.State + store adt.Store +} + +func (s *state5) PubkeyAddress() (address.Address, error) { + return s.Address, nil +} + +func (s *state5) GetState() interface{} { + return &s.State +} + +func (s *state5) ActorKey() string { + return manifest.AccountKey +} + +func (s *state5) ActorVersion() actorstypes.Version { + return actorstypes.Version5 +} + +func (s *state5) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/account/v6.go b/chain/actors/builtin/account/v6.go new file mode 100644 index 000000000..16369f38c --- /dev/null +++ b/chain/actors/builtin/account/v6.go @@ -0,0 +1,62 @@ +package account + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-address" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + account6 "github.com/filecoin-project/specs-actors/v6/actors/builtin/account" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state6)(nil) + +func load6(store adt.Store, root cid.Cid) (State, error) { + out := state6{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make6(store adt.Store, addr address.Address) (State, error) { + out := state6{store: store} + out.State = account6.State{Address: addr} + return &out, nil +} + +type state6 struct { + account6.State + store adt.Store +} + +func (s *state6) PubkeyAddress() (address.Address, error) { + return s.Address, nil +} + +func (s *state6) GetState() interface{} { + return &s.State +} + +func (s *state6) ActorKey() string { + return manifest.AccountKey +} + +func (s *state6) ActorVersion() actorstypes.Version { + return actorstypes.Version6 +} + +func (s *state6) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/account/v7.go b/chain/actors/builtin/account/v7.go new file mode 100644 index 000000000..cd420da92 --- /dev/null +++ b/chain/actors/builtin/account/v7.go @@ -0,0 +1,62 @@ +package account + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-address" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + account7 "github.com/filecoin-project/specs-actors/v7/actors/builtin/account" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state7)(nil) + +func load7(store adt.Store, root cid.Cid) (State, error) { + out := state7{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make7(store adt.Store, addr address.Address) (State, error) { + out := state7{store: store} + out.State = account7.State{Address: addr} + return &out, nil +} + +type state7 struct { + account7.State + store adt.Store +} + +func (s *state7) PubkeyAddress() (address.Address, error) { + return s.Address, nil +} + +func (s *state7) GetState() interface{} { + return &s.State +} + +func (s *state7) ActorKey() string { + return manifest.AccountKey +} + +func (s *state7) ActorVersion() actorstypes.Version { + return actorstypes.Version7 +} + +func (s *state7) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/account/v8.go b/chain/actors/builtin/account/v8.go new file mode 100644 index 000000000..13b478de8 --- /dev/null +++ b/chain/actors/builtin/account/v8.go @@ -0,0 +1,62 @@ +package account + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-address" + actorstypes "github.com/filecoin-project/go-state-types/actors" + account8 "github.com/filecoin-project/go-state-types/builtin/v8/account" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state8)(nil) + +func load8(store adt.Store, root cid.Cid) (State, error) { + out := state8{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make8(store adt.Store, addr address.Address) (State, error) { + out := state8{store: store} + out.State = account8.State{Address: addr} + return &out, nil +} + +type state8 struct { + account8.State + store adt.Store +} + +func (s *state8) PubkeyAddress() (address.Address, error) { + return s.Address, nil +} + +func (s *state8) GetState() interface{} { + return &s.State +} + +func (s *state8) ActorKey() string { + return manifest.AccountKey +} + +func (s *state8) ActorVersion() actorstypes.Version { + return actorstypes.Version8 +} + +func (s *state8) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/account/v9.go b/chain/actors/builtin/account/v9.go new file mode 100644 index 000000000..fc1fc4d14 --- /dev/null +++ b/chain/actors/builtin/account/v9.go @@ -0,0 +1,62 @@ +package account + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-address" + actorstypes "github.com/filecoin-project/go-state-types/actors" + account9 "github.com/filecoin-project/go-state-types/builtin/v9/account" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state9)(nil) + +func load9(store adt.Store, root cid.Cid) (State, error) { + out := state9{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make9(store adt.Store, addr address.Address) (State, error) { + out := state9{store: store} + out.State = account9.State{Address: addr} + return &out, nil +} + +type state9 struct { + account9.State + store adt.Store +} + +func (s *state9) PubkeyAddress() (address.Address, error) { + return s.Address, nil +} + +func (s *state9) GetState() interface{} { + return &s.State +} + +func (s *state9) ActorKey() string { + return manifest.AccountKey +} + +func (s *state9) ActorVersion() actorstypes.Version { + return actorstypes.Version9 +} + +func (s *state9) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/builtin.go b/chain/actors/builtin/builtin.go new file mode 100644 index 000000000..414a11e72 --- /dev/null +++ b/chain/actors/builtin/builtin.go @@ -0,0 +1,312 @@ +package builtin + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/builtin" + smoothingtypes "github.com/filecoin-project/go-state-types/builtin/v8/util/smoothing" + minertypes "github.com/filecoin-project/go-state-types/builtin/v9/miner" + "github.com/filecoin-project/go-state-types/manifest" + "github.com/filecoin-project/go-state-types/proof" + builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + builtin3 "github.com/filecoin-project/specs-actors/v3/actors/builtin" + builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" + builtin6 "github.com/filecoin-project/specs-actors/v6/actors/builtin" + builtin7 "github.com/filecoin-project/specs-actors/v7/actors/builtin" + + "github.com/filecoin-project/lotus/chain/actors" +) + +var SystemActorAddr = builtin.SystemActorAddr +var BurntFundsActorAddr = builtin.BurntFundsActorAddr +var CronActorAddr = builtin.CronActorAddr +var EthereumAddressManagerActorAddr = builtin.EthereumAddressManagerActorAddr +var SaftAddress = makeAddress("t0122") +var ReserveAddress = makeAddress("t090") +var RootVerifierAddress = makeAddress("t080") + +var ( + ExpectedLeadersPerEpoch = builtin.ExpectedLeadersPerEpoch +) + +const ( + EpochDurationSeconds = builtin.EpochDurationSeconds + EpochsInDay = builtin.EpochsInDay + SecondsInDay = builtin.SecondsInDay +) + +const ( + MethodSend = builtin.MethodSend + MethodConstructor = builtin.MethodConstructor +) + +// These are all just type aliases across actor versions. In the future, that might change +// and we might need to do something fancier. +type SectorInfo = proof.SectorInfo +type ExtendedSectorInfo = proof.ExtendedSectorInfo +type PoStProof = proof.PoStProof +type FilterEstimate = smoothingtypes.FilterEstimate + +func QAPowerForWeight(size abi.SectorSize, duration abi.ChainEpoch, dealWeight, verifiedWeight abi.DealWeight) abi.StoragePower { + return minertypes.QAPowerForWeight(size, duration, dealWeight, verifiedWeight) +} + +func ActorNameByCode(c cid.Cid) string { + if name, version, ok := actors.GetActorMetaByCode(c); ok { + return fmt.Sprintf("fil/%d/%s", version, name) + } + + switch { + + case builtin0.IsBuiltinActor(c): + return builtin0.ActorNameByCode(c) + + case builtin2.IsBuiltinActor(c): + return builtin2.ActorNameByCode(c) + + case builtin3.IsBuiltinActor(c): + return builtin3.ActorNameByCode(c) + + case builtin4.IsBuiltinActor(c): + return builtin4.ActorNameByCode(c) + + case builtin5.IsBuiltinActor(c): + return builtin5.ActorNameByCode(c) + + case builtin6.IsBuiltinActor(c): + return builtin6.ActorNameByCode(c) + + case builtin7.IsBuiltinActor(c): + return builtin7.ActorNameByCode(c) + + default: + return "" + } +} + +func IsBuiltinActor(c cid.Cid) bool { + _, _, ok := actors.GetActorMetaByCode(c) + if ok { + return true + } + + if builtin0.IsBuiltinActor(c) { + return true + } + + if builtin2.IsBuiltinActor(c) { + return true + } + + if builtin3.IsBuiltinActor(c) { + return true + } + + if builtin4.IsBuiltinActor(c) { + return true + } + + if builtin5.IsBuiltinActor(c) { + return true + } + + if builtin6.IsBuiltinActor(c) { + return true + } + + if builtin7.IsBuiltinActor(c) { + return true + } + + return false +} + +func IsAccountActor(c cid.Cid) bool { + name, _, ok := actors.GetActorMetaByCode(c) + if ok { + return name == "account" + } + + if c == builtin0.AccountActorCodeID { + return true + } + + if c == builtin2.AccountActorCodeID { + return true + } + + if c == builtin3.AccountActorCodeID { + return true + } + + if c == builtin4.AccountActorCodeID { + return true + } + + if c == builtin5.AccountActorCodeID { + return true + } + + if c == builtin6.AccountActorCodeID { + return true + } + + if c == builtin7.AccountActorCodeID { + return true + } + + return false +} + +func IsStorageMinerActor(c cid.Cid) bool { + name, _, ok := actors.GetActorMetaByCode(c) + if ok { + return name == manifest.MinerKey + } + + if c == builtin0.StorageMinerActorCodeID { + return true + } + + if c == builtin2.StorageMinerActorCodeID { + return true + } + + if c == builtin3.StorageMinerActorCodeID { + return true + } + + if c == builtin4.StorageMinerActorCodeID { + return true + } + + if c == builtin5.StorageMinerActorCodeID { + return true + } + + if c == builtin6.StorageMinerActorCodeID { + return true + } + + if c == builtin7.StorageMinerActorCodeID { + return true + } + + return false +} + +func IsMultisigActor(c cid.Cid) bool { + name, _, ok := actors.GetActorMetaByCode(c) + if ok { + return name == manifest.MultisigKey + } + + if c == builtin0.MultisigActorCodeID { + return true + } + + if c == builtin2.MultisigActorCodeID { + return true + } + + if c == builtin3.MultisigActorCodeID { + return true + } + + if c == builtin4.MultisigActorCodeID { + return true + } + + if c == builtin5.MultisigActorCodeID { + return true + } + + if c == builtin6.MultisigActorCodeID { + return true + } + + if c == builtin7.MultisigActorCodeID { + return true + } + + return false +} + +func IsPaymentChannelActor(c cid.Cid) bool { + name, _, ok := actors.GetActorMetaByCode(c) + if ok { + return name == "paymentchannel" + } + + if c == builtin0.PaymentChannelActorCodeID { + return true + } + + if c == builtin2.PaymentChannelActorCodeID { + return true + } + + if c == builtin3.PaymentChannelActorCodeID { + return true + } + + if c == builtin4.PaymentChannelActorCodeID { + return true + } + + if c == builtin5.PaymentChannelActorCodeID { + return true + } + + if c == builtin6.PaymentChannelActorCodeID { + return true + } + + if c == builtin7.PaymentChannelActorCodeID { + return true + } + + return false +} + +func IsPlaceholderActor(c cid.Cid) bool { + name, _, ok := actors.GetActorMetaByCode(c) + if ok { + return name == manifest.PlaceholderKey + } + + return false +} + +func IsEvmActor(c cid.Cid) bool { + name, _, ok := actors.GetActorMetaByCode(c) + if ok { + return name == manifest.EvmKey + } + + return false +} + +func IsEthAccountActor(c cid.Cid) bool { + name, _, ok := actors.GetActorMetaByCode(c) + if ok { + return name == manifest.EthAccountKey + } + + return false +} + +func makeAddress(addr string) address.Address { + ret, err := address.NewFromString(addr) + if err != nil { + panic(err) + } + + return ret +} diff --git a/chain/actors/builtin/builtin.go.template b/chain/actors/builtin/builtin.go.template new file mode 100644 index 000000000..3b737c47e --- /dev/null +++ b/chain/actors/builtin/builtin.go.template @@ -0,0 +1,191 @@ +package builtin + +import ( + "fmt" + + "github.com/filecoin-project/go-address" + "github.com/ipfs/go-cid" + +{{range .versions}} + {{if (le . 7)}} + builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin" + {{end}} +{{end}} + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/proof" + "github.com/filecoin-project/go-state-types/builtin" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + + minertypes "github.com/filecoin-project/go-state-types/builtin/v9/miner" + smoothingtypes "github.com/filecoin-project/go-state-types/builtin/v8/util/smoothing" +) + +var SystemActorAddr = builtin.SystemActorAddr +var BurntFundsActorAddr = builtin.BurntFundsActorAddr +var CronActorAddr = builtin.CronActorAddr +var EthereumAddressManagerActorAddr = builtin.EthereumAddressManagerActorAddr +var SaftAddress = makeAddress("t0122") +var ReserveAddress = makeAddress("t090") +var RootVerifierAddress = makeAddress("t080") + +var ( + ExpectedLeadersPerEpoch = builtin.ExpectedLeadersPerEpoch +) + +const ( + EpochDurationSeconds = builtin.EpochDurationSeconds + EpochsInDay = builtin.EpochsInDay + SecondsInDay = builtin.SecondsInDay +) + +const ( + MethodSend = builtin.MethodSend + MethodConstructor = builtin.MethodConstructor +) + +// These are all just type aliases across actor versions. In the future, that might change +// and we might need to do something fancier. +type SectorInfo = proof.SectorInfo +type ExtendedSectorInfo = proof.ExtendedSectorInfo +type PoStProof = proof.PoStProof +type FilterEstimate = smoothingtypes.FilterEstimate + +func QAPowerForWeight(size abi.SectorSize, duration abi.ChainEpoch, dealWeight, verifiedWeight abi.DealWeight) abi.StoragePower { + return minertypes.QAPowerForWeight(size, duration, dealWeight, verifiedWeight) +} + +func ActorNameByCode(c cid.Cid) string { + if name, version, ok := actors.GetActorMetaByCode(c); ok { + return fmt.Sprintf("fil/%d/%s", version, name) + } + + switch { + {{range .versions}} + {{if (le . 7)}} + case builtin{{.}}.IsBuiltinActor(c): + return builtin{{.}}.ActorNameByCode(c) + {{end}} + {{end}} + default: + return "" + } +} + +func IsBuiltinActor(c cid.Cid) bool { + _, _, ok := actors.GetActorMetaByCode(c) + if ok { + return true + } + + {{range .versions}} + {{if (le . 7)}} + if builtin{{.}}.IsBuiltinActor(c) { + return true + } + {{end}} + {{end}} + return false +} + +func IsAccountActor(c cid.Cid) bool { + name, _, ok := actors.GetActorMetaByCode(c) + if ok { + return name == "account" + } + + {{range .versions}} + {{if (le . 7)}} + if c == builtin{{.}}.AccountActorCodeID { + return true + } + {{end}} + {{end}} + return false +} + +func IsStorageMinerActor(c cid.Cid) bool { + name, _, ok := actors.GetActorMetaByCode(c) + if ok { + return name == manifest.MinerKey + } + + {{range .versions}} + {{if (le . 7)}} + if c == builtin{{.}}.StorageMinerActorCodeID { + return true + } + {{end}} + {{end}} + return false +} + +func IsMultisigActor(c cid.Cid) bool { + name, _, ok := actors.GetActorMetaByCode(c) + if ok { + return name == manifest.MultisigKey + } + + {{range .versions}} + {{if (le . 7)}} + if c == builtin{{.}}.MultisigActorCodeID { + return true + } + {{end}} + {{end}} + return false +} + +func IsPaymentChannelActor(c cid.Cid) bool { + name, _, ok := actors.GetActorMetaByCode(c) + if ok { + return name == "paymentchannel" + } + + {{range .versions}} + {{if (le . 7)}} + if c == builtin{{.}}.PaymentChannelActorCodeID { + return true + } + {{end}} + {{end}} + return false +} + +func IsPlaceholderActor(c cid.Cid) bool { + name, _, ok := actors.GetActorMetaByCode(c) + if ok { + return name == manifest.PlaceholderKey + } + + return false +} + +func IsEvmActor(c cid.Cid) bool { + name, _, ok := actors.GetActorMetaByCode(c) + if ok { + return name == manifest.EvmKey + } + + return false +} + +func IsEthAccountActor(c cid.Cid) bool { + name, _, ok := actors.GetActorMetaByCode(c) + if ok { + return name == manifest.EthAccountKey + } + + return false +} + +func makeAddress(addr string) address.Address { + ret, err := address.NewFromString(addr) + if err != nil { + panic(err) + } + + return ret +} diff --git a/chain/actors/builtin/cron/actor.go.template b/chain/actors/builtin/cron/actor.go.template new file mode 100644 index 000000000..7e01483d2 --- /dev/null +++ b/chain/actors/builtin/cron/actor.go.template @@ -0,0 +1,77 @@ +package cron + +import ( + "github.com/ipfs/go-cid" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "golang.org/x/xerrors" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/go-state-types/manifest" + +{{range .versions}} + {{if (le . 7)}} + builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin" + {{end}} +{{end}} + builtin{{.latestVersion}} "github.com/filecoin-project/go-state-types/builtin" +) + +func Load(store adt.Store, act *types.Actor) (State, error) { + if name, av, ok := actors.GetActorMetaByCode(act.Code); ok { + if name != manifest.CronKey { + return nil, xerrors.Errorf("actor code is not cron: %s", name) + } + + switch av { + {{range .versions}} + {{if (ge . 8)}} + case actorstypes.Version{{.}}: + return load{{.}}(store, act.Head) + {{end}} + {{end}} + } + } + + switch act.Code { +{{range .versions}} + {{if (le . 7)}} + case builtin{{.}}.CronActorCodeID: + return load{{.}}(store, act.Head) + {{end}} +{{end}} + } + + return nil, xerrors.Errorf("unknown actor code %s", act.Code) +} + +func MakeState(store adt.Store, av actorstypes.Version) (State, error) { + switch av { +{{range .versions}} + case actorstypes.Version{{.}}: + return make{{.}}(store) +{{end}} +} + return nil, xerrors.Errorf("unknown actor version %d", av) +} + +var ( + Address = builtin{{.latestVersion}}.CronActorAddr + Methods = builtin{{.latestVersion}}.MethodsCron +) + + +type State interface { + Code() cid.Cid + ActorKey() string + ActorVersion() actorstypes.Version + + GetState() interface{} +} + +func AllCodes() []cid.Cid { + return []cid.Cid{ {{range .versions}} + (&state{{.}}{}).Code(), + {{- end}} + } +} diff --git a/chain/actors/builtin/cron/cron.go b/chain/actors/builtin/cron/cron.go new file mode 100644 index 000000000..c2f758698 --- /dev/null +++ b/chain/actors/builtin/cron/cron.go @@ -0,0 +1,141 @@ +package cron + +import ( + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtin11 "github.com/filecoin-project/go-state-types/builtin" + "github.com/filecoin-project/go-state-types/manifest" + builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + builtin3 "github.com/filecoin-project/specs-actors/v3/actors/builtin" + builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" + builtin6 "github.com/filecoin-project/specs-actors/v6/actors/builtin" + builtin7 "github.com/filecoin-project/specs-actors/v7/actors/builtin" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/types" +) + +func Load(store adt.Store, act *types.Actor) (State, error) { + if name, av, ok := actors.GetActorMetaByCode(act.Code); ok { + if name != manifest.CronKey { + return nil, xerrors.Errorf("actor code is not cron: %s", name) + } + + switch av { + + case actorstypes.Version8: + return load8(store, act.Head) + + case actorstypes.Version9: + return load9(store, act.Head) + + case actorstypes.Version10: + return load10(store, act.Head) + + case actorstypes.Version11: + return load11(store, act.Head) + + } + } + + switch act.Code { + + case builtin0.CronActorCodeID: + return load0(store, act.Head) + + case builtin2.CronActorCodeID: + return load2(store, act.Head) + + case builtin3.CronActorCodeID: + return load3(store, act.Head) + + case builtin4.CronActorCodeID: + return load4(store, act.Head) + + case builtin5.CronActorCodeID: + return load5(store, act.Head) + + case builtin6.CronActorCodeID: + return load6(store, act.Head) + + case builtin7.CronActorCodeID: + return load7(store, act.Head) + + } + + return nil, xerrors.Errorf("unknown actor code %s", act.Code) +} + +func MakeState(store adt.Store, av actorstypes.Version) (State, error) { + switch av { + + case actorstypes.Version0: + return make0(store) + + case actorstypes.Version2: + return make2(store) + + case actorstypes.Version3: + return make3(store) + + case actorstypes.Version4: + return make4(store) + + case actorstypes.Version5: + return make5(store) + + case actorstypes.Version6: + return make6(store) + + case actorstypes.Version7: + return make7(store) + + case actorstypes.Version8: + return make8(store) + + case actorstypes.Version9: + return make9(store) + + case actorstypes.Version10: + return make10(store) + + case actorstypes.Version11: + return make11(store) + + } + return nil, xerrors.Errorf("unknown actor version %d", av) +} + +var ( + Address = builtin11.CronActorAddr + Methods = builtin11.MethodsCron +) + +type State interface { + Code() cid.Cid + ActorKey() string + ActorVersion() actorstypes.Version + + GetState() interface{} +} + +func AllCodes() []cid.Cid { + return []cid.Cid{ + (&state0{}).Code(), + (&state2{}).Code(), + (&state3{}).Code(), + (&state4{}).Code(), + (&state5{}).Code(), + (&state6{}).Code(), + (&state7{}).Code(), + (&state8{}).Code(), + (&state9{}).Code(), + (&state10{}).Code(), + (&state11{}).Code(), + } +} diff --git a/chain/actors/builtin/cron/state.go.template b/chain/actors/builtin/cron/state.go.template new file mode 100644 index 000000000..ca82d8be8 --- /dev/null +++ b/chain/actors/builtin/cron/state.go.template @@ -0,0 +1,60 @@ +package cron + +import ( + "fmt" + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + +{{if (le .v 7)}} + cron{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/builtin/cron" +{{else}} + cron{{.v}} "github.com/filecoin-project/go-state-types/builtin{{.import}}cron" +{{end}} +) + +var _ State = (*state{{.v}})(nil) + +func load{{.v}}(store adt.Store, root cid.Cid) (State, error) { + out := state{{.v}}{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make{{.v}}(store adt.Store) (State, error) { + out := state{{.v}}{store: store} + out.State = *cron{{.v}}.ConstructState(cron{{.v}}.BuiltInEntries()) + return &out, nil +} + +type state{{.v}} struct { + cron{{.v}}.State + store adt.Store +} + +func (s *state{{.v}}) GetState() interface{} { + return &s.State +} + +func (s *state{{.v}}) ActorKey() string { + return manifest.CronKey +} + +func (s *state{{.v}}) ActorVersion() actorstypes.Version { + return actorstypes.Version{{.v}} +} + +func (s *state{{.v}}) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/cron/v0.go b/chain/actors/builtin/cron/v0.go new file mode 100644 index 000000000..6dce524f6 --- /dev/null +++ b/chain/actors/builtin/cron/v0.go @@ -0,0 +1,57 @@ +package cron + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + cron0 "github.com/filecoin-project/specs-actors/actors/builtin/cron" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state0)(nil) + +func load0(store adt.Store, root cid.Cid) (State, error) { + out := state0{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make0(store adt.Store) (State, error) { + out := state0{store: store} + out.State = *cron0.ConstructState(cron0.BuiltInEntries()) + return &out, nil +} + +type state0 struct { + cron0.State + store adt.Store +} + +func (s *state0) GetState() interface{} { + return &s.State +} + +func (s *state0) ActorKey() string { + return manifest.CronKey +} + +func (s *state0) ActorVersion() actorstypes.Version { + return actorstypes.Version0 +} + +func (s *state0) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/cron/v10.go b/chain/actors/builtin/cron/v10.go new file mode 100644 index 000000000..2d20e2401 --- /dev/null +++ b/chain/actors/builtin/cron/v10.go @@ -0,0 +1,57 @@ +package cron + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + actorstypes "github.com/filecoin-project/go-state-types/actors" + cron10 "github.com/filecoin-project/go-state-types/builtin/v10/cron" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state10)(nil) + +func load10(store adt.Store, root cid.Cid) (State, error) { + out := state10{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make10(store adt.Store) (State, error) { + out := state10{store: store} + out.State = *cron10.ConstructState(cron10.BuiltInEntries()) + return &out, nil +} + +type state10 struct { + cron10.State + store adt.Store +} + +func (s *state10) GetState() interface{} { + return &s.State +} + +func (s *state10) ActorKey() string { + return manifest.CronKey +} + +func (s *state10) ActorVersion() actorstypes.Version { + return actorstypes.Version10 +} + +func (s *state10) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/cron/v11.go b/chain/actors/builtin/cron/v11.go new file mode 100644 index 000000000..5c489cede --- /dev/null +++ b/chain/actors/builtin/cron/v11.go @@ -0,0 +1,57 @@ +package cron + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + actorstypes "github.com/filecoin-project/go-state-types/actors" + cron11 "github.com/filecoin-project/go-state-types/builtin/v11/cron" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state11)(nil) + +func load11(store adt.Store, root cid.Cid) (State, error) { + out := state11{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make11(store adt.Store) (State, error) { + out := state11{store: store} + out.State = *cron11.ConstructState(cron11.BuiltInEntries()) + return &out, nil +} + +type state11 struct { + cron11.State + store adt.Store +} + +func (s *state11) GetState() interface{} { + return &s.State +} + +func (s *state11) ActorKey() string { + return manifest.CronKey +} + +func (s *state11) ActorVersion() actorstypes.Version { + return actorstypes.Version11 +} + +func (s *state11) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/cron/v2.go b/chain/actors/builtin/cron/v2.go new file mode 100644 index 000000000..97b3ffbe0 --- /dev/null +++ b/chain/actors/builtin/cron/v2.go @@ -0,0 +1,57 @@ +package cron + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + cron2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/cron" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state2)(nil) + +func load2(store adt.Store, root cid.Cid) (State, error) { + out := state2{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make2(store adt.Store) (State, error) { + out := state2{store: store} + out.State = *cron2.ConstructState(cron2.BuiltInEntries()) + return &out, nil +} + +type state2 struct { + cron2.State + store adt.Store +} + +func (s *state2) GetState() interface{} { + return &s.State +} + +func (s *state2) ActorKey() string { + return manifest.CronKey +} + +func (s *state2) ActorVersion() actorstypes.Version { + return actorstypes.Version2 +} + +func (s *state2) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/cron/v3.go b/chain/actors/builtin/cron/v3.go new file mode 100644 index 000000000..4c0d4f1d9 --- /dev/null +++ b/chain/actors/builtin/cron/v3.go @@ -0,0 +1,57 @@ +package cron + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + cron3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/cron" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state3)(nil) + +func load3(store adt.Store, root cid.Cid) (State, error) { + out := state3{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make3(store adt.Store) (State, error) { + out := state3{store: store} + out.State = *cron3.ConstructState(cron3.BuiltInEntries()) + return &out, nil +} + +type state3 struct { + cron3.State + store adt.Store +} + +func (s *state3) GetState() interface{} { + return &s.State +} + +func (s *state3) ActorKey() string { + return manifest.CronKey +} + +func (s *state3) ActorVersion() actorstypes.Version { + return actorstypes.Version3 +} + +func (s *state3) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/cron/v4.go b/chain/actors/builtin/cron/v4.go new file mode 100644 index 000000000..a222f0d93 --- /dev/null +++ b/chain/actors/builtin/cron/v4.go @@ -0,0 +1,57 @@ +package cron + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + cron4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/cron" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state4)(nil) + +func load4(store adt.Store, root cid.Cid) (State, error) { + out := state4{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make4(store adt.Store) (State, error) { + out := state4{store: store} + out.State = *cron4.ConstructState(cron4.BuiltInEntries()) + return &out, nil +} + +type state4 struct { + cron4.State + store adt.Store +} + +func (s *state4) GetState() interface{} { + return &s.State +} + +func (s *state4) ActorKey() string { + return manifest.CronKey +} + +func (s *state4) ActorVersion() actorstypes.Version { + return actorstypes.Version4 +} + +func (s *state4) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/cron/v5.go b/chain/actors/builtin/cron/v5.go new file mode 100644 index 000000000..2487cbbc6 --- /dev/null +++ b/chain/actors/builtin/cron/v5.go @@ -0,0 +1,57 @@ +package cron + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + cron5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/cron" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state5)(nil) + +func load5(store adt.Store, root cid.Cid) (State, error) { + out := state5{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make5(store adt.Store) (State, error) { + out := state5{store: store} + out.State = *cron5.ConstructState(cron5.BuiltInEntries()) + return &out, nil +} + +type state5 struct { + cron5.State + store adt.Store +} + +func (s *state5) GetState() interface{} { + return &s.State +} + +func (s *state5) ActorKey() string { + return manifest.CronKey +} + +func (s *state5) ActorVersion() actorstypes.Version { + return actorstypes.Version5 +} + +func (s *state5) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/cron/v6.go b/chain/actors/builtin/cron/v6.go new file mode 100644 index 000000000..673e7588a --- /dev/null +++ b/chain/actors/builtin/cron/v6.go @@ -0,0 +1,57 @@ +package cron + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + cron6 "github.com/filecoin-project/specs-actors/v6/actors/builtin/cron" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state6)(nil) + +func load6(store adt.Store, root cid.Cid) (State, error) { + out := state6{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make6(store adt.Store) (State, error) { + out := state6{store: store} + out.State = *cron6.ConstructState(cron6.BuiltInEntries()) + return &out, nil +} + +type state6 struct { + cron6.State + store adt.Store +} + +func (s *state6) GetState() interface{} { + return &s.State +} + +func (s *state6) ActorKey() string { + return manifest.CronKey +} + +func (s *state6) ActorVersion() actorstypes.Version { + return actorstypes.Version6 +} + +func (s *state6) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/cron/v7.go b/chain/actors/builtin/cron/v7.go new file mode 100644 index 000000000..cd71bd418 --- /dev/null +++ b/chain/actors/builtin/cron/v7.go @@ -0,0 +1,57 @@ +package cron + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + cron7 "github.com/filecoin-project/specs-actors/v7/actors/builtin/cron" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state7)(nil) + +func load7(store adt.Store, root cid.Cid) (State, error) { + out := state7{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make7(store adt.Store) (State, error) { + out := state7{store: store} + out.State = *cron7.ConstructState(cron7.BuiltInEntries()) + return &out, nil +} + +type state7 struct { + cron7.State + store adt.Store +} + +func (s *state7) GetState() interface{} { + return &s.State +} + +func (s *state7) ActorKey() string { + return manifest.CronKey +} + +func (s *state7) ActorVersion() actorstypes.Version { + return actorstypes.Version7 +} + +func (s *state7) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/cron/v8.go b/chain/actors/builtin/cron/v8.go new file mode 100644 index 000000000..904de5496 --- /dev/null +++ b/chain/actors/builtin/cron/v8.go @@ -0,0 +1,57 @@ +package cron + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + actorstypes "github.com/filecoin-project/go-state-types/actors" + cron8 "github.com/filecoin-project/go-state-types/builtin/v8/cron" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state8)(nil) + +func load8(store adt.Store, root cid.Cid) (State, error) { + out := state8{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make8(store adt.Store) (State, error) { + out := state8{store: store} + out.State = *cron8.ConstructState(cron8.BuiltInEntries()) + return &out, nil +} + +type state8 struct { + cron8.State + store adt.Store +} + +func (s *state8) GetState() interface{} { + return &s.State +} + +func (s *state8) ActorKey() string { + return manifest.CronKey +} + +func (s *state8) ActorVersion() actorstypes.Version { + return actorstypes.Version8 +} + +func (s *state8) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/cron/v9.go b/chain/actors/builtin/cron/v9.go new file mode 100644 index 000000000..201348b6c --- /dev/null +++ b/chain/actors/builtin/cron/v9.go @@ -0,0 +1,57 @@ +package cron + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + actorstypes "github.com/filecoin-project/go-state-types/actors" + cron9 "github.com/filecoin-project/go-state-types/builtin/v9/cron" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state9)(nil) + +func load9(store adt.Store, root cid.Cid) (State, error) { + out := state9{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make9(store adt.Store) (State, error) { + out := state9{store: store} + out.State = *cron9.ConstructState(cron9.BuiltInEntries()) + return &out, nil +} + +type state9 struct { + cron9.State + store adt.Store +} + +func (s *state9) GetState() interface{} { + return &s.State +} + +func (s *state9) ActorKey() string { + return manifest.CronKey +} + +func (s *state9) ActorVersion() actorstypes.Version { + return actorstypes.Version9 +} + +func (s *state9) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/datacap/actor.go.template b/chain/actors/builtin/datacap/actor.go.template new file mode 100644 index 000000000..7bf4fbef2 --- /dev/null +++ b/chain/actors/builtin/datacap/actor.go.template @@ -0,0 +1,70 @@ +package datacap + +import ( + "golang.org/x/xerrors" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtin{{.latestVersion}} "github.com/filecoin-project/go-state-types/builtin" + "github.com/filecoin-project/go-state-types/cbor" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/go-state-types/manifest" +) + +var ( + Address = builtin{{.latestVersion}}.DatacapActorAddr + Methods = builtin{{.latestVersion}}.MethodsDatacap +) + +func Load(store adt.Store, act *types.Actor) (State, error) { + if name, av, ok := actors.GetActorMetaByCode(act.Code); ok { + if name != manifest.DatacapKey { + return nil, xerrors.Errorf("actor code is not datacap: %s", name) + } + + switch av { + {{range .versions}} + case actorstypes.Version{{.}}: + return load{{.}}(store, act.Head) + {{end}} + } + } + + return nil, xerrors.Errorf("unknown actor code %s", act.Code) +} + +func MakeState(store adt.Store, av actorstypes.Version, governor address.Address, bitwidth uint64) (State, error) { + switch av { +{{range .versions}} + case actorstypes.Version{{.}}: + return make{{.}}(store, governor, bitwidth) +{{end}} + default: return nil, xerrors.Errorf("datacap actor only valid for actors v9 and above, got %d", av) + } +} + +type State interface { + cbor.Marshaler + + Code() cid.Cid + ActorKey() string + ActorVersion() actorstypes.Version + + ForEachClient(func(addr address.Address, dcap abi.StoragePower) error) error + VerifiedClientDataCap(address.Address) (bool, abi.StoragePower, error) + Governor() (address.Address, error) + GetState() interface{} +} + +func AllCodes() []cid.Cid { + return []cid.Cid{ {{range .versions}} + (&state{{.}}{}).Code(), + {{- end}} + } +} diff --git a/chain/actors/builtin/datacap/datacap.go b/chain/actors/builtin/datacap/datacap.go new file mode 100644 index 000000000..3cf557e6c --- /dev/null +++ b/chain/actors/builtin/datacap/datacap.go @@ -0,0 +1,83 @@ +package datacap + +import ( + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtin11 "github.com/filecoin-project/go-state-types/builtin" + "github.com/filecoin-project/go-state-types/cbor" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/types" +) + +var ( + Address = builtin11.DatacapActorAddr + Methods = builtin11.MethodsDatacap +) + +func Load(store adt.Store, act *types.Actor) (State, error) { + if name, av, ok := actors.GetActorMetaByCode(act.Code); ok { + if name != manifest.DatacapKey { + return nil, xerrors.Errorf("actor code is not datacap: %s", name) + } + + switch av { + + case actorstypes.Version9: + return load9(store, act.Head) + + case actorstypes.Version10: + return load10(store, act.Head) + + case actorstypes.Version11: + return load11(store, act.Head) + + } + } + + return nil, xerrors.Errorf("unknown actor code %s", act.Code) +} + +func MakeState(store adt.Store, av actorstypes.Version, governor address.Address, bitwidth uint64) (State, error) { + switch av { + + case actorstypes.Version9: + return make9(store, governor, bitwidth) + + case actorstypes.Version10: + return make10(store, governor, bitwidth) + + case actorstypes.Version11: + return make11(store, governor, bitwidth) + + default: + return nil, xerrors.Errorf("datacap actor only valid for actors v9 and above, got %d", av) + } +} + +type State interface { + cbor.Marshaler + + Code() cid.Cid + ActorKey() string + ActorVersion() actorstypes.Version + + ForEachClient(func(addr address.Address, dcap abi.StoragePower) error) error + VerifiedClientDataCap(address.Address) (bool, abi.StoragePower, error) + Governor() (address.Address, error) + GetState() interface{} +} + +func AllCodes() []cid.Cid { + return []cid.Cid{ + (&state9{}).Code(), + (&state10{}).Code(), + (&state11{}).Code(), + } +} diff --git a/chain/actors/builtin/datacap/state.go.template b/chain/actors/builtin/datacap/state.go.template new file mode 100644 index 000000000..de0ccb4be --- /dev/null +++ b/chain/actors/builtin/datacap/state.go.template @@ -0,0 +1,81 @@ +package datacap + +import ( + "fmt" + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + actorstypes "github.com/filecoin-project/go-state-types/actors" + + datacap{{.v}} "github.com/filecoin-project/go-state-types/builtin{{.import}}datacap" + adt{{.v}} "github.com/filecoin-project/go-state-types/builtin{{.import}}util/adt" + "github.com/filecoin-project/go-state-types/manifest" +) + +var _ State = (*state{{.v}})(nil) + +func load{{.v}}(store adt.Store, root cid.Cid) (State, error) { + out := state{{.v}}{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make{{.v}}(store adt.Store, governor address.Address, bitwidth uint64) (State, error) { + out := state{{.v}}{store: store} + s, err := datacap{{.v}}.ConstructState(store, governor, bitwidth) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state{{.v}} struct { + datacap{{.v}}.State + store adt.Store +} + +func (s *state{{.v}}) Governor() (address.Address, error) { + return s.State.Governor, nil +} + +func (s *state{{.v}}) GetState() interface{} { + return &s.State +} + +func (s *state{{.v}}) ForEachClient(cb func(addr address.Address, dcap abi.StoragePower) error) error { + return forEachClient(s.store, actors.Version{{.v}}, s.verifiedClients, cb) +} + +func (s *state{{.v}}) verifiedClients() (adt.Map, error) { + return adt{{.v}}.AsMap(s.store, s.Token.Balances, int(s.Token.HamtBitWidth)) +} + +func (s *state{{.v}}) VerifiedClientDataCap(addr address.Address) (bool, abi.StoragePower, error) { + return getDataCap(s.store, actors.Version{{.v}}, s.verifiedClients, addr) +} + +func (s *state{{.v}}) ActorKey() string { + return manifest.DatacapKey +} + +func (s *state{{.v}}) ActorVersion() actorstypes.Version { + return actorstypes.Version{{.v}} +} + +func (s *state{{.v}}) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/datacap/util.go b/chain/actors/builtin/datacap/util.go new file mode 100644 index 000000000..03e941d6e --- /dev/null +++ b/chain/actors/builtin/datacap/util.go @@ -0,0 +1,63 @@ +package datacap + +import ( + "github.com/multiformats/go-varint" + "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/builtin/v9/verifreg" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +// taking this as a function instead of asking the caller to call it helps reduce some of the error +// checking boilerplate. +// +// "go made me do it" +type rootFunc func() (adt.Map, error) + +func getDataCap(store adt.Store, ver actors.Version, root rootFunc, addr address.Address) (bool, abi.StoragePower, error) { + if addr.Protocol() != address.ID { + return false, big.Zero(), xerrors.Errorf("can only look up ID addresses") + } + vh, err := root() + if err != nil { + return false, big.Zero(), xerrors.Errorf("loading datacap actor: %w", err) + } + + var dcap abi.StoragePower + if found, err := vh.Get(abi.IdAddrKey(addr), &dcap); err != nil { + return false, big.Zero(), xerrors.Errorf("looking up addr: %w", err) + } else if !found { + return false, big.Zero(), nil + } + + return true, big.Div(dcap, verifreg.DataCapGranularity), nil +} + +func forEachClient(store adt.Store, ver actors.Version, root rootFunc, cb func(addr address.Address, dcap abi.StoragePower) error) error { + vh, err := root() + if err != nil { + return xerrors.Errorf("loading verified clients: %w", err) + } + var dcap abi.StoragePower + return vh.ForEach(&dcap, func(key string) error { + id, n, err := varint.FromUvarint([]byte(key)) + if n != len([]byte(key)) { + return xerrors.Errorf("could not get varint from address string") + } + if err != nil { + return err + } + + a, err := address.NewIDAddress(id) + if err != nil { + return xerrors.Errorf("creating ID address from actor ID: %w", err) + } + + return cb(a, big.Div(dcap, verifreg.DataCapGranularity)) + }) +} diff --git a/chain/actors/builtin/datacap/v10.go b/chain/actors/builtin/datacap/v10.go new file mode 100644 index 000000000..25eec4ea8 --- /dev/null +++ b/chain/actors/builtin/datacap/v10.go @@ -0,0 +1,82 @@ +package datacap + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + datacap10 "github.com/filecoin-project/go-state-types/builtin/v10/datacap" + adt10 "github.com/filecoin-project/go-state-types/builtin/v10/util/adt" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state10)(nil) + +func load10(store adt.Store, root cid.Cid) (State, error) { + out := state10{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make10(store adt.Store, governor address.Address, bitwidth uint64) (State, error) { + out := state10{store: store} + s, err := datacap10.ConstructState(store, governor, bitwidth) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state10 struct { + datacap10.State + store adt.Store +} + +func (s *state10) Governor() (address.Address, error) { + return s.State.Governor, nil +} + +func (s *state10) GetState() interface{} { + return &s.State +} + +func (s *state10) ForEachClient(cb func(addr address.Address, dcap abi.StoragePower) error) error { + return forEachClient(s.store, actors.Version10, s.verifiedClients, cb) +} + +func (s *state10) verifiedClients() (adt.Map, error) { + return adt10.AsMap(s.store, s.Token.Balances, int(s.Token.HamtBitWidth)) +} + +func (s *state10) VerifiedClientDataCap(addr address.Address) (bool, abi.StoragePower, error) { + return getDataCap(s.store, actors.Version10, s.verifiedClients, addr) +} + +func (s *state10) ActorKey() string { + return manifest.DatacapKey +} + +func (s *state10) ActorVersion() actorstypes.Version { + return actorstypes.Version10 +} + +func (s *state10) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/datacap/v11.go b/chain/actors/builtin/datacap/v11.go new file mode 100644 index 000000000..0c302b5e1 --- /dev/null +++ b/chain/actors/builtin/datacap/v11.go @@ -0,0 +1,82 @@ +package datacap + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + datacap11 "github.com/filecoin-project/go-state-types/builtin/v11/datacap" + adt11 "github.com/filecoin-project/go-state-types/builtin/v11/util/adt" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state11)(nil) + +func load11(store adt.Store, root cid.Cid) (State, error) { + out := state11{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make11(store adt.Store, governor address.Address, bitwidth uint64) (State, error) { + out := state11{store: store} + s, err := datacap11.ConstructState(store, governor, bitwidth) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state11 struct { + datacap11.State + store adt.Store +} + +func (s *state11) Governor() (address.Address, error) { + return s.State.Governor, nil +} + +func (s *state11) GetState() interface{} { + return &s.State +} + +func (s *state11) ForEachClient(cb func(addr address.Address, dcap abi.StoragePower) error) error { + return forEachClient(s.store, actors.Version11, s.verifiedClients, cb) +} + +func (s *state11) verifiedClients() (adt.Map, error) { + return adt11.AsMap(s.store, s.Token.Balances, int(s.Token.HamtBitWidth)) +} + +func (s *state11) VerifiedClientDataCap(addr address.Address) (bool, abi.StoragePower, error) { + return getDataCap(s.store, actors.Version11, s.verifiedClients, addr) +} + +func (s *state11) ActorKey() string { + return manifest.DatacapKey +} + +func (s *state11) ActorVersion() actorstypes.Version { + return actorstypes.Version11 +} + +func (s *state11) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/datacap/v9.go b/chain/actors/builtin/datacap/v9.go new file mode 100644 index 000000000..1d239fb95 --- /dev/null +++ b/chain/actors/builtin/datacap/v9.go @@ -0,0 +1,82 @@ +package datacap + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + datacap9 "github.com/filecoin-project/go-state-types/builtin/v9/datacap" + adt9 "github.com/filecoin-project/go-state-types/builtin/v9/util/adt" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state9)(nil) + +func load9(store adt.Store, root cid.Cid) (State, error) { + out := state9{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make9(store adt.Store, governor address.Address, bitwidth uint64) (State, error) { + out := state9{store: store} + s, err := datacap9.ConstructState(store, governor, bitwidth) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state9 struct { + datacap9.State + store adt.Store +} + +func (s *state9) Governor() (address.Address, error) { + return s.State.Governor, nil +} + +func (s *state9) GetState() interface{} { + return &s.State +} + +func (s *state9) ForEachClient(cb func(addr address.Address, dcap abi.StoragePower) error) error { + return forEachClient(s.store, actors.Version9, s.verifiedClients, cb) +} + +func (s *state9) verifiedClients() (adt.Map, error) { + return adt9.AsMap(s.store, s.Token.Balances, int(s.Token.HamtBitWidth)) +} + +func (s *state9) VerifiedClientDataCap(addr address.Address) (bool, abi.StoragePower, error) { + return getDataCap(s.store, actors.Version9, s.verifiedClients, addr) +} + +func (s *state9) ActorKey() string { + return manifest.DatacapKey +} + +func (s *state9) ActorVersion() actorstypes.Version { + return actorstypes.Version9 +} + +func (s *state9) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/evm/actor.go.template b/chain/actors/builtin/evm/actor.go.template new file mode 100644 index 000000000..62da06867 --- /dev/null +++ b/chain/actors/builtin/evm/actor.go.template @@ -0,0 +1,57 @@ +package evm + +import ( + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/cbor" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/go-state-types/manifest" + + builtin{{.latestVersion}} "github.com/filecoin-project/go-state-types/builtin" +) + +var Methods = builtin{{.latestVersion}}.MethodsEVM + +func Load(store adt.Store, act *types.Actor) (State, error) { + if name, av, ok := actors.GetActorMetaByCode(act.Code); ok { + if name != manifest.EvmKey { + return nil, xerrors.Errorf("actor code is not evm: %s", name) + } + + switch av { + {{range .versions}} + case actorstypes.Version{{.}}: + return load{{.}}(store, act.Head) + {{end}} + } + } + + return nil, xerrors.Errorf("unknown actor code %s", act.Code) +} + +func MakeState(store adt.Store, av actorstypes.Version, bytecode cid.Cid) (State, error) { + switch av { +{{range .versions}} + case actorstypes.Version{{.}}: + return make{{.}}(store, bytecode) +{{end}} + default: return nil, xerrors.Errorf("evm actor only valid for actors v10 and above, got %d", av) + } +} + +type State interface { + cbor.Marshaler + + Nonce() (uint64, error) + IsAlive() (bool, error) + GetState() interface{} + + GetBytecode() ([]byte, error) + GetBytecodeCID() (cid.Cid, error) + GetBytecodeHash() ([32]byte, error) +} diff --git a/chain/actors/builtin/evm/evm.go b/chain/actors/builtin/evm/evm.go new file mode 100644 index 000000000..7c28295f2 --- /dev/null +++ b/chain/actors/builtin/evm/evm.go @@ -0,0 +1,63 @@ +package evm + +import ( + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtin11 "github.com/filecoin-project/go-state-types/builtin" + "github.com/filecoin-project/go-state-types/cbor" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/types" +) + +var Methods = builtin11.MethodsEVM + +func Load(store adt.Store, act *types.Actor) (State, error) { + if name, av, ok := actors.GetActorMetaByCode(act.Code); ok { + if name != manifest.EvmKey { + return nil, xerrors.Errorf("actor code is not evm: %s", name) + } + + switch av { + + case actorstypes.Version10: + return load10(store, act.Head) + + case actorstypes.Version11: + return load11(store, act.Head) + + } + } + + return nil, xerrors.Errorf("unknown actor code %s", act.Code) +} + +func MakeState(store adt.Store, av actorstypes.Version, bytecode cid.Cid) (State, error) { + switch av { + + case actorstypes.Version10: + return make10(store, bytecode) + + case actorstypes.Version11: + return make11(store, bytecode) + + default: + return nil, xerrors.Errorf("evm actor only valid for actors v10 and above, got %d", av) + } +} + +type State interface { + cbor.Marshaler + + Nonce() (uint64, error) + IsAlive() (bool, error) + GetState() interface{} + + GetBytecode() ([]byte, error) + GetBytecodeCID() (cid.Cid, error) + GetBytecodeHash() ([32]byte, error) +} diff --git a/chain/actors/builtin/evm/state.go.template b/chain/actors/builtin/evm/state.go.template new file mode 100644 index 000000000..f193733d1 --- /dev/null +++ b/chain/actors/builtin/evm/state.go.template @@ -0,0 +1,73 @@ +package evm + +import ( + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/go-state-types/abi" + + evm{{.v}} "github.com/filecoin-project/go-state-types/builtin{{.import}}evm" +) + +var _ State = (*state{{.v}})(nil) + +func load{{.v}}(store adt.Store, root cid.Cid) (State, error) { + out := state{{.v}}{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make{{.v}}(store adt.Store, bytecode cid.Cid) (State, error) { + out := state{{.v}}{store: store} + s, err := evm{{.v}}.ConstructState(store, bytecode) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state{{.v}} struct { + evm{{.v}}.State + store adt.Store +} + +func (s *state{{.v}}) Nonce() (uint64, error) { + return s.State.Nonce, nil +} + +func (s *state{{.v}}) IsAlive() (bool, error) { + return s.State.Tombstone == nil, nil +} + +func (s *state{{.v}}) GetState() interface{} { + return &s.State +} + +func (s *state{{.v}}) GetBytecodeCID() (cid.Cid, error) { + return s.State.Bytecode, nil +} + +func (s *state{{.v}}) GetBytecodeHash() ([32]byte, error) { + return s.State.BytecodeHash, nil +} + +func (s *state{{.v}}) GetBytecode() ([]byte, error) { + bc, err := s.GetBytecodeCID() + if err != nil { + return nil, err + } + + var byteCode abi.CborBytesTransparent + if err := s.store.Get(s.store.Context(), bc, &byteCode); err != nil { + return nil, err + } + + return byteCode, nil +} diff --git a/chain/actors/builtin/evm/v10.go b/chain/actors/builtin/evm/v10.go new file mode 100644 index 000000000..d467aa187 --- /dev/null +++ b/chain/actors/builtin/evm/v10.go @@ -0,0 +1,72 @@ +package evm + +import ( + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-state-types/abi" + evm10 "github.com/filecoin-project/go-state-types/builtin/v10/evm" + + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state10)(nil) + +func load10(store adt.Store, root cid.Cid) (State, error) { + out := state10{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make10(store adt.Store, bytecode cid.Cid) (State, error) { + out := state10{store: store} + s, err := evm10.ConstructState(store, bytecode) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state10 struct { + evm10.State + store adt.Store +} + +func (s *state10) Nonce() (uint64, error) { + return s.State.Nonce, nil +} + +func (s *state10) IsAlive() (bool, error) { + return s.State.Tombstone == nil, nil +} + +func (s *state10) GetState() interface{} { + return &s.State +} + +func (s *state10) GetBytecodeCID() (cid.Cid, error) { + return s.State.Bytecode, nil +} + +func (s *state10) GetBytecodeHash() ([32]byte, error) { + return s.State.BytecodeHash, nil +} + +func (s *state10) GetBytecode() ([]byte, error) { + bc, err := s.GetBytecodeCID() + if err != nil { + return nil, err + } + + var byteCode abi.CborBytesTransparent + if err := s.store.Get(s.store.Context(), bc, &byteCode); err != nil { + return nil, err + } + + return byteCode, nil +} diff --git a/chain/actors/builtin/evm/v11.go b/chain/actors/builtin/evm/v11.go new file mode 100644 index 000000000..9e2e984e4 --- /dev/null +++ b/chain/actors/builtin/evm/v11.go @@ -0,0 +1,72 @@ +package evm + +import ( + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-state-types/abi" + evm11 "github.com/filecoin-project/go-state-types/builtin/v11/evm" + + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state11)(nil) + +func load11(store adt.Store, root cid.Cid) (State, error) { + out := state11{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make11(store adt.Store, bytecode cid.Cid) (State, error) { + out := state11{store: store} + s, err := evm11.ConstructState(store, bytecode) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state11 struct { + evm11.State + store adt.Store +} + +func (s *state11) Nonce() (uint64, error) { + return s.State.Nonce, nil +} + +func (s *state11) IsAlive() (bool, error) { + return s.State.Tombstone == nil, nil +} + +func (s *state11) GetState() interface{} { + return &s.State +} + +func (s *state11) GetBytecodeCID() (cid.Cid, error) { + return s.State.Bytecode, nil +} + +func (s *state11) GetBytecodeHash() ([32]byte, error) { + return s.State.BytecodeHash, nil +} + +func (s *state11) GetBytecode() ([]byte, error) { + bc, err := s.GetBytecodeCID() + if err != nil { + return nil, err + } + + var byteCode abi.CborBytesTransparent + if err := s.store.Get(s.store.Context(), bc, &byteCode); err != nil { + return nil, err + } + + return byteCode, nil +} diff --git a/chain/actors/builtin/init/actor.go.template b/chain/actors/builtin/init/actor.go.template new file mode 100644 index 000000000..1d19ebb40 --- /dev/null +++ b/chain/actors/builtin/init/actor.go.template @@ -0,0 +1,107 @@ +package init + +import ( + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/lotus/chain/actors" + "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/cbor" + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/node/modules/dtypes" + "github.com/filecoin-project/go-state-types/manifest" +{{range .versions}} + {{if (le . 7)}} + builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin" + {{end}} +{{end}} + builtin{{.latestVersion}} "github.com/filecoin-project/go-state-types/builtin" +) + +var ( + Address = builtin{{.latestVersion}}.InitActorAddr + Methods = builtin{{.latestVersion}}.MethodsInit +) + +func Load(store adt.Store, act *types.Actor) (State, error) { + if name, av, ok := actors.GetActorMetaByCode(act.Code); ok { + if name != manifest.InitKey { + return nil, xerrors.Errorf("actor code is not init: %s", name) + } + + switch av { + {{range .versions}} + {{if (ge . 8)}} + case actorstypes.Version{{.}}: + return load{{.}}(store, act.Head) + {{end}} + {{end}} + } + } + + switch act.Code { +{{range .versions}} + {{if (le . 7)}} + case builtin{{.}}.InitActorCodeID: + return load{{.}}(store, act.Head) + {{end}} +{{end}} + } + + return nil, xerrors.Errorf("unknown actor code %s", act.Code) +} + +func MakeState(store adt.Store, av actorstypes.Version, networkName string) (State, error) { + switch av { +{{range .versions}} + case actorstypes.Version{{.}}: + return make{{.}}(store, networkName) +{{end}} +} + return nil, xerrors.Errorf("unknown actor version %d", av) +} + +type State interface { + cbor.Marshaler + + Code() cid.Cid + ActorKey() string + ActorVersion() actorstypes.Version + + ResolveAddress(address address.Address) (address.Address, bool, error) + MapAddressToNewID(address address.Address) (address.Address, error) + NetworkName() (dtypes.NetworkName, error) + + ForEachActor(func(id abi.ActorID, address address.Address) error) error + + // Remove exists to support tooling that manipulates state for testing. + // It should not be used in production code, as init actor entries are + // immutable. + Remove(addrs ...address.Address) error + + // Sets the network's name. This should only be used on upgrade/fork. + SetNetworkName(name string) error + + // Sets the next ID for the init actor. This should only be used for testing. + SetNextID(id abi.ActorID) error + + // Sets the address map for the init actor. This should only be used for testing. + SetAddressMap(mcid cid.Cid) error + + GetState() interface{} + + AddressMap() (adt.Map, error) + AddressMapBitWidth() int + AddressMapHashFunction() func(input []byte) []byte +} + +func AllCodes() []cid.Cid { + return []cid.Cid{ {{range .versions}} + (&state{{.}}{}).Code(), + {{- end}} + } +} diff --git a/chain/actors/builtin/init/diff.go b/chain/actors/builtin/init/diff.go new file mode 100644 index 000000000..60b5a5bff --- /dev/null +++ b/chain/actors/builtin/init/diff.go @@ -0,0 +1,153 @@ +package init + +import ( + "bytes" + + typegen "github.com/whyrusleeping/cbor-gen" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +func DiffAddressMap(pre, cur State) (*AddressMapChanges, error) { + prem, err := pre.AddressMap() + if err != nil { + return nil, err + } + + curm, err := cur.AddressMap() + if err != nil { + return nil, err + } + + preRoot, err := prem.Root() + if err != nil { + return nil, err + } + + curRoot, err := curm.Root() + if err != nil { + return nil, err + } + + results := new(AddressMapChanges) + // no change. + if curRoot.Equals(preRoot) { + return results, nil + } + + err = adt.DiffAdtMap(prem, curm, &addressMapDiffer{results, pre, cur}) + if err != nil { + return nil, err + } + + return results, nil +} + +type addressMapDiffer struct { + Results *AddressMapChanges + pre, adter State +} + +type AddressMapChanges struct { + Added []AddressPair + Modified []AddressChange + Removed []AddressPair +} + +func (i *addressMapDiffer) AsKey(key string) (abi.Keyer, error) { + addr, err := address.NewFromBytes([]byte(key)) + if err != nil { + return nil, err + } + return abi.AddrKey(addr), nil +} + +func (i *addressMapDiffer) Add(key string, val *typegen.Deferred) error { + pkAddr, err := address.NewFromBytes([]byte(key)) + if err != nil { + return err + } + id := new(typegen.CborInt) + if err := id.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return err + } + idAddr, err := address.NewIDAddress(uint64(*id)) + if err != nil { + return err + } + i.Results.Added = append(i.Results.Added, AddressPair{ + ID: idAddr, + PK: pkAddr, + }) + return nil +} + +func (i *addressMapDiffer) Modify(key string, from, to *typegen.Deferred) error { + pkAddr, err := address.NewFromBytes([]byte(key)) + if err != nil { + return err + } + + fromID := new(typegen.CborInt) + if err := fromID.UnmarshalCBOR(bytes.NewReader(from.Raw)); err != nil { + return err + } + fromIDAddr, err := address.NewIDAddress(uint64(*fromID)) + if err != nil { + return err + } + + toID := new(typegen.CborInt) + if err := toID.UnmarshalCBOR(bytes.NewReader(to.Raw)); err != nil { + return err + } + toIDAddr, err := address.NewIDAddress(uint64(*toID)) + if err != nil { + return err + } + + i.Results.Modified = append(i.Results.Modified, AddressChange{ + From: AddressPair{ + ID: fromIDAddr, + PK: pkAddr, + }, + To: AddressPair{ + ID: toIDAddr, + PK: pkAddr, + }, + }) + return nil +} + +func (i *addressMapDiffer) Remove(key string, val *typegen.Deferred) error { + pkAddr, err := address.NewFromBytes([]byte(key)) + if err != nil { + return err + } + id := new(typegen.CborInt) + if err := id.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return err + } + idAddr, err := address.NewIDAddress(uint64(*id)) + if err != nil { + return err + } + i.Results.Removed = append(i.Results.Removed, AddressPair{ + ID: idAddr, + PK: pkAddr, + }) + return nil +} + +type AddressChange struct { + From AddressPair + To AddressPair +} + +type AddressPair struct { + ID address.Address + PK address.Address +} diff --git a/chain/actors/builtin/init/init.go b/chain/actors/builtin/init/init.go new file mode 100644 index 000000000..2d9e41275 --- /dev/null +++ b/chain/actors/builtin/init/init.go @@ -0,0 +1,171 @@ +package init + +import ( + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtin11 "github.com/filecoin-project/go-state-types/builtin" + "github.com/filecoin-project/go-state-types/cbor" + "github.com/filecoin-project/go-state-types/manifest" + builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + builtin3 "github.com/filecoin-project/specs-actors/v3/actors/builtin" + builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" + builtin6 "github.com/filecoin-project/specs-actors/v6/actors/builtin" + builtin7 "github.com/filecoin-project/specs-actors/v7/actors/builtin" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/node/modules/dtypes" +) + +var ( + Address = builtin11.InitActorAddr + Methods = builtin11.MethodsInit +) + +func Load(store adt.Store, act *types.Actor) (State, error) { + if name, av, ok := actors.GetActorMetaByCode(act.Code); ok { + if name != manifest.InitKey { + return nil, xerrors.Errorf("actor code is not init: %s", name) + } + + switch av { + + case actorstypes.Version8: + return load8(store, act.Head) + + case actorstypes.Version9: + return load9(store, act.Head) + + case actorstypes.Version10: + return load10(store, act.Head) + + case actorstypes.Version11: + return load11(store, act.Head) + + } + } + + switch act.Code { + + case builtin0.InitActorCodeID: + return load0(store, act.Head) + + case builtin2.InitActorCodeID: + return load2(store, act.Head) + + case builtin3.InitActorCodeID: + return load3(store, act.Head) + + case builtin4.InitActorCodeID: + return load4(store, act.Head) + + case builtin5.InitActorCodeID: + return load5(store, act.Head) + + case builtin6.InitActorCodeID: + return load6(store, act.Head) + + case builtin7.InitActorCodeID: + return load7(store, act.Head) + + } + + return nil, xerrors.Errorf("unknown actor code %s", act.Code) +} + +func MakeState(store adt.Store, av actorstypes.Version, networkName string) (State, error) { + switch av { + + case actorstypes.Version0: + return make0(store, networkName) + + case actorstypes.Version2: + return make2(store, networkName) + + case actorstypes.Version3: + return make3(store, networkName) + + case actorstypes.Version4: + return make4(store, networkName) + + case actorstypes.Version5: + return make5(store, networkName) + + case actorstypes.Version6: + return make6(store, networkName) + + case actorstypes.Version7: + return make7(store, networkName) + + case actorstypes.Version8: + return make8(store, networkName) + + case actorstypes.Version9: + return make9(store, networkName) + + case actorstypes.Version10: + return make10(store, networkName) + + case actorstypes.Version11: + return make11(store, networkName) + + } + return nil, xerrors.Errorf("unknown actor version %d", av) +} + +type State interface { + cbor.Marshaler + + Code() cid.Cid + ActorKey() string + ActorVersion() actorstypes.Version + + ResolveAddress(address address.Address) (address.Address, bool, error) + MapAddressToNewID(address address.Address) (address.Address, error) + NetworkName() (dtypes.NetworkName, error) + + ForEachActor(func(id abi.ActorID, address address.Address) error) error + + // Remove exists to support tooling that manipulates state for testing. + // It should not be used in production code, as init actor entries are + // immutable. + Remove(addrs ...address.Address) error + + // Sets the network's name. This should only be used on upgrade/fork. + SetNetworkName(name string) error + + // Sets the next ID for the init actor. This should only be used for testing. + SetNextID(id abi.ActorID) error + + // Sets the address map for the init actor. This should only be used for testing. + SetAddressMap(mcid cid.Cid) error + + GetState() interface{} + + AddressMap() (adt.Map, error) + AddressMapBitWidth() int + AddressMapHashFunction() func(input []byte) []byte +} + +func AllCodes() []cid.Cid { + return []cid.Cid{ + (&state0{}).Code(), + (&state2{}).Code(), + (&state3{}).Code(), + (&state4{}).Code(), + (&state5{}).Code(), + (&state6{}).Code(), + (&state7{}).Code(), + (&state8{}).Code(), + (&state9{}).Code(), + (&state10{}).Code(), + (&state11{}).Code(), + } +} diff --git a/chain/actors/builtin/init/state.go.template b/chain/actors/builtin/init/state.go.template new file mode 100644 index 000000000..52e223a85 --- /dev/null +++ b/chain/actors/builtin/init/state.go.template @@ -0,0 +1,166 @@ +package init + +import ( + "crypto/sha256" + "fmt" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/lotus/node/modules/dtypes" + "github.com/filecoin-project/go-state-types/manifest" + +{{if (le .v 7)}} + {{if (ge .v 3)}} + builtin{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/builtin" + {{end}} + init{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/builtin/init" + adt{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/util/adt" +{{else}} + init{{.v}} "github.com/filecoin-project/go-state-types/builtin{{.import}}init" + adt{{.v}} "github.com/filecoin-project/go-state-types/builtin{{.import}}util/adt" + builtin{{.v}} "github.com/filecoin-project/go-state-types/builtin" +{{end}} +) + +var _ State = (*state{{.v}})(nil) + +func load{{.v}}(store adt.Store, root cid.Cid) (State, error) { + out := state{{.v}}{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make{{.v}}(store adt.Store, networkName string) (State, error) { + out := state{{.v}}{store: store} + {{if (le .v 2)}} + mr, err := adt{{.v}}.MakeEmptyMap(store).Root() + if err != nil { + return nil, err + } + + out.State = *init{{.v}}.ConstructState(mr, networkName) + {{else}} + s, err := init{{.v}}.ConstructState(store, networkName) + if err != nil { + return nil, err + } + + out.State = *s + {{end}} + return &out, nil +} + +type state{{.v}} struct { + init{{.v}}.State + store adt.Store +} + +func (s *state{{.v}}) ResolveAddress(address address.Address) (address.Address, bool, error) { + return s.State.ResolveAddress(s.store, address) +} + +func (s *state{{.v}}) MapAddressToNewID(address address.Address) (address.Address, error) { + return s.State.MapAddressToNewID(s.store, address) +} + +func (s *state{{.v}}) ForEachActor(cb func(id abi.ActorID, address address.Address) error) error { + addrs, err := adt{{.v}}.AsMap(s.store, s.State.AddressMap{{if (ge .v 3)}}, builtin{{.v}}.DefaultHamtBitwidth{{end}}) + if err != nil { + return err + } + var actorID cbg.CborInt + return addrs.ForEach(&actorID, func(key string) error { + addr, err := address.NewFromBytes([]byte(key)) + if err != nil { + return err + } + return cb(abi.ActorID(actorID), addr) + }) +} + +func (s *state{{.v}}) NetworkName() (dtypes.NetworkName, error) { + return dtypes.NetworkName(s.State.NetworkName), nil +} + +func (s *state{{.v}}) SetNetworkName(name string) error { + s.State.NetworkName = name + return nil +} + +func (s *state{{.v}}) SetNextID(id abi.ActorID) error { + s.State.NextID = id + return nil +} + +func (s *state{{.v}}) Remove(addrs ...address.Address) (err error) { + m, err := adt{{.v}}.AsMap(s.store, s.State.AddressMap{{if (ge .v 3)}}, builtin{{.v}}.DefaultHamtBitwidth{{end}}) + if err != nil { + return err + } + for _, addr := range addrs { + if err = m.Delete(abi.AddrKey(addr)); err != nil { + return xerrors.Errorf("failed to delete entry for address: %s; err: %w", addr, err) + } + } + amr, err := m.Root() + if err != nil { + return xerrors.Errorf("failed to get address map root: %w", err) + } + s.State.AddressMap = amr + return nil +} + +func (s *state{{.v}}) SetAddressMap(mcid cid.Cid) error { + s.State.AddressMap = mcid + return nil +} + +func (s *state{{.v}}) GetState() interface{} { + return &s.State +} + +func (s *state{{.v}}) AddressMap() (adt.Map, error) { + return adt{{.v}}.AsMap(s.store, s.State.AddressMap{{if (ge .v 3)}}, builtin{{.v}}.DefaultHamtBitwidth{{end}}) +} + +func (s *state{{.v}}) AddressMapBitWidth() int { + {{- if (ge .v 3)}} + return builtin{{.v}}.DefaultHamtBitwidth + {{- else}} + return 5 + {{- end}} +} + +func (s *state{{.v}}) AddressMapHashFunction() func(input []byte) []byte { + return func(input []byte) []byte { + res := sha256.Sum256(input) + return res[:] + } +} + +func (s *state{{.v}}) ActorKey() string { + return manifest.InitKey +} + +func (s *state{{.v}}) ActorVersion() actorstypes.Version { + return actorstypes.Version{{.v}} +} + +func (s *state{{.v}}) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/init/v0.go b/chain/actors/builtin/init/v0.go new file mode 100644 index 000000000..7e48dda9e --- /dev/null +++ b/chain/actors/builtin/init/v0.go @@ -0,0 +1,146 @@ +package init + +import ( + "crypto/sha256" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + init0 "github.com/filecoin-project/specs-actors/actors/builtin/init" + adt0 "github.com/filecoin-project/specs-actors/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/node/modules/dtypes" +) + +var _ State = (*state0)(nil) + +func load0(store adt.Store, root cid.Cid) (State, error) { + out := state0{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make0(store adt.Store, networkName string) (State, error) { + out := state0{store: store} + + mr, err := adt0.MakeEmptyMap(store).Root() + if err != nil { + return nil, err + } + + out.State = *init0.ConstructState(mr, networkName) + + return &out, nil +} + +type state0 struct { + init0.State + store adt.Store +} + +func (s *state0) ResolveAddress(address address.Address) (address.Address, bool, error) { + return s.State.ResolveAddress(s.store, address) +} + +func (s *state0) MapAddressToNewID(address address.Address) (address.Address, error) { + return s.State.MapAddressToNewID(s.store, address) +} + +func (s *state0) ForEachActor(cb func(id abi.ActorID, address address.Address) error) error { + addrs, err := adt0.AsMap(s.store, s.State.AddressMap) + if err != nil { + return err + } + var actorID cbg.CborInt + return addrs.ForEach(&actorID, func(key string) error { + addr, err := address.NewFromBytes([]byte(key)) + if err != nil { + return err + } + return cb(abi.ActorID(actorID), addr) + }) +} + +func (s *state0) NetworkName() (dtypes.NetworkName, error) { + return dtypes.NetworkName(s.State.NetworkName), nil +} + +func (s *state0) SetNetworkName(name string) error { + s.State.NetworkName = name + return nil +} + +func (s *state0) SetNextID(id abi.ActorID) error { + s.State.NextID = id + return nil +} + +func (s *state0) Remove(addrs ...address.Address) (err error) { + m, err := adt0.AsMap(s.store, s.State.AddressMap) + if err != nil { + return err + } + for _, addr := range addrs { + if err = m.Delete(abi.AddrKey(addr)); err != nil { + return xerrors.Errorf("failed to delete entry for address: %s; err: %w", addr, err) + } + } + amr, err := m.Root() + if err != nil { + return xerrors.Errorf("failed to get address map root: %w", err) + } + s.State.AddressMap = amr + return nil +} + +func (s *state0) SetAddressMap(mcid cid.Cid) error { + s.State.AddressMap = mcid + return nil +} + +func (s *state0) GetState() interface{} { + return &s.State +} + +func (s *state0) AddressMap() (adt.Map, error) { + return adt0.AsMap(s.store, s.State.AddressMap) +} + +func (s *state0) AddressMapBitWidth() int { + return 5 +} + +func (s *state0) AddressMapHashFunction() func(input []byte) []byte { + return func(input []byte) []byte { + res := sha256.Sum256(input) + return res[:] + } +} + +func (s *state0) ActorKey() string { + return manifest.InitKey +} + +func (s *state0) ActorVersion() actorstypes.Version { + return actorstypes.Version0 +} + +func (s *state0) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/init/v10.go b/chain/actors/builtin/init/v10.go new file mode 100644 index 000000000..dd8c778dd --- /dev/null +++ b/chain/actors/builtin/init/v10.go @@ -0,0 +1,147 @@ +package init + +import ( + "crypto/sha256" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtin10 "github.com/filecoin-project/go-state-types/builtin" + init10 "github.com/filecoin-project/go-state-types/builtin/v10/init" + adt10 "github.com/filecoin-project/go-state-types/builtin/v10/util/adt" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/node/modules/dtypes" +) + +var _ State = (*state10)(nil) + +func load10(store adt.Store, root cid.Cid) (State, error) { + out := state10{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make10(store adt.Store, networkName string) (State, error) { + out := state10{store: store} + + s, err := init10.ConstructState(store, networkName) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state10 struct { + init10.State + store adt.Store +} + +func (s *state10) ResolveAddress(address address.Address) (address.Address, bool, error) { + return s.State.ResolveAddress(s.store, address) +} + +func (s *state10) MapAddressToNewID(address address.Address) (address.Address, error) { + return s.State.MapAddressToNewID(s.store, address) +} + +func (s *state10) ForEachActor(cb func(id abi.ActorID, address address.Address) error) error { + addrs, err := adt10.AsMap(s.store, s.State.AddressMap, builtin10.DefaultHamtBitwidth) + if err != nil { + return err + } + var actorID cbg.CborInt + return addrs.ForEach(&actorID, func(key string) error { + addr, err := address.NewFromBytes([]byte(key)) + if err != nil { + return err + } + return cb(abi.ActorID(actorID), addr) + }) +} + +func (s *state10) NetworkName() (dtypes.NetworkName, error) { + return dtypes.NetworkName(s.State.NetworkName), nil +} + +func (s *state10) SetNetworkName(name string) error { + s.State.NetworkName = name + return nil +} + +func (s *state10) SetNextID(id abi.ActorID) error { + s.State.NextID = id + return nil +} + +func (s *state10) Remove(addrs ...address.Address) (err error) { + m, err := adt10.AsMap(s.store, s.State.AddressMap, builtin10.DefaultHamtBitwidth) + if err != nil { + return err + } + for _, addr := range addrs { + if err = m.Delete(abi.AddrKey(addr)); err != nil { + return xerrors.Errorf("failed to delete entry for address: %s; err: %w", addr, err) + } + } + amr, err := m.Root() + if err != nil { + return xerrors.Errorf("failed to get address map root: %w", err) + } + s.State.AddressMap = amr + return nil +} + +func (s *state10) SetAddressMap(mcid cid.Cid) error { + s.State.AddressMap = mcid + return nil +} + +func (s *state10) GetState() interface{} { + return &s.State +} + +func (s *state10) AddressMap() (adt.Map, error) { + return adt10.AsMap(s.store, s.State.AddressMap, builtin10.DefaultHamtBitwidth) +} + +func (s *state10) AddressMapBitWidth() int { + return builtin10.DefaultHamtBitwidth +} + +func (s *state10) AddressMapHashFunction() func(input []byte) []byte { + return func(input []byte) []byte { + res := sha256.Sum256(input) + return res[:] + } +} + +func (s *state10) ActorKey() string { + return manifest.InitKey +} + +func (s *state10) ActorVersion() actorstypes.Version { + return actorstypes.Version10 +} + +func (s *state10) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/init/v11.go b/chain/actors/builtin/init/v11.go new file mode 100644 index 000000000..3d8d72e49 --- /dev/null +++ b/chain/actors/builtin/init/v11.go @@ -0,0 +1,147 @@ +package init + +import ( + "crypto/sha256" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtin11 "github.com/filecoin-project/go-state-types/builtin" + init11 "github.com/filecoin-project/go-state-types/builtin/v11/init" + adt11 "github.com/filecoin-project/go-state-types/builtin/v11/util/adt" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/node/modules/dtypes" +) + +var _ State = (*state11)(nil) + +func load11(store adt.Store, root cid.Cid) (State, error) { + out := state11{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make11(store adt.Store, networkName string) (State, error) { + out := state11{store: store} + + s, err := init11.ConstructState(store, networkName) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state11 struct { + init11.State + store adt.Store +} + +func (s *state11) ResolveAddress(address address.Address) (address.Address, bool, error) { + return s.State.ResolveAddress(s.store, address) +} + +func (s *state11) MapAddressToNewID(address address.Address) (address.Address, error) { + return s.State.MapAddressToNewID(s.store, address) +} + +func (s *state11) ForEachActor(cb func(id abi.ActorID, address address.Address) error) error { + addrs, err := adt11.AsMap(s.store, s.State.AddressMap, builtin11.DefaultHamtBitwidth) + if err != nil { + return err + } + var actorID cbg.CborInt + return addrs.ForEach(&actorID, func(key string) error { + addr, err := address.NewFromBytes([]byte(key)) + if err != nil { + return err + } + return cb(abi.ActorID(actorID), addr) + }) +} + +func (s *state11) NetworkName() (dtypes.NetworkName, error) { + return dtypes.NetworkName(s.State.NetworkName), nil +} + +func (s *state11) SetNetworkName(name string) error { + s.State.NetworkName = name + return nil +} + +func (s *state11) SetNextID(id abi.ActorID) error { + s.State.NextID = id + return nil +} + +func (s *state11) Remove(addrs ...address.Address) (err error) { + m, err := adt11.AsMap(s.store, s.State.AddressMap, builtin11.DefaultHamtBitwidth) + if err != nil { + return err + } + for _, addr := range addrs { + if err = m.Delete(abi.AddrKey(addr)); err != nil { + return xerrors.Errorf("failed to delete entry for address: %s; err: %w", addr, err) + } + } + amr, err := m.Root() + if err != nil { + return xerrors.Errorf("failed to get address map root: %w", err) + } + s.State.AddressMap = amr + return nil +} + +func (s *state11) SetAddressMap(mcid cid.Cid) error { + s.State.AddressMap = mcid + return nil +} + +func (s *state11) GetState() interface{} { + return &s.State +} + +func (s *state11) AddressMap() (adt.Map, error) { + return adt11.AsMap(s.store, s.State.AddressMap, builtin11.DefaultHamtBitwidth) +} + +func (s *state11) AddressMapBitWidth() int { + return builtin11.DefaultHamtBitwidth +} + +func (s *state11) AddressMapHashFunction() func(input []byte) []byte { + return func(input []byte) []byte { + res := sha256.Sum256(input) + return res[:] + } +} + +func (s *state11) ActorKey() string { + return manifest.InitKey +} + +func (s *state11) ActorVersion() actorstypes.Version { + return actorstypes.Version11 +} + +func (s *state11) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/init/v2.go b/chain/actors/builtin/init/v2.go new file mode 100644 index 000000000..c107bd52d --- /dev/null +++ b/chain/actors/builtin/init/v2.go @@ -0,0 +1,146 @@ +package init + +import ( + "crypto/sha256" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + init2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/init" + adt2 "github.com/filecoin-project/specs-actors/v2/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/node/modules/dtypes" +) + +var _ State = (*state2)(nil) + +func load2(store adt.Store, root cid.Cid) (State, error) { + out := state2{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make2(store adt.Store, networkName string) (State, error) { + out := state2{store: store} + + mr, err := adt2.MakeEmptyMap(store).Root() + if err != nil { + return nil, err + } + + out.State = *init2.ConstructState(mr, networkName) + + return &out, nil +} + +type state2 struct { + init2.State + store adt.Store +} + +func (s *state2) ResolveAddress(address address.Address) (address.Address, bool, error) { + return s.State.ResolveAddress(s.store, address) +} + +func (s *state2) MapAddressToNewID(address address.Address) (address.Address, error) { + return s.State.MapAddressToNewID(s.store, address) +} + +func (s *state2) ForEachActor(cb func(id abi.ActorID, address address.Address) error) error { + addrs, err := adt2.AsMap(s.store, s.State.AddressMap) + if err != nil { + return err + } + var actorID cbg.CborInt + return addrs.ForEach(&actorID, func(key string) error { + addr, err := address.NewFromBytes([]byte(key)) + if err != nil { + return err + } + return cb(abi.ActorID(actorID), addr) + }) +} + +func (s *state2) NetworkName() (dtypes.NetworkName, error) { + return dtypes.NetworkName(s.State.NetworkName), nil +} + +func (s *state2) SetNetworkName(name string) error { + s.State.NetworkName = name + return nil +} + +func (s *state2) SetNextID(id abi.ActorID) error { + s.State.NextID = id + return nil +} + +func (s *state2) Remove(addrs ...address.Address) (err error) { + m, err := adt2.AsMap(s.store, s.State.AddressMap) + if err != nil { + return err + } + for _, addr := range addrs { + if err = m.Delete(abi.AddrKey(addr)); err != nil { + return xerrors.Errorf("failed to delete entry for address: %s; err: %w", addr, err) + } + } + amr, err := m.Root() + if err != nil { + return xerrors.Errorf("failed to get address map root: %w", err) + } + s.State.AddressMap = amr + return nil +} + +func (s *state2) SetAddressMap(mcid cid.Cid) error { + s.State.AddressMap = mcid + return nil +} + +func (s *state2) GetState() interface{} { + return &s.State +} + +func (s *state2) AddressMap() (adt.Map, error) { + return adt2.AsMap(s.store, s.State.AddressMap) +} + +func (s *state2) AddressMapBitWidth() int { + return 5 +} + +func (s *state2) AddressMapHashFunction() func(input []byte) []byte { + return func(input []byte) []byte { + res := sha256.Sum256(input) + return res[:] + } +} + +func (s *state2) ActorKey() string { + return manifest.InitKey +} + +func (s *state2) ActorVersion() actorstypes.Version { + return actorstypes.Version2 +} + +func (s *state2) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/init/v3.go b/chain/actors/builtin/init/v3.go new file mode 100644 index 000000000..0be11f976 --- /dev/null +++ b/chain/actors/builtin/init/v3.go @@ -0,0 +1,147 @@ +package init + +import ( + "crypto/sha256" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + builtin3 "github.com/filecoin-project/specs-actors/v3/actors/builtin" + init3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/init" + adt3 "github.com/filecoin-project/specs-actors/v3/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/node/modules/dtypes" +) + +var _ State = (*state3)(nil) + +func load3(store adt.Store, root cid.Cid) (State, error) { + out := state3{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make3(store adt.Store, networkName string) (State, error) { + out := state3{store: store} + + s, err := init3.ConstructState(store, networkName) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state3 struct { + init3.State + store adt.Store +} + +func (s *state3) ResolveAddress(address address.Address) (address.Address, bool, error) { + return s.State.ResolveAddress(s.store, address) +} + +func (s *state3) MapAddressToNewID(address address.Address) (address.Address, error) { + return s.State.MapAddressToNewID(s.store, address) +} + +func (s *state3) ForEachActor(cb func(id abi.ActorID, address address.Address) error) error { + addrs, err := adt3.AsMap(s.store, s.State.AddressMap, builtin3.DefaultHamtBitwidth) + if err != nil { + return err + } + var actorID cbg.CborInt + return addrs.ForEach(&actorID, func(key string) error { + addr, err := address.NewFromBytes([]byte(key)) + if err != nil { + return err + } + return cb(abi.ActorID(actorID), addr) + }) +} + +func (s *state3) NetworkName() (dtypes.NetworkName, error) { + return dtypes.NetworkName(s.State.NetworkName), nil +} + +func (s *state3) SetNetworkName(name string) error { + s.State.NetworkName = name + return nil +} + +func (s *state3) SetNextID(id abi.ActorID) error { + s.State.NextID = id + return nil +} + +func (s *state3) Remove(addrs ...address.Address) (err error) { + m, err := adt3.AsMap(s.store, s.State.AddressMap, builtin3.DefaultHamtBitwidth) + if err != nil { + return err + } + for _, addr := range addrs { + if err = m.Delete(abi.AddrKey(addr)); err != nil { + return xerrors.Errorf("failed to delete entry for address: %s; err: %w", addr, err) + } + } + amr, err := m.Root() + if err != nil { + return xerrors.Errorf("failed to get address map root: %w", err) + } + s.State.AddressMap = amr + return nil +} + +func (s *state3) SetAddressMap(mcid cid.Cid) error { + s.State.AddressMap = mcid + return nil +} + +func (s *state3) GetState() interface{} { + return &s.State +} + +func (s *state3) AddressMap() (adt.Map, error) { + return adt3.AsMap(s.store, s.State.AddressMap, builtin3.DefaultHamtBitwidth) +} + +func (s *state3) AddressMapBitWidth() int { + return builtin3.DefaultHamtBitwidth +} + +func (s *state3) AddressMapHashFunction() func(input []byte) []byte { + return func(input []byte) []byte { + res := sha256.Sum256(input) + return res[:] + } +} + +func (s *state3) ActorKey() string { + return manifest.InitKey +} + +func (s *state3) ActorVersion() actorstypes.Version { + return actorstypes.Version3 +} + +func (s *state3) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/init/v4.go b/chain/actors/builtin/init/v4.go new file mode 100644 index 000000000..5ca6bc1c8 --- /dev/null +++ b/chain/actors/builtin/init/v4.go @@ -0,0 +1,147 @@ +package init + +import ( + "crypto/sha256" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" + init4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/init" + adt4 "github.com/filecoin-project/specs-actors/v4/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/node/modules/dtypes" +) + +var _ State = (*state4)(nil) + +func load4(store adt.Store, root cid.Cid) (State, error) { + out := state4{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make4(store adt.Store, networkName string) (State, error) { + out := state4{store: store} + + s, err := init4.ConstructState(store, networkName) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state4 struct { + init4.State + store adt.Store +} + +func (s *state4) ResolveAddress(address address.Address) (address.Address, bool, error) { + return s.State.ResolveAddress(s.store, address) +} + +func (s *state4) MapAddressToNewID(address address.Address) (address.Address, error) { + return s.State.MapAddressToNewID(s.store, address) +} + +func (s *state4) ForEachActor(cb func(id abi.ActorID, address address.Address) error) error { + addrs, err := adt4.AsMap(s.store, s.State.AddressMap, builtin4.DefaultHamtBitwidth) + if err != nil { + return err + } + var actorID cbg.CborInt + return addrs.ForEach(&actorID, func(key string) error { + addr, err := address.NewFromBytes([]byte(key)) + if err != nil { + return err + } + return cb(abi.ActorID(actorID), addr) + }) +} + +func (s *state4) NetworkName() (dtypes.NetworkName, error) { + return dtypes.NetworkName(s.State.NetworkName), nil +} + +func (s *state4) SetNetworkName(name string) error { + s.State.NetworkName = name + return nil +} + +func (s *state4) SetNextID(id abi.ActorID) error { + s.State.NextID = id + return nil +} + +func (s *state4) Remove(addrs ...address.Address) (err error) { + m, err := adt4.AsMap(s.store, s.State.AddressMap, builtin4.DefaultHamtBitwidth) + if err != nil { + return err + } + for _, addr := range addrs { + if err = m.Delete(abi.AddrKey(addr)); err != nil { + return xerrors.Errorf("failed to delete entry for address: %s; err: %w", addr, err) + } + } + amr, err := m.Root() + if err != nil { + return xerrors.Errorf("failed to get address map root: %w", err) + } + s.State.AddressMap = amr + return nil +} + +func (s *state4) SetAddressMap(mcid cid.Cid) error { + s.State.AddressMap = mcid + return nil +} + +func (s *state4) GetState() interface{} { + return &s.State +} + +func (s *state4) AddressMap() (adt.Map, error) { + return adt4.AsMap(s.store, s.State.AddressMap, builtin4.DefaultHamtBitwidth) +} + +func (s *state4) AddressMapBitWidth() int { + return builtin4.DefaultHamtBitwidth +} + +func (s *state4) AddressMapHashFunction() func(input []byte) []byte { + return func(input []byte) []byte { + res := sha256.Sum256(input) + return res[:] + } +} + +func (s *state4) ActorKey() string { + return manifest.InitKey +} + +func (s *state4) ActorVersion() actorstypes.Version { + return actorstypes.Version4 +} + +func (s *state4) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/init/v5.go b/chain/actors/builtin/init/v5.go new file mode 100644 index 000000000..f6450789d --- /dev/null +++ b/chain/actors/builtin/init/v5.go @@ -0,0 +1,147 @@ +package init + +import ( + "crypto/sha256" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" + init5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/init" + adt5 "github.com/filecoin-project/specs-actors/v5/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/node/modules/dtypes" +) + +var _ State = (*state5)(nil) + +func load5(store adt.Store, root cid.Cid) (State, error) { + out := state5{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make5(store adt.Store, networkName string) (State, error) { + out := state5{store: store} + + s, err := init5.ConstructState(store, networkName) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state5 struct { + init5.State + store adt.Store +} + +func (s *state5) ResolveAddress(address address.Address) (address.Address, bool, error) { + return s.State.ResolveAddress(s.store, address) +} + +func (s *state5) MapAddressToNewID(address address.Address) (address.Address, error) { + return s.State.MapAddressToNewID(s.store, address) +} + +func (s *state5) ForEachActor(cb func(id abi.ActorID, address address.Address) error) error { + addrs, err := adt5.AsMap(s.store, s.State.AddressMap, builtin5.DefaultHamtBitwidth) + if err != nil { + return err + } + var actorID cbg.CborInt + return addrs.ForEach(&actorID, func(key string) error { + addr, err := address.NewFromBytes([]byte(key)) + if err != nil { + return err + } + return cb(abi.ActorID(actorID), addr) + }) +} + +func (s *state5) NetworkName() (dtypes.NetworkName, error) { + return dtypes.NetworkName(s.State.NetworkName), nil +} + +func (s *state5) SetNetworkName(name string) error { + s.State.NetworkName = name + return nil +} + +func (s *state5) SetNextID(id abi.ActorID) error { + s.State.NextID = id + return nil +} + +func (s *state5) Remove(addrs ...address.Address) (err error) { + m, err := adt5.AsMap(s.store, s.State.AddressMap, builtin5.DefaultHamtBitwidth) + if err != nil { + return err + } + for _, addr := range addrs { + if err = m.Delete(abi.AddrKey(addr)); err != nil { + return xerrors.Errorf("failed to delete entry for address: %s; err: %w", addr, err) + } + } + amr, err := m.Root() + if err != nil { + return xerrors.Errorf("failed to get address map root: %w", err) + } + s.State.AddressMap = amr + return nil +} + +func (s *state5) SetAddressMap(mcid cid.Cid) error { + s.State.AddressMap = mcid + return nil +} + +func (s *state5) GetState() interface{} { + return &s.State +} + +func (s *state5) AddressMap() (adt.Map, error) { + return adt5.AsMap(s.store, s.State.AddressMap, builtin5.DefaultHamtBitwidth) +} + +func (s *state5) AddressMapBitWidth() int { + return builtin5.DefaultHamtBitwidth +} + +func (s *state5) AddressMapHashFunction() func(input []byte) []byte { + return func(input []byte) []byte { + res := sha256.Sum256(input) + return res[:] + } +} + +func (s *state5) ActorKey() string { + return manifest.InitKey +} + +func (s *state5) ActorVersion() actorstypes.Version { + return actorstypes.Version5 +} + +func (s *state5) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/init/v6.go b/chain/actors/builtin/init/v6.go new file mode 100644 index 000000000..4d2267aa1 --- /dev/null +++ b/chain/actors/builtin/init/v6.go @@ -0,0 +1,147 @@ +package init + +import ( + "crypto/sha256" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + builtin6 "github.com/filecoin-project/specs-actors/v6/actors/builtin" + init6 "github.com/filecoin-project/specs-actors/v6/actors/builtin/init" + adt6 "github.com/filecoin-project/specs-actors/v6/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/node/modules/dtypes" +) + +var _ State = (*state6)(nil) + +func load6(store adt.Store, root cid.Cid) (State, error) { + out := state6{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make6(store adt.Store, networkName string) (State, error) { + out := state6{store: store} + + s, err := init6.ConstructState(store, networkName) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state6 struct { + init6.State + store adt.Store +} + +func (s *state6) ResolveAddress(address address.Address) (address.Address, bool, error) { + return s.State.ResolveAddress(s.store, address) +} + +func (s *state6) MapAddressToNewID(address address.Address) (address.Address, error) { + return s.State.MapAddressToNewID(s.store, address) +} + +func (s *state6) ForEachActor(cb func(id abi.ActorID, address address.Address) error) error { + addrs, err := adt6.AsMap(s.store, s.State.AddressMap, builtin6.DefaultHamtBitwidth) + if err != nil { + return err + } + var actorID cbg.CborInt + return addrs.ForEach(&actorID, func(key string) error { + addr, err := address.NewFromBytes([]byte(key)) + if err != nil { + return err + } + return cb(abi.ActorID(actorID), addr) + }) +} + +func (s *state6) NetworkName() (dtypes.NetworkName, error) { + return dtypes.NetworkName(s.State.NetworkName), nil +} + +func (s *state6) SetNetworkName(name string) error { + s.State.NetworkName = name + return nil +} + +func (s *state6) SetNextID(id abi.ActorID) error { + s.State.NextID = id + return nil +} + +func (s *state6) Remove(addrs ...address.Address) (err error) { + m, err := adt6.AsMap(s.store, s.State.AddressMap, builtin6.DefaultHamtBitwidth) + if err != nil { + return err + } + for _, addr := range addrs { + if err = m.Delete(abi.AddrKey(addr)); err != nil { + return xerrors.Errorf("failed to delete entry for address: %s; err: %w", addr, err) + } + } + amr, err := m.Root() + if err != nil { + return xerrors.Errorf("failed to get address map root: %w", err) + } + s.State.AddressMap = amr + return nil +} + +func (s *state6) SetAddressMap(mcid cid.Cid) error { + s.State.AddressMap = mcid + return nil +} + +func (s *state6) GetState() interface{} { + return &s.State +} + +func (s *state6) AddressMap() (adt.Map, error) { + return adt6.AsMap(s.store, s.State.AddressMap, builtin6.DefaultHamtBitwidth) +} + +func (s *state6) AddressMapBitWidth() int { + return builtin6.DefaultHamtBitwidth +} + +func (s *state6) AddressMapHashFunction() func(input []byte) []byte { + return func(input []byte) []byte { + res := sha256.Sum256(input) + return res[:] + } +} + +func (s *state6) ActorKey() string { + return manifest.InitKey +} + +func (s *state6) ActorVersion() actorstypes.Version { + return actorstypes.Version6 +} + +func (s *state6) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/init/v7.go b/chain/actors/builtin/init/v7.go new file mode 100644 index 000000000..052faf985 --- /dev/null +++ b/chain/actors/builtin/init/v7.go @@ -0,0 +1,147 @@ +package init + +import ( + "crypto/sha256" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + builtin7 "github.com/filecoin-project/specs-actors/v7/actors/builtin" + init7 "github.com/filecoin-project/specs-actors/v7/actors/builtin/init" + adt7 "github.com/filecoin-project/specs-actors/v7/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/node/modules/dtypes" +) + +var _ State = (*state7)(nil) + +func load7(store adt.Store, root cid.Cid) (State, error) { + out := state7{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make7(store adt.Store, networkName string) (State, error) { + out := state7{store: store} + + s, err := init7.ConstructState(store, networkName) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state7 struct { + init7.State + store adt.Store +} + +func (s *state7) ResolveAddress(address address.Address) (address.Address, bool, error) { + return s.State.ResolveAddress(s.store, address) +} + +func (s *state7) MapAddressToNewID(address address.Address) (address.Address, error) { + return s.State.MapAddressToNewID(s.store, address) +} + +func (s *state7) ForEachActor(cb func(id abi.ActorID, address address.Address) error) error { + addrs, err := adt7.AsMap(s.store, s.State.AddressMap, builtin7.DefaultHamtBitwidth) + if err != nil { + return err + } + var actorID cbg.CborInt + return addrs.ForEach(&actorID, func(key string) error { + addr, err := address.NewFromBytes([]byte(key)) + if err != nil { + return err + } + return cb(abi.ActorID(actorID), addr) + }) +} + +func (s *state7) NetworkName() (dtypes.NetworkName, error) { + return dtypes.NetworkName(s.State.NetworkName), nil +} + +func (s *state7) SetNetworkName(name string) error { + s.State.NetworkName = name + return nil +} + +func (s *state7) SetNextID(id abi.ActorID) error { + s.State.NextID = id + return nil +} + +func (s *state7) Remove(addrs ...address.Address) (err error) { + m, err := adt7.AsMap(s.store, s.State.AddressMap, builtin7.DefaultHamtBitwidth) + if err != nil { + return err + } + for _, addr := range addrs { + if err = m.Delete(abi.AddrKey(addr)); err != nil { + return xerrors.Errorf("failed to delete entry for address: %s; err: %w", addr, err) + } + } + amr, err := m.Root() + if err != nil { + return xerrors.Errorf("failed to get address map root: %w", err) + } + s.State.AddressMap = amr + return nil +} + +func (s *state7) SetAddressMap(mcid cid.Cid) error { + s.State.AddressMap = mcid + return nil +} + +func (s *state7) GetState() interface{} { + return &s.State +} + +func (s *state7) AddressMap() (adt.Map, error) { + return adt7.AsMap(s.store, s.State.AddressMap, builtin7.DefaultHamtBitwidth) +} + +func (s *state7) AddressMapBitWidth() int { + return builtin7.DefaultHamtBitwidth +} + +func (s *state7) AddressMapHashFunction() func(input []byte) []byte { + return func(input []byte) []byte { + res := sha256.Sum256(input) + return res[:] + } +} + +func (s *state7) ActorKey() string { + return manifest.InitKey +} + +func (s *state7) ActorVersion() actorstypes.Version { + return actorstypes.Version7 +} + +func (s *state7) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/init/v8.go b/chain/actors/builtin/init/v8.go new file mode 100644 index 000000000..c7c7860d3 --- /dev/null +++ b/chain/actors/builtin/init/v8.go @@ -0,0 +1,147 @@ +package init + +import ( + "crypto/sha256" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtin8 "github.com/filecoin-project/go-state-types/builtin" + init8 "github.com/filecoin-project/go-state-types/builtin/v8/init" + adt8 "github.com/filecoin-project/go-state-types/builtin/v8/util/adt" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/node/modules/dtypes" +) + +var _ State = (*state8)(nil) + +func load8(store adt.Store, root cid.Cid) (State, error) { + out := state8{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make8(store adt.Store, networkName string) (State, error) { + out := state8{store: store} + + s, err := init8.ConstructState(store, networkName) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state8 struct { + init8.State + store adt.Store +} + +func (s *state8) ResolveAddress(address address.Address) (address.Address, bool, error) { + return s.State.ResolveAddress(s.store, address) +} + +func (s *state8) MapAddressToNewID(address address.Address) (address.Address, error) { + return s.State.MapAddressToNewID(s.store, address) +} + +func (s *state8) ForEachActor(cb func(id abi.ActorID, address address.Address) error) error { + addrs, err := adt8.AsMap(s.store, s.State.AddressMap, builtin8.DefaultHamtBitwidth) + if err != nil { + return err + } + var actorID cbg.CborInt + return addrs.ForEach(&actorID, func(key string) error { + addr, err := address.NewFromBytes([]byte(key)) + if err != nil { + return err + } + return cb(abi.ActorID(actorID), addr) + }) +} + +func (s *state8) NetworkName() (dtypes.NetworkName, error) { + return dtypes.NetworkName(s.State.NetworkName), nil +} + +func (s *state8) SetNetworkName(name string) error { + s.State.NetworkName = name + return nil +} + +func (s *state8) SetNextID(id abi.ActorID) error { + s.State.NextID = id + return nil +} + +func (s *state8) Remove(addrs ...address.Address) (err error) { + m, err := adt8.AsMap(s.store, s.State.AddressMap, builtin8.DefaultHamtBitwidth) + if err != nil { + return err + } + for _, addr := range addrs { + if err = m.Delete(abi.AddrKey(addr)); err != nil { + return xerrors.Errorf("failed to delete entry for address: %s; err: %w", addr, err) + } + } + amr, err := m.Root() + if err != nil { + return xerrors.Errorf("failed to get address map root: %w", err) + } + s.State.AddressMap = amr + return nil +} + +func (s *state8) SetAddressMap(mcid cid.Cid) error { + s.State.AddressMap = mcid + return nil +} + +func (s *state8) GetState() interface{} { + return &s.State +} + +func (s *state8) AddressMap() (adt.Map, error) { + return adt8.AsMap(s.store, s.State.AddressMap, builtin8.DefaultHamtBitwidth) +} + +func (s *state8) AddressMapBitWidth() int { + return builtin8.DefaultHamtBitwidth +} + +func (s *state8) AddressMapHashFunction() func(input []byte) []byte { + return func(input []byte) []byte { + res := sha256.Sum256(input) + return res[:] + } +} + +func (s *state8) ActorKey() string { + return manifest.InitKey +} + +func (s *state8) ActorVersion() actorstypes.Version { + return actorstypes.Version8 +} + +func (s *state8) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/init/v9.go b/chain/actors/builtin/init/v9.go new file mode 100644 index 000000000..a221a4a7c --- /dev/null +++ b/chain/actors/builtin/init/v9.go @@ -0,0 +1,147 @@ +package init + +import ( + "crypto/sha256" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtin9 "github.com/filecoin-project/go-state-types/builtin" + init9 "github.com/filecoin-project/go-state-types/builtin/v9/init" + adt9 "github.com/filecoin-project/go-state-types/builtin/v9/util/adt" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/node/modules/dtypes" +) + +var _ State = (*state9)(nil) + +func load9(store adt.Store, root cid.Cid) (State, error) { + out := state9{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make9(store adt.Store, networkName string) (State, error) { + out := state9{store: store} + + s, err := init9.ConstructState(store, networkName) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state9 struct { + init9.State + store adt.Store +} + +func (s *state9) ResolveAddress(address address.Address) (address.Address, bool, error) { + return s.State.ResolveAddress(s.store, address) +} + +func (s *state9) MapAddressToNewID(address address.Address) (address.Address, error) { + return s.State.MapAddressToNewID(s.store, address) +} + +func (s *state9) ForEachActor(cb func(id abi.ActorID, address address.Address) error) error { + addrs, err := adt9.AsMap(s.store, s.State.AddressMap, builtin9.DefaultHamtBitwidth) + if err != nil { + return err + } + var actorID cbg.CborInt + return addrs.ForEach(&actorID, func(key string) error { + addr, err := address.NewFromBytes([]byte(key)) + if err != nil { + return err + } + return cb(abi.ActorID(actorID), addr) + }) +} + +func (s *state9) NetworkName() (dtypes.NetworkName, error) { + return dtypes.NetworkName(s.State.NetworkName), nil +} + +func (s *state9) SetNetworkName(name string) error { + s.State.NetworkName = name + return nil +} + +func (s *state9) SetNextID(id abi.ActorID) error { + s.State.NextID = id + return nil +} + +func (s *state9) Remove(addrs ...address.Address) (err error) { + m, err := adt9.AsMap(s.store, s.State.AddressMap, builtin9.DefaultHamtBitwidth) + if err != nil { + return err + } + for _, addr := range addrs { + if err = m.Delete(abi.AddrKey(addr)); err != nil { + return xerrors.Errorf("failed to delete entry for address: %s; err: %w", addr, err) + } + } + amr, err := m.Root() + if err != nil { + return xerrors.Errorf("failed to get address map root: %w", err) + } + s.State.AddressMap = amr + return nil +} + +func (s *state9) SetAddressMap(mcid cid.Cid) error { + s.State.AddressMap = mcid + return nil +} + +func (s *state9) GetState() interface{} { + return &s.State +} + +func (s *state9) AddressMap() (adt.Map, error) { + return adt9.AsMap(s.store, s.State.AddressMap, builtin9.DefaultHamtBitwidth) +} + +func (s *state9) AddressMapBitWidth() int { + return builtin9.DefaultHamtBitwidth +} + +func (s *state9) AddressMapHashFunction() func(input []byte) []byte { + return func(input []byte) []byte { + res := sha256.Sum256(input) + return res[:] + } +} + +func (s *state9) ActorKey() string { + return manifest.InitKey +} + +func (s *state9) ActorVersion() actorstypes.Version { + return actorstypes.Version9 +} + +func (s *state9) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/market/actor.go.template b/chain/actors/builtin/market/actor.go.template new file mode 100644 index 000000000..a84c04ab9 --- /dev/null +++ b/chain/actors/builtin/market/actor.go.template @@ -0,0 +1,216 @@ +package market + +import ( + "github.com/ipfs/go-cid" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "unicode/utf8" + + "github.com/filecoin-project/go-state-types/network" + "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/cbor" + cbg "github.com/whyrusleeping/cbor-gen" + "github.com/filecoin-project/go-state-types/manifest" + + markettypes "github.com/filecoin-project/go-state-types/builtin/v9/market" + verifregtypes "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" +{{range .versions}} + {{if (le . 7)}} + builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin" + {{end}} +{{end}} + + builtintypes "github.com/filecoin-project/go-state-types/builtin" + + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/types" +) + +var ( + Address = builtintypes.StorageMarketActorAddr + Methods = builtintypes.MethodsMarket +) + +func Load(store adt.Store, act *types.Actor) (State, error) { + if name, av, ok := actors.GetActorMetaByCode(act.Code); ok { + if name != manifest.MarketKey { + return nil, xerrors.Errorf("actor code is not market: %s", name) + } + + switch av { + {{range .versions}} + {{if (ge . 8)}} + case actorstypes.Version{{.}}: + return load{{.}}(store, act.Head) + {{end}} + {{end}} + } + } + + switch act.Code { +{{range .versions}} + {{if (le . 7)}} + case builtin{{.}}.StorageMarketActorCodeID: + return load{{.}}(store, act.Head) + {{end}} +{{end}} + } + + return nil, xerrors.Errorf("unknown actor code %s", act.Code) +} + +func MakeState(store adt.Store, av actorstypes.Version) (State, error) { + switch av { +{{range .versions}} + case actorstypes.Version{{.}}: + return make{{.}}(store) +{{end}} +} + return nil, xerrors.Errorf("unknown actor version %d", av) +} + +type State interface { + cbor.Marshaler + + Code() cid.Cid + ActorKey() string + ActorVersion() actorstypes.Version + + BalancesChanged(State) (bool, error) + EscrowTable() (BalanceTable, error) + LockedTable() (BalanceTable, error) + TotalLocked() (abi.TokenAmount, error) + StatesChanged(State) (bool, error) + States() (DealStates, error) + ProposalsChanged(State) (bool, error) + Proposals() (DealProposals, error) + VerifyDealsForActivation( + minerAddr address.Address, deals []abi.DealID, currEpoch, sectorExpiry abi.ChainEpoch, + ) (weight, verifiedWeight abi.DealWeight, err error) + NextID() (abi.DealID, error) + GetState() interface{} + GetAllocationIdForPendingDeal(dealId abi.DealID) (verifregtypes.AllocationId, error) +} + +type BalanceTable interface { + ForEach(cb func(address.Address, abi.TokenAmount) error) error + Get(key address.Address) (abi.TokenAmount, error) +} + +type DealStates interface { + ForEach(cb func(id abi.DealID, ds DealState) error) error + Get(id abi.DealID) (*DealState, bool, error) + + array() adt.Array + decode(*cbg.Deferred) (*DealState, error) +} + +type DealProposals interface { + ForEach(cb func(id abi.DealID, dp markettypes.DealProposal) error) error + Get(id abi.DealID) (*markettypes.DealProposal, bool, error) + + array() adt.Array + decode(*cbg.Deferred) (*markettypes.DealProposal, error) +} + + +type PublishStorageDealsReturn interface { + DealIDs() ([]abi.DealID, error) + // Note that this index is based on the batch of deals that were published, NOT the DealID + IsDealValid(index uint64) (bool, int, error) +} + +func DecodePublishStorageDealsReturn(b []byte, nv network.Version) (PublishStorageDealsReturn, error) { + av, err := actorstypes.VersionForNetwork(nv) + if err != nil { + return nil, err + } + + switch av { +{{range .versions}} + case actorstypes.Version{{.}}: + return decodePublishStorageDealsReturn{{.}}(b) +{{end}} +} + return nil, xerrors.Errorf("unknown actor version %d", av) +} + +type DealProposal = markettypes.DealProposal +type DealLabel = markettypes.DealLabel + +type DealState = markettypes.DealState + +type DealStateChanges struct { + Added []DealIDState + Modified []DealStateChange + Removed []DealIDState +} + +type DealIDState struct { + ID abi.DealID + Deal DealState +} + +// DealStateChange is a change in deal state from -> to +type DealStateChange struct { + ID abi.DealID + From *DealState + To *DealState +} + +type DealProposalChanges struct { + Added []ProposalIDState + Removed []ProposalIDState +} + +type ProposalIDState struct { + ID abi.DealID + Proposal markettypes.DealProposal +} + +func EmptyDealState() *DealState { + return &DealState{ + SectorStartEpoch: -1, + SlashEpoch: -1, + LastUpdatedEpoch: -1, + } +} + +// returns the earned fees and pending fees for a given deal +func GetDealFees(deal markettypes.DealProposal, height abi.ChainEpoch) (abi.TokenAmount, abi.TokenAmount) { + tf := big.Mul(deal.StoragePricePerEpoch, big.NewInt(int64(deal.EndEpoch-deal.StartEpoch))) + + ef := big.Mul(deal.StoragePricePerEpoch, big.NewInt(int64(height-deal.StartEpoch))) + if ef.LessThan(big.Zero()) { + ef = big.Zero() + } + + if ef.GreaterThan(tf) { + ef = tf + } + + return ef, big.Sub(tf, ef) +} + +func IsDealActive(state markettypes.DealState) bool { + return state.SectorStartEpoch > -1 && state.SlashEpoch == -1 +} + +func labelFromGoString(s string) (markettypes.DealLabel, error) { + if utf8.ValidString(s) { + return markettypes.NewLabelFromString(s) + } else { + return markettypes.NewLabelFromBytes([]byte(s)) + } +} + +func AllCodes() []cid.Cid { + return []cid.Cid{ {{range .versions}} + (&state{{.}}{}).Code(), + {{- end}} + } +} diff --git a/chain/actors/builtin/market/diff.go b/chain/actors/builtin/market/diff.go new file mode 100644 index 000000000..ef3c2c28d --- /dev/null +++ b/chain/actors/builtin/market/diff.go @@ -0,0 +1,93 @@ +package market + +import ( + "fmt" + + cbg "github.com/whyrusleeping/cbor-gen" + + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +func DiffDealProposals(pre, cur DealProposals) (*DealProposalChanges, error) { + results := new(DealProposalChanges) + if err := adt.DiffAdtArray(pre.array(), cur.array(), &marketProposalsDiffer{results, pre, cur}); err != nil { + return nil, fmt.Errorf("diffing deal states: %w", err) + } + return results, nil +} + +type marketProposalsDiffer struct { + Results *DealProposalChanges + pre, cur DealProposals +} + +func (d *marketProposalsDiffer) Add(key uint64, val *cbg.Deferred) error { + dp, err := d.cur.decode(val) + if err != nil { + return err + } + d.Results.Added = append(d.Results.Added, ProposalIDState{abi.DealID(key), *dp}) + return nil +} + +func (d *marketProposalsDiffer) Modify(key uint64, from, to *cbg.Deferred) error { + // short circuit, DealProposals are static + return nil +} + +func (d *marketProposalsDiffer) Remove(key uint64, val *cbg.Deferred) error { + dp, err := d.pre.decode(val) + if err != nil { + return err + } + d.Results.Removed = append(d.Results.Removed, ProposalIDState{abi.DealID(key), *dp}) + return nil +} + +func DiffDealStates(pre, cur DealStates) (*DealStateChanges, error) { + results := new(DealStateChanges) + if err := adt.DiffAdtArray(pre.array(), cur.array(), &marketStatesDiffer{results, pre, cur}); err != nil { + return nil, fmt.Errorf("diffing deal states: %w", err) + } + return results, nil +} + +type marketStatesDiffer struct { + Results *DealStateChanges + pre, cur DealStates +} + +func (d *marketStatesDiffer) Add(key uint64, val *cbg.Deferred) error { + ds, err := d.cur.decode(val) + if err != nil { + return err + } + d.Results.Added = append(d.Results.Added, DealIDState{abi.DealID(key), *ds}) + return nil +} + +func (d *marketStatesDiffer) Modify(key uint64, from, to *cbg.Deferred) error { + dsFrom, err := d.pre.decode(from) + if err != nil { + return err + } + dsTo, err := d.cur.decode(to) + if err != nil { + return err + } + if *dsFrom != *dsTo { + d.Results.Modified = append(d.Results.Modified, DealStateChange{abi.DealID(key), dsFrom, dsTo}) + } + return nil +} + +func (d *marketStatesDiffer) Remove(key uint64, val *cbg.Deferred) error { + ds, err := d.pre.decode(val) + if err != nil { + return err + } + d.Results.Removed = append(d.Results.Removed, DealIDState{abi.DealID(key), *ds}) + return nil +} diff --git a/chain/actors/builtin/market/market.go b/chain/actors/builtin/market/market.go new file mode 100644 index 000000000..36936e787 --- /dev/null +++ b/chain/actors/builtin/market/market.go @@ -0,0 +1,307 @@ +package market + +import ( + "unicode/utf8" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + builtintypes "github.com/filecoin-project/go-state-types/builtin" + markettypes "github.com/filecoin-project/go-state-types/builtin/v9/market" + verifregtypes "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" + "github.com/filecoin-project/go-state-types/cbor" + "github.com/filecoin-project/go-state-types/manifest" + "github.com/filecoin-project/go-state-types/network" + builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + builtin3 "github.com/filecoin-project/specs-actors/v3/actors/builtin" + builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" + builtin6 "github.com/filecoin-project/specs-actors/v6/actors/builtin" + builtin7 "github.com/filecoin-project/specs-actors/v7/actors/builtin" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/types" +) + +var ( + Address = builtintypes.StorageMarketActorAddr + Methods = builtintypes.MethodsMarket +) + +func Load(store adt.Store, act *types.Actor) (State, error) { + if name, av, ok := actors.GetActorMetaByCode(act.Code); ok { + if name != manifest.MarketKey { + return nil, xerrors.Errorf("actor code is not market: %s", name) + } + + switch av { + + case actorstypes.Version8: + return load8(store, act.Head) + + case actorstypes.Version9: + return load9(store, act.Head) + + case actorstypes.Version10: + return load10(store, act.Head) + + case actorstypes.Version11: + return load11(store, act.Head) + + } + } + + switch act.Code { + + case builtin0.StorageMarketActorCodeID: + return load0(store, act.Head) + + case builtin2.StorageMarketActorCodeID: + return load2(store, act.Head) + + case builtin3.StorageMarketActorCodeID: + return load3(store, act.Head) + + case builtin4.StorageMarketActorCodeID: + return load4(store, act.Head) + + case builtin5.StorageMarketActorCodeID: + return load5(store, act.Head) + + case builtin6.StorageMarketActorCodeID: + return load6(store, act.Head) + + case builtin7.StorageMarketActorCodeID: + return load7(store, act.Head) + + } + + return nil, xerrors.Errorf("unknown actor code %s", act.Code) +} + +func MakeState(store adt.Store, av actorstypes.Version) (State, error) { + switch av { + + case actorstypes.Version0: + return make0(store) + + case actorstypes.Version2: + return make2(store) + + case actorstypes.Version3: + return make3(store) + + case actorstypes.Version4: + return make4(store) + + case actorstypes.Version5: + return make5(store) + + case actorstypes.Version6: + return make6(store) + + case actorstypes.Version7: + return make7(store) + + case actorstypes.Version8: + return make8(store) + + case actorstypes.Version9: + return make9(store) + + case actorstypes.Version10: + return make10(store) + + case actorstypes.Version11: + return make11(store) + + } + return nil, xerrors.Errorf("unknown actor version %d", av) +} + +type State interface { + cbor.Marshaler + + Code() cid.Cid + ActorKey() string + ActorVersion() actorstypes.Version + + BalancesChanged(State) (bool, error) + EscrowTable() (BalanceTable, error) + LockedTable() (BalanceTable, error) + TotalLocked() (abi.TokenAmount, error) + StatesChanged(State) (bool, error) + States() (DealStates, error) + ProposalsChanged(State) (bool, error) + Proposals() (DealProposals, error) + VerifyDealsForActivation( + minerAddr address.Address, deals []abi.DealID, currEpoch, sectorExpiry abi.ChainEpoch, + ) (weight, verifiedWeight abi.DealWeight, err error) + NextID() (abi.DealID, error) + GetState() interface{} + GetAllocationIdForPendingDeal(dealId abi.DealID) (verifregtypes.AllocationId, error) +} + +type BalanceTable interface { + ForEach(cb func(address.Address, abi.TokenAmount) error) error + Get(key address.Address) (abi.TokenAmount, error) +} + +type DealStates interface { + ForEach(cb func(id abi.DealID, ds DealState) error) error + Get(id abi.DealID) (*DealState, bool, error) + + array() adt.Array + decode(*cbg.Deferred) (*DealState, error) +} + +type DealProposals interface { + ForEach(cb func(id abi.DealID, dp markettypes.DealProposal) error) error + Get(id abi.DealID) (*markettypes.DealProposal, bool, error) + + array() adt.Array + decode(*cbg.Deferred) (*markettypes.DealProposal, error) +} + +type PublishStorageDealsReturn interface { + DealIDs() ([]abi.DealID, error) + // Note that this index is based on the batch of deals that were published, NOT the DealID + IsDealValid(index uint64) (bool, int, error) +} + +func DecodePublishStorageDealsReturn(b []byte, nv network.Version) (PublishStorageDealsReturn, error) { + av, err := actorstypes.VersionForNetwork(nv) + if err != nil { + return nil, err + } + + switch av { + + case actorstypes.Version0: + return decodePublishStorageDealsReturn0(b) + + case actorstypes.Version2: + return decodePublishStorageDealsReturn2(b) + + case actorstypes.Version3: + return decodePublishStorageDealsReturn3(b) + + case actorstypes.Version4: + return decodePublishStorageDealsReturn4(b) + + case actorstypes.Version5: + return decodePublishStorageDealsReturn5(b) + + case actorstypes.Version6: + return decodePublishStorageDealsReturn6(b) + + case actorstypes.Version7: + return decodePublishStorageDealsReturn7(b) + + case actorstypes.Version8: + return decodePublishStorageDealsReturn8(b) + + case actorstypes.Version9: + return decodePublishStorageDealsReturn9(b) + + case actorstypes.Version10: + return decodePublishStorageDealsReturn10(b) + + case actorstypes.Version11: + return decodePublishStorageDealsReturn11(b) + + } + return nil, xerrors.Errorf("unknown actor version %d", av) +} + +type DealProposal = markettypes.DealProposal +type DealLabel = markettypes.DealLabel + +type DealState = markettypes.DealState + +type DealStateChanges struct { + Added []DealIDState + Modified []DealStateChange + Removed []DealIDState +} + +type DealIDState struct { + ID abi.DealID + Deal DealState +} + +// DealStateChange is a change in deal state from -> to +type DealStateChange struct { + ID abi.DealID + From *DealState + To *DealState +} + +type DealProposalChanges struct { + Added []ProposalIDState + Removed []ProposalIDState +} + +type ProposalIDState struct { + ID abi.DealID + Proposal markettypes.DealProposal +} + +func EmptyDealState() *DealState { + return &DealState{ + SectorStartEpoch: -1, + SlashEpoch: -1, + LastUpdatedEpoch: -1, + } +} + +// returns the earned fees and pending fees for a given deal +func GetDealFees(deal markettypes.DealProposal, height abi.ChainEpoch) (abi.TokenAmount, abi.TokenAmount) { + tf := big.Mul(deal.StoragePricePerEpoch, big.NewInt(int64(deal.EndEpoch-deal.StartEpoch))) + + ef := big.Mul(deal.StoragePricePerEpoch, big.NewInt(int64(height-deal.StartEpoch))) + if ef.LessThan(big.Zero()) { + ef = big.Zero() + } + + if ef.GreaterThan(tf) { + ef = tf + } + + return ef, big.Sub(tf, ef) +} + +func IsDealActive(state markettypes.DealState) bool { + return state.SectorStartEpoch > -1 && state.SlashEpoch == -1 +} + +func labelFromGoString(s string) (markettypes.DealLabel, error) { + if utf8.ValidString(s) { + return markettypes.NewLabelFromString(s) + } else { + return markettypes.NewLabelFromBytes([]byte(s)) + } +} + +func AllCodes() []cid.Cid { + return []cid.Cid{ + (&state0{}).Code(), + (&state2{}).Code(), + (&state3{}).Code(), + (&state4{}).Code(), + (&state5{}).Code(), + (&state6{}).Code(), + (&state7{}).Code(), + (&state8{}).Code(), + (&state9{}).Code(), + (&state10{}).Code(), + (&state11{}).Code(), + } +} diff --git a/chain/actors/builtin/market/state.go.template b/chain/actors/builtin/market/state.go.template new file mode 100644 index 000000000..bbaa5c775 --- /dev/null +++ b/chain/actors/builtin/market/state.go.template @@ -0,0 +1,412 @@ +package market + +import ( + "fmt" + "bytes" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + {{if (ge .v 6)}} + rlepluslazy "github.com/filecoin-project/go-bitfield/rle" + "github.com/filecoin-project/go-bitfield" + {{end}} + + + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/types" + verifregtypes "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" + "github.com/filecoin-project/go-state-types/manifest" + +{{if (le .v 7)}} + market{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/builtin/market" + adt{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/util/adt" +{{else}} + market{{.v}} "github.com/filecoin-project/go-state-types/builtin{{.import}}market" + markettypes "github.com/filecoin-project/go-state-types/builtin/v9/market" + adt{{.v}} "github.com/filecoin-project/go-state-types/builtin{{.import}}util/adt" +{{end}} +{{if (ge .v 9)}} + "github.com/filecoin-project/go-state-types/builtin" +{{end}} +) + +var _ State = (*state{{.v}})(nil) + +func load{{.v}}(store adt.Store, root cid.Cid) (State, error) { + out := state{{.v}}{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make{{.v}}(store adt.Store) (State, error) { + out := state{{.v}}{store: store} + {{if (le .v 2)}} + ea, err := adt{{.v}}.MakeEmptyArray(store).Root() + if err != nil { + return nil, err + } + + em, err := adt{{.v}}.MakeEmptyMap(store).Root() + if err != nil { + return nil, err + } + + out.State = *market{{.v}}.ConstructState(ea, em, em) + {{else}} + s, err := market{{.v}}.ConstructState(store) + if err != nil { + return nil, err + } + + out.State = *s + {{end}} + return &out, nil +} + +type state{{.v}} struct { + market{{.v}}.State + store adt.Store +} + +func (s *state{{.v}}) TotalLocked() (abi.TokenAmount, error) { + fml := types.BigAdd(s.TotalClientLockedCollateral, s.TotalProviderLockedCollateral) + fml = types.BigAdd(fml, s.TotalClientStorageFee) + return fml, nil +} + +func (s *state{{.v}}) BalancesChanged(otherState State) (bool, error) { + otherState{{.v}}, ok := otherState.(*state{{.v}}) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.EscrowTable.Equals(otherState{{.v}}.State.EscrowTable) || !s.State.LockedTable.Equals(otherState{{.v}}.State.LockedTable), nil +} + +func (s *state{{.v}}) StatesChanged(otherState State) (bool, error) { + otherState{{.v}}, ok := otherState.(*state{{.v}}) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.States.Equals(otherState{{.v}}.State.States), nil +} + +func (s *state{{.v}}) States() (DealStates, error) { + stateArray, err := adt{{.v}}.AsArray(s.store, s.State.States{{if (ge .v 3)}}, market{{.v}}.StatesAmtBitwidth{{end}}) + if err != nil { + return nil, err + } + return &dealStates{{.v}}{stateArray}, nil +} + +func (s *state{{.v}}) ProposalsChanged(otherState State) (bool, error) { + otherState{{.v}}, ok := otherState.(*state{{.v}}) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.Proposals.Equals(otherState{{.v}}.State.Proposals), nil +} + +func (s *state{{.v}}) Proposals() (DealProposals, error) { + proposalArray, err := adt{{.v}}.AsArray(s.store, s.State.Proposals{{if (ge .v 3)}}, market{{.v}}.ProposalsAmtBitwidth{{end}}) + if err != nil { + return nil, err + } + return &dealProposals{{.v}}{proposalArray}, nil +} + +func (s *state{{.v}}) EscrowTable() (BalanceTable, error) { + bt, err := adt{{.v}}.AsBalanceTable(s.store, s.State.EscrowTable) + if err != nil { + return nil, err + } + return &balanceTable{{.v}}{bt}, nil +} + +func (s *state{{.v}}) LockedTable() (BalanceTable, error) { + bt, err := adt{{.v}}.AsBalanceTable(s.store, s.State.LockedTable) + if err != nil { + return nil, err + } + return &balanceTable{{.v}}{bt}, nil +} + +func (s *state{{.v}}) VerifyDealsForActivation( + minerAddr address.Address, deals []abi.DealID, currEpoch, sectorExpiry abi.ChainEpoch, +) (weight, verifiedWeight abi.DealWeight, err error) { + w, vw{{if (ge .v 2)}}, _{{end}}, err := market{{.v}}.ValidateDealsForActivation(&s.State, s.store, deals, minerAddr, sectorExpiry, currEpoch) + return w, vw, err +} + +func (s *state{{.v}}) NextID() (abi.DealID, error) { + return s.State.NextID, nil +} + +type balanceTable{{.v}} struct { + *adt{{.v}}.BalanceTable +} + +func (bt *balanceTable{{.v}}) ForEach(cb func(address.Address, abi.TokenAmount) error) error { + asMap := (*adt{{.v}}.Map)(bt.BalanceTable) + var ta abi.TokenAmount + return asMap.ForEach(&ta, func(key string) error { + a, err := address.NewFromBytes([]byte(key)) + if err != nil { + return err + } + return cb(a, ta) + }) +} + +type dealStates{{.v}} struct { + adt.Array +} + +func (s *dealStates{{.v}}) Get(dealID abi.DealID) (*DealState, bool, error) { + var deal{{.v}} market{{.v}}.DealState + found, err := s.Array.Get(uint64(dealID), &deal{{.v}}) + if err != nil { + return nil, false, err + } + if !found { + return nil, false, nil + } + deal := fromV{{.v}}DealState(deal{{.v}}) + return &deal, true, nil +} + +func (s *dealStates{{.v}}) ForEach(cb func(dealID abi.DealID, ds DealState) error) error { + var ds{{.v}} market{{.v}}.DealState + return s.Array.ForEach(&ds{{.v}}, func(idx int64) error { + return cb(abi.DealID(idx), fromV{{.v}}DealState(ds{{.v}})) + }) +} + +func (s *dealStates{{.v}}) decode(val *cbg.Deferred) (*DealState, error) { + var ds{{.v}} market{{.v}}.DealState + if err := ds{{.v}}.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return nil, err + } + ds := fromV{{.v}}DealState(ds{{.v}}) + return &ds, nil +} + +func (s *dealStates{{.v}}) array() adt.Array { + return s.Array +} + +func fromV{{.v}}DealState(v{{.v}} market{{.v}}.DealState) DealState { + ret := DealState{ + SectorStartEpoch: v{{.v}}.SectorStartEpoch, + LastUpdatedEpoch: v{{.v}}.LastUpdatedEpoch, + SlashEpoch: v{{.v}}.SlashEpoch, + VerifiedClaim: 0, + } + {{if (ge .v 9)}} + ret.VerifiedClaim = verifregtypes.AllocationId(v{{.v}}.VerifiedClaim) + {{end}} + + return ret +} + +type dealProposals{{.v}} struct { + adt.Array +} + +func (s *dealProposals{{.v}}) Get(dealID abi.DealID) (*DealProposal, bool, error) { + var proposal{{.v}} market{{.v}}.DealProposal + found, err := s.Array.Get(uint64(dealID), &proposal{{.v}}) + if err != nil { + return nil, false, err + } + if !found { + return nil, false, nil + } + + proposal, err := fromV{{.v}}DealProposal(proposal{{.v}}) + if err != nil { + return nil, true, xerrors.Errorf("decoding proposal: %w", err) + } + + return &proposal, true, nil +} + +func (s *dealProposals{{.v}}) ForEach(cb func(dealID abi.DealID, dp DealProposal) error) error { + var dp{{.v}} market{{.v}}.DealProposal + return s.Array.ForEach(&dp{{.v}}, func(idx int64) error { + dp, err := fromV{{.v}}DealProposal(dp{{.v}}) + if err != nil { + return xerrors.Errorf("decoding proposal: %w", err) + } + + return cb(abi.DealID(idx), dp) + }) +} + +func (s *dealProposals{{.v}}) decode(val *cbg.Deferred) (*DealProposal, error) { + var dp{{.v}} market{{.v}}.DealProposal + if err := dp{{.v}}.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return nil, err + } + + dp, err := fromV{{.v}}DealProposal(dp{{.v}}) + if err != nil { + return nil, err + } + + return &dp, nil +} + +func (s *dealProposals{{.v}}) array() adt.Array { + return s.Array +} + +func fromV{{.v}}DealProposal(v{{.v}} market{{.v}}.DealProposal) (DealProposal, error) { + {{if (le .v 7)}} + label, err := labelFromGoString(v{{.v}}.Label) + {{else}} + label, err := fromV{{.v}}Label(v{{.v}}.Label) + {{end}} + if err != nil { + return DealProposal{}, xerrors.Errorf("error setting deal label: %w", err) + } + + return DealProposal{ + PieceCID: v{{.v}}.PieceCID, + PieceSize: v{{.v}}.PieceSize, + VerifiedDeal: v{{.v}}.VerifiedDeal, + Client: v{{.v}}.Client, + Provider: v{{.v}}.Provider, + + Label: label, + + StartEpoch: v{{.v}}.StartEpoch, + EndEpoch: v{{.v}}.EndEpoch, + StoragePricePerEpoch: v{{.v}}.StoragePricePerEpoch, + + ProviderCollateral: v{{.v}}.ProviderCollateral, + ClientCollateral: v{{.v}}.ClientCollateral, + }, nil +} + +{{if (ge .v 8)}} + func fromV{{.v}}Label(v{{.v}} market{{.v}}.DealLabel) (DealLabel, error) { + if v{{.v}}.IsString() { + str, err := v{{.v}}.ToString() + if err != nil { + return markettypes.EmptyDealLabel, xerrors.Errorf("failed to convert string label to string: %w", err) + } + return markettypes.NewLabelFromString(str) + } + + bs, err := v{{.v}}.ToBytes() + if err != nil { + return markettypes.EmptyDealLabel, xerrors.Errorf("failed to convert bytes label to bytes: %w", err) + } + return markettypes.NewLabelFromBytes(bs) + } +{{end}} + + + +func (s *state{{.v}}) GetState() interface{} { + return &s.State +} + +var _ PublishStorageDealsReturn = (*publishStorageDealsReturn{{.v}})(nil) + +func decodePublishStorageDealsReturn{{.v}}(b []byte) (PublishStorageDealsReturn, error) { + var retval market{{.v}}.PublishStorageDealsReturn + if err := retval.UnmarshalCBOR(bytes.NewReader(b)); err != nil { + return nil, xerrors.Errorf("failed to unmarshal PublishStorageDealsReturn: %w", err) + } + + return &publishStorageDealsReturn{{.v}}{retval}, nil +} + +type publishStorageDealsReturn{{.v}} struct { + market{{.v}}.PublishStorageDealsReturn +} + +func (r *publishStorageDealsReturn{{.v}}) IsDealValid(index uint64) (bool, int, error) { + {{if (ge .v 6)}} + set, err := r.ValidDeals.IsSet(index) + if err != nil || !set { + return false, -1, err + } + maskBf, err := bitfield.NewFromIter(&rlepluslazy.RunSliceIterator{ + Runs: []rlepluslazy.Run{rlepluslazy.Run{Val: true, Len: index}}}) + if err != nil { + return false, -1, err + } + before, err := bitfield.IntersectBitField(maskBf, r.ValidDeals) + if err != nil { + return false, -1, err + } + outIdx, err := before.Count() + if err != nil { + return false, -1, err + } + return set, int(outIdx), nil + {{else}} + // PublishStorageDeals only succeeded if all deals were valid in this version of actors + return true, int(index), nil + {{end}} +} + +func (r *publishStorageDealsReturn{{.v}}) DealIDs() ([]abi.DealID, error) { + return r.IDs, nil +} + +func (s *state{{.v}}) GetAllocationIdForPendingDeal(dealId abi.DealID) (verifregtypes.AllocationId, error) { +{{if (le .v 8)}} + return verifregtypes.NoAllocationID, xerrors.Errorf("unsupported before actors v9") +{{else}} + allocations, err := adt{{.v}}.AsMap(s.store, s.PendingDealAllocationIds, builtin.DefaultHamtBitwidth) + if err != nil { + return verifregtypes.NoAllocationID, xerrors.Errorf("failed to load allocation id for %d: %w", dealId, err) + } + + var allocationId cbg.CborInt + found, err := allocations.Get(abi.UIntKey(uint64(dealId)), &allocationId) + if err != nil { + return verifregtypes.NoAllocationID, xerrors.Errorf("failed to load allocation id for %d: %w", dealId, err) + } + if !found { + return verifregtypes.NoAllocationID, nil + } + + return verifregtypes.AllocationId(allocationId), nil +{{end}} +} + + +func (s *state{{.v}}) ActorKey() string { + return manifest.MarketKey +} + +func (s *state{{.v}}) ActorVersion() actorstypes.Version { + return actorstypes.Version{{.v}} +} + +func (s *state{{.v}}) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/market/v0.go b/chain/actors/builtin/market/v0.go new file mode 100644 index 000000000..c0a628b47 --- /dev/null +++ b/chain/actors/builtin/market/v0.go @@ -0,0 +1,330 @@ +package market + +import ( + "bytes" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + verifregtypes "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" + "github.com/filecoin-project/go-state-types/manifest" + market0 "github.com/filecoin-project/specs-actors/actors/builtin/market" + adt0 "github.com/filecoin-project/specs-actors/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/types" +) + +var _ State = (*state0)(nil) + +func load0(store adt.Store, root cid.Cid) (State, error) { + out := state0{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make0(store adt.Store) (State, error) { + out := state0{store: store} + + ea, err := adt0.MakeEmptyArray(store).Root() + if err != nil { + return nil, err + } + + em, err := adt0.MakeEmptyMap(store).Root() + if err != nil { + return nil, err + } + + out.State = *market0.ConstructState(ea, em, em) + + return &out, nil +} + +type state0 struct { + market0.State + store adt.Store +} + +func (s *state0) TotalLocked() (abi.TokenAmount, error) { + fml := types.BigAdd(s.TotalClientLockedCollateral, s.TotalProviderLockedCollateral) + fml = types.BigAdd(fml, s.TotalClientStorageFee) + return fml, nil +} + +func (s *state0) BalancesChanged(otherState State) (bool, error) { + otherState0, ok := otherState.(*state0) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.EscrowTable.Equals(otherState0.State.EscrowTable) || !s.State.LockedTable.Equals(otherState0.State.LockedTable), nil +} + +func (s *state0) StatesChanged(otherState State) (bool, error) { + otherState0, ok := otherState.(*state0) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.States.Equals(otherState0.State.States), nil +} + +func (s *state0) States() (DealStates, error) { + stateArray, err := adt0.AsArray(s.store, s.State.States) + if err != nil { + return nil, err + } + return &dealStates0{stateArray}, nil +} + +func (s *state0) ProposalsChanged(otherState State) (bool, error) { + otherState0, ok := otherState.(*state0) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.Proposals.Equals(otherState0.State.Proposals), nil +} + +func (s *state0) Proposals() (DealProposals, error) { + proposalArray, err := adt0.AsArray(s.store, s.State.Proposals) + if err != nil { + return nil, err + } + return &dealProposals0{proposalArray}, nil +} + +func (s *state0) EscrowTable() (BalanceTable, error) { + bt, err := adt0.AsBalanceTable(s.store, s.State.EscrowTable) + if err != nil { + return nil, err + } + return &balanceTable0{bt}, nil +} + +func (s *state0) LockedTable() (BalanceTable, error) { + bt, err := adt0.AsBalanceTable(s.store, s.State.LockedTable) + if err != nil { + return nil, err + } + return &balanceTable0{bt}, nil +} + +func (s *state0) VerifyDealsForActivation( + minerAddr address.Address, deals []abi.DealID, currEpoch, sectorExpiry abi.ChainEpoch, +) (weight, verifiedWeight abi.DealWeight, err error) { + w, vw, err := market0.ValidateDealsForActivation(&s.State, s.store, deals, minerAddr, sectorExpiry, currEpoch) + return w, vw, err +} + +func (s *state0) NextID() (abi.DealID, error) { + return s.State.NextID, nil +} + +type balanceTable0 struct { + *adt0.BalanceTable +} + +func (bt *balanceTable0) ForEach(cb func(address.Address, abi.TokenAmount) error) error { + asMap := (*adt0.Map)(bt.BalanceTable) + var ta abi.TokenAmount + return asMap.ForEach(&ta, func(key string) error { + a, err := address.NewFromBytes([]byte(key)) + if err != nil { + return err + } + return cb(a, ta) + }) +} + +type dealStates0 struct { + adt.Array +} + +func (s *dealStates0) Get(dealID abi.DealID) (*DealState, bool, error) { + var deal0 market0.DealState + found, err := s.Array.Get(uint64(dealID), &deal0) + if err != nil { + return nil, false, err + } + if !found { + return nil, false, nil + } + deal := fromV0DealState(deal0) + return &deal, true, nil +} + +func (s *dealStates0) ForEach(cb func(dealID abi.DealID, ds DealState) error) error { + var ds0 market0.DealState + return s.Array.ForEach(&ds0, func(idx int64) error { + return cb(abi.DealID(idx), fromV0DealState(ds0)) + }) +} + +func (s *dealStates0) decode(val *cbg.Deferred) (*DealState, error) { + var ds0 market0.DealState + if err := ds0.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return nil, err + } + ds := fromV0DealState(ds0) + return &ds, nil +} + +func (s *dealStates0) array() adt.Array { + return s.Array +} + +func fromV0DealState(v0 market0.DealState) DealState { + ret := DealState{ + SectorStartEpoch: v0.SectorStartEpoch, + LastUpdatedEpoch: v0.LastUpdatedEpoch, + SlashEpoch: v0.SlashEpoch, + VerifiedClaim: 0, + } + + return ret +} + +type dealProposals0 struct { + adt.Array +} + +func (s *dealProposals0) Get(dealID abi.DealID) (*DealProposal, bool, error) { + var proposal0 market0.DealProposal + found, err := s.Array.Get(uint64(dealID), &proposal0) + if err != nil { + return nil, false, err + } + if !found { + return nil, false, nil + } + + proposal, err := fromV0DealProposal(proposal0) + if err != nil { + return nil, true, xerrors.Errorf("decoding proposal: %w", err) + } + + return &proposal, true, nil +} + +func (s *dealProposals0) ForEach(cb func(dealID abi.DealID, dp DealProposal) error) error { + var dp0 market0.DealProposal + return s.Array.ForEach(&dp0, func(idx int64) error { + dp, err := fromV0DealProposal(dp0) + if err != nil { + return xerrors.Errorf("decoding proposal: %w", err) + } + + return cb(abi.DealID(idx), dp) + }) +} + +func (s *dealProposals0) decode(val *cbg.Deferred) (*DealProposal, error) { + var dp0 market0.DealProposal + if err := dp0.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return nil, err + } + + dp, err := fromV0DealProposal(dp0) + if err != nil { + return nil, err + } + + return &dp, nil +} + +func (s *dealProposals0) array() adt.Array { + return s.Array +} + +func fromV0DealProposal(v0 market0.DealProposal) (DealProposal, error) { + + label, err := labelFromGoString(v0.Label) + + if err != nil { + return DealProposal{}, xerrors.Errorf("error setting deal label: %w", err) + } + + return DealProposal{ + PieceCID: v0.PieceCID, + PieceSize: v0.PieceSize, + VerifiedDeal: v0.VerifiedDeal, + Client: v0.Client, + Provider: v0.Provider, + + Label: label, + + StartEpoch: v0.StartEpoch, + EndEpoch: v0.EndEpoch, + StoragePricePerEpoch: v0.StoragePricePerEpoch, + + ProviderCollateral: v0.ProviderCollateral, + ClientCollateral: v0.ClientCollateral, + }, nil +} + +func (s *state0) GetState() interface{} { + return &s.State +} + +var _ PublishStorageDealsReturn = (*publishStorageDealsReturn0)(nil) + +func decodePublishStorageDealsReturn0(b []byte) (PublishStorageDealsReturn, error) { + var retval market0.PublishStorageDealsReturn + if err := retval.UnmarshalCBOR(bytes.NewReader(b)); err != nil { + return nil, xerrors.Errorf("failed to unmarshal PublishStorageDealsReturn: %w", err) + } + + return &publishStorageDealsReturn0{retval}, nil +} + +type publishStorageDealsReturn0 struct { + market0.PublishStorageDealsReturn +} + +func (r *publishStorageDealsReturn0) IsDealValid(index uint64) (bool, int, error) { + + // PublishStorageDeals only succeeded if all deals were valid in this version of actors + return true, int(index), nil + +} + +func (r *publishStorageDealsReturn0) DealIDs() ([]abi.DealID, error) { + return r.IDs, nil +} + +func (s *state0) GetAllocationIdForPendingDeal(dealId abi.DealID) (verifregtypes.AllocationId, error) { + + return verifregtypes.NoAllocationID, xerrors.Errorf("unsupported before actors v9") + +} + +func (s *state0) ActorKey() string { + return manifest.MarketKey +} + +func (s *state0) ActorVersion() actorstypes.Version { + return actorstypes.Version0 +} + +func (s *state0) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/market/v10.go b/chain/actors/builtin/market/v10.go new file mode 100644 index 000000000..aaa0ee0f1 --- /dev/null +++ b/chain/actors/builtin/market/v10.go @@ -0,0 +1,377 @@ +package market + +import ( + "bytes" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-bitfield" + rlepluslazy "github.com/filecoin-project/go-bitfield/rle" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/builtin" + market10 "github.com/filecoin-project/go-state-types/builtin/v10/market" + adt10 "github.com/filecoin-project/go-state-types/builtin/v10/util/adt" + markettypes "github.com/filecoin-project/go-state-types/builtin/v9/market" + verifregtypes "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/types" +) + +var _ State = (*state10)(nil) + +func load10(store adt.Store, root cid.Cid) (State, error) { + out := state10{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make10(store adt.Store) (State, error) { + out := state10{store: store} + + s, err := market10.ConstructState(store) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state10 struct { + market10.State + store adt.Store +} + +func (s *state10) TotalLocked() (abi.TokenAmount, error) { + fml := types.BigAdd(s.TotalClientLockedCollateral, s.TotalProviderLockedCollateral) + fml = types.BigAdd(fml, s.TotalClientStorageFee) + return fml, nil +} + +func (s *state10) BalancesChanged(otherState State) (bool, error) { + otherState10, ok := otherState.(*state10) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.EscrowTable.Equals(otherState10.State.EscrowTable) || !s.State.LockedTable.Equals(otherState10.State.LockedTable), nil +} + +func (s *state10) StatesChanged(otherState State) (bool, error) { + otherState10, ok := otherState.(*state10) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.States.Equals(otherState10.State.States), nil +} + +func (s *state10) States() (DealStates, error) { + stateArray, err := adt10.AsArray(s.store, s.State.States, market10.StatesAmtBitwidth) + if err != nil { + return nil, err + } + return &dealStates10{stateArray}, nil +} + +func (s *state10) ProposalsChanged(otherState State) (bool, error) { + otherState10, ok := otherState.(*state10) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.Proposals.Equals(otherState10.State.Proposals), nil +} + +func (s *state10) Proposals() (DealProposals, error) { + proposalArray, err := adt10.AsArray(s.store, s.State.Proposals, market10.ProposalsAmtBitwidth) + if err != nil { + return nil, err + } + return &dealProposals10{proposalArray}, nil +} + +func (s *state10) EscrowTable() (BalanceTable, error) { + bt, err := adt10.AsBalanceTable(s.store, s.State.EscrowTable) + if err != nil { + return nil, err + } + return &balanceTable10{bt}, nil +} + +func (s *state10) LockedTable() (BalanceTable, error) { + bt, err := adt10.AsBalanceTable(s.store, s.State.LockedTable) + if err != nil { + return nil, err + } + return &balanceTable10{bt}, nil +} + +func (s *state10) VerifyDealsForActivation( + minerAddr address.Address, deals []abi.DealID, currEpoch, sectorExpiry abi.ChainEpoch, +) (weight, verifiedWeight abi.DealWeight, err error) { + w, vw, _, err := market10.ValidateDealsForActivation(&s.State, s.store, deals, minerAddr, sectorExpiry, currEpoch) + return w, vw, err +} + +func (s *state10) NextID() (abi.DealID, error) { + return s.State.NextID, nil +} + +type balanceTable10 struct { + *adt10.BalanceTable +} + +func (bt *balanceTable10) ForEach(cb func(address.Address, abi.TokenAmount) error) error { + asMap := (*adt10.Map)(bt.BalanceTable) + var ta abi.TokenAmount + return asMap.ForEach(&ta, func(key string) error { + a, err := address.NewFromBytes([]byte(key)) + if err != nil { + return err + } + return cb(a, ta) + }) +} + +type dealStates10 struct { + adt.Array +} + +func (s *dealStates10) Get(dealID abi.DealID) (*DealState, bool, error) { + var deal10 market10.DealState + found, err := s.Array.Get(uint64(dealID), &deal10) + if err != nil { + return nil, false, err + } + if !found { + return nil, false, nil + } + deal := fromV10DealState(deal10) + return &deal, true, nil +} + +func (s *dealStates10) ForEach(cb func(dealID abi.DealID, ds DealState) error) error { + var ds10 market10.DealState + return s.Array.ForEach(&ds10, func(idx int64) error { + return cb(abi.DealID(idx), fromV10DealState(ds10)) + }) +} + +func (s *dealStates10) decode(val *cbg.Deferred) (*DealState, error) { + var ds10 market10.DealState + if err := ds10.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return nil, err + } + ds := fromV10DealState(ds10) + return &ds, nil +} + +func (s *dealStates10) array() adt.Array { + return s.Array +} + +func fromV10DealState(v10 market10.DealState) DealState { + ret := DealState{ + SectorStartEpoch: v10.SectorStartEpoch, + LastUpdatedEpoch: v10.LastUpdatedEpoch, + SlashEpoch: v10.SlashEpoch, + VerifiedClaim: 0, + } + + ret.VerifiedClaim = verifregtypes.AllocationId(v10.VerifiedClaim) + + return ret +} + +type dealProposals10 struct { + adt.Array +} + +func (s *dealProposals10) Get(dealID abi.DealID) (*DealProposal, bool, error) { + var proposal10 market10.DealProposal + found, err := s.Array.Get(uint64(dealID), &proposal10) + if err != nil { + return nil, false, err + } + if !found { + return nil, false, nil + } + + proposal, err := fromV10DealProposal(proposal10) + if err != nil { + return nil, true, xerrors.Errorf("decoding proposal: %w", err) + } + + return &proposal, true, nil +} + +func (s *dealProposals10) ForEach(cb func(dealID abi.DealID, dp DealProposal) error) error { + var dp10 market10.DealProposal + return s.Array.ForEach(&dp10, func(idx int64) error { + dp, err := fromV10DealProposal(dp10) + if err != nil { + return xerrors.Errorf("decoding proposal: %w", err) + } + + return cb(abi.DealID(idx), dp) + }) +} + +func (s *dealProposals10) decode(val *cbg.Deferred) (*DealProposal, error) { + var dp10 market10.DealProposal + if err := dp10.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return nil, err + } + + dp, err := fromV10DealProposal(dp10) + if err != nil { + return nil, err + } + + return &dp, nil +} + +func (s *dealProposals10) array() adt.Array { + return s.Array +} + +func fromV10DealProposal(v10 market10.DealProposal) (DealProposal, error) { + + label, err := fromV10Label(v10.Label) + + if err != nil { + return DealProposal{}, xerrors.Errorf("error setting deal label: %w", err) + } + + return DealProposal{ + PieceCID: v10.PieceCID, + PieceSize: v10.PieceSize, + VerifiedDeal: v10.VerifiedDeal, + Client: v10.Client, + Provider: v10.Provider, + + Label: label, + + StartEpoch: v10.StartEpoch, + EndEpoch: v10.EndEpoch, + StoragePricePerEpoch: v10.StoragePricePerEpoch, + + ProviderCollateral: v10.ProviderCollateral, + ClientCollateral: v10.ClientCollateral, + }, nil +} + +func fromV10Label(v10 market10.DealLabel) (DealLabel, error) { + if v10.IsString() { + str, err := v10.ToString() + if err != nil { + return markettypes.EmptyDealLabel, xerrors.Errorf("failed to convert string label to string: %w", err) + } + return markettypes.NewLabelFromString(str) + } + + bs, err := v10.ToBytes() + if err != nil { + return markettypes.EmptyDealLabel, xerrors.Errorf("failed to convert bytes label to bytes: %w", err) + } + return markettypes.NewLabelFromBytes(bs) +} + +func (s *state10) GetState() interface{} { + return &s.State +} + +var _ PublishStorageDealsReturn = (*publishStorageDealsReturn10)(nil) + +func decodePublishStorageDealsReturn10(b []byte) (PublishStorageDealsReturn, error) { + var retval market10.PublishStorageDealsReturn + if err := retval.UnmarshalCBOR(bytes.NewReader(b)); err != nil { + return nil, xerrors.Errorf("failed to unmarshal PublishStorageDealsReturn: %w", err) + } + + return &publishStorageDealsReturn10{retval}, nil +} + +type publishStorageDealsReturn10 struct { + market10.PublishStorageDealsReturn +} + +func (r *publishStorageDealsReturn10) IsDealValid(index uint64) (bool, int, error) { + + set, err := r.ValidDeals.IsSet(index) + if err != nil || !set { + return false, -1, err + } + maskBf, err := bitfield.NewFromIter(&rlepluslazy.RunSliceIterator{ + Runs: []rlepluslazy.Run{rlepluslazy.Run{Val: true, Len: index}}}) + if err != nil { + return false, -1, err + } + before, err := bitfield.IntersectBitField(maskBf, r.ValidDeals) + if err != nil { + return false, -1, err + } + outIdx, err := before.Count() + if err != nil { + return false, -1, err + } + return set, int(outIdx), nil + +} + +func (r *publishStorageDealsReturn10) DealIDs() ([]abi.DealID, error) { + return r.IDs, nil +} + +func (s *state10) GetAllocationIdForPendingDeal(dealId abi.DealID) (verifregtypes.AllocationId, error) { + + allocations, err := adt10.AsMap(s.store, s.PendingDealAllocationIds, builtin.DefaultHamtBitwidth) + if err != nil { + return verifregtypes.NoAllocationID, xerrors.Errorf("failed to load allocation id for %d: %w", dealId, err) + } + + var allocationId cbg.CborInt + found, err := allocations.Get(abi.UIntKey(uint64(dealId)), &allocationId) + if err != nil { + return verifregtypes.NoAllocationID, xerrors.Errorf("failed to load allocation id for %d: %w", dealId, err) + } + if !found { + return verifregtypes.NoAllocationID, nil + } + + return verifregtypes.AllocationId(allocationId), nil + +} + +func (s *state10) ActorKey() string { + return manifest.MarketKey +} + +func (s *state10) ActorVersion() actorstypes.Version { + return actorstypes.Version10 +} + +func (s *state10) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/market/v11.go b/chain/actors/builtin/market/v11.go new file mode 100644 index 000000000..a816e3409 --- /dev/null +++ b/chain/actors/builtin/market/v11.go @@ -0,0 +1,377 @@ +package market + +import ( + "bytes" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-bitfield" + rlepluslazy "github.com/filecoin-project/go-bitfield/rle" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/builtin" + market11 "github.com/filecoin-project/go-state-types/builtin/v11/market" + adt11 "github.com/filecoin-project/go-state-types/builtin/v11/util/adt" + markettypes "github.com/filecoin-project/go-state-types/builtin/v9/market" + verifregtypes "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/types" +) + +var _ State = (*state11)(nil) + +func load11(store adt.Store, root cid.Cid) (State, error) { + out := state11{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make11(store adt.Store) (State, error) { + out := state11{store: store} + + s, err := market11.ConstructState(store) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state11 struct { + market11.State + store adt.Store +} + +func (s *state11) TotalLocked() (abi.TokenAmount, error) { + fml := types.BigAdd(s.TotalClientLockedCollateral, s.TotalProviderLockedCollateral) + fml = types.BigAdd(fml, s.TotalClientStorageFee) + return fml, nil +} + +func (s *state11) BalancesChanged(otherState State) (bool, error) { + otherState11, ok := otherState.(*state11) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.EscrowTable.Equals(otherState11.State.EscrowTable) || !s.State.LockedTable.Equals(otherState11.State.LockedTable), nil +} + +func (s *state11) StatesChanged(otherState State) (bool, error) { + otherState11, ok := otherState.(*state11) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.States.Equals(otherState11.State.States), nil +} + +func (s *state11) States() (DealStates, error) { + stateArray, err := adt11.AsArray(s.store, s.State.States, market11.StatesAmtBitwidth) + if err != nil { + return nil, err + } + return &dealStates11{stateArray}, nil +} + +func (s *state11) ProposalsChanged(otherState State) (bool, error) { + otherState11, ok := otherState.(*state11) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.Proposals.Equals(otherState11.State.Proposals), nil +} + +func (s *state11) Proposals() (DealProposals, error) { + proposalArray, err := adt11.AsArray(s.store, s.State.Proposals, market11.ProposalsAmtBitwidth) + if err != nil { + return nil, err + } + return &dealProposals11{proposalArray}, nil +} + +func (s *state11) EscrowTable() (BalanceTable, error) { + bt, err := adt11.AsBalanceTable(s.store, s.State.EscrowTable) + if err != nil { + return nil, err + } + return &balanceTable11{bt}, nil +} + +func (s *state11) LockedTable() (BalanceTable, error) { + bt, err := adt11.AsBalanceTable(s.store, s.State.LockedTable) + if err != nil { + return nil, err + } + return &balanceTable11{bt}, nil +} + +func (s *state11) VerifyDealsForActivation( + minerAddr address.Address, deals []abi.DealID, currEpoch, sectorExpiry abi.ChainEpoch, +) (weight, verifiedWeight abi.DealWeight, err error) { + w, vw, _, err := market11.ValidateDealsForActivation(&s.State, s.store, deals, minerAddr, sectorExpiry, currEpoch) + return w, vw, err +} + +func (s *state11) NextID() (abi.DealID, error) { + return s.State.NextID, nil +} + +type balanceTable11 struct { + *adt11.BalanceTable +} + +func (bt *balanceTable11) ForEach(cb func(address.Address, abi.TokenAmount) error) error { + asMap := (*adt11.Map)(bt.BalanceTable) + var ta abi.TokenAmount + return asMap.ForEach(&ta, func(key string) error { + a, err := address.NewFromBytes([]byte(key)) + if err != nil { + return err + } + return cb(a, ta) + }) +} + +type dealStates11 struct { + adt.Array +} + +func (s *dealStates11) Get(dealID abi.DealID) (*DealState, bool, error) { + var deal11 market11.DealState + found, err := s.Array.Get(uint64(dealID), &deal11) + if err != nil { + return nil, false, err + } + if !found { + return nil, false, nil + } + deal := fromV11DealState(deal11) + return &deal, true, nil +} + +func (s *dealStates11) ForEach(cb func(dealID abi.DealID, ds DealState) error) error { + var ds11 market11.DealState + return s.Array.ForEach(&ds11, func(idx int64) error { + return cb(abi.DealID(idx), fromV11DealState(ds11)) + }) +} + +func (s *dealStates11) decode(val *cbg.Deferred) (*DealState, error) { + var ds11 market11.DealState + if err := ds11.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return nil, err + } + ds := fromV11DealState(ds11) + return &ds, nil +} + +func (s *dealStates11) array() adt.Array { + return s.Array +} + +func fromV11DealState(v11 market11.DealState) DealState { + ret := DealState{ + SectorStartEpoch: v11.SectorStartEpoch, + LastUpdatedEpoch: v11.LastUpdatedEpoch, + SlashEpoch: v11.SlashEpoch, + VerifiedClaim: 0, + } + + ret.VerifiedClaim = verifregtypes.AllocationId(v11.VerifiedClaim) + + return ret +} + +type dealProposals11 struct { + adt.Array +} + +func (s *dealProposals11) Get(dealID abi.DealID) (*DealProposal, bool, error) { + var proposal11 market11.DealProposal + found, err := s.Array.Get(uint64(dealID), &proposal11) + if err != nil { + return nil, false, err + } + if !found { + return nil, false, nil + } + + proposal, err := fromV11DealProposal(proposal11) + if err != nil { + return nil, true, xerrors.Errorf("decoding proposal: %w", err) + } + + return &proposal, true, nil +} + +func (s *dealProposals11) ForEach(cb func(dealID abi.DealID, dp DealProposal) error) error { + var dp11 market11.DealProposal + return s.Array.ForEach(&dp11, func(idx int64) error { + dp, err := fromV11DealProposal(dp11) + if err != nil { + return xerrors.Errorf("decoding proposal: %w", err) + } + + return cb(abi.DealID(idx), dp) + }) +} + +func (s *dealProposals11) decode(val *cbg.Deferred) (*DealProposal, error) { + var dp11 market11.DealProposal + if err := dp11.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return nil, err + } + + dp, err := fromV11DealProposal(dp11) + if err != nil { + return nil, err + } + + return &dp, nil +} + +func (s *dealProposals11) array() adt.Array { + return s.Array +} + +func fromV11DealProposal(v11 market11.DealProposal) (DealProposal, error) { + + label, err := fromV11Label(v11.Label) + + if err != nil { + return DealProposal{}, xerrors.Errorf("error setting deal label: %w", err) + } + + return DealProposal{ + PieceCID: v11.PieceCID, + PieceSize: v11.PieceSize, + VerifiedDeal: v11.VerifiedDeal, + Client: v11.Client, + Provider: v11.Provider, + + Label: label, + + StartEpoch: v11.StartEpoch, + EndEpoch: v11.EndEpoch, + StoragePricePerEpoch: v11.StoragePricePerEpoch, + + ProviderCollateral: v11.ProviderCollateral, + ClientCollateral: v11.ClientCollateral, + }, nil +} + +func fromV11Label(v11 market11.DealLabel) (DealLabel, error) { + if v11.IsString() { + str, err := v11.ToString() + if err != nil { + return markettypes.EmptyDealLabel, xerrors.Errorf("failed to convert string label to string: %w", err) + } + return markettypes.NewLabelFromString(str) + } + + bs, err := v11.ToBytes() + if err != nil { + return markettypes.EmptyDealLabel, xerrors.Errorf("failed to convert bytes label to bytes: %w", err) + } + return markettypes.NewLabelFromBytes(bs) +} + +func (s *state11) GetState() interface{} { + return &s.State +} + +var _ PublishStorageDealsReturn = (*publishStorageDealsReturn11)(nil) + +func decodePublishStorageDealsReturn11(b []byte) (PublishStorageDealsReturn, error) { + var retval market11.PublishStorageDealsReturn + if err := retval.UnmarshalCBOR(bytes.NewReader(b)); err != nil { + return nil, xerrors.Errorf("failed to unmarshal PublishStorageDealsReturn: %w", err) + } + + return &publishStorageDealsReturn11{retval}, nil +} + +type publishStorageDealsReturn11 struct { + market11.PublishStorageDealsReturn +} + +func (r *publishStorageDealsReturn11) IsDealValid(index uint64) (bool, int, error) { + + set, err := r.ValidDeals.IsSet(index) + if err != nil || !set { + return false, -1, err + } + maskBf, err := bitfield.NewFromIter(&rlepluslazy.RunSliceIterator{ + Runs: []rlepluslazy.Run{rlepluslazy.Run{Val: true, Len: index}}}) + if err != nil { + return false, -1, err + } + before, err := bitfield.IntersectBitField(maskBf, r.ValidDeals) + if err != nil { + return false, -1, err + } + outIdx, err := before.Count() + if err != nil { + return false, -1, err + } + return set, int(outIdx), nil + +} + +func (r *publishStorageDealsReturn11) DealIDs() ([]abi.DealID, error) { + return r.IDs, nil +} + +func (s *state11) GetAllocationIdForPendingDeal(dealId abi.DealID) (verifregtypes.AllocationId, error) { + + allocations, err := adt11.AsMap(s.store, s.PendingDealAllocationIds, builtin.DefaultHamtBitwidth) + if err != nil { + return verifregtypes.NoAllocationID, xerrors.Errorf("failed to load allocation id for %d: %w", dealId, err) + } + + var allocationId cbg.CborInt + found, err := allocations.Get(abi.UIntKey(uint64(dealId)), &allocationId) + if err != nil { + return verifregtypes.NoAllocationID, xerrors.Errorf("failed to load allocation id for %d: %w", dealId, err) + } + if !found { + return verifregtypes.NoAllocationID, nil + } + + return verifregtypes.AllocationId(allocationId), nil + +} + +func (s *state11) ActorKey() string { + return manifest.MarketKey +} + +func (s *state11) ActorVersion() actorstypes.Version { + return actorstypes.Version11 +} + +func (s *state11) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/market/v2.go b/chain/actors/builtin/market/v2.go new file mode 100644 index 000000000..89ffdde8f --- /dev/null +++ b/chain/actors/builtin/market/v2.go @@ -0,0 +1,330 @@ +package market + +import ( + "bytes" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + verifregtypes "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" + "github.com/filecoin-project/go-state-types/manifest" + market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market" + adt2 "github.com/filecoin-project/specs-actors/v2/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/types" +) + +var _ State = (*state2)(nil) + +func load2(store adt.Store, root cid.Cid) (State, error) { + out := state2{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make2(store adt.Store) (State, error) { + out := state2{store: store} + + ea, err := adt2.MakeEmptyArray(store).Root() + if err != nil { + return nil, err + } + + em, err := adt2.MakeEmptyMap(store).Root() + if err != nil { + return nil, err + } + + out.State = *market2.ConstructState(ea, em, em) + + return &out, nil +} + +type state2 struct { + market2.State + store adt.Store +} + +func (s *state2) TotalLocked() (abi.TokenAmount, error) { + fml := types.BigAdd(s.TotalClientLockedCollateral, s.TotalProviderLockedCollateral) + fml = types.BigAdd(fml, s.TotalClientStorageFee) + return fml, nil +} + +func (s *state2) BalancesChanged(otherState State) (bool, error) { + otherState2, ok := otherState.(*state2) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.EscrowTable.Equals(otherState2.State.EscrowTable) || !s.State.LockedTable.Equals(otherState2.State.LockedTable), nil +} + +func (s *state2) StatesChanged(otherState State) (bool, error) { + otherState2, ok := otherState.(*state2) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.States.Equals(otherState2.State.States), nil +} + +func (s *state2) States() (DealStates, error) { + stateArray, err := adt2.AsArray(s.store, s.State.States) + if err != nil { + return nil, err + } + return &dealStates2{stateArray}, nil +} + +func (s *state2) ProposalsChanged(otherState State) (bool, error) { + otherState2, ok := otherState.(*state2) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.Proposals.Equals(otherState2.State.Proposals), nil +} + +func (s *state2) Proposals() (DealProposals, error) { + proposalArray, err := adt2.AsArray(s.store, s.State.Proposals) + if err != nil { + return nil, err + } + return &dealProposals2{proposalArray}, nil +} + +func (s *state2) EscrowTable() (BalanceTable, error) { + bt, err := adt2.AsBalanceTable(s.store, s.State.EscrowTable) + if err != nil { + return nil, err + } + return &balanceTable2{bt}, nil +} + +func (s *state2) LockedTable() (BalanceTable, error) { + bt, err := adt2.AsBalanceTable(s.store, s.State.LockedTable) + if err != nil { + return nil, err + } + return &balanceTable2{bt}, nil +} + +func (s *state2) VerifyDealsForActivation( + minerAddr address.Address, deals []abi.DealID, currEpoch, sectorExpiry abi.ChainEpoch, +) (weight, verifiedWeight abi.DealWeight, err error) { + w, vw, _, err := market2.ValidateDealsForActivation(&s.State, s.store, deals, minerAddr, sectorExpiry, currEpoch) + return w, vw, err +} + +func (s *state2) NextID() (abi.DealID, error) { + return s.State.NextID, nil +} + +type balanceTable2 struct { + *adt2.BalanceTable +} + +func (bt *balanceTable2) ForEach(cb func(address.Address, abi.TokenAmount) error) error { + asMap := (*adt2.Map)(bt.BalanceTable) + var ta abi.TokenAmount + return asMap.ForEach(&ta, func(key string) error { + a, err := address.NewFromBytes([]byte(key)) + if err != nil { + return err + } + return cb(a, ta) + }) +} + +type dealStates2 struct { + adt.Array +} + +func (s *dealStates2) Get(dealID abi.DealID) (*DealState, bool, error) { + var deal2 market2.DealState + found, err := s.Array.Get(uint64(dealID), &deal2) + if err != nil { + return nil, false, err + } + if !found { + return nil, false, nil + } + deal := fromV2DealState(deal2) + return &deal, true, nil +} + +func (s *dealStates2) ForEach(cb func(dealID abi.DealID, ds DealState) error) error { + var ds2 market2.DealState + return s.Array.ForEach(&ds2, func(idx int64) error { + return cb(abi.DealID(idx), fromV2DealState(ds2)) + }) +} + +func (s *dealStates2) decode(val *cbg.Deferred) (*DealState, error) { + var ds2 market2.DealState + if err := ds2.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return nil, err + } + ds := fromV2DealState(ds2) + return &ds, nil +} + +func (s *dealStates2) array() adt.Array { + return s.Array +} + +func fromV2DealState(v2 market2.DealState) DealState { + ret := DealState{ + SectorStartEpoch: v2.SectorStartEpoch, + LastUpdatedEpoch: v2.LastUpdatedEpoch, + SlashEpoch: v2.SlashEpoch, + VerifiedClaim: 0, + } + + return ret +} + +type dealProposals2 struct { + adt.Array +} + +func (s *dealProposals2) Get(dealID abi.DealID) (*DealProposal, bool, error) { + var proposal2 market2.DealProposal + found, err := s.Array.Get(uint64(dealID), &proposal2) + if err != nil { + return nil, false, err + } + if !found { + return nil, false, nil + } + + proposal, err := fromV2DealProposal(proposal2) + if err != nil { + return nil, true, xerrors.Errorf("decoding proposal: %w", err) + } + + return &proposal, true, nil +} + +func (s *dealProposals2) ForEach(cb func(dealID abi.DealID, dp DealProposal) error) error { + var dp2 market2.DealProposal + return s.Array.ForEach(&dp2, func(idx int64) error { + dp, err := fromV2DealProposal(dp2) + if err != nil { + return xerrors.Errorf("decoding proposal: %w", err) + } + + return cb(abi.DealID(idx), dp) + }) +} + +func (s *dealProposals2) decode(val *cbg.Deferred) (*DealProposal, error) { + var dp2 market2.DealProposal + if err := dp2.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return nil, err + } + + dp, err := fromV2DealProposal(dp2) + if err != nil { + return nil, err + } + + return &dp, nil +} + +func (s *dealProposals2) array() adt.Array { + return s.Array +} + +func fromV2DealProposal(v2 market2.DealProposal) (DealProposal, error) { + + label, err := labelFromGoString(v2.Label) + + if err != nil { + return DealProposal{}, xerrors.Errorf("error setting deal label: %w", err) + } + + return DealProposal{ + PieceCID: v2.PieceCID, + PieceSize: v2.PieceSize, + VerifiedDeal: v2.VerifiedDeal, + Client: v2.Client, + Provider: v2.Provider, + + Label: label, + + StartEpoch: v2.StartEpoch, + EndEpoch: v2.EndEpoch, + StoragePricePerEpoch: v2.StoragePricePerEpoch, + + ProviderCollateral: v2.ProviderCollateral, + ClientCollateral: v2.ClientCollateral, + }, nil +} + +func (s *state2) GetState() interface{} { + return &s.State +} + +var _ PublishStorageDealsReturn = (*publishStorageDealsReturn2)(nil) + +func decodePublishStorageDealsReturn2(b []byte) (PublishStorageDealsReturn, error) { + var retval market2.PublishStorageDealsReturn + if err := retval.UnmarshalCBOR(bytes.NewReader(b)); err != nil { + return nil, xerrors.Errorf("failed to unmarshal PublishStorageDealsReturn: %w", err) + } + + return &publishStorageDealsReturn2{retval}, nil +} + +type publishStorageDealsReturn2 struct { + market2.PublishStorageDealsReturn +} + +func (r *publishStorageDealsReturn2) IsDealValid(index uint64) (bool, int, error) { + + // PublishStorageDeals only succeeded if all deals were valid in this version of actors + return true, int(index), nil + +} + +func (r *publishStorageDealsReturn2) DealIDs() ([]abi.DealID, error) { + return r.IDs, nil +} + +func (s *state2) GetAllocationIdForPendingDeal(dealId abi.DealID) (verifregtypes.AllocationId, error) { + + return verifregtypes.NoAllocationID, xerrors.Errorf("unsupported before actors v9") + +} + +func (s *state2) ActorKey() string { + return manifest.MarketKey +} + +func (s *state2) ActorVersion() actorstypes.Version { + return actorstypes.Version2 +} + +func (s *state2) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/market/v3.go b/chain/actors/builtin/market/v3.go new file mode 100644 index 000000000..f4d073ed8 --- /dev/null +++ b/chain/actors/builtin/market/v3.go @@ -0,0 +1,325 @@ +package market + +import ( + "bytes" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + verifregtypes "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" + "github.com/filecoin-project/go-state-types/manifest" + market3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/market" + adt3 "github.com/filecoin-project/specs-actors/v3/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/types" +) + +var _ State = (*state3)(nil) + +func load3(store adt.Store, root cid.Cid) (State, error) { + out := state3{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make3(store adt.Store) (State, error) { + out := state3{store: store} + + s, err := market3.ConstructState(store) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state3 struct { + market3.State + store adt.Store +} + +func (s *state3) TotalLocked() (abi.TokenAmount, error) { + fml := types.BigAdd(s.TotalClientLockedCollateral, s.TotalProviderLockedCollateral) + fml = types.BigAdd(fml, s.TotalClientStorageFee) + return fml, nil +} + +func (s *state3) BalancesChanged(otherState State) (bool, error) { + otherState3, ok := otherState.(*state3) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.EscrowTable.Equals(otherState3.State.EscrowTable) || !s.State.LockedTable.Equals(otherState3.State.LockedTable), nil +} + +func (s *state3) StatesChanged(otherState State) (bool, error) { + otherState3, ok := otherState.(*state3) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.States.Equals(otherState3.State.States), nil +} + +func (s *state3) States() (DealStates, error) { + stateArray, err := adt3.AsArray(s.store, s.State.States, market3.StatesAmtBitwidth) + if err != nil { + return nil, err + } + return &dealStates3{stateArray}, nil +} + +func (s *state3) ProposalsChanged(otherState State) (bool, error) { + otherState3, ok := otherState.(*state3) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.Proposals.Equals(otherState3.State.Proposals), nil +} + +func (s *state3) Proposals() (DealProposals, error) { + proposalArray, err := adt3.AsArray(s.store, s.State.Proposals, market3.ProposalsAmtBitwidth) + if err != nil { + return nil, err + } + return &dealProposals3{proposalArray}, nil +} + +func (s *state3) EscrowTable() (BalanceTable, error) { + bt, err := adt3.AsBalanceTable(s.store, s.State.EscrowTable) + if err != nil { + return nil, err + } + return &balanceTable3{bt}, nil +} + +func (s *state3) LockedTable() (BalanceTable, error) { + bt, err := adt3.AsBalanceTable(s.store, s.State.LockedTable) + if err != nil { + return nil, err + } + return &balanceTable3{bt}, nil +} + +func (s *state3) VerifyDealsForActivation( + minerAddr address.Address, deals []abi.DealID, currEpoch, sectorExpiry abi.ChainEpoch, +) (weight, verifiedWeight abi.DealWeight, err error) { + w, vw, _, err := market3.ValidateDealsForActivation(&s.State, s.store, deals, minerAddr, sectorExpiry, currEpoch) + return w, vw, err +} + +func (s *state3) NextID() (abi.DealID, error) { + return s.State.NextID, nil +} + +type balanceTable3 struct { + *adt3.BalanceTable +} + +func (bt *balanceTable3) ForEach(cb func(address.Address, abi.TokenAmount) error) error { + asMap := (*adt3.Map)(bt.BalanceTable) + var ta abi.TokenAmount + return asMap.ForEach(&ta, func(key string) error { + a, err := address.NewFromBytes([]byte(key)) + if err != nil { + return err + } + return cb(a, ta) + }) +} + +type dealStates3 struct { + adt.Array +} + +func (s *dealStates3) Get(dealID abi.DealID) (*DealState, bool, error) { + var deal3 market3.DealState + found, err := s.Array.Get(uint64(dealID), &deal3) + if err != nil { + return nil, false, err + } + if !found { + return nil, false, nil + } + deal := fromV3DealState(deal3) + return &deal, true, nil +} + +func (s *dealStates3) ForEach(cb func(dealID abi.DealID, ds DealState) error) error { + var ds3 market3.DealState + return s.Array.ForEach(&ds3, func(idx int64) error { + return cb(abi.DealID(idx), fromV3DealState(ds3)) + }) +} + +func (s *dealStates3) decode(val *cbg.Deferred) (*DealState, error) { + var ds3 market3.DealState + if err := ds3.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return nil, err + } + ds := fromV3DealState(ds3) + return &ds, nil +} + +func (s *dealStates3) array() adt.Array { + return s.Array +} + +func fromV3DealState(v3 market3.DealState) DealState { + ret := DealState{ + SectorStartEpoch: v3.SectorStartEpoch, + LastUpdatedEpoch: v3.LastUpdatedEpoch, + SlashEpoch: v3.SlashEpoch, + VerifiedClaim: 0, + } + + return ret +} + +type dealProposals3 struct { + adt.Array +} + +func (s *dealProposals3) Get(dealID abi.DealID) (*DealProposal, bool, error) { + var proposal3 market3.DealProposal + found, err := s.Array.Get(uint64(dealID), &proposal3) + if err != nil { + return nil, false, err + } + if !found { + return nil, false, nil + } + + proposal, err := fromV3DealProposal(proposal3) + if err != nil { + return nil, true, xerrors.Errorf("decoding proposal: %w", err) + } + + return &proposal, true, nil +} + +func (s *dealProposals3) ForEach(cb func(dealID abi.DealID, dp DealProposal) error) error { + var dp3 market3.DealProposal + return s.Array.ForEach(&dp3, func(idx int64) error { + dp, err := fromV3DealProposal(dp3) + if err != nil { + return xerrors.Errorf("decoding proposal: %w", err) + } + + return cb(abi.DealID(idx), dp) + }) +} + +func (s *dealProposals3) decode(val *cbg.Deferred) (*DealProposal, error) { + var dp3 market3.DealProposal + if err := dp3.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return nil, err + } + + dp, err := fromV3DealProposal(dp3) + if err != nil { + return nil, err + } + + return &dp, nil +} + +func (s *dealProposals3) array() adt.Array { + return s.Array +} + +func fromV3DealProposal(v3 market3.DealProposal) (DealProposal, error) { + + label, err := labelFromGoString(v3.Label) + + if err != nil { + return DealProposal{}, xerrors.Errorf("error setting deal label: %w", err) + } + + return DealProposal{ + PieceCID: v3.PieceCID, + PieceSize: v3.PieceSize, + VerifiedDeal: v3.VerifiedDeal, + Client: v3.Client, + Provider: v3.Provider, + + Label: label, + + StartEpoch: v3.StartEpoch, + EndEpoch: v3.EndEpoch, + StoragePricePerEpoch: v3.StoragePricePerEpoch, + + ProviderCollateral: v3.ProviderCollateral, + ClientCollateral: v3.ClientCollateral, + }, nil +} + +func (s *state3) GetState() interface{} { + return &s.State +} + +var _ PublishStorageDealsReturn = (*publishStorageDealsReturn3)(nil) + +func decodePublishStorageDealsReturn3(b []byte) (PublishStorageDealsReturn, error) { + var retval market3.PublishStorageDealsReturn + if err := retval.UnmarshalCBOR(bytes.NewReader(b)); err != nil { + return nil, xerrors.Errorf("failed to unmarshal PublishStorageDealsReturn: %w", err) + } + + return &publishStorageDealsReturn3{retval}, nil +} + +type publishStorageDealsReturn3 struct { + market3.PublishStorageDealsReturn +} + +func (r *publishStorageDealsReturn3) IsDealValid(index uint64) (bool, int, error) { + + // PublishStorageDeals only succeeded if all deals were valid in this version of actors + return true, int(index), nil + +} + +func (r *publishStorageDealsReturn3) DealIDs() ([]abi.DealID, error) { + return r.IDs, nil +} + +func (s *state3) GetAllocationIdForPendingDeal(dealId abi.DealID) (verifregtypes.AllocationId, error) { + + return verifregtypes.NoAllocationID, xerrors.Errorf("unsupported before actors v9") + +} + +func (s *state3) ActorKey() string { + return manifest.MarketKey +} + +func (s *state3) ActorVersion() actorstypes.Version { + return actorstypes.Version3 +} + +func (s *state3) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/market/v4.go b/chain/actors/builtin/market/v4.go new file mode 100644 index 000000000..422a30cbb --- /dev/null +++ b/chain/actors/builtin/market/v4.go @@ -0,0 +1,325 @@ +package market + +import ( + "bytes" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + verifregtypes "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" + "github.com/filecoin-project/go-state-types/manifest" + market4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/market" + adt4 "github.com/filecoin-project/specs-actors/v4/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/types" +) + +var _ State = (*state4)(nil) + +func load4(store adt.Store, root cid.Cid) (State, error) { + out := state4{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make4(store adt.Store) (State, error) { + out := state4{store: store} + + s, err := market4.ConstructState(store) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state4 struct { + market4.State + store adt.Store +} + +func (s *state4) TotalLocked() (abi.TokenAmount, error) { + fml := types.BigAdd(s.TotalClientLockedCollateral, s.TotalProviderLockedCollateral) + fml = types.BigAdd(fml, s.TotalClientStorageFee) + return fml, nil +} + +func (s *state4) BalancesChanged(otherState State) (bool, error) { + otherState4, ok := otherState.(*state4) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.EscrowTable.Equals(otherState4.State.EscrowTable) || !s.State.LockedTable.Equals(otherState4.State.LockedTable), nil +} + +func (s *state4) StatesChanged(otherState State) (bool, error) { + otherState4, ok := otherState.(*state4) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.States.Equals(otherState4.State.States), nil +} + +func (s *state4) States() (DealStates, error) { + stateArray, err := adt4.AsArray(s.store, s.State.States, market4.StatesAmtBitwidth) + if err != nil { + return nil, err + } + return &dealStates4{stateArray}, nil +} + +func (s *state4) ProposalsChanged(otherState State) (bool, error) { + otherState4, ok := otherState.(*state4) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.Proposals.Equals(otherState4.State.Proposals), nil +} + +func (s *state4) Proposals() (DealProposals, error) { + proposalArray, err := adt4.AsArray(s.store, s.State.Proposals, market4.ProposalsAmtBitwidth) + if err != nil { + return nil, err + } + return &dealProposals4{proposalArray}, nil +} + +func (s *state4) EscrowTable() (BalanceTable, error) { + bt, err := adt4.AsBalanceTable(s.store, s.State.EscrowTable) + if err != nil { + return nil, err + } + return &balanceTable4{bt}, nil +} + +func (s *state4) LockedTable() (BalanceTable, error) { + bt, err := adt4.AsBalanceTable(s.store, s.State.LockedTable) + if err != nil { + return nil, err + } + return &balanceTable4{bt}, nil +} + +func (s *state4) VerifyDealsForActivation( + minerAddr address.Address, deals []abi.DealID, currEpoch, sectorExpiry abi.ChainEpoch, +) (weight, verifiedWeight abi.DealWeight, err error) { + w, vw, _, err := market4.ValidateDealsForActivation(&s.State, s.store, deals, minerAddr, sectorExpiry, currEpoch) + return w, vw, err +} + +func (s *state4) NextID() (abi.DealID, error) { + return s.State.NextID, nil +} + +type balanceTable4 struct { + *adt4.BalanceTable +} + +func (bt *balanceTable4) ForEach(cb func(address.Address, abi.TokenAmount) error) error { + asMap := (*adt4.Map)(bt.BalanceTable) + var ta abi.TokenAmount + return asMap.ForEach(&ta, func(key string) error { + a, err := address.NewFromBytes([]byte(key)) + if err != nil { + return err + } + return cb(a, ta) + }) +} + +type dealStates4 struct { + adt.Array +} + +func (s *dealStates4) Get(dealID abi.DealID) (*DealState, bool, error) { + var deal4 market4.DealState + found, err := s.Array.Get(uint64(dealID), &deal4) + if err != nil { + return nil, false, err + } + if !found { + return nil, false, nil + } + deal := fromV4DealState(deal4) + return &deal, true, nil +} + +func (s *dealStates4) ForEach(cb func(dealID abi.DealID, ds DealState) error) error { + var ds4 market4.DealState + return s.Array.ForEach(&ds4, func(idx int64) error { + return cb(abi.DealID(idx), fromV4DealState(ds4)) + }) +} + +func (s *dealStates4) decode(val *cbg.Deferred) (*DealState, error) { + var ds4 market4.DealState + if err := ds4.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return nil, err + } + ds := fromV4DealState(ds4) + return &ds, nil +} + +func (s *dealStates4) array() adt.Array { + return s.Array +} + +func fromV4DealState(v4 market4.DealState) DealState { + ret := DealState{ + SectorStartEpoch: v4.SectorStartEpoch, + LastUpdatedEpoch: v4.LastUpdatedEpoch, + SlashEpoch: v4.SlashEpoch, + VerifiedClaim: 0, + } + + return ret +} + +type dealProposals4 struct { + adt.Array +} + +func (s *dealProposals4) Get(dealID abi.DealID) (*DealProposal, bool, error) { + var proposal4 market4.DealProposal + found, err := s.Array.Get(uint64(dealID), &proposal4) + if err != nil { + return nil, false, err + } + if !found { + return nil, false, nil + } + + proposal, err := fromV4DealProposal(proposal4) + if err != nil { + return nil, true, xerrors.Errorf("decoding proposal: %w", err) + } + + return &proposal, true, nil +} + +func (s *dealProposals4) ForEach(cb func(dealID abi.DealID, dp DealProposal) error) error { + var dp4 market4.DealProposal + return s.Array.ForEach(&dp4, func(idx int64) error { + dp, err := fromV4DealProposal(dp4) + if err != nil { + return xerrors.Errorf("decoding proposal: %w", err) + } + + return cb(abi.DealID(idx), dp) + }) +} + +func (s *dealProposals4) decode(val *cbg.Deferred) (*DealProposal, error) { + var dp4 market4.DealProposal + if err := dp4.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return nil, err + } + + dp, err := fromV4DealProposal(dp4) + if err != nil { + return nil, err + } + + return &dp, nil +} + +func (s *dealProposals4) array() adt.Array { + return s.Array +} + +func fromV4DealProposal(v4 market4.DealProposal) (DealProposal, error) { + + label, err := labelFromGoString(v4.Label) + + if err != nil { + return DealProposal{}, xerrors.Errorf("error setting deal label: %w", err) + } + + return DealProposal{ + PieceCID: v4.PieceCID, + PieceSize: v4.PieceSize, + VerifiedDeal: v4.VerifiedDeal, + Client: v4.Client, + Provider: v4.Provider, + + Label: label, + + StartEpoch: v4.StartEpoch, + EndEpoch: v4.EndEpoch, + StoragePricePerEpoch: v4.StoragePricePerEpoch, + + ProviderCollateral: v4.ProviderCollateral, + ClientCollateral: v4.ClientCollateral, + }, nil +} + +func (s *state4) GetState() interface{} { + return &s.State +} + +var _ PublishStorageDealsReturn = (*publishStorageDealsReturn4)(nil) + +func decodePublishStorageDealsReturn4(b []byte) (PublishStorageDealsReturn, error) { + var retval market4.PublishStorageDealsReturn + if err := retval.UnmarshalCBOR(bytes.NewReader(b)); err != nil { + return nil, xerrors.Errorf("failed to unmarshal PublishStorageDealsReturn: %w", err) + } + + return &publishStorageDealsReturn4{retval}, nil +} + +type publishStorageDealsReturn4 struct { + market4.PublishStorageDealsReturn +} + +func (r *publishStorageDealsReturn4) IsDealValid(index uint64) (bool, int, error) { + + // PublishStorageDeals only succeeded if all deals were valid in this version of actors + return true, int(index), nil + +} + +func (r *publishStorageDealsReturn4) DealIDs() ([]abi.DealID, error) { + return r.IDs, nil +} + +func (s *state4) GetAllocationIdForPendingDeal(dealId abi.DealID) (verifregtypes.AllocationId, error) { + + return verifregtypes.NoAllocationID, xerrors.Errorf("unsupported before actors v9") + +} + +func (s *state4) ActorKey() string { + return manifest.MarketKey +} + +func (s *state4) ActorVersion() actorstypes.Version { + return actorstypes.Version4 +} + +func (s *state4) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/market/v5.go b/chain/actors/builtin/market/v5.go new file mode 100644 index 000000000..b30decb03 --- /dev/null +++ b/chain/actors/builtin/market/v5.go @@ -0,0 +1,325 @@ +package market + +import ( + "bytes" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + verifregtypes "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" + "github.com/filecoin-project/go-state-types/manifest" + market5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/market" + adt5 "github.com/filecoin-project/specs-actors/v5/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/types" +) + +var _ State = (*state5)(nil) + +func load5(store adt.Store, root cid.Cid) (State, error) { + out := state5{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make5(store adt.Store) (State, error) { + out := state5{store: store} + + s, err := market5.ConstructState(store) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state5 struct { + market5.State + store adt.Store +} + +func (s *state5) TotalLocked() (abi.TokenAmount, error) { + fml := types.BigAdd(s.TotalClientLockedCollateral, s.TotalProviderLockedCollateral) + fml = types.BigAdd(fml, s.TotalClientStorageFee) + return fml, nil +} + +func (s *state5) BalancesChanged(otherState State) (bool, error) { + otherState5, ok := otherState.(*state5) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.EscrowTable.Equals(otherState5.State.EscrowTable) || !s.State.LockedTable.Equals(otherState5.State.LockedTable), nil +} + +func (s *state5) StatesChanged(otherState State) (bool, error) { + otherState5, ok := otherState.(*state5) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.States.Equals(otherState5.State.States), nil +} + +func (s *state5) States() (DealStates, error) { + stateArray, err := adt5.AsArray(s.store, s.State.States, market5.StatesAmtBitwidth) + if err != nil { + return nil, err + } + return &dealStates5{stateArray}, nil +} + +func (s *state5) ProposalsChanged(otherState State) (bool, error) { + otherState5, ok := otherState.(*state5) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.Proposals.Equals(otherState5.State.Proposals), nil +} + +func (s *state5) Proposals() (DealProposals, error) { + proposalArray, err := adt5.AsArray(s.store, s.State.Proposals, market5.ProposalsAmtBitwidth) + if err != nil { + return nil, err + } + return &dealProposals5{proposalArray}, nil +} + +func (s *state5) EscrowTable() (BalanceTable, error) { + bt, err := adt5.AsBalanceTable(s.store, s.State.EscrowTable) + if err != nil { + return nil, err + } + return &balanceTable5{bt}, nil +} + +func (s *state5) LockedTable() (BalanceTable, error) { + bt, err := adt5.AsBalanceTable(s.store, s.State.LockedTable) + if err != nil { + return nil, err + } + return &balanceTable5{bt}, nil +} + +func (s *state5) VerifyDealsForActivation( + minerAddr address.Address, deals []abi.DealID, currEpoch, sectorExpiry abi.ChainEpoch, +) (weight, verifiedWeight abi.DealWeight, err error) { + w, vw, _, err := market5.ValidateDealsForActivation(&s.State, s.store, deals, minerAddr, sectorExpiry, currEpoch) + return w, vw, err +} + +func (s *state5) NextID() (abi.DealID, error) { + return s.State.NextID, nil +} + +type balanceTable5 struct { + *adt5.BalanceTable +} + +func (bt *balanceTable5) ForEach(cb func(address.Address, abi.TokenAmount) error) error { + asMap := (*adt5.Map)(bt.BalanceTable) + var ta abi.TokenAmount + return asMap.ForEach(&ta, func(key string) error { + a, err := address.NewFromBytes([]byte(key)) + if err != nil { + return err + } + return cb(a, ta) + }) +} + +type dealStates5 struct { + adt.Array +} + +func (s *dealStates5) Get(dealID abi.DealID) (*DealState, bool, error) { + var deal5 market5.DealState + found, err := s.Array.Get(uint64(dealID), &deal5) + if err != nil { + return nil, false, err + } + if !found { + return nil, false, nil + } + deal := fromV5DealState(deal5) + return &deal, true, nil +} + +func (s *dealStates5) ForEach(cb func(dealID abi.DealID, ds DealState) error) error { + var ds5 market5.DealState + return s.Array.ForEach(&ds5, func(idx int64) error { + return cb(abi.DealID(idx), fromV5DealState(ds5)) + }) +} + +func (s *dealStates5) decode(val *cbg.Deferred) (*DealState, error) { + var ds5 market5.DealState + if err := ds5.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return nil, err + } + ds := fromV5DealState(ds5) + return &ds, nil +} + +func (s *dealStates5) array() adt.Array { + return s.Array +} + +func fromV5DealState(v5 market5.DealState) DealState { + ret := DealState{ + SectorStartEpoch: v5.SectorStartEpoch, + LastUpdatedEpoch: v5.LastUpdatedEpoch, + SlashEpoch: v5.SlashEpoch, + VerifiedClaim: 0, + } + + return ret +} + +type dealProposals5 struct { + adt.Array +} + +func (s *dealProposals5) Get(dealID abi.DealID) (*DealProposal, bool, error) { + var proposal5 market5.DealProposal + found, err := s.Array.Get(uint64(dealID), &proposal5) + if err != nil { + return nil, false, err + } + if !found { + return nil, false, nil + } + + proposal, err := fromV5DealProposal(proposal5) + if err != nil { + return nil, true, xerrors.Errorf("decoding proposal: %w", err) + } + + return &proposal, true, nil +} + +func (s *dealProposals5) ForEach(cb func(dealID abi.DealID, dp DealProposal) error) error { + var dp5 market5.DealProposal + return s.Array.ForEach(&dp5, func(idx int64) error { + dp, err := fromV5DealProposal(dp5) + if err != nil { + return xerrors.Errorf("decoding proposal: %w", err) + } + + return cb(abi.DealID(idx), dp) + }) +} + +func (s *dealProposals5) decode(val *cbg.Deferred) (*DealProposal, error) { + var dp5 market5.DealProposal + if err := dp5.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return nil, err + } + + dp, err := fromV5DealProposal(dp5) + if err != nil { + return nil, err + } + + return &dp, nil +} + +func (s *dealProposals5) array() adt.Array { + return s.Array +} + +func fromV5DealProposal(v5 market5.DealProposal) (DealProposal, error) { + + label, err := labelFromGoString(v5.Label) + + if err != nil { + return DealProposal{}, xerrors.Errorf("error setting deal label: %w", err) + } + + return DealProposal{ + PieceCID: v5.PieceCID, + PieceSize: v5.PieceSize, + VerifiedDeal: v5.VerifiedDeal, + Client: v5.Client, + Provider: v5.Provider, + + Label: label, + + StartEpoch: v5.StartEpoch, + EndEpoch: v5.EndEpoch, + StoragePricePerEpoch: v5.StoragePricePerEpoch, + + ProviderCollateral: v5.ProviderCollateral, + ClientCollateral: v5.ClientCollateral, + }, nil +} + +func (s *state5) GetState() interface{} { + return &s.State +} + +var _ PublishStorageDealsReturn = (*publishStorageDealsReturn5)(nil) + +func decodePublishStorageDealsReturn5(b []byte) (PublishStorageDealsReturn, error) { + var retval market5.PublishStorageDealsReturn + if err := retval.UnmarshalCBOR(bytes.NewReader(b)); err != nil { + return nil, xerrors.Errorf("failed to unmarshal PublishStorageDealsReturn: %w", err) + } + + return &publishStorageDealsReturn5{retval}, nil +} + +type publishStorageDealsReturn5 struct { + market5.PublishStorageDealsReturn +} + +func (r *publishStorageDealsReturn5) IsDealValid(index uint64) (bool, int, error) { + + // PublishStorageDeals only succeeded if all deals were valid in this version of actors + return true, int(index), nil + +} + +func (r *publishStorageDealsReturn5) DealIDs() ([]abi.DealID, error) { + return r.IDs, nil +} + +func (s *state5) GetAllocationIdForPendingDeal(dealId abi.DealID) (verifregtypes.AllocationId, error) { + + return verifregtypes.NoAllocationID, xerrors.Errorf("unsupported before actors v9") + +} + +func (s *state5) ActorKey() string { + return manifest.MarketKey +} + +func (s *state5) ActorVersion() actorstypes.Version { + return actorstypes.Version5 +} + +func (s *state5) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/market/v6.go b/chain/actors/builtin/market/v6.go new file mode 100644 index 000000000..377b278ae --- /dev/null +++ b/chain/actors/builtin/market/v6.go @@ -0,0 +1,343 @@ +package market + +import ( + "bytes" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-bitfield" + rlepluslazy "github.com/filecoin-project/go-bitfield/rle" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + verifregtypes "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" + "github.com/filecoin-project/go-state-types/manifest" + market6 "github.com/filecoin-project/specs-actors/v6/actors/builtin/market" + adt6 "github.com/filecoin-project/specs-actors/v6/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/types" +) + +var _ State = (*state6)(nil) + +func load6(store adt.Store, root cid.Cid) (State, error) { + out := state6{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make6(store adt.Store) (State, error) { + out := state6{store: store} + + s, err := market6.ConstructState(store) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state6 struct { + market6.State + store adt.Store +} + +func (s *state6) TotalLocked() (abi.TokenAmount, error) { + fml := types.BigAdd(s.TotalClientLockedCollateral, s.TotalProviderLockedCollateral) + fml = types.BigAdd(fml, s.TotalClientStorageFee) + return fml, nil +} + +func (s *state6) BalancesChanged(otherState State) (bool, error) { + otherState6, ok := otherState.(*state6) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.EscrowTable.Equals(otherState6.State.EscrowTable) || !s.State.LockedTable.Equals(otherState6.State.LockedTable), nil +} + +func (s *state6) StatesChanged(otherState State) (bool, error) { + otherState6, ok := otherState.(*state6) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.States.Equals(otherState6.State.States), nil +} + +func (s *state6) States() (DealStates, error) { + stateArray, err := adt6.AsArray(s.store, s.State.States, market6.StatesAmtBitwidth) + if err != nil { + return nil, err + } + return &dealStates6{stateArray}, nil +} + +func (s *state6) ProposalsChanged(otherState State) (bool, error) { + otherState6, ok := otherState.(*state6) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.Proposals.Equals(otherState6.State.Proposals), nil +} + +func (s *state6) Proposals() (DealProposals, error) { + proposalArray, err := adt6.AsArray(s.store, s.State.Proposals, market6.ProposalsAmtBitwidth) + if err != nil { + return nil, err + } + return &dealProposals6{proposalArray}, nil +} + +func (s *state6) EscrowTable() (BalanceTable, error) { + bt, err := adt6.AsBalanceTable(s.store, s.State.EscrowTable) + if err != nil { + return nil, err + } + return &balanceTable6{bt}, nil +} + +func (s *state6) LockedTable() (BalanceTable, error) { + bt, err := adt6.AsBalanceTable(s.store, s.State.LockedTable) + if err != nil { + return nil, err + } + return &balanceTable6{bt}, nil +} + +func (s *state6) VerifyDealsForActivation( + minerAddr address.Address, deals []abi.DealID, currEpoch, sectorExpiry abi.ChainEpoch, +) (weight, verifiedWeight abi.DealWeight, err error) { + w, vw, _, err := market6.ValidateDealsForActivation(&s.State, s.store, deals, minerAddr, sectorExpiry, currEpoch) + return w, vw, err +} + +func (s *state6) NextID() (abi.DealID, error) { + return s.State.NextID, nil +} + +type balanceTable6 struct { + *adt6.BalanceTable +} + +func (bt *balanceTable6) ForEach(cb func(address.Address, abi.TokenAmount) error) error { + asMap := (*adt6.Map)(bt.BalanceTable) + var ta abi.TokenAmount + return asMap.ForEach(&ta, func(key string) error { + a, err := address.NewFromBytes([]byte(key)) + if err != nil { + return err + } + return cb(a, ta) + }) +} + +type dealStates6 struct { + adt.Array +} + +func (s *dealStates6) Get(dealID abi.DealID) (*DealState, bool, error) { + var deal6 market6.DealState + found, err := s.Array.Get(uint64(dealID), &deal6) + if err != nil { + return nil, false, err + } + if !found { + return nil, false, nil + } + deal := fromV6DealState(deal6) + return &deal, true, nil +} + +func (s *dealStates6) ForEach(cb func(dealID abi.DealID, ds DealState) error) error { + var ds6 market6.DealState + return s.Array.ForEach(&ds6, func(idx int64) error { + return cb(abi.DealID(idx), fromV6DealState(ds6)) + }) +} + +func (s *dealStates6) decode(val *cbg.Deferred) (*DealState, error) { + var ds6 market6.DealState + if err := ds6.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return nil, err + } + ds := fromV6DealState(ds6) + return &ds, nil +} + +func (s *dealStates6) array() adt.Array { + return s.Array +} + +func fromV6DealState(v6 market6.DealState) DealState { + ret := DealState{ + SectorStartEpoch: v6.SectorStartEpoch, + LastUpdatedEpoch: v6.LastUpdatedEpoch, + SlashEpoch: v6.SlashEpoch, + VerifiedClaim: 0, + } + + return ret +} + +type dealProposals6 struct { + adt.Array +} + +func (s *dealProposals6) Get(dealID abi.DealID) (*DealProposal, bool, error) { + var proposal6 market6.DealProposal + found, err := s.Array.Get(uint64(dealID), &proposal6) + if err != nil { + return nil, false, err + } + if !found { + return nil, false, nil + } + + proposal, err := fromV6DealProposal(proposal6) + if err != nil { + return nil, true, xerrors.Errorf("decoding proposal: %w", err) + } + + return &proposal, true, nil +} + +func (s *dealProposals6) ForEach(cb func(dealID abi.DealID, dp DealProposal) error) error { + var dp6 market6.DealProposal + return s.Array.ForEach(&dp6, func(idx int64) error { + dp, err := fromV6DealProposal(dp6) + if err != nil { + return xerrors.Errorf("decoding proposal: %w", err) + } + + return cb(abi.DealID(idx), dp) + }) +} + +func (s *dealProposals6) decode(val *cbg.Deferred) (*DealProposal, error) { + var dp6 market6.DealProposal + if err := dp6.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return nil, err + } + + dp, err := fromV6DealProposal(dp6) + if err != nil { + return nil, err + } + + return &dp, nil +} + +func (s *dealProposals6) array() adt.Array { + return s.Array +} + +func fromV6DealProposal(v6 market6.DealProposal) (DealProposal, error) { + + label, err := labelFromGoString(v6.Label) + + if err != nil { + return DealProposal{}, xerrors.Errorf("error setting deal label: %w", err) + } + + return DealProposal{ + PieceCID: v6.PieceCID, + PieceSize: v6.PieceSize, + VerifiedDeal: v6.VerifiedDeal, + Client: v6.Client, + Provider: v6.Provider, + + Label: label, + + StartEpoch: v6.StartEpoch, + EndEpoch: v6.EndEpoch, + StoragePricePerEpoch: v6.StoragePricePerEpoch, + + ProviderCollateral: v6.ProviderCollateral, + ClientCollateral: v6.ClientCollateral, + }, nil +} + +func (s *state6) GetState() interface{} { + return &s.State +} + +var _ PublishStorageDealsReturn = (*publishStorageDealsReturn6)(nil) + +func decodePublishStorageDealsReturn6(b []byte) (PublishStorageDealsReturn, error) { + var retval market6.PublishStorageDealsReturn + if err := retval.UnmarshalCBOR(bytes.NewReader(b)); err != nil { + return nil, xerrors.Errorf("failed to unmarshal PublishStorageDealsReturn: %w", err) + } + + return &publishStorageDealsReturn6{retval}, nil +} + +type publishStorageDealsReturn6 struct { + market6.PublishStorageDealsReturn +} + +func (r *publishStorageDealsReturn6) IsDealValid(index uint64) (bool, int, error) { + + set, err := r.ValidDeals.IsSet(index) + if err != nil || !set { + return false, -1, err + } + maskBf, err := bitfield.NewFromIter(&rlepluslazy.RunSliceIterator{ + Runs: []rlepluslazy.Run{rlepluslazy.Run{Val: true, Len: index}}}) + if err != nil { + return false, -1, err + } + before, err := bitfield.IntersectBitField(maskBf, r.ValidDeals) + if err != nil { + return false, -1, err + } + outIdx, err := before.Count() + if err != nil { + return false, -1, err + } + return set, int(outIdx), nil + +} + +func (r *publishStorageDealsReturn6) DealIDs() ([]abi.DealID, error) { + return r.IDs, nil +} + +func (s *state6) GetAllocationIdForPendingDeal(dealId abi.DealID) (verifregtypes.AllocationId, error) { + + return verifregtypes.NoAllocationID, xerrors.Errorf("unsupported before actors v9") + +} + +func (s *state6) ActorKey() string { + return manifest.MarketKey +} + +func (s *state6) ActorVersion() actorstypes.Version { + return actorstypes.Version6 +} + +func (s *state6) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/market/v7.go b/chain/actors/builtin/market/v7.go new file mode 100644 index 000000000..cd4607cbe --- /dev/null +++ b/chain/actors/builtin/market/v7.go @@ -0,0 +1,343 @@ +package market + +import ( + "bytes" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-bitfield" + rlepluslazy "github.com/filecoin-project/go-bitfield/rle" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + verifregtypes "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" + "github.com/filecoin-project/go-state-types/manifest" + market7 "github.com/filecoin-project/specs-actors/v7/actors/builtin/market" + adt7 "github.com/filecoin-project/specs-actors/v7/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/types" +) + +var _ State = (*state7)(nil) + +func load7(store adt.Store, root cid.Cid) (State, error) { + out := state7{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make7(store adt.Store) (State, error) { + out := state7{store: store} + + s, err := market7.ConstructState(store) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state7 struct { + market7.State + store adt.Store +} + +func (s *state7) TotalLocked() (abi.TokenAmount, error) { + fml := types.BigAdd(s.TotalClientLockedCollateral, s.TotalProviderLockedCollateral) + fml = types.BigAdd(fml, s.TotalClientStorageFee) + return fml, nil +} + +func (s *state7) BalancesChanged(otherState State) (bool, error) { + otherState7, ok := otherState.(*state7) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.EscrowTable.Equals(otherState7.State.EscrowTable) || !s.State.LockedTable.Equals(otherState7.State.LockedTable), nil +} + +func (s *state7) StatesChanged(otherState State) (bool, error) { + otherState7, ok := otherState.(*state7) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.States.Equals(otherState7.State.States), nil +} + +func (s *state7) States() (DealStates, error) { + stateArray, err := adt7.AsArray(s.store, s.State.States, market7.StatesAmtBitwidth) + if err != nil { + return nil, err + } + return &dealStates7{stateArray}, nil +} + +func (s *state7) ProposalsChanged(otherState State) (bool, error) { + otherState7, ok := otherState.(*state7) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.Proposals.Equals(otherState7.State.Proposals), nil +} + +func (s *state7) Proposals() (DealProposals, error) { + proposalArray, err := adt7.AsArray(s.store, s.State.Proposals, market7.ProposalsAmtBitwidth) + if err != nil { + return nil, err + } + return &dealProposals7{proposalArray}, nil +} + +func (s *state7) EscrowTable() (BalanceTable, error) { + bt, err := adt7.AsBalanceTable(s.store, s.State.EscrowTable) + if err != nil { + return nil, err + } + return &balanceTable7{bt}, nil +} + +func (s *state7) LockedTable() (BalanceTable, error) { + bt, err := adt7.AsBalanceTable(s.store, s.State.LockedTable) + if err != nil { + return nil, err + } + return &balanceTable7{bt}, nil +} + +func (s *state7) VerifyDealsForActivation( + minerAddr address.Address, deals []abi.DealID, currEpoch, sectorExpiry abi.ChainEpoch, +) (weight, verifiedWeight abi.DealWeight, err error) { + w, vw, _, err := market7.ValidateDealsForActivation(&s.State, s.store, deals, minerAddr, sectorExpiry, currEpoch) + return w, vw, err +} + +func (s *state7) NextID() (abi.DealID, error) { + return s.State.NextID, nil +} + +type balanceTable7 struct { + *adt7.BalanceTable +} + +func (bt *balanceTable7) ForEach(cb func(address.Address, abi.TokenAmount) error) error { + asMap := (*adt7.Map)(bt.BalanceTable) + var ta abi.TokenAmount + return asMap.ForEach(&ta, func(key string) error { + a, err := address.NewFromBytes([]byte(key)) + if err != nil { + return err + } + return cb(a, ta) + }) +} + +type dealStates7 struct { + adt.Array +} + +func (s *dealStates7) Get(dealID abi.DealID) (*DealState, bool, error) { + var deal7 market7.DealState + found, err := s.Array.Get(uint64(dealID), &deal7) + if err != nil { + return nil, false, err + } + if !found { + return nil, false, nil + } + deal := fromV7DealState(deal7) + return &deal, true, nil +} + +func (s *dealStates7) ForEach(cb func(dealID abi.DealID, ds DealState) error) error { + var ds7 market7.DealState + return s.Array.ForEach(&ds7, func(idx int64) error { + return cb(abi.DealID(idx), fromV7DealState(ds7)) + }) +} + +func (s *dealStates7) decode(val *cbg.Deferred) (*DealState, error) { + var ds7 market7.DealState + if err := ds7.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return nil, err + } + ds := fromV7DealState(ds7) + return &ds, nil +} + +func (s *dealStates7) array() adt.Array { + return s.Array +} + +func fromV7DealState(v7 market7.DealState) DealState { + ret := DealState{ + SectorStartEpoch: v7.SectorStartEpoch, + LastUpdatedEpoch: v7.LastUpdatedEpoch, + SlashEpoch: v7.SlashEpoch, + VerifiedClaim: 0, + } + + return ret +} + +type dealProposals7 struct { + adt.Array +} + +func (s *dealProposals7) Get(dealID abi.DealID) (*DealProposal, bool, error) { + var proposal7 market7.DealProposal + found, err := s.Array.Get(uint64(dealID), &proposal7) + if err != nil { + return nil, false, err + } + if !found { + return nil, false, nil + } + + proposal, err := fromV7DealProposal(proposal7) + if err != nil { + return nil, true, xerrors.Errorf("decoding proposal: %w", err) + } + + return &proposal, true, nil +} + +func (s *dealProposals7) ForEach(cb func(dealID abi.DealID, dp DealProposal) error) error { + var dp7 market7.DealProposal + return s.Array.ForEach(&dp7, func(idx int64) error { + dp, err := fromV7DealProposal(dp7) + if err != nil { + return xerrors.Errorf("decoding proposal: %w", err) + } + + return cb(abi.DealID(idx), dp) + }) +} + +func (s *dealProposals7) decode(val *cbg.Deferred) (*DealProposal, error) { + var dp7 market7.DealProposal + if err := dp7.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return nil, err + } + + dp, err := fromV7DealProposal(dp7) + if err != nil { + return nil, err + } + + return &dp, nil +} + +func (s *dealProposals7) array() adt.Array { + return s.Array +} + +func fromV7DealProposal(v7 market7.DealProposal) (DealProposal, error) { + + label, err := labelFromGoString(v7.Label) + + if err != nil { + return DealProposal{}, xerrors.Errorf("error setting deal label: %w", err) + } + + return DealProposal{ + PieceCID: v7.PieceCID, + PieceSize: v7.PieceSize, + VerifiedDeal: v7.VerifiedDeal, + Client: v7.Client, + Provider: v7.Provider, + + Label: label, + + StartEpoch: v7.StartEpoch, + EndEpoch: v7.EndEpoch, + StoragePricePerEpoch: v7.StoragePricePerEpoch, + + ProviderCollateral: v7.ProviderCollateral, + ClientCollateral: v7.ClientCollateral, + }, nil +} + +func (s *state7) GetState() interface{} { + return &s.State +} + +var _ PublishStorageDealsReturn = (*publishStorageDealsReturn7)(nil) + +func decodePublishStorageDealsReturn7(b []byte) (PublishStorageDealsReturn, error) { + var retval market7.PublishStorageDealsReturn + if err := retval.UnmarshalCBOR(bytes.NewReader(b)); err != nil { + return nil, xerrors.Errorf("failed to unmarshal PublishStorageDealsReturn: %w", err) + } + + return &publishStorageDealsReturn7{retval}, nil +} + +type publishStorageDealsReturn7 struct { + market7.PublishStorageDealsReturn +} + +func (r *publishStorageDealsReturn7) IsDealValid(index uint64) (bool, int, error) { + + set, err := r.ValidDeals.IsSet(index) + if err != nil || !set { + return false, -1, err + } + maskBf, err := bitfield.NewFromIter(&rlepluslazy.RunSliceIterator{ + Runs: []rlepluslazy.Run{rlepluslazy.Run{Val: true, Len: index}}}) + if err != nil { + return false, -1, err + } + before, err := bitfield.IntersectBitField(maskBf, r.ValidDeals) + if err != nil { + return false, -1, err + } + outIdx, err := before.Count() + if err != nil { + return false, -1, err + } + return set, int(outIdx), nil + +} + +func (r *publishStorageDealsReturn7) DealIDs() ([]abi.DealID, error) { + return r.IDs, nil +} + +func (s *state7) GetAllocationIdForPendingDeal(dealId abi.DealID) (verifregtypes.AllocationId, error) { + + return verifregtypes.NoAllocationID, xerrors.Errorf("unsupported before actors v9") + +} + +func (s *state7) ActorKey() string { + return manifest.MarketKey +} + +func (s *state7) ActorVersion() actorstypes.Version { + return actorstypes.Version7 +} + +func (s *state7) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/market/v8.go b/chain/actors/builtin/market/v8.go new file mode 100644 index 000000000..5cce06d3a --- /dev/null +++ b/chain/actors/builtin/market/v8.go @@ -0,0 +1,360 @@ +package market + +import ( + "bytes" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-bitfield" + rlepluslazy "github.com/filecoin-project/go-bitfield/rle" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + market8 "github.com/filecoin-project/go-state-types/builtin/v8/market" + adt8 "github.com/filecoin-project/go-state-types/builtin/v8/util/adt" + markettypes "github.com/filecoin-project/go-state-types/builtin/v9/market" + verifregtypes "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/types" +) + +var _ State = (*state8)(nil) + +func load8(store adt.Store, root cid.Cid) (State, error) { + out := state8{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make8(store adt.Store) (State, error) { + out := state8{store: store} + + s, err := market8.ConstructState(store) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state8 struct { + market8.State + store adt.Store +} + +func (s *state8) TotalLocked() (abi.TokenAmount, error) { + fml := types.BigAdd(s.TotalClientLockedCollateral, s.TotalProviderLockedCollateral) + fml = types.BigAdd(fml, s.TotalClientStorageFee) + return fml, nil +} + +func (s *state8) BalancesChanged(otherState State) (bool, error) { + otherState8, ok := otherState.(*state8) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.EscrowTable.Equals(otherState8.State.EscrowTable) || !s.State.LockedTable.Equals(otherState8.State.LockedTable), nil +} + +func (s *state8) StatesChanged(otherState State) (bool, error) { + otherState8, ok := otherState.(*state8) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.States.Equals(otherState8.State.States), nil +} + +func (s *state8) States() (DealStates, error) { + stateArray, err := adt8.AsArray(s.store, s.State.States, market8.StatesAmtBitwidth) + if err != nil { + return nil, err + } + return &dealStates8{stateArray}, nil +} + +func (s *state8) ProposalsChanged(otherState State) (bool, error) { + otherState8, ok := otherState.(*state8) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.Proposals.Equals(otherState8.State.Proposals), nil +} + +func (s *state8) Proposals() (DealProposals, error) { + proposalArray, err := adt8.AsArray(s.store, s.State.Proposals, market8.ProposalsAmtBitwidth) + if err != nil { + return nil, err + } + return &dealProposals8{proposalArray}, nil +} + +func (s *state8) EscrowTable() (BalanceTable, error) { + bt, err := adt8.AsBalanceTable(s.store, s.State.EscrowTable) + if err != nil { + return nil, err + } + return &balanceTable8{bt}, nil +} + +func (s *state8) LockedTable() (BalanceTable, error) { + bt, err := adt8.AsBalanceTable(s.store, s.State.LockedTable) + if err != nil { + return nil, err + } + return &balanceTable8{bt}, nil +} + +func (s *state8) VerifyDealsForActivation( + minerAddr address.Address, deals []abi.DealID, currEpoch, sectorExpiry abi.ChainEpoch, +) (weight, verifiedWeight abi.DealWeight, err error) { + w, vw, _, err := market8.ValidateDealsForActivation(&s.State, s.store, deals, minerAddr, sectorExpiry, currEpoch) + return w, vw, err +} + +func (s *state8) NextID() (abi.DealID, error) { + return s.State.NextID, nil +} + +type balanceTable8 struct { + *adt8.BalanceTable +} + +func (bt *balanceTable8) ForEach(cb func(address.Address, abi.TokenAmount) error) error { + asMap := (*adt8.Map)(bt.BalanceTable) + var ta abi.TokenAmount + return asMap.ForEach(&ta, func(key string) error { + a, err := address.NewFromBytes([]byte(key)) + if err != nil { + return err + } + return cb(a, ta) + }) +} + +type dealStates8 struct { + adt.Array +} + +func (s *dealStates8) Get(dealID abi.DealID) (*DealState, bool, error) { + var deal8 market8.DealState + found, err := s.Array.Get(uint64(dealID), &deal8) + if err != nil { + return nil, false, err + } + if !found { + return nil, false, nil + } + deal := fromV8DealState(deal8) + return &deal, true, nil +} + +func (s *dealStates8) ForEach(cb func(dealID abi.DealID, ds DealState) error) error { + var ds8 market8.DealState + return s.Array.ForEach(&ds8, func(idx int64) error { + return cb(abi.DealID(idx), fromV8DealState(ds8)) + }) +} + +func (s *dealStates8) decode(val *cbg.Deferred) (*DealState, error) { + var ds8 market8.DealState + if err := ds8.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return nil, err + } + ds := fromV8DealState(ds8) + return &ds, nil +} + +func (s *dealStates8) array() adt.Array { + return s.Array +} + +func fromV8DealState(v8 market8.DealState) DealState { + ret := DealState{ + SectorStartEpoch: v8.SectorStartEpoch, + LastUpdatedEpoch: v8.LastUpdatedEpoch, + SlashEpoch: v8.SlashEpoch, + VerifiedClaim: 0, + } + + return ret +} + +type dealProposals8 struct { + adt.Array +} + +func (s *dealProposals8) Get(dealID abi.DealID) (*DealProposal, bool, error) { + var proposal8 market8.DealProposal + found, err := s.Array.Get(uint64(dealID), &proposal8) + if err != nil { + return nil, false, err + } + if !found { + return nil, false, nil + } + + proposal, err := fromV8DealProposal(proposal8) + if err != nil { + return nil, true, xerrors.Errorf("decoding proposal: %w", err) + } + + return &proposal, true, nil +} + +func (s *dealProposals8) ForEach(cb func(dealID abi.DealID, dp DealProposal) error) error { + var dp8 market8.DealProposal + return s.Array.ForEach(&dp8, func(idx int64) error { + dp, err := fromV8DealProposal(dp8) + if err != nil { + return xerrors.Errorf("decoding proposal: %w", err) + } + + return cb(abi.DealID(idx), dp) + }) +} + +func (s *dealProposals8) decode(val *cbg.Deferred) (*DealProposal, error) { + var dp8 market8.DealProposal + if err := dp8.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return nil, err + } + + dp, err := fromV8DealProposal(dp8) + if err != nil { + return nil, err + } + + return &dp, nil +} + +func (s *dealProposals8) array() adt.Array { + return s.Array +} + +func fromV8DealProposal(v8 market8.DealProposal) (DealProposal, error) { + + label, err := fromV8Label(v8.Label) + + if err != nil { + return DealProposal{}, xerrors.Errorf("error setting deal label: %w", err) + } + + return DealProposal{ + PieceCID: v8.PieceCID, + PieceSize: v8.PieceSize, + VerifiedDeal: v8.VerifiedDeal, + Client: v8.Client, + Provider: v8.Provider, + + Label: label, + + StartEpoch: v8.StartEpoch, + EndEpoch: v8.EndEpoch, + StoragePricePerEpoch: v8.StoragePricePerEpoch, + + ProviderCollateral: v8.ProviderCollateral, + ClientCollateral: v8.ClientCollateral, + }, nil +} + +func fromV8Label(v8 market8.DealLabel) (DealLabel, error) { + if v8.IsString() { + str, err := v8.ToString() + if err != nil { + return markettypes.EmptyDealLabel, xerrors.Errorf("failed to convert string label to string: %w", err) + } + return markettypes.NewLabelFromString(str) + } + + bs, err := v8.ToBytes() + if err != nil { + return markettypes.EmptyDealLabel, xerrors.Errorf("failed to convert bytes label to bytes: %w", err) + } + return markettypes.NewLabelFromBytes(bs) +} + +func (s *state8) GetState() interface{} { + return &s.State +} + +var _ PublishStorageDealsReturn = (*publishStorageDealsReturn8)(nil) + +func decodePublishStorageDealsReturn8(b []byte) (PublishStorageDealsReturn, error) { + var retval market8.PublishStorageDealsReturn + if err := retval.UnmarshalCBOR(bytes.NewReader(b)); err != nil { + return nil, xerrors.Errorf("failed to unmarshal PublishStorageDealsReturn: %w", err) + } + + return &publishStorageDealsReturn8{retval}, nil +} + +type publishStorageDealsReturn8 struct { + market8.PublishStorageDealsReturn +} + +func (r *publishStorageDealsReturn8) IsDealValid(index uint64) (bool, int, error) { + + set, err := r.ValidDeals.IsSet(index) + if err != nil || !set { + return false, -1, err + } + maskBf, err := bitfield.NewFromIter(&rlepluslazy.RunSliceIterator{ + Runs: []rlepluslazy.Run{rlepluslazy.Run{Val: true, Len: index}}}) + if err != nil { + return false, -1, err + } + before, err := bitfield.IntersectBitField(maskBf, r.ValidDeals) + if err != nil { + return false, -1, err + } + outIdx, err := before.Count() + if err != nil { + return false, -1, err + } + return set, int(outIdx), nil + +} + +func (r *publishStorageDealsReturn8) DealIDs() ([]abi.DealID, error) { + return r.IDs, nil +} + +func (s *state8) GetAllocationIdForPendingDeal(dealId abi.DealID) (verifregtypes.AllocationId, error) { + + return verifregtypes.NoAllocationID, xerrors.Errorf("unsupported before actors v9") + +} + +func (s *state8) ActorKey() string { + return manifest.MarketKey +} + +func (s *state8) ActorVersion() actorstypes.Version { + return actorstypes.Version8 +} + +func (s *state8) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/market/v9.go b/chain/actors/builtin/market/v9.go new file mode 100644 index 000000000..095c20850 --- /dev/null +++ b/chain/actors/builtin/market/v9.go @@ -0,0 +1,377 @@ +package market + +import ( + "bytes" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-bitfield" + rlepluslazy "github.com/filecoin-project/go-bitfield/rle" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/builtin" + market9 "github.com/filecoin-project/go-state-types/builtin/v9/market" + markettypes "github.com/filecoin-project/go-state-types/builtin/v9/market" + adt9 "github.com/filecoin-project/go-state-types/builtin/v9/util/adt" + verifregtypes "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/types" +) + +var _ State = (*state9)(nil) + +func load9(store adt.Store, root cid.Cid) (State, error) { + out := state9{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make9(store adt.Store) (State, error) { + out := state9{store: store} + + s, err := market9.ConstructState(store) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state9 struct { + market9.State + store adt.Store +} + +func (s *state9) TotalLocked() (abi.TokenAmount, error) { + fml := types.BigAdd(s.TotalClientLockedCollateral, s.TotalProviderLockedCollateral) + fml = types.BigAdd(fml, s.TotalClientStorageFee) + return fml, nil +} + +func (s *state9) BalancesChanged(otherState State) (bool, error) { + otherState9, ok := otherState.(*state9) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.EscrowTable.Equals(otherState9.State.EscrowTable) || !s.State.LockedTable.Equals(otherState9.State.LockedTable), nil +} + +func (s *state9) StatesChanged(otherState State) (bool, error) { + otherState9, ok := otherState.(*state9) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.States.Equals(otherState9.State.States), nil +} + +func (s *state9) States() (DealStates, error) { + stateArray, err := adt9.AsArray(s.store, s.State.States, market9.StatesAmtBitwidth) + if err != nil { + return nil, err + } + return &dealStates9{stateArray}, nil +} + +func (s *state9) ProposalsChanged(otherState State) (bool, error) { + otherState9, ok := otherState.(*state9) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.Proposals.Equals(otherState9.State.Proposals), nil +} + +func (s *state9) Proposals() (DealProposals, error) { + proposalArray, err := adt9.AsArray(s.store, s.State.Proposals, market9.ProposalsAmtBitwidth) + if err != nil { + return nil, err + } + return &dealProposals9{proposalArray}, nil +} + +func (s *state9) EscrowTable() (BalanceTable, error) { + bt, err := adt9.AsBalanceTable(s.store, s.State.EscrowTable) + if err != nil { + return nil, err + } + return &balanceTable9{bt}, nil +} + +func (s *state9) LockedTable() (BalanceTable, error) { + bt, err := adt9.AsBalanceTable(s.store, s.State.LockedTable) + if err != nil { + return nil, err + } + return &balanceTable9{bt}, nil +} + +func (s *state9) VerifyDealsForActivation( + minerAddr address.Address, deals []abi.DealID, currEpoch, sectorExpiry abi.ChainEpoch, +) (weight, verifiedWeight abi.DealWeight, err error) { + w, vw, _, err := market9.ValidateDealsForActivation(&s.State, s.store, deals, minerAddr, sectorExpiry, currEpoch) + return w, vw, err +} + +func (s *state9) NextID() (abi.DealID, error) { + return s.State.NextID, nil +} + +type balanceTable9 struct { + *adt9.BalanceTable +} + +func (bt *balanceTable9) ForEach(cb func(address.Address, abi.TokenAmount) error) error { + asMap := (*adt9.Map)(bt.BalanceTable) + var ta abi.TokenAmount + return asMap.ForEach(&ta, func(key string) error { + a, err := address.NewFromBytes([]byte(key)) + if err != nil { + return err + } + return cb(a, ta) + }) +} + +type dealStates9 struct { + adt.Array +} + +func (s *dealStates9) Get(dealID abi.DealID) (*DealState, bool, error) { + var deal9 market9.DealState + found, err := s.Array.Get(uint64(dealID), &deal9) + if err != nil { + return nil, false, err + } + if !found { + return nil, false, nil + } + deal := fromV9DealState(deal9) + return &deal, true, nil +} + +func (s *dealStates9) ForEach(cb func(dealID abi.DealID, ds DealState) error) error { + var ds9 market9.DealState + return s.Array.ForEach(&ds9, func(idx int64) error { + return cb(abi.DealID(idx), fromV9DealState(ds9)) + }) +} + +func (s *dealStates9) decode(val *cbg.Deferred) (*DealState, error) { + var ds9 market9.DealState + if err := ds9.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return nil, err + } + ds := fromV9DealState(ds9) + return &ds, nil +} + +func (s *dealStates9) array() adt.Array { + return s.Array +} + +func fromV9DealState(v9 market9.DealState) DealState { + ret := DealState{ + SectorStartEpoch: v9.SectorStartEpoch, + LastUpdatedEpoch: v9.LastUpdatedEpoch, + SlashEpoch: v9.SlashEpoch, + VerifiedClaim: 0, + } + + ret.VerifiedClaim = verifregtypes.AllocationId(v9.VerifiedClaim) + + return ret +} + +type dealProposals9 struct { + adt.Array +} + +func (s *dealProposals9) Get(dealID abi.DealID) (*DealProposal, bool, error) { + var proposal9 market9.DealProposal + found, err := s.Array.Get(uint64(dealID), &proposal9) + if err != nil { + return nil, false, err + } + if !found { + return nil, false, nil + } + + proposal, err := fromV9DealProposal(proposal9) + if err != nil { + return nil, true, xerrors.Errorf("decoding proposal: %w", err) + } + + return &proposal, true, nil +} + +func (s *dealProposals9) ForEach(cb func(dealID abi.DealID, dp DealProposal) error) error { + var dp9 market9.DealProposal + return s.Array.ForEach(&dp9, func(idx int64) error { + dp, err := fromV9DealProposal(dp9) + if err != nil { + return xerrors.Errorf("decoding proposal: %w", err) + } + + return cb(abi.DealID(idx), dp) + }) +} + +func (s *dealProposals9) decode(val *cbg.Deferred) (*DealProposal, error) { + var dp9 market9.DealProposal + if err := dp9.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return nil, err + } + + dp, err := fromV9DealProposal(dp9) + if err != nil { + return nil, err + } + + return &dp, nil +} + +func (s *dealProposals9) array() adt.Array { + return s.Array +} + +func fromV9DealProposal(v9 market9.DealProposal) (DealProposal, error) { + + label, err := fromV9Label(v9.Label) + + if err != nil { + return DealProposal{}, xerrors.Errorf("error setting deal label: %w", err) + } + + return DealProposal{ + PieceCID: v9.PieceCID, + PieceSize: v9.PieceSize, + VerifiedDeal: v9.VerifiedDeal, + Client: v9.Client, + Provider: v9.Provider, + + Label: label, + + StartEpoch: v9.StartEpoch, + EndEpoch: v9.EndEpoch, + StoragePricePerEpoch: v9.StoragePricePerEpoch, + + ProviderCollateral: v9.ProviderCollateral, + ClientCollateral: v9.ClientCollateral, + }, nil +} + +func fromV9Label(v9 market9.DealLabel) (DealLabel, error) { + if v9.IsString() { + str, err := v9.ToString() + if err != nil { + return markettypes.EmptyDealLabel, xerrors.Errorf("failed to convert string label to string: %w", err) + } + return markettypes.NewLabelFromString(str) + } + + bs, err := v9.ToBytes() + if err != nil { + return markettypes.EmptyDealLabel, xerrors.Errorf("failed to convert bytes label to bytes: %w", err) + } + return markettypes.NewLabelFromBytes(bs) +} + +func (s *state9) GetState() interface{} { + return &s.State +} + +var _ PublishStorageDealsReturn = (*publishStorageDealsReturn9)(nil) + +func decodePublishStorageDealsReturn9(b []byte) (PublishStorageDealsReturn, error) { + var retval market9.PublishStorageDealsReturn + if err := retval.UnmarshalCBOR(bytes.NewReader(b)); err != nil { + return nil, xerrors.Errorf("failed to unmarshal PublishStorageDealsReturn: %w", err) + } + + return &publishStorageDealsReturn9{retval}, nil +} + +type publishStorageDealsReturn9 struct { + market9.PublishStorageDealsReturn +} + +func (r *publishStorageDealsReturn9) IsDealValid(index uint64) (bool, int, error) { + + set, err := r.ValidDeals.IsSet(index) + if err != nil || !set { + return false, -1, err + } + maskBf, err := bitfield.NewFromIter(&rlepluslazy.RunSliceIterator{ + Runs: []rlepluslazy.Run{rlepluslazy.Run{Val: true, Len: index}}}) + if err != nil { + return false, -1, err + } + before, err := bitfield.IntersectBitField(maskBf, r.ValidDeals) + if err != nil { + return false, -1, err + } + outIdx, err := before.Count() + if err != nil { + return false, -1, err + } + return set, int(outIdx), nil + +} + +func (r *publishStorageDealsReturn9) DealIDs() ([]abi.DealID, error) { + return r.IDs, nil +} + +func (s *state9) GetAllocationIdForPendingDeal(dealId abi.DealID) (verifregtypes.AllocationId, error) { + + allocations, err := adt9.AsMap(s.store, s.PendingDealAllocationIds, builtin.DefaultHamtBitwidth) + if err != nil { + return verifregtypes.NoAllocationID, xerrors.Errorf("failed to load allocation id for %d: %w", dealId, err) + } + + var allocationId cbg.CborInt + found, err := allocations.Get(abi.UIntKey(uint64(dealId)), &allocationId) + if err != nil { + return verifregtypes.NoAllocationID, xerrors.Errorf("failed to load allocation id for %d: %w", dealId, err) + } + if !found { + return verifregtypes.NoAllocationID, nil + } + + return verifregtypes.AllocationId(allocationId), nil + +} + +func (s *state9) ActorKey() string { + return manifest.MarketKey +} + +func (s *state9) ActorVersion() actorstypes.Version { + return actorstypes.Version9 +} + +func (s *state9) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/miner/actor.go.template b/chain/actors/builtin/miner/actor.go.template new file mode 100644 index 000000000..7319ee9c5 --- /dev/null +++ b/chain/actors/builtin/miner/actor.go.template @@ -0,0 +1,262 @@ +package miner + +import ( + "github.com/ipfs/go-cid" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/network" + "github.com/filecoin-project/lotus/chain/actors" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-bitfield" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/cbor" + "github.com/filecoin-project/go-state-types/dline" + "github.com/filecoin-project/go-state-types/proof" + + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/types" + minertypes "github.com/filecoin-project/go-state-types/builtin/v9/miner" + "github.com/filecoin-project/go-state-types/manifest" + +{{range .versions}} + {{if (le . 7)}} + builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin" + {{end}} +{{end}} +) + +func Load(store adt.Store, act *types.Actor) (State, error) { + if name, av, ok := actors.GetActorMetaByCode(act.Code); ok { + if name != manifest.MinerKey { + return nil, xerrors.Errorf("actor code is not miner: %s", name) + } + + switch av { + {{range .versions}} + {{if (ge . 8)}} + case actorstypes.Version{{.}}: + return load{{.}}(store, act.Head) + {{end}} + {{end}} + } + } + + switch act.Code { +{{range .versions}} + {{if (le . 7)}} + case builtin{{.}}.StorageMinerActorCodeID: + return load{{.}}(store, act.Head) + {{end}} +{{end}} + } + + return nil, xerrors.Errorf("unknown actor code %s", act.Code) +} + +func MakeState(store adt.Store, av actors.Version) (State, error) { + switch av { +{{range .versions}} + case actors.Version{{.}}: + return make{{.}}(store) +{{end}} +} + return nil, xerrors.Errorf("unknown actor version %d", av) +} + +type State interface { + cbor.Marshaler + + Code() cid.Cid + ActorKey() string + ActorVersion() actorstypes.Version + + // Total available balance to spend. + AvailableBalance(abi.TokenAmount) (abi.TokenAmount, error) + // Funds that will vest by the given epoch. + VestedFunds(abi.ChainEpoch) (abi.TokenAmount, error) + // Funds locked for various reasons. + LockedFunds() (LockedFunds, error) + FeeDebt() (abi.TokenAmount, error) + + // Returns nil, nil if sector is not found + GetSector(abi.SectorNumber) (*SectorOnChainInfo, error) + 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) + GetAllocatedSectors() (*bitfield.BitField, error) + + // Note that ProvingPeriodStart is deprecated and will be renamed / removed in a future version of actors + GetProvingPeriodStart() (abi.ChainEpoch, error) + // Testing only + EraseAllUnproven() error + + LoadDeadline(idx uint64) (Deadline, error) + ForEachDeadline(cb func(idx uint64, dl Deadline) error) error + NumDeadlines() (uint64, error) + DeadlinesChanged(State) (bool, error) + + Info() (MinerInfo, error) + MinerInfoChanged(State) (bool, error) + + DeadlineInfo(epoch abi.ChainEpoch) (*dline.Info, error) + DeadlineCronActive() (bool, error) + + // Diff helpers. Used by Diff* functions internally. + sectors() (adt.Array, error) + decodeSectorOnChainInfo(*cbg.Deferred) (SectorOnChainInfo, error) + precommits() (adt.Map, error) + decodeSectorPreCommitOnChainInfo(*cbg.Deferred) (SectorPreCommitOnChainInfo, error) + GetState() interface{} +} + +type Deadline interface { + LoadPartition(idx uint64) (Partition, error) + ForEachPartition(cb func(idx uint64, part Partition) error) error + PartitionsPoSted() (bitfield.BitField, error) + + PartitionsChanged(Deadline) (bool, error) + DisputableProofCount() (uint64, error) +} + +type Partition interface { + // AllSectors returns all sector numbers in this partition, including faulty, unproven, and terminated sectors + AllSectors() (bitfield.BitField, error) + + // Subset of sectors detected/declared faulty and not yet recovered (excl. from PoSt). + // Faults ∩ Terminated = ∅ + FaultySectors() (bitfield.BitField, error) + + // Subset of faulty sectors expected to recover on next PoSt + // Recoveries ∩ Terminated = ∅ + RecoveringSectors() (bitfield.BitField, error) + + // Live sectors are those that are not terminated (but may be faulty). + LiveSectors() (bitfield.BitField, error) + + // Active sectors are those that are neither terminated nor faulty nor unproven, i.e. actively contributing power. + ActiveSectors() (bitfield.BitField, error) + + // Unproven sectors in this partition. This bitfield will be cleared on + // a successful window post (or at the end of the partition's next + // deadline). At that time, any still unproven sectors will be added to + // the faulty sector bitfield. + UnprovenSectors() (bitfield.BitField, error) +} + +type SectorOnChainInfo = minertypes.SectorOnChainInfo + +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 + // ones in network version 8. + if nver < network.Version7 { + switch proof { + case abi.RegisteredPoStProof_StackedDrgWindow2KiBV1: + return abi.RegisteredSealProof_StackedDrg2KiBV1, nil + case abi.RegisteredPoStProof_StackedDrgWindow8MiBV1: + return abi.RegisteredSealProof_StackedDrg8MiBV1, nil + case abi.RegisteredPoStProof_StackedDrgWindow512MiBV1: + return abi.RegisteredSealProof_StackedDrg512MiBV1, nil + case abi.RegisteredPoStProof_StackedDrgWindow32GiBV1: + return abi.RegisteredSealProof_StackedDrg32GiBV1, nil + case abi.RegisteredPoStProof_StackedDrgWindow64GiBV1: + return abi.RegisteredSealProof_StackedDrg64GiBV1, nil + default: + return -1, xerrors.Errorf("unrecognized window post type: %d", proof) + } + } + + switch proof { + case abi.RegisteredPoStProof_StackedDrgWindow2KiBV1, abi.RegisteredPoStProof_StackedDrgWindow2KiBV1_1: + return abi.RegisteredSealProof_StackedDrg2KiBV1_1, nil + case abi.RegisteredPoStProof_StackedDrgWindow8MiBV1, abi.RegisteredPoStProof_StackedDrgWindow8MiBV1_1: + return abi.RegisteredSealProof_StackedDrg8MiBV1_1, nil + case abi.RegisteredPoStProof_StackedDrgWindow512MiBV1, abi.RegisteredPoStProof_StackedDrgWindow512MiBV1_1: + return abi.RegisteredSealProof_StackedDrg512MiBV1_1, nil + case abi.RegisteredPoStProof_StackedDrgWindow32GiBV1, abi.RegisteredPoStProof_StackedDrgWindow32GiBV1_1: + return abi.RegisteredSealProof_StackedDrg32GiBV1_1, nil + case abi.RegisteredPoStProof_StackedDrgWindow64GiBV1, abi.RegisteredPoStProof_StackedDrgWindow64GiBV1_1: + return abi.RegisteredSealProof_StackedDrg64GiBV1_1, nil + default: + return -1, xerrors.Errorf("unrecognized window post type: %d", proof) + } +} + +func WinningPoStProofTypeFromWindowPoStProofType(nver network.Version, proof abi.RegisteredPoStProof) (abi.RegisteredPoStProof, error) { + switch proof { + case abi.RegisteredPoStProof_StackedDrgWindow2KiBV1, abi.RegisteredPoStProof_StackedDrgWindow2KiBV1_1: + return abi.RegisteredPoStProof_StackedDrgWinning2KiBV1, nil + case abi.RegisteredPoStProof_StackedDrgWindow8MiBV1, abi.RegisteredPoStProof_StackedDrgWindow8MiBV1_1: + return abi.RegisteredPoStProof_StackedDrgWinning8MiBV1, nil + case abi.RegisteredPoStProof_StackedDrgWindow512MiBV1, abi.RegisteredPoStProof_StackedDrgWindow512MiBV1_1: + return abi.RegisteredPoStProof_StackedDrgWinning512MiBV1, nil + case abi.RegisteredPoStProof_StackedDrgWindow32GiBV1, abi.RegisteredPoStProof_StackedDrgWindow32GiBV1_1: + return abi.RegisteredPoStProof_StackedDrgWinning32GiBV1, nil + case abi.RegisteredPoStProof_StackedDrgWindow64GiBV1, abi.RegisteredPoStProof_StackedDrgWindow64GiBV1_1: + return abi.RegisteredPoStProof_StackedDrgWinning64GiBV1, nil + default: + return -1, xerrors.Errorf("unknown proof type %d", proof) + } +} + +type MinerInfo = minertypes.MinerInfo +type BeneficiaryTerm = minertypes.BeneficiaryTerm +type PendingBeneficiaryChange = minertypes.PendingBeneficiaryChange +type WorkerKeyChange = minertypes.WorkerKeyChange +type SectorPreCommitOnChainInfo = minertypes.SectorPreCommitOnChainInfo +type SectorPreCommitInfo = minertypes.SectorPreCommitInfo +type WindowPostVerifyInfo = proof.WindowPoStVerifyInfo + +type SectorExpiration struct { + OnTime abi.ChainEpoch + + // non-zero if sector is faulty, epoch at which it will be permanently + // removed if it doesn't recover + Early abi.ChainEpoch +} + +type SectorLocation struct { + Deadline uint64 + Partition uint64 +} + +type SectorChanges struct { + Added []SectorOnChainInfo + Extended []SectorExtensions + Removed []SectorOnChainInfo +} + +type SectorExtensions struct { + From SectorOnChainInfo + To SectorOnChainInfo +} + +type PreCommitChanges struct { + Added []SectorPreCommitOnChainInfo + Removed []SectorPreCommitOnChainInfo +} + +type LockedFunds struct { + VestingFunds abi.TokenAmount + InitialPledgeRequirement abi.TokenAmount + PreCommitDeposits abi.TokenAmount +} + +func (lf LockedFunds) TotalLockedFunds() abi.TokenAmount { + return big.Add(lf.VestingFunds, big.Add(lf.InitialPledgeRequirement, lf.PreCommitDeposits)) +} + +func AllCodes() []cid.Cid { + return []cid.Cid{ {{range .versions}} + (&state{{.}}{}).Code(), + {{- end}} + } +} diff --git a/chain/actors/builtin/miner/diff.go b/chain/actors/builtin/miner/diff.go new file mode 100644 index 000000000..d20a43746 --- /dev/null +++ b/chain/actors/builtin/miner/diff.go @@ -0,0 +1,129 @@ +package miner + +import ( + cbg "github.com/whyrusleeping/cbor-gen" + + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +func DiffPreCommits(pre, cur State) (*PreCommitChanges, error) { + results := new(PreCommitChanges) + + prep, err := pre.precommits() + if err != nil { + return nil, err + } + + curp, err := cur.precommits() + if err != nil { + return nil, err + } + + err = adt.DiffAdtMap(prep, curp, &preCommitDiffer{results, pre, cur}) + if err != nil { + return nil, err + } + + return results, nil +} + +type preCommitDiffer struct { + Results *PreCommitChanges + pre, after State +} + +func (m *preCommitDiffer) AsKey(key string) (abi.Keyer, error) { + sector, err := abi.ParseUIntKey(key) + if err != nil { + return nil, err + } + return abi.UIntKey(sector), nil +} + +func (m *preCommitDiffer) Add(key string, val *cbg.Deferred) error { + sp, err := m.after.decodeSectorPreCommitOnChainInfo(val) + if err != nil { + return err + } + m.Results.Added = append(m.Results.Added, sp) + return nil +} + +func (m *preCommitDiffer) Modify(key string, from, to *cbg.Deferred) error { + return nil +} + +func (m *preCommitDiffer) Remove(key string, val *cbg.Deferred) error { + sp, err := m.pre.decodeSectorPreCommitOnChainInfo(val) + if err != nil { + return err + } + m.Results.Removed = append(m.Results.Removed, sp) + return nil +} + +func DiffSectors(pre, cur State) (*SectorChanges, error) { + results := new(SectorChanges) + + pres, err := pre.sectors() + if err != nil { + return nil, err + } + + curs, err := cur.sectors() + if err != nil { + return nil, err + } + + err = adt.DiffAdtArray(pres, curs, §orDiffer{results, pre, cur}) + if err != nil { + return nil, err + } + + return results, nil +} + +type sectorDiffer struct { + Results *SectorChanges + pre, after State +} + +func (m *sectorDiffer) Add(key uint64, val *cbg.Deferred) error { + si, err := m.after.decodeSectorOnChainInfo(val) + if err != nil { + return err + } + m.Results.Added = append(m.Results.Added, si) + return nil +} + +func (m *sectorDiffer) Modify(key uint64, from, to *cbg.Deferred) error { + siFrom, err := m.pre.decodeSectorOnChainInfo(from) + if err != nil { + return err + } + + siTo, err := m.after.decodeSectorOnChainInfo(to) + if err != nil { + return err + } + + if siFrom.Expiration != siTo.Expiration { + m.Results.Extended = append(m.Results.Extended, SectorExtensions{ + From: siFrom, + To: siTo, + }) + } + return nil +} + +func (m *sectorDiffer) Remove(key uint64, val *cbg.Deferred) error { + si, err := m.pre.decodeSectorOnChainInfo(val) + if err != nil { + return err + } + m.Results.Removed = append(m.Results.Removed, si) + return nil +} diff --git a/chain/actors/builtin/miner/diff_deadlines.go b/chain/actors/builtin/miner/diff_deadlines.go new file mode 100644 index 000000000..7d686ece5 --- /dev/null +++ b/chain/actors/builtin/miner/diff_deadlines.go @@ -0,0 +1,176 @@ +package miner + +import ( + "errors" + + "github.com/filecoin-project/go-bitfield" + "github.com/filecoin-project/go-state-types/exitcode" +) + +type DeadlinesDiff map[uint64]DeadlineDiff + +func DiffDeadlines(pre, cur State) (DeadlinesDiff, error) { + changed, err := pre.DeadlinesChanged(cur) + if err != nil { + return nil, err + } + if !changed { + return nil, nil + } + + dlDiff := make(DeadlinesDiff) + if err := pre.ForEachDeadline(func(idx uint64, preDl Deadline) error { + curDl, err := cur.LoadDeadline(idx) + if err != nil { + return err + } + + diff, err := DiffDeadline(preDl, curDl) + if err != nil { + return err + } + + dlDiff[idx] = diff + return nil + }); err != nil { + return nil, err + } + return dlDiff, nil +} + +type DeadlineDiff map[uint64]*PartitionDiff + +func DiffDeadline(pre, cur Deadline) (DeadlineDiff, error) { + changed, err := pre.PartitionsChanged(cur) + if err != nil { + return nil, err + } + if !changed { + return nil, nil + } + + partDiff := make(DeadlineDiff) + if err := pre.ForEachPartition(func(idx uint64, prePart Partition) error { + // try loading current partition at this index + curPart, err := cur.LoadPartition(idx) + if err != nil { + if errors.Is(err, exitcode.ErrNotFound) { + // TODO correctness? + return nil // the partition was removed. + } + return err + } + + // compare it with the previous partition + diff, err := DiffPartition(prePart, curPart) + if err != nil { + return err + } + + partDiff[idx] = diff + return nil + }); err != nil { + return nil, err + } + + // all previous partitions have been walked. + // all partitions in cur and not in prev are new... can they be faulty already? + // TODO is this correct? + if err := cur.ForEachPartition(func(idx uint64, curPart Partition) error { + if _, found := partDiff[idx]; found { + return nil + } + faults, err := curPart.FaultySectors() + if err != nil { + return err + } + recovering, err := curPart.RecoveringSectors() + if err != nil { + return err + } + partDiff[idx] = &PartitionDiff{ + Removed: bitfield.New(), + Recovered: bitfield.New(), + Faulted: faults, + Recovering: recovering, + } + + return nil + }); err != nil { + return nil, err + } + + return partDiff, nil +} + +type PartitionDiff struct { + Removed bitfield.BitField + Recovered bitfield.BitField + Faulted bitfield.BitField + Recovering bitfield.BitField +} + +func DiffPartition(pre, cur Partition) (*PartitionDiff, error) { + prevLiveSectors, err := pre.LiveSectors() + if err != nil { + return nil, err + } + curLiveSectors, err := cur.LiveSectors() + if err != nil { + return nil, err + } + + removed, err := bitfield.SubtractBitField(prevLiveSectors, curLiveSectors) + if err != nil { + return nil, err + } + + prevRecoveries, err := pre.RecoveringSectors() + if err != nil { + return nil, err + } + + curRecoveries, err := cur.RecoveringSectors() + if err != nil { + return nil, err + } + + recovering, err := bitfield.SubtractBitField(curRecoveries, prevRecoveries) + if err != nil { + return nil, err + } + + prevFaults, err := pre.FaultySectors() + if err != nil { + return nil, err + } + + curFaults, err := cur.FaultySectors() + if err != nil { + return nil, err + } + + faulted, err := bitfield.SubtractBitField(curFaults, prevFaults) + if err != nil { + return nil, err + } + + // all current good sectors + curActiveSectors, err := cur.ActiveSectors() + if err != nil { + return nil, err + } + + // sectors that were previously fault and are now currently active are considered recovered. + recovered, err := bitfield.IntersectBitField(prevFaults, curActiveSectors) + if err != nil { + return nil, err + } + + return &PartitionDiff{ + Removed: removed, + Recovered: recovered, + Faulted: faulted, + Recovering: recovering, + }, nil +} diff --git a/chain/actors/builtin/miner/miner.go b/chain/actors/builtin/miner/miner.go new file mode 100644 index 000000000..1433945d9 --- /dev/null +++ b/chain/actors/builtin/miner/miner.go @@ -0,0 +1,325 @@ +package miner + +import ( + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-bitfield" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + minertypes "github.com/filecoin-project/go-state-types/builtin/v9/miner" + "github.com/filecoin-project/go-state-types/cbor" + "github.com/filecoin-project/go-state-types/dline" + "github.com/filecoin-project/go-state-types/manifest" + "github.com/filecoin-project/go-state-types/network" + "github.com/filecoin-project/go-state-types/proof" + builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + builtin3 "github.com/filecoin-project/specs-actors/v3/actors/builtin" + builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" + builtin6 "github.com/filecoin-project/specs-actors/v6/actors/builtin" + builtin7 "github.com/filecoin-project/specs-actors/v7/actors/builtin" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/types" +) + +func Load(store adt.Store, act *types.Actor) (State, error) { + if name, av, ok := actors.GetActorMetaByCode(act.Code); ok { + if name != manifest.MinerKey { + return nil, xerrors.Errorf("actor code is not miner: %s", name) + } + + switch av { + + case actorstypes.Version8: + return load8(store, act.Head) + + case actorstypes.Version9: + return load9(store, act.Head) + + case actorstypes.Version10: + return load10(store, act.Head) + + case actorstypes.Version11: + return load11(store, act.Head) + + } + } + + switch act.Code { + + case builtin0.StorageMinerActorCodeID: + return load0(store, act.Head) + + case builtin2.StorageMinerActorCodeID: + return load2(store, act.Head) + + case builtin3.StorageMinerActorCodeID: + return load3(store, act.Head) + + case builtin4.StorageMinerActorCodeID: + return load4(store, act.Head) + + case builtin5.StorageMinerActorCodeID: + return load5(store, act.Head) + + case builtin6.StorageMinerActorCodeID: + return load6(store, act.Head) + + case builtin7.StorageMinerActorCodeID: + return load7(store, act.Head) + + } + + return nil, xerrors.Errorf("unknown actor code %s", act.Code) +} + +func MakeState(store adt.Store, av actors.Version) (State, error) { + switch av { + + case actors.Version0: + return make0(store) + + case actors.Version2: + return make2(store) + + case actors.Version3: + return make3(store) + + case actors.Version4: + return make4(store) + + case actors.Version5: + return make5(store) + + case actors.Version6: + return make6(store) + + case actors.Version7: + return make7(store) + + case actors.Version8: + return make8(store) + + case actors.Version9: + return make9(store) + + case actors.Version10: + return make10(store) + + case actors.Version11: + return make11(store) + + } + return nil, xerrors.Errorf("unknown actor version %d", av) +} + +type State interface { + cbor.Marshaler + + Code() cid.Cid + ActorKey() string + ActorVersion() actorstypes.Version + + // Total available balance to spend. + AvailableBalance(abi.TokenAmount) (abi.TokenAmount, error) + // Funds that will vest by the given epoch. + VestedFunds(abi.ChainEpoch) (abi.TokenAmount, error) + // Funds locked for various reasons. + LockedFunds() (LockedFunds, error) + FeeDebt() (abi.TokenAmount, error) + + // Returns nil, nil if sector is not found + GetSector(abi.SectorNumber) (*SectorOnChainInfo, error) + 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) + GetAllocatedSectors() (*bitfield.BitField, error) + + // Note that ProvingPeriodStart is deprecated and will be renamed / removed in a future version of actors + GetProvingPeriodStart() (abi.ChainEpoch, error) + // Testing only + EraseAllUnproven() error + + LoadDeadline(idx uint64) (Deadline, error) + ForEachDeadline(cb func(idx uint64, dl Deadline) error) error + NumDeadlines() (uint64, error) + DeadlinesChanged(State) (bool, error) + + Info() (MinerInfo, error) + MinerInfoChanged(State) (bool, error) + + DeadlineInfo(epoch abi.ChainEpoch) (*dline.Info, error) + DeadlineCronActive() (bool, error) + + // Diff helpers. Used by Diff* functions internally. + sectors() (adt.Array, error) + decodeSectorOnChainInfo(*cbg.Deferred) (SectorOnChainInfo, error) + precommits() (adt.Map, error) + decodeSectorPreCommitOnChainInfo(*cbg.Deferred) (SectorPreCommitOnChainInfo, error) + GetState() interface{} +} + +type Deadline interface { + LoadPartition(idx uint64) (Partition, error) + ForEachPartition(cb func(idx uint64, part Partition) error) error + PartitionsPoSted() (bitfield.BitField, error) + + PartitionsChanged(Deadline) (bool, error) + DisputableProofCount() (uint64, error) +} + +type Partition interface { + // AllSectors returns all sector numbers in this partition, including faulty, unproven, and terminated sectors + AllSectors() (bitfield.BitField, error) + + // Subset of sectors detected/declared faulty and not yet recovered (excl. from PoSt). + // Faults ∩ Terminated = ∅ + FaultySectors() (bitfield.BitField, error) + + // Subset of faulty sectors expected to recover on next PoSt + // Recoveries ∩ Terminated = ∅ + RecoveringSectors() (bitfield.BitField, error) + + // Live sectors are those that are not terminated (but may be faulty). + LiveSectors() (bitfield.BitField, error) + + // Active sectors are those that are neither terminated nor faulty nor unproven, i.e. actively contributing power. + ActiveSectors() (bitfield.BitField, error) + + // Unproven sectors in this partition. This bitfield will be cleared on + // a successful window post (or at the end of the partition's next + // deadline). At that time, any still unproven sectors will be added to + // the faulty sector bitfield. + UnprovenSectors() (bitfield.BitField, error) +} + +type SectorOnChainInfo = minertypes.SectorOnChainInfo + +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 + // ones in network version 8. + if nver < network.Version7 { + switch proof { + case abi.RegisteredPoStProof_StackedDrgWindow2KiBV1: + return abi.RegisteredSealProof_StackedDrg2KiBV1, nil + case abi.RegisteredPoStProof_StackedDrgWindow8MiBV1: + return abi.RegisteredSealProof_StackedDrg8MiBV1, nil + case abi.RegisteredPoStProof_StackedDrgWindow512MiBV1: + return abi.RegisteredSealProof_StackedDrg512MiBV1, nil + case abi.RegisteredPoStProof_StackedDrgWindow32GiBV1: + return abi.RegisteredSealProof_StackedDrg32GiBV1, nil + case abi.RegisteredPoStProof_StackedDrgWindow64GiBV1: + return abi.RegisteredSealProof_StackedDrg64GiBV1, nil + default: + return -1, xerrors.Errorf("unrecognized window post type: %d", proof) + } + } + + switch proof { + case abi.RegisteredPoStProof_StackedDrgWindow2KiBV1, abi.RegisteredPoStProof_StackedDrgWindow2KiBV1_1: + return abi.RegisteredSealProof_StackedDrg2KiBV1_1, nil + case abi.RegisteredPoStProof_StackedDrgWindow8MiBV1, abi.RegisteredPoStProof_StackedDrgWindow8MiBV1_1: + return abi.RegisteredSealProof_StackedDrg8MiBV1_1, nil + case abi.RegisteredPoStProof_StackedDrgWindow512MiBV1, abi.RegisteredPoStProof_StackedDrgWindow512MiBV1_1: + return abi.RegisteredSealProof_StackedDrg512MiBV1_1, nil + case abi.RegisteredPoStProof_StackedDrgWindow32GiBV1, abi.RegisteredPoStProof_StackedDrgWindow32GiBV1_1: + return abi.RegisteredSealProof_StackedDrg32GiBV1_1, nil + case abi.RegisteredPoStProof_StackedDrgWindow64GiBV1, abi.RegisteredPoStProof_StackedDrgWindow64GiBV1_1: + return abi.RegisteredSealProof_StackedDrg64GiBV1_1, nil + default: + return -1, xerrors.Errorf("unrecognized window post type: %d", proof) + } +} + +func WinningPoStProofTypeFromWindowPoStProofType(nver network.Version, proof abi.RegisteredPoStProof) (abi.RegisteredPoStProof, error) { + switch proof { + case abi.RegisteredPoStProof_StackedDrgWindow2KiBV1, abi.RegisteredPoStProof_StackedDrgWindow2KiBV1_1: + return abi.RegisteredPoStProof_StackedDrgWinning2KiBV1, nil + case abi.RegisteredPoStProof_StackedDrgWindow8MiBV1, abi.RegisteredPoStProof_StackedDrgWindow8MiBV1_1: + return abi.RegisteredPoStProof_StackedDrgWinning8MiBV1, nil + case abi.RegisteredPoStProof_StackedDrgWindow512MiBV1, abi.RegisteredPoStProof_StackedDrgWindow512MiBV1_1: + return abi.RegisteredPoStProof_StackedDrgWinning512MiBV1, nil + case abi.RegisteredPoStProof_StackedDrgWindow32GiBV1, abi.RegisteredPoStProof_StackedDrgWindow32GiBV1_1: + return abi.RegisteredPoStProof_StackedDrgWinning32GiBV1, nil + case abi.RegisteredPoStProof_StackedDrgWindow64GiBV1, abi.RegisteredPoStProof_StackedDrgWindow64GiBV1_1: + return abi.RegisteredPoStProof_StackedDrgWinning64GiBV1, nil + default: + return -1, xerrors.Errorf("unknown proof type %d", proof) + } +} + +type MinerInfo = minertypes.MinerInfo +type BeneficiaryTerm = minertypes.BeneficiaryTerm +type PendingBeneficiaryChange = minertypes.PendingBeneficiaryChange +type WorkerKeyChange = minertypes.WorkerKeyChange +type SectorPreCommitOnChainInfo = minertypes.SectorPreCommitOnChainInfo +type SectorPreCommitInfo = minertypes.SectorPreCommitInfo +type WindowPostVerifyInfo = proof.WindowPoStVerifyInfo + +type SectorExpiration struct { + OnTime abi.ChainEpoch + + // non-zero if sector is faulty, epoch at which it will be permanently + // removed if it doesn't recover + Early abi.ChainEpoch +} + +type SectorLocation struct { + Deadline uint64 + Partition uint64 +} + +type SectorChanges struct { + Added []SectorOnChainInfo + Extended []SectorExtensions + Removed []SectorOnChainInfo +} + +type SectorExtensions struct { + From SectorOnChainInfo + To SectorOnChainInfo +} + +type PreCommitChanges struct { + Added []SectorPreCommitOnChainInfo + Removed []SectorPreCommitOnChainInfo +} + +type LockedFunds struct { + VestingFunds abi.TokenAmount + InitialPledgeRequirement abi.TokenAmount + PreCommitDeposits abi.TokenAmount +} + +func (lf LockedFunds) TotalLockedFunds() abi.TokenAmount { + return big.Add(lf.VestingFunds, big.Add(lf.InitialPledgeRequirement, lf.PreCommitDeposits)) +} + +func AllCodes() []cid.Cid { + return []cid.Cid{ + (&state0{}).Code(), + (&state2{}).Code(), + (&state3{}).Code(), + (&state4{}).Code(), + (&state5{}).Code(), + (&state6{}).Code(), + (&state7{}).Code(), + (&state8{}).Code(), + (&state9{}).Code(), + (&state10{}).Code(), + (&state11{}).Code(), + } +} diff --git a/chain/actors/builtin/miner/state.go.template b/chain/actors/builtin/miner/state.go.template new file mode 100644 index 000000000..b322b2283 --- /dev/null +++ b/chain/actors/builtin/miner/state.go.template @@ -0,0 +1,632 @@ +package miner + +import ( + "fmt" + "bytes" + "errors" +{{if (le .v 1)}} + "github.com/filecoin-project/go-state-types/big" +{{end}} + "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" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + +{{if (le .v 7)}} + {{if (ge .v 3)}} + builtin{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/builtin" + {{end}} + miner{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/builtin/miner" + adt{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/util/adt" +{{else}} + miner{{.v}} "github.com/filecoin-project/go-state-types/builtin{{.import}}miner" + adt{{.v}} "github.com/filecoin-project/go-state-types/builtin{{.import}}util/adt" + builtin{{.v}} "github.com/filecoin-project/go-state-types/builtin" +{{end}} +) + +var _ State = (*state{{.v}})(nil) + +func load{{.v}}(store adt.Store, root cid.Cid) (State, error) { + out := state{{.v}}{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make{{.v}}(store adt.Store) (State, error) { + out := state{{.v}}{store: store} + out.State = miner{{.v}}.State{} + return &out, nil +} + +type state{{.v}} struct { + miner{{.v}}.State + store adt.Store +} + +type deadline{{.v}} struct { + miner{{.v}}.Deadline + store adt.Store +} + +type partition{{.v}} struct { + miner{{.v}}.Partition + store adt.Store +} + +func (s *state{{.v}}) AvailableBalance(bal abi.TokenAmount) (available abi.TokenAmount, err error) { + defer func() { + if r := recover(); r != nil { + err = xerrors.Errorf("failed to get available balance: %w", r) + available = abi.NewTokenAmount(0) + } + }() + // this panics if the miner doesnt have enough funds to cover their locked pledge + available{{if (ge .v 2)}}, err{{end}} = s.GetAvailableBalance(bal) + return available, err +} + +func (s *state{{.v}}) VestedFunds(epoch abi.ChainEpoch) (abi.TokenAmount, error) { + return s.CheckVestedFunds(s.store, epoch) +} + +func (s *state{{.v}}) LockedFunds() (LockedFunds, error) { + return LockedFunds{ + VestingFunds: s.State.LockedFunds, + InitialPledgeRequirement: s.State.InitialPledge{{if (le .v 1)}}Requirement{{end}}, + PreCommitDeposits: s.State.PreCommitDeposits, + }, nil +} + +func (s *state{{.v}}) FeeDebt() (abi.TokenAmount, error) { + return {{if (ge .v 2)}}s.State.FeeDebt{{else}}big.Zero(){{end}}, nil +} + +func (s *state{{.v}}) InitialPledge() (abi.TokenAmount, error) { + return s.State.InitialPledge{{if (le .v 1)}}Requirement{{end}}, nil +} + +func (s *state{{.v}}) PreCommitDeposits() (abi.TokenAmount, error) { + return s.State.PreCommitDeposits, nil +} + +// Returns nil, nil if sector is not found +func (s *state{{.v}}) GetSector(num abi.SectorNumber) (*SectorOnChainInfo, error) { + info, ok, err := s.State.GetSector(s.store, num) + if !ok || err != nil { + return nil, err + } + + ret := fromV{{.v}}SectorOnChainInfo(*info) + return &ret, nil +} + +func (s *state{{.v}}) FindSector(num abi.SectorNumber) (*SectorLocation, error) { + dlIdx, partIdx, err := s.State.FindSector(s.store, num) + if err != nil { + return nil, err + } + return &SectorLocation{ + Deadline: dlIdx, + Partition: partIdx, + }, nil +} + +func (s *state{{.v}}) NumLiveSectors() (uint64, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return 0, err + } + var total uint64 + if err := dls.ForEach(s.store, func(dlIdx uint64, dl *miner{{.v}}.Deadline) error { + total += dl.LiveSectors + return nil + }); err != nil { + return 0, err + } + return total, nil +} + +// GetSectorExpiration returns the effective expiration of the given sector. +// +// If the sector does not expire early, the Early expiration field is 0. +func (s *state{{.v}}) GetSectorExpiration(num abi.SectorNumber) (*SectorExpiration, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return nil, err + } + // NOTE: this can be optimized significantly. +{{if (ge .v 7) -}} + // 1. If the sector is non-faulty, it will expire on-time (can be + // learned from the sector info). +{{- else -}} + // 1. If the sector is non-faulty, it will either expire on-time (can be + // learned from the sector info), or in the next quantized expiration + // epoch (i.e., the first element in the partition's expiration queue. +{{- end}} +{{if (ge .v 6) -}} + // 2. If it's faulty, it will expire early within the first 42 entries + // of the expiration queue. +{{- else -}} + // 2. If it's faulty, it will expire early within the first 14 entries + // of the expiration queue. +{{- end}} + + stopErr := errors.New("stop") + out := SectorExpiration{} + err = dls.ForEach(s.store, func(dlIdx uint64, dl *miner{{.v}}.Deadline) error { + partitions, err := dl.PartitionsArray(s.store) + if err != nil { + return err + } + quant := s.State.QuantSpecForDeadline(dlIdx) + var part miner{{.v}}.Partition + return partitions.ForEach(&part, func(partIdx int64) error { + if found, err := part.Sectors.IsSet(uint64(num)); err != nil { + return err + } else if !found { + return nil + } + if found, err := part.Terminated.IsSet(uint64(num)); err != nil { + return err + } else if found { + // already terminated + return stopErr + } + + q, err := miner{{.v}}.LoadExpirationQueue(s.store, part.ExpirationsEpochs, quant{{if (ge .v 3)}}, miner{{.v}}.PartitionExpirationAmtBitwidth{{end}}) + if err != nil { + return err + } + var exp miner{{.v}}.ExpirationSet + return q.ForEach(&exp, func(epoch int64) error { + if early, err := exp.EarlySectors.IsSet(uint64(num)); err != nil { + return err + } else if early { + out.Early = abi.ChainEpoch(epoch) + return nil + } + if onTime, err := exp.OnTimeSectors.IsSet(uint64(num)); err != nil { + return err + } else if onTime { + out.OnTime = abi.ChainEpoch(epoch) + return stopErr + } + return nil + }) + }) + }) + if err == stopErr { + err = nil + } + if err != nil { + return nil, err + } + if out.Early == 0 && out.OnTime == 0 { + return nil, xerrors.Errorf("failed to find sector %d", num) + } + return &out, nil +} + +func (s *state{{.v}}) GetPrecommittedSector(num abi.SectorNumber) (*SectorPreCommitOnChainInfo, error) { + info, ok, err := s.State.GetPrecommittedSector(s.store, num) + if !ok || err != nil { + return nil, err + } + + ret := fromV{{.v}}SectorPreCommitOnChainInfo(*info) + + 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 { + return nil, err + } + + // If no sector numbers are specified, load all. + if snos == nil { + infos := make([]*SectorOnChainInfo, 0, sectors.Length()) + var info{{.v}} miner{{.v}}.SectorOnChainInfo + if err := sectors.ForEach(&info{{.v}}, func(_ int64) error { + info := fromV{{.v}}SectorOnChainInfo(info{{.v}}) + infos = append(infos, &info) + return nil + }); err != nil { + return nil, err + } + return infos, nil + } + + // Otherwise, load selected. + infos{{.v}}, err := sectors.Load(*snos) + if err != nil { + return nil, err + } + infos := make([]*SectorOnChainInfo, len(infos{{.v}})) + for i, info{{.v}} := range infos{{.v}} { + info := fromV{{.v}}SectorOnChainInfo(*info{{.v}}) + infos[i] = &info + } + return infos, nil +} + +func (s *state{{.v}}) loadAllocatedSectorNumbers() (bitfield.BitField, error) { + var allocatedSectors bitfield.BitField + 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 + } + + return allocatedSectors.IsSet(uint64(num)) +} + +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}}) GetAllocatedSectors() (*bitfield.BitField, error) { + var allocatedSectors bitfield.BitField + if err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors); err != nil { + return nil, err + } + + return &allocatedSectors, nil +} + +func (s *state{{.v}}) LoadDeadline(idx uint64) (Deadline, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return nil, err + } + dl, err := dls.LoadDeadline(s.store, idx) + if err != nil { + return nil, err + } + return &deadline{{.v}}{*dl, s.store}, nil +} + +func (s *state{{.v}}) ForEachDeadline(cb func(uint64, Deadline) error) error { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return err + } + return dls.ForEach(s.store, func(i uint64, dl *miner{{.v}}.Deadline) error { + return cb(i, &deadline{{.v}}{*dl, s.store}) + }) +} + +func (s *state{{.v}}) NumDeadlines() (uint64, error) { + return miner{{.v}}.WPoStPeriodDeadlines, nil +} + +func (s *state{{.v}}) DeadlinesChanged(other State) (bool, error) { + other{{.v}}, ok := other.(*state{{.v}}) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + + return !s.State.Deadlines.Equals(other{{.v}}.Deadlines), nil +} + +func (s *state{{.v}}) MinerInfoChanged(other State) (bool, error) { + other0, ok := other.(*state{{.v}}) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.Info.Equals(other0.State.Info), nil +} + +func (s *state{{.v}}) Info() (MinerInfo, error) { + info, err := s.State.GetInfo(s.store) + if err != nil { + return MinerInfo{}, err + } + +{{if (le .v 2)}} + wpp, err := info.SealProofType.RegisteredWindowPoStProof() + if err != nil { + return MinerInfo{}, err + } +{{end}} + mi := MinerInfo{ + Owner: info.Owner, + Worker: info.Worker, + ControlAddresses: info.ControlAddresses, + + PendingWorkerKey: (*WorkerKeyChange)(info.PendingWorkerKey), + + PeerId: info.PeerId, + Multiaddrs: info.Multiaddrs, + WindowPoStProofType: {{if (ge .v 3)}}info.WindowPoStProofType{{else}}wpp{{end}}, + SectorSize: info.SectorSize, + WindowPoStPartitionSectors: info.WindowPoStPartitionSectors, + ConsensusFaultElapsed: {{if (ge .v 2)}}info.ConsensusFaultElapsed{{else}}-1{{end}}, + {{if (ge .v 9)}} + Beneficiary: info.Beneficiary, + BeneficiaryTerm: BeneficiaryTerm(info.BeneficiaryTerm), + PendingBeneficiaryTerm: (*PendingBeneficiaryChange)(info.PendingBeneficiaryTerm), + {{end}} + } + + return mi, nil +} + +func (s *state{{.v}}) DeadlineInfo(epoch abi.ChainEpoch) (*dline.Info, error) { + return s.State.{{if (ge .v 4)}}Recorded{{end}}DeadlineInfo(epoch), nil +} + +func (s *state{{.v}}) DeadlineCronActive() (bool, error) { + return {{if (ge .v 4)}}s.State.DeadlineCronActive{{else}}true{{end}}, nil{{if (lt .v 4)}} // always active in this version{{end}} +} + +func (s *state{{.v}}) sectors() (adt.Array, error) { + return adt{{.v}}.AsArray(s.store, s.Sectors{{if (ge .v 3)}}, miner{{.v}}.SectorsAmtBitwidth{{end}}) +} + +func (s *state{{.v}}) decodeSectorOnChainInfo(val *cbg.Deferred) (SectorOnChainInfo, error) { + var si miner{{.v}}.SectorOnChainInfo + err := si.UnmarshalCBOR(bytes.NewReader(val.Raw)) + if err != nil { + return SectorOnChainInfo{}, err + } + + return fromV{{.v}}SectorOnChainInfo(si), nil +} + +func (s *state{{.v}}) precommits() (adt.Map, error) { + return adt{{.v}}.AsMap(s.store, s.PreCommittedSectors{{if (ge .v 3)}}, builtin{{.v}}.DefaultHamtBitwidth{{end}}) +} + +func (s *state{{.v}}) decodeSectorPreCommitOnChainInfo(val *cbg.Deferred) (SectorPreCommitOnChainInfo, error) { + var sp miner{{.v}}.SectorPreCommitOnChainInfo + err := sp.UnmarshalCBOR(bytes.NewReader(val.Raw)) + if err != nil { + return SectorPreCommitOnChainInfo{}, err + } + + return fromV{{.v}}SectorPreCommitOnChainInfo(sp), nil +} + +func (s *state{{.v}}) EraseAllUnproven() error { + {{if (ge .v 2)}} + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return err + } + + err = dls.ForEach(s.store, func(dindx uint64, dl *miner{{.v}}.Deadline) error { + ps, err := dl.PartitionsArray(s.store) + if err != nil { + return err + } + + var part miner{{.v}}.Partition + err = ps.ForEach(&part, func(pindx int64) error { + _ = part.ActivateUnproven() + err = ps.Set(uint64(pindx), &part) + return nil + }) + + if err != nil { + return err + } + + dl.Partitions, err = ps.Root() + if err != nil { + return err + } + + return dls.UpdateDeadline(s.store, dindx, dl) + }) + if err != nil { + return err + } + + return s.State.SaveDeadlines(s.store, dls) + {{else}} + // field doesn't exist until v2 + return nil + {{end}} +} + +func (d *deadline{{.v}}) LoadPartition(idx uint64) (Partition, error) { + p, err := d.Deadline.LoadPartition(d.store, idx) + if err != nil { + return nil, err + } + return &partition{{.v}}{*p, d.store}, nil +} + +func (d *deadline{{.v}}) ForEachPartition(cb func(uint64, Partition) error) error { + ps, err := d.Deadline.PartitionsArray(d.store) + if err != nil { + return err + } + var part miner{{.v}}.Partition + return ps.ForEach(&part, func(i int64) error { + return cb(uint64(i), &partition{{.v}}{part, d.store}) + }) +} + +func (d *deadline{{.v}}) PartitionsChanged(other Deadline) (bool, error) { + other{{.v}}, ok := other.(*deadline{{.v}}) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + + return !d.Deadline.Partitions.Equals(other{{.v}}.Deadline.Partitions), nil +} + +func (d *deadline{{.v}}) PartitionsPoSted() (bitfield.BitField, error) { + return d.Deadline.{{if (ge .v 3)}}PartitionsPoSted{{else}}PostSubmissions{{end}}, nil +} + +func (d *deadline{{.v}}) DisputableProofCount() (uint64, error) { +{{if (ge .v 3)}} + ops, err := d.OptimisticProofsSnapshotArray(d.store) + if err != nil { + return 0, err + } + + return ops.Length(), nil +{{else}} + // field doesn't exist until v3 + return 0, nil +{{end}} +} + +func (p *partition{{.v}}) AllSectors() (bitfield.BitField, error) { + return p.Partition.Sectors, nil +} + +func (p *partition{{.v}}) FaultySectors() (bitfield.BitField, error) { + return p.Partition.Faults, nil +} + +func (p *partition{{.v}}) RecoveringSectors() (bitfield.BitField, error) { + return p.Partition.Recoveries, nil +} + +func (p *partition{{.v}}) UnprovenSectors() (bitfield.BitField, error) { + return {{if (ge .v 2)}}p.Partition.Unproven{{else}}bitfield.New(){{end}}, nil +} + +func fromV{{.v}}SectorOnChainInfo(v{{.v}} miner{{.v}}.SectorOnChainInfo) SectorOnChainInfo { + info := SectorOnChainInfo{ + SectorNumber: v{{.v}}.SectorNumber, + SealProof: v{{.v}}.SealProof, + SealedCID: v{{.v}}.SealedCID, + DealIDs: v{{.v}}.DealIDs, + Activation: v{{.v}}.Activation, + Expiration: v{{.v}}.Expiration, + DealWeight: v{{.v}}.DealWeight, + VerifiedDealWeight: v{{.v}}.VerifiedDealWeight, + InitialPledge: v{{.v}}.InitialPledge, + ExpectedDayReward: v{{.v}}.ExpectedDayReward, + ExpectedStoragePledge: v{{.v}}.ExpectedStoragePledge, + {{if (ge .v 7)}} + SectorKeyCID: v{{.v}}.SectorKeyCID, + {{end}} + } + return info +} + +func fromV{{.v}}SectorPreCommitOnChainInfo(v{{.v}} miner{{.v}}.SectorPreCommitOnChainInfo) SectorPreCommitOnChainInfo { + ret := SectorPreCommitOnChainInfo{ + Info: SectorPreCommitInfo{ + SealProof: v{{.v}}.Info.SealProof, + SectorNumber: v{{.v}}.Info.SectorNumber, + SealedCID: v{{.v}}.Info.SealedCID, + SealRandEpoch: v{{.v}}.Info.SealRandEpoch, + DealIDs: v{{.v}}.Info.DealIDs, + Expiration: v{{.v}}.Info.Expiration, + UnsealedCid: nil, + }, + PreCommitDeposit: v{{.v}}.PreCommitDeposit, + PreCommitEpoch: v{{.v}}.PreCommitEpoch, + } + + {{if (ge .v 9)}} + ret.Info.UnsealedCid = v{{.v}}.Info.UnsealedCid + {{end}} + + return ret +} + +func (s *state{{.v}}) GetState() interface{} { + return &s.State +} + +func (s *state{{.v}}) ActorKey() string { + return manifest.MinerKey +} + +func (s *state{{.v}}) ActorVersion() actorstypes.Version { + return actorstypes.Version{{.v}} +} + +func (s *state{{.v}}) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/miner/utils.go b/chain/actors/builtin/miner/utils.go new file mode 100644 index 000000000..dae3d3bc2 --- /dev/null +++ b/chain/actors/builtin/miner/utils.go @@ -0,0 +1,107 @@ +package miner + +import ( + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-bitfield" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/network" +) + +func AllPartSectors(mas State, sget func(Partition) (bitfield.BitField, error)) (bitfield.BitField, error) { + var parts []bitfield.BitField + + err := mas.ForEachDeadline(func(dlidx uint64, dl Deadline) error { + return dl.ForEachPartition(func(partidx uint64, part Partition) error { + s, err := sget(part) + if err != nil { + return xerrors.Errorf("getting sector list (dl: %d, part %d): %w", dlidx, partidx, err) + } + + parts = append(parts, s) + return nil + }) + }) + if err != nil { + return bitfield.BitField{}, err + } + + return bitfield.MultiMerge(parts...) +} + +// SealProofTypeFromSectorSize returns preferred seal proof type for creating +// new miner actors and new sectors +func SealProofTypeFromSectorSize(ssize abi.SectorSize, nv network.Version) (abi.RegisteredSealProof, error) { + switch { + case nv < network.Version7: + switch ssize { + case 2 << 10: + return abi.RegisteredSealProof_StackedDrg2KiBV1, nil + case 8 << 20: + return abi.RegisteredSealProof_StackedDrg8MiBV1, nil + case 512 << 20: + return abi.RegisteredSealProof_StackedDrg512MiBV1, nil + case 32 << 30: + return abi.RegisteredSealProof_StackedDrg32GiBV1, nil + case 64 << 30: + return abi.RegisteredSealProof_StackedDrg64GiBV1, nil + default: + return 0, xerrors.Errorf("unsupported sector size for miner: %v", ssize) + } + case nv >= network.Version7: + switch ssize { + case 2 << 10: + return abi.RegisteredSealProof_StackedDrg2KiBV1_1, nil + case 8 << 20: + return abi.RegisteredSealProof_StackedDrg8MiBV1_1, nil + case 512 << 20: + return abi.RegisteredSealProof_StackedDrg512MiBV1_1, nil + case 32 << 30: + return abi.RegisteredSealProof_StackedDrg32GiBV1_1, nil + case 64 << 30: + return abi.RegisteredSealProof_StackedDrg64GiBV1_1, nil + default: + return 0, xerrors.Errorf("unsupported sector size for miner: %v", ssize) + } + } + + return 0, xerrors.Errorf("unsupported network version") +} + +// WindowPoStProofTypeFromSectorSize returns preferred post proof type for creating +// new miner actors and new sectors +func WindowPoStProofTypeFromSectorSize(ssize abi.SectorSize, nv network.Version) (abi.RegisteredPoStProof, error) { + switch { + case nv < network.Version19: + switch ssize { + case 2 << 10: + return abi.RegisteredPoStProof_StackedDrgWindow2KiBV1, nil + case 8 << 20: + return abi.RegisteredPoStProof_StackedDrgWindow8MiBV1, nil + case 512 << 20: + return abi.RegisteredPoStProof_StackedDrgWindow512MiBV1, nil + case 32 << 30: + return abi.RegisteredPoStProof_StackedDrgWindow32GiBV1, nil + case 64 << 30: + return abi.RegisteredPoStProof_StackedDrgWindow64GiBV1, nil + default: + return 0, xerrors.Errorf("unsupported sector size for miner: %v", ssize) + } + case nv >= network.Version19: + switch ssize { + case 2 << 10: + return abi.RegisteredPoStProof_StackedDrgWindow2KiBV1_1, nil + case 8 << 20: + return abi.RegisteredPoStProof_StackedDrgWindow8MiBV1_1, nil + case 512 << 20: + return abi.RegisteredPoStProof_StackedDrgWindow512MiBV1_1, nil + case 32 << 30: + return abi.RegisteredPoStProof_StackedDrgWindow32GiBV1_1, nil + case 64 << 30: + return abi.RegisteredPoStProof_StackedDrgWindow64GiBV1_1, nil + default: + return 0, xerrors.Errorf("unsupported sector size for miner: %v", ssize) + } + } + return 0, xerrors.Errorf("unsupported network version") +} diff --git a/chain/actors/builtin/miner/v0.go b/chain/actors/builtin/miner/v0.go new file mode 100644 index 000000000..7d5eaf8e0 --- /dev/null +++ b/chain/actors/builtin/miner/v0.go @@ -0,0 +1,553 @@ +package miner + +import ( + "bytes" + "errors" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-bitfield" + rle "github.com/filecoin-project/go-bitfield/rle" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/dline" + "github.com/filecoin-project/go-state-types/manifest" + miner0 "github.com/filecoin-project/specs-actors/actors/builtin/miner" + adt0 "github.com/filecoin-project/specs-actors/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state0)(nil) + +func load0(store adt.Store, root cid.Cid) (State, error) { + out := state0{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make0(store adt.Store) (State, error) { + out := state0{store: store} + out.State = miner0.State{} + return &out, nil +} + +type state0 struct { + miner0.State + store adt.Store +} + +type deadline0 struct { + miner0.Deadline + store adt.Store +} + +type partition0 struct { + miner0.Partition + store adt.Store +} + +func (s *state0) AvailableBalance(bal abi.TokenAmount) (available abi.TokenAmount, err error) { + defer func() { + if r := recover(); r != nil { + err = xerrors.Errorf("failed to get available balance: %w", r) + available = abi.NewTokenAmount(0) + } + }() + // this panics if the miner doesnt have enough funds to cover their locked pledge + available = s.GetAvailableBalance(bal) + return available, err +} + +func (s *state0) VestedFunds(epoch abi.ChainEpoch) (abi.TokenAmount, error) { + return s.CheckVestedFunds(s.store, epoch) +} + +func (s *state0) LockedFunds() (LockedFunds, error) { + return LockedFunds{ + VestingFunds: s.State.LockedFunds, + InitialPledgeRequirement: s.State.InitialPledgeRequirement, + PreCommitDeposits: s.State.PreCommitDeposits, + }, nil +} + +func (s *state0) FeeDebt() (abi.TokenAmount, error) { + return big.Zero(), nil +} + +func (s *state0) InitialPledge() (abi.TokenAmount, error) { + return s.State.InitialPledgeRequirement, nil +} + +func (s *state0) PreCommitDeposits() (abi.TokenAmount, error) { + return s.State.PreCommitDeposits, nil +} + +// Returns nil, nil if sector is not found +func (s *state0) GetSector(num abi.SectorNumber) (*SectorOnChainInfo, error) { + info, ok, err := s.State.GetSector(s.store, num) + if !ok || err != nil { + return nil, err + } + + ret := fromV0SectorOnChainInfo(*info) + return &ret, nil +} + +func (s *state0) FindSector(num abi.SectorNumber) (*SectorLocation, error) { + dlIdx, partIdx, err := s.State.FindSector(s.store, num) + if err != nil { + return nil, err + } + return &SectorLocation{ + Deadline: dlIdx, + Partition: partIdx, + }, nil +} + +func (s *state0) NumLiveSectors() (uint64, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return 0, err + } + var total uint64 + if err := dls.ForEach(s.store, func(dlIdx uint64, dl *miner0.Deadline) error { + total += dl.LiveSectors + return nil + }); err != nil { + return 0, err + } + return total, nil +} + +// GetSectorExpiration returns the effective expiration of the given sector. +// +// If the sector does not expire early, the Early expiration field is 0. +func (s *state0) GetSectorExpiration(num abi.SectorNumber) (*SectorExpiration, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return nil, err + } + // NOTE: this can be optimized significantly. + // 1. If the sector is non-faulty, it will either expire on-time (can be + // learned from the sector info), or in the next quantized expiration + // epoch (i.e., the first element in the partition's expiration queue. + // 2. If it's faulty, it will expire early within the first 14 entries + // of the expiration queue. + + stopErr := errors.New("stop") + out := SectorExpiration{} + err = dls.ForEach(s.store, func(dlIdx uint64, dl *miner0.Deadline) error { + partitions, err := dl.PartitionsArray(s.store) + if err != nil { + return err + } + quant := s.State.QuantSpecForDeadline(dlIdx) + var part miner0.Partition + return partitions.ForEach(&part, func(partIdx int64) error { + if found, err := part.Sectors.IsSet(uint64(num)); err != nil { + return err + } else if !found { + return nil + } + if found, err := part.Terminated.IsSet(uint64(num)); err != nil { + return err + } else if found { + // already terminated + return stopErr + } + + q, err := miner0.LoadExpirationQueue(s.store, part.ExpirationsEpochs, quant) + if err != nil { + return err + } + var exp miner0.ExpirationSet + return q.ForEach(&exp, func(epoch int64) error { + if early, err := exp.EarlySectors.IsSet(uint64(num)); err != nil { + return err + } else if early { + out.Early = abi.ChainEpoch(epoch) + return nil + } + if onTime, err := exp.OnTimeSectors.IsSet(uint64(num)); err != nil { + return err + } else if onTime { + out.OnTime = abi.ChainEpoch(epoch) + return stopErr + } + return nil + }) + }) + }) + if err == stopErr { + err = nil + } + if err != nil { + return nil, err + } + if out.Early == 0 && out.OnTime == 0 { + return nil, xerrors.Errorf("failed to find sector %d", num) + } + return &out, nil +} + +func (s *state0) GetPrecommittedSector(num abi.SectorNumber) (*SectorPreCommitOnChainInfo, error) { + info, ok, err := s.State.GetPrecommittedSector(s.store, num) + if !ok || err != nil { + return nil, err + } + + ret := fromV0SectorPreCommitOnChainInfo(*info) + + 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 { + return nil, err + } + + // If no sector numbers are specified, load all. + if snos == nil { + infos := make([]*SectorOnChainInfo, 0, sectors.Length()) + var info0 miner0.SectorOnChainInfo + if err := sectors.ForEach(&info0, func(_ int64) error { + info := fromV0SectorOnChainInfo(info0) + infos = append(infos, &info) + return nil + }); err != nil { + return nil, err + } + return infos, nil + } + + // Otherwise, load selected. + infos0, err := sectors.Load(*snos) + if err != nil { + return nil, err + } + infos := make([]*SectorOnChainInfo, len(infos0)) + for i, info0 := range infos0 { + info := fromV0SectorOnChainInfo(*info0) + infos[i] = &info + } + return infos, nil +} + +func (s *state0) loadAllocatedSectorNumbers() (bitfield.BitField, error) { + var allocatedSectors bitfield.BitField + 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 + } + + return allocatedSectors.IsSet(uint64(num)) +} + +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) GetAllocatedSectors() (*bitfield.BitField, error) { + var allocatedSectors bitfield.BitField + if err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors); err != nil { + return nil, err + } + + return &allocatedSectors, nil +} + +func (s *state0) LoadDeadline(idx uint64) (Deadline, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return nil, err + } + dl, err := dls.LoadDeadline(s.store, idx) + if err != nil { + return nil, err + } + return &deadline0{*dl, s.store}, nil +} + +func (s *state0) ForEachDeadline(cb func(uint64, Deadline) error) error { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return err + } + return dls.ForEach(s.store, func(i uint64, dl *miner0.Deadline) error { + return cb(i, &deadline0{*dl, s.store}) + }) +} + +func (s *state0) NumDeadlines() (uint64, error) { + return miner0.WPoStPeriodDeadlines, nil +} + +func (s *state0) DeadlinesChanged(other State) (bool, error) { + other0, ok := other.(*state0) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + + return !s.State.Deadlines.Equals(other0.Deadlines), nil +} + +func (s *state0) MinerInfoChanged(other State) (bool, error) { + other0, ok := other.(*state0) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.Info.Equals(other0.State.Info), nil +} + +func (s *state0) Info() (MinerInfo, error) { + info, err := s.State.GetInfo(s.store) + if err != nil { + return MinerInfo{}, err + } + + wpp, err := info.SealProofType.RegisteredWindowPoStProof() + if err != nil { + return MinerInfo{}, err + } + + mi := MinerInfo{ + Owner: info.Owner, + Worker: info.Worker, + ControlAddresses: info.ControlAddresses, + + PendingWorkerKey: (*WorkerKeyChange)(info.PendingWorkerKey), + + PeerId: info.PeerId, + Multiaddrs: info.Multiaddrs, + WindowPoStProofType: wpp, + SectorSize: info.SectorSize, + WindowPoStPartitionSectors: info.WindowPoStPartitionSectors, + ConsensusFaultElapsed: -1, + } + + return mi, nil +} + +func (s *state0) DeadlineInfo(epoch abi.ChainEpoch) (*dline.Info, error) { + return s.State.DeadlineInfo(epoch), nil +} + +func (s *state0) DeadlineCronActive() (bool, error) { + return true, nil // always active in this version +} + +func (s *state0) sectors() (adt.Array, error) { + return adt0.AsArray(s.store, s.Sectors) +} + +func (s *state0) decodeSectorOnChainInfo(val *cbg.Deferred) (SectorOnChainInfo, error) { + var si miner0.SectorOnChainInfo + err := si.UnmarshalCBOR(bytes.NewReader(val.Raw)) + if err != nil { + return SectorOnChainInfo{}, err + } + + return fromV0SectorOnChainInfo(si), nil +} + +func (s *state0) precommits() (adt.Map, error) { + return adt0.AsMap(s.store, s.PreCommittedSectors) +} + +func (s *state0) decodeSectorPreCommitOnChainInfo(val *cbg.Deferred) (SectorPreCommitOnChainInfo, error) { + var sp miner0.SectorPreCommitOnChainInfo + err := sp.UnmarshalCBOR(bytes.NewReader(val.Raw)) + if err != nil { + return SectorPreCommitOnChainInfo{}, err + } + + return fromV0SectorPreCommitOnChainInfo(sp), nil +} + +func (s *state0) EraseAllUnproven() error { + + // field doesn't exist until v2 + return nil + +} + +func (d *deadline0) LoadPartition(idx uint64) (Partition, error) { + p, err := d.Deadline.LoadPartition(d.store, idx) + if err != nil { + return nil, err + } + return &partition0{*p, d.store}, nil +} + +func (d *deadline0) ForEachPartition(cb func(uint64, Partition) error) error { + ps, err := d.Deadline.PartitionsArray(d.store) + if err != nil { + return err + } + var part miner0.Partition + return ps.ForEach(&part, func(i int64) error { + return cb(uint64(i), &partition0{part, d.store}) + }) +} + +func (d *deadline0) PartitionsChanged(other Deadline) (bool, error) { + other0, ok := other.(*deadline0) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + + return !d.Deadline.Partitions.Equals(other0.Deadline.Partitions), nil +} + +func (d *deadline0) PartitionsPoSted() (bitfield.BitField, error) { + return d.Deadline.PostSubmissions, nil +} + +func (d *deadline0) DisputableProofCount() (uint64, error) { + + // field doesn't exist until v3 + return 0, nil + +} + +func (p *partition0) AllSectors() (bitfield.BitField, error) { + return p.Partition.Sectors, nil +} + +func (p *partition0) FaultySectors() (bitfield.BitField, error) { + return p.Partition.Faults, nil +} + +func (p *partition0) RecoveringSectors() (bitfield.BitField, error) { + return p.Partition.Recoveries, nil +} + +func (p *partition0) UnprovenSectors() (bitfield.BitField, error) { + return bitfield.New(), nil +} + +func fromV0SectorOnChainInfo(v0 miner0.SectorOnChainInfo) SectorOnChainInfo { + info := SectorOnChainInfo{ + SectorNumber: v0.SectorNumber, + SealProof: v0.SealProof, + SealedCID: v0.SealedCID, + DealIDs: v0.DealIDs, + Activation: v0.Activation, + Expiration: v0.Expiration, + DealWeight: v0.DealWeight, + VerifiedDealWeight: v0.VerifiedDealWeight, + InitialPledge: v0.InitialPledge, + ExpectedDayReward: v0.ExpectedDayReward, + ExpectedStoragePledge: v0.ExpectedStoragePledge, + } + return info +} + +func fromV0SectorPreCommitOnChainInfo(v0 miner0.SectorPreCommitOnChainInfo) SectorPreCommitOnChainInfo { + ret := SectorPreCommitOnChainInfo{ + Info: SectorPreCommitInfo{ + SealProof: v0.Info.SealProof, + SectorNumber: v0.Info.SectorNumber, + SealedCID: v0.Info.SealedCID, + SealRandEpoch: v0.Info.SealRandEpoch, + DealIDs: v0.Info.DealIDs, + Expiration: v0.Info.Expiration, + UnsealedCid: nil, + }, + PreCommitDeposit: v0.PreCommitDeposit, + PreCommitEpoch: v0.PreCommitEpoch, + } + + return ret +} + +func (s *state0) GetState() interface{} { + return &s.State +} + +func (s *state0) ActorKey() string { + return manifest.MinerKey +} + +func (s *state0) ActorVersion() actorstypes.Version { + return actorstypes.Version0 +} + +func (s *state0) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/miner/v10.go b/chain/actors/builtin/miner/v10.go new file mode 100644 index 000000000..4d47ba396 --- /dev/null +++ b/chain/actors/builtin/miner/v10.go @@ -0,0 +1,591 @@ +package miner + +import ( + "bytes" + "errors" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-bitfield" + rle "github.com/filecoin-project/go-bitfield/rle" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtin10 "github.com/filecoin-project/go-state-types/builtin" + miner10 "github.com/filecoin-project/go-state-types/builtin/v10/miner" + adt10 "github.com/filecoin-project/go-state-types/builtin/v10/util/adt" + "github.com/filecoin-project/go-state-types/dline" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state10)(nil) + +func load10(store adt.Store, root cid.Cid) (State, error) { + out := state10{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make10(store adt.Store) (State, error) { + out := state10{store: store} + out.State = miner10.State{} + return &out, nil +} + +type state10 struct { + miner10.State + store adt.Store +} + +type deadline10 struct { + miner10.Deadline + store adt.Store +} + +type partition10 struct { + miner10.Partition + store adt.Store +} + +func (s *state10) AvailableBalance(bal abi.TokenAmount) (available abi.TokenAmount, err error) { + defer func() { + if r := recover(); r != nil { + err = xerrors.Errorf("failed to get available balance: %w", r) + available = abi.NewTokenAmount(0) + } + }() + // this panics if the miner doesnt have enough funds to cover their locked pledge + available, err = s.GetAvailableBalance(bal) + return available, err +} + +func (s *state10) VestedFunds(epoch abi.ChainEpoch) (abi.TokenAmount, error) { + return s.CheckVestedFunds(s.store, epoch) +} + +func (s *state10) LockedFunds() (LockedFunds, error) { + return LockedFunds{ + VestingFunds: s.State.LockedFunds, + InitialPledgeRequirement: s.State.InitialPledge, + PreCommitDeposits: s.State.PreCommitDeposits, + }, nil +} + +func (s *state10) FeeDebt() (abi.TokenAmount, error) { + return s.State.FeeDebt, nil +} + +func (s *state10) InitialPledge() (abi.TokenAmount, error) { + return s.State.InitialPledge, nil +} + +func (s *state10) PreCommitDeposits() (abi.TokenAmount, error) { + return s.State.PreCommitDeposits, nil +} + +// Returns nil, nil if sector is not found +func (s *state10) GetSector(num abi.SectorNumber) (*SectorOnChainInfo, error) { + info, ok, err := s.State.GetSector(s.store, num) + if !ok || err != nil { + return nil, err + } + + ret := fromV10SectorOnChainInfo(*info) + return &ret, nil +} + +func (s *state10) FindSector(num abi.SectorNumber) (*SectorLocation, error) { + dlIdx, partIdx, err := s.State.FindSector(s.store, num) + if err != nil { + return nil, err + } + return &SectorLocation{ + Deadline: dlIdx, + Partition: partIdx, + }, nil +} + +func (s *state10) NumLiveSectors() (uint64, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return 0, err + } + var total uint64 + if err := dls.ForEach(s.store, func(dlIdx uint64, dl *miner10.Deadline) error { + total += dl.LiveSectors + return nil + }); err != nil { + return 0, err + } + return total, nil +} + +// GetSectorExpiration returns the effective expiration of the given sector. +// +// If the sector does not expire early, the Early expiration field is 0. +func (s *state10) GetSectorExpiration(num abi.SectorNumber) (*SectorExpiration, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return nil, err + } + // NOTE: this can be optimized significantly. + // 1. If the sector is non-faulty, it will expire on-time (can be + // learned from the sector info). + // 2. If it's faulty, it will expire early within the first 42 entries + // of the expiration queue. + + stopErr := errors.New("stop") + out := SectorExpiration{} + err = dls.ForEach(s.store, func(dlIdx uint64, dl *miner10.Deadline) error { + partitions, err := dl.PartitionsArray(s.store) + if err != nil { + return err + } + quant := s.State.QuantSpecForDeadline(dlIdx) + var part miner10.Partition + return partitions.ForEach(&part, func(partIdx int64) error { + if found, err := part.Sectors.IsSet(uint64(num)); err != nil { + return err + } else if !found { + return nil + } + if found, err := part.Terminated.IsSet(uint64(num)); err != nil { + return err + } else if found { + // already terminated + return stopErr + } + + q, err := miner10.LoadExpirationQueue(s.store, part.ExpirationsEpochs, quant, miner10.PartitionExpirationAmtBitwidth) + if err != nil { + return err + } + var exp miner10.ExpirationSet + return q.ForEach(&exp, func(epoch int64) error { + if early, err := exp.EarlySectors.IsSet(uint64(num)); err != nil { + return err + } else if early { + out.Early = abi.ChainEpoch(epoch) + return nil + } + if onTime, err := exp.OnTimeSectors.IsSet(uint64(num)); err != nil { + return err + } else if onTime { + out.OnTime = abi.ChainEpoch(epoch) + return stopErr + } + return nil + }) + }) + }) + if err == stopErr { + err = nil + } + if err != nil { + return nil, err + } + if out.Early == 0 && out.OnTime == 0 { + return nil, xerrors.Errorf("failed to find sector %d", num) + } + return &out, nil +} + +func (s *state10) GetPrecommittedSector(num abi.SectorNumber) (*SectorPreCommitOnChainInfo, error) { + info, ok, err := s.State.GetPrecommittedSector(s.store, num) + if !ok || err != nil { + return nil, err + } + + ret := fromV10SectorPreCommitOnChainInfo(*info) + + return &ret, nil +} + +func (s *state10) ForEachPrecommittedSector(cb func(SectorPreCommitOnChainInfo) error) error { + precommitted, err := adt10.AsMap(s.store, s.State.PreCommittedSectors, builtin10.DefaultHamtBitwidth) + if err != nil { + return err + } + + var info miner10.SectorPreCommitOnChainInfo + if err := precommitted.ForEach(&info, func(_ string) error { + return cb(fromV10SectorPreCommitOnChainInfo(info)) + }); err != nil { + return err + } + + return nil +} + +func (s *state10) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, error) { + sectors, err := miner10.LoadSectors(s.store, s.State.Sectors) + if err != nil { + return nil, err + } + + // If no sector numbers are specified, load all. + if snos == nil { + infos := make([]*SectorOnChainInfo, 0, sectors.Length()) + var info10 miner10.SectorOnChainInfo + if err := sectors.ForEach(&info10, func(_ int64) error { + info := fromV10SectorOnChainInfo(info10) + infos = append(infos, &info) + return nil + }); err != nil { + return nil, err + } + return infos, nil + } + + // Otherwise, load selected. + infos10, err := sectors.Load(*snos) + if err != nil { + return nil, err + } + infos := make([]*SectorOnChainInfo, len(infos10)) + for i, info10 := range infos10 { + info := fromV10SectorOnChainInfo(*info10) + infos[i] = &info + } + return infos, nil +} + +func (s *state10) loadAllocatedSectorNumbers() (bitfield.BitField, error) { + var allocatedSectors bitfield.BitField + err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors) + return allocatedSectors, err +} + +func (s *state10) IsAllocated(num abi.SectorNumber) (bool, error) { + allocatedSectors, err := s.loadAllocatedSectorNumbers() + if err != nil { + return false, err + } + + return allocatedSectors.IsSet(uint64(num)) +} + +func (s *state10) GetProvingPeriodStart() (abi.ChainEpoch, error) { + return s.State.ProvingPeriodStart, nil +} + +func (s *state10) 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 *state10) GetAllocatedSectors() (*bitfield.BitField, error) { + var allocatedSectors bitfield.BitField + if err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors); err != nil { + return nil, err + } + + return &allocatedSectors, nil +} + +func (s *state10) LoadDeadline(idx uint64) (Deadline, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return nil, err + } + dl, err := dls.LoadDeadline(s.store, idx) + if err != nil { + return nil, err + } + return &deadline10{*dl, s.store}, nil +} + +func (s *state10) ForEachDeadline(cb func(uint64, Deadline) error) error { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return err + } + return dls.ForEach(s.store, func(i uint64, dl *miner10.Deadline) error { + return cb(i, &deadline10{*dl, s.store}) + }) +} + +func (s *state10) NumDeadlines() (uint64, error) { + return miner10.WPoStPeriodDeadlines, nil +} + +func (s *state10) DeadlinesChanged(other State) (bool, error) { + other10, ok := other.(*state10) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + + return !s.State.Deadlines.Equals(other10.Deadlines), nil +} + +func (s *state10) MinerInfoChanged(other State) (bool, error) { + other0, ok := other.(*state10) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.Info.Equals(other0.State.Info), nil +} + +func (s *state10) Info() (MinerInfo, error) { + info, err := s.State.GetInfo(s.store) + if err != nil { + return MinerInfo{}, err + } + + mi := MinerInfo{ + Owner: info.Owner, + Worker: info.Worker, + ControlAddresses: info.ControlAddresses, + + PendingWorkerKey: (*WorkerKeyChange)(info.PendingWorkerKey), + + PeerId: info.PeerId, + Multiaddrs: info.Multiaddrs, + WindowPoStProofType: info.WindowPoStProofType, + SectorSize: info.SectorSize, + WindowPoStPartitionSectors: info.WindowPoStPartitionSectors, + ConsensusFaultElapsed: info.ConsensusFaultElapsed, + + Beneficiary: info.Beneficiary, + BeneficiaryTerm: BeneficiaryTerm(info.BeneficiaryTerm), + PendingBeneficiaryTerm: (*PendingBeneficiaryChange)(info.PendingBeneficiaryTerm), + } + + return mi, nil +} + +func (s *state10) DeadlineInfo(epoch abi.ChainEpoch) (*dline.Info, error) { + return s.State.RecordedDeadlineInfo(epoch), nil +} + +func (s *state10) DeadlineCronActive() (bool, error) { + return s.State.DeadlineCronActive, nil +} + +func (s *state10) sectors() (adt.Array, error) { + return adt10.AsArray(s.store, s.Sectors, miner10.SectorsAmtBitwidth) +} + +func (s *state10) decodeSectorOnChainInfo(val *cbg.Deferred) (SectorOnChainInfo, error) { + var si miner10.SectorOnChainInfo + err := si.UnmarshalCBOR(bytes.NewReader(val.Raw)) + if err != nil { + return SectorOnChainInfo{}, err + } + + return fromV10SectorOnChainInfo(si), nil +} + +func (s *state10) precommits() (adt.Map, error) { + return adt10.AsMap(s.store, s.PreCommittedSectors, builtin10.DefaultHamtBitwidth) +} + +func (s *state10) decodeSectorPreCommitOnChainInfo(val *cbg.Deferred) (SectorPreCommitOnChainInfo, error) { + var sp miner10.SectorPreCommitOnChainInfo + err := sp.UnmarshalCBOR(bytes.NewReader(val.Raw)) + if err != nil { + return SectorPreCommitOnChainInfo{}, err + } + + return fromV10SectorPreCommitOnChainInfo(sp), nil +} + +func (s *state10) EraseAllUnproven() error { + + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return err + } + + err = dls.ForEach(s.store, func(dindx uint64, dl *miner10.Deadline) error { + ps, err := dl.PartitionsArray(s.store) + if err != nil { + return err + } + + var part miner10.Partition + err = ps.ForEach(&part, func(pindx int64) error { + _ = part.ActivateUnproven() + err = ps.Set(uint64(pindx), &part) + return nil + }) + + if err != nil { + return err + } + + dl.Partitions, err = ps.Root() + if err != nil { + return err + } + + return dls.UpdateDeadline(s.store, dindx, dl) + }) + if err != nil { + return err + } + + return s.State.SaveDeadlines(s.store, dls) + +} + +func (d *deadline10) LoadPartition(idx uint64) (Partition, error) { + p, err := d.Deadline.LoadPartition(d.store, idx) + if err != nil { + return nil, err + } + return &partition10{*p, d.store}, nil +} + +func (d *deadline10) ForEachPartition(cb func(uint64, Partition) error) error { + ps, err := d.Deadline.PartitionsArray(d.store) + if err != nil { + return err + } + var part miner10.Partition + return ps.ForEach(&part, func(i int64) error { + return cb(uint64(i), &partition10{part, d.store}) + }) +} + +func (d *deadline10) PartitionsChanged(other Deadline) (bool, error) { + other10, ok := other.(*deadline10) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + + return !d.Deadline.Partitions.Equals(other10.Deadline.Partitions), nil +} + +func (d *deadline10) PartitionsPoSted() (bitfield.BitField, error) { + return d.Deadline.PartitionsPoSted, nil +} + +func (d *deadline10) DisputableProofCount() (uint64, error) { + + ops, err := d.OptimisticProofsSnapshotArray(d.store) + if err != nil { + return 0, err + } + + return ops.Length(), nil + +} + +func (p *partition10) AllSectors() (bitfield.BitField, error) { + return p.Partition.Sectors, nil +} + +func (p *partition10) FaultySectors() (bitfield.BitField, error) { + return p.Partition.Faults, nil +} + +func (p *partition10) RecoveringSectors() (bitfield.BitField, error) { + return p.Partition.Recoveries, nil +} + +func (p *partition10) UnprovenSectors() (bitfield.BitField, error) { + return p.Partition.Unproven, nil +} + +func fromV10SectorOnChainInfo(v10 miner10.SectorOnChainInfo) SectorOnChainInfo { + info := SectorOnChainInfo{ + SectorNumber: v10.SectorNumber, + SealProof: v10.SealProof, + SealedCID: v10.SealedCID, + DealIDs: v10.DealIDs, + Activation: v10.Activation, + Expiration: v10.Expiration, + DealWeight: v10.DealWeight, + VerifiedDealWeight: v10.VerifiedDealWeight, + InitialPledge: v10.InitialPledge, + ExpectedDayReward: v10.ExpectedDayReward, + ExpectedStoragePledge: v10.ExpectedStoragePledge, + + SectorKeyCID: v10.SectorKeyCID, + } + return info +} + +func fromV10SectorPreCommitOnChainInfo(v10 miner10.SectorPreCommitOnChainInfo) SectorPreCommitOnChainInfo { + ret := SectorPreCommitOnChainInfo{ + Info: SectorPreCommitInfo{ + SealProof: v10.Info.SealProof, + SectorNumber: v10.Info.SectorNumber, + SealedCID: v10.Info.SealedCID, + SealRandEpoch: v10.Info.SealRandEpoch, + DealIDs: v10.Info.DealIDs, + Expiration: v10.Info.Expiration, + UnsealedCid: nil, + }, + PreCommitDeposit: v10.PreCommitDeposit, + PreCommitEpoch: v10.PreCommitEpoch, + } + + ret.Info.UnsealedCid = v10.Info.UnsealedCid + + return ret +} + +func (s *state10) GetState() interface{} { + return &s.State +} + +func (s *state10) ActorKey() string { + return manifest.MinerKey +} + +func (s *state10) ActorVersion() actorstypes.Version { + return actorstypes.Version10 +} + +func (s *state10) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/miner/v11.go b/chain/actors/builtin/miner/v11.go new file mode 100644 index 000000000..a3ffd606f --- /dev/null +++ b/chain/actors/builtin/miner/v11.go @@ -0,0 +1,591 @@ +package miner + +import ( + "bytes" + "errors" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-bitfield" + rle "github.com/filecoin-project/go-bitfield/rle" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtin11 "github.com/filecoin-project/go-state-types/builtin" + miner11 "github.com/filecoin-project/go-state-types/builtin/v11/miner" + adt11 "github.com/filecoin-project/go-state-types/builtin/v11/util/adt" + "github.com/filecoin-project/go-state-types/dline" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state11)(nil) + +func load11(store adt.Store, root cid.Cid) (State, error) { + out := state11{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make11(store adt.Store) (State, error) { + out := state11{store: store} + out.State = miner11.State{} + return &out, nil +} + +type state11 struct { + miner11.State + store adt.Store +} + +type deadline11 struct { + miner11.Deadline + store adt.Store +} + +type partition11 struct { + miner11.Partition + store adt.Store +} + +func (s *state11) AvailableBalance(bal abi.TokenAmount) (available abi.TokenAmount, err error) { + defer func() { + if r := recover(); r != nil { + err = xerrors.Errorf("failed to get available balance: %w", r) + available = abi.NewTokenAmount(0) + } + }() + // this panics if the miner doesnt have enough funds to cover their locked pledge + available, err = s.GetAvailableBalance(bal) + return available, err +} + +func (s *state11) VestedFunds(epoch abi.ChainEpoch) (abi.TokenAmount, error) { + return s.CheckVestedFunds(s.store, epoch) +} + +func (s *state11) LockedFunds() (LockedFunds, error) { + return LockedFunds{ + VestingFunds: s.State.LockedFunds, + InitialPledgeRequirement: s.State.InitialPledge, + PreCommitDeposits: s.State.PreCommitDeposits, + }, nil +} + +func (s *state11) FeeDebt() (abi.TokenAmount, error) { + return s.State.FeeDebt, nil +} + +func (s *state11) InitialPledge() (abi.TokenAmount, error) { + return s.State.InitialPledge, nil +} + +func (s *state11) PreCommitDeposits() (abi.TokenAmount, error) { + return s.State.PreCommitDeposits, nil +} + +// Returns nil, nil if sector is not found +func (s *state11) GetSector(num abi.SectorNumber) (*SectorOnChainInfo, error) { + info, ok, err := s.State.GetSector(s.store, num) + if !ok || err != nil { + return nil, err + } + + ret := fromV11SectorOnChainInfo(*info) + return &ret, nil +} + +func (s *state11) FindSector(num abi.SectorNumber) (*SectorLocation, error) { + dlIdx, partIdx, err := s.State.FindSector(s.store, num) + if err != nil { + return nil, err + } + return &SectorLocation{ + Deadline: dlIdx, + Partition: partIdx, + }, nil +} + +func (s *state11) NumLiveSectors() (uint64, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return 0, err + } + var total uint64 + if err := dls.ForEach(s.store, func(dlIdx uint64, dl *miner11.Deadline) error { + total += dl.LiveSectors + return nil + }); err != nil { + return 0, err + } + return total, nil +} + +// GetSectorExpiration returns the effective expiration of the given sector. +// +// If the sector does not expire early, the Early expiration field is 0. +func (s *state11) GetSectorExpiration(num abi.SectorNumber) (*SectorExpiration, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return nil, err + } + // NOTE: this can be optimized significantly. + // 1. If the sector is non-faulty, it will expire on-time (can be + // learned from the sector info). + // 2. If it's faulty, it will expire early within the first 42 entries + // of the expiration queue. + + stopErr := errors.New("stop") + out := SectorExpiration{} + err = dls.ForEach(s.store, func(dlIdx uint64, dl *miner11.Deadline) error { + partitions, err := dl.PartitionsArray(s.store) + if err != nil { + return err + } + quant := s.State.QuantSpecForDeadline(dlIdx) + var part miner11.Partition + return partitions.ForEach(&part, func(partIdx int64) error { + if found, err := part.Sectors.IsSet(uint64(num)); err != nil { + return err + } else if !found { + return nil + } + if found, err := part.Terminated.IsSet(uint64(num)); err != nil { + return err + } else if found { + // already terminated + return stopErr + } + + q, err := miner11.LoadExpirationQueue(s.store, part.ExpirationsEpochs, quant, miner11.PartitionExpirationAmtBitwidth) + if err != nil { + return err + } + var exp miner11.ExpirationSet + return q.ForEach(&exp, func(epoch int64) error { + if early, err := exp.EarlySectors.IsSet(uint64(num)); err != nil { + return err + } else if early { + out.Early = abi.ChainEpoch(epoch) + return nil + } + if onTime, err := exp.OnTimeSectors.IsSet(uint64(num)); err != nil { + return err + } else if onTime { + out.OnTime = abi.ChainEpoch(epoch) + return stopErr + } + return nil + }) + }) + }) + if err == stopErr { + err = nil + } + if err != nil { + return nil, err + } + if out.Early == 0 && out.OnTime == 0 { + return nil, xerrors.Errorf("failed to find sector %d", num) + } + return &out, nil +} + +func (s *state11) GetPrecommittedSector(num abi.SectorNumber) (*SectorPreCommitOnChainInfo, error) { + info, ok, err := s.State.GetPrecommittedSector(s.store, num) + if !ok || err != nil { + return nil, err + } + + ret := fromV11SectorPreCommitOnChainInfo(*info) + + return &ret, nil +} + +func (s *state11) ForEachPrecommittedSector(cb func(SectorPreCommitOnChainInfo) error) error { + precommitted, err := adt11.AsMap(s.store, s.State.PreCommittedSectors, builtin11.DefaultHamtBitwidth) + if err != nil { + return err + } + + var info miner11.SectorPreCommitOnChainInfo + if err := precommitted.ForEach(&info, func(_ string) error { + return cb(fromV11SectorPreCommitOnChainInfo(info)) + }); err != nil { + return err + } + + return nil +} + +func (s *state11) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, error) { + sectors, err := miner11.LoadSectors(s.store, s.State.Sectors) + if err != nil { + return nil, err + } + + // If no sector numbers are specified, load all. + if snos == nil { + infos := make([]*SectorOnChainInfo, 0, sectors.Length()) + var info11 miner11.SectorOnChainInfo + if err := sectors.ForEach(&info11, func(_ int64) error { + info := fromV11SectorOnChainInfo(info11) + infos = append(infos, &info) + return nil + }); err != nil { + return nil, err + } + return infos, nil + } + + // Otherwise, load selected. + infos11, err := sectors.Load(*snos) + if err != nil { + return nil, err + } + infos := make([]*SectorOnChainInfo, len(infos11)) + for i, info11 := range infos11 { + info := fromV11SectorOnChainInfo(*info11) + infos[i] = &info + } + return infos, nil +} + +func (s *state11) loadAllocatedSectorNumbers() (bitfield.BitField, error) { + var allocatedSectors bitfield.BitField + err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors) + return allocatedSectors, err +} + +func (s *state11) IsAllocated(num abi.SectorNumber) (bool, error) { + allocatedSectors, err := s.loadAllocatedSectorNumbers() + if err != nil { + return false, err + } + + return allocatedSectors.IsSet(uint64(num)) +} + +func (s *state11) GetProvingPeriodStart() (abi.ChainEpoch, error) { + return s.State.ProvingPeriodStart, nil +} + +func (s *state11) 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 *state11) GetAllocatedSectors() (*bitfield.BitField, error) { + var allocatedSectors bitfield.BitField + if err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors); err != nil { + return nil, err + } + + return &allocatedSectors, nil +} + +func (s *state11) LoadDeadline(idx uint64) (Deadline, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return nil, err + } + dl, err := dls.LoadDeadline(s.store, idx) + if err != nil { + return nil, err + } + return &deadline11{*dl, s.store}, nil +} + +func (s *state11) ForEachDeadline(cb func(uint64, Deadline) error) error { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return err + } + return dls.ForEach(s.store, func(i uint64, dl *miner11.Deadline) error { + return cb(i, &deadline11{*dl, s.store}) + }) +} + +func (s *state11) NumDeadlines() (uint64, error) { + return miner11.WPoStPeriodDeadlines, nil +} + +func (s *state11) DeadlinesChanged(other State) (bool, error) { + other11, ok := other.(*state11) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + + return !s.State.Deadlines.Equals(other11.Deadlines), nil +} + +func (s *state11) MinerInfoChanged(other State) (bool, error) { + other0, ok := other.(*state11) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.Info.Equals(other0.State.Info), nil +} + +func (s *state11) Info() (MinerInfo, error) { + info, err := s.State.GetInfo(s.store) + if err != nil { + return MinerInfo{}, err + } + + mi := MinerInfo{ + Owner: info.Owner, + Worker: info.Worker, + ControlAddresses: info.ControlAddresses, + + PendingWorkerKey: (*WorkerKeyChange)(info.PendingWorkerKey), + + PeerId: info.PeerId, + Multiaddrs: info.Multiaddrs, + WindowPoStProofType: info.WindowPoStProofType, + SectorSize: info.SectorSize, + WindowPoStPartitionSectors: info.WindowPoStPartitionSectors, + ConsensusFaultElapsed: info.ConsensusFaultElapsed, + + Beneficiary: info.Beneficiary, + BeneficiaryTerm: BeneficiaryTerm(info.BeneficiaryTerm), + PendingBeneficiaryTerm: (*PendingBeneficiaryChange)(info.PendingBeneficiaryTerm), + } + + return mi, nil +} + +func (s *state11) DeadlineInfo(epoch abi.ChainEpoch) (*dline.Info, error) { + return s.State.RecordedDeadlineInfo(epoch), nil +} + +func (s *state11) DeadlineCronActive() (bool, error) { + return s.State.DeadlineCronActive, nil +} + +func (s *state11) sectors() (adt.Array, error) { + return adt11.AsArray(s.store, s.Sectors, miner11.SectorsAmtBitwidth) +} + +func (s *state11) decodeSectorOnChainInfo(val *cbg.Deferred) (SectorOnChainInfo, error) { + var si miner11.SectorOnChainInfo + err := si.UnmarshalCBOR(bytes.NewReader(val.Raw)) + if err != nil { + return SectorOnChainInfo{}, err + } + + return fromV11SectorOnChainInfo(si), nil +} + +func (s *state11) precommits() (adt.Map, error) { + return adt11.AsMap(s.store, s.PreCommittedSectors, builtin11.DefaultHamtBitwidth) +} + +func (s *state11) decodeSectorPreCommitOnChainInfo(val *cbg.Deferred) (SectorPreCommitOnChainInfo, error) { + var sp miner11.SectorPreCommitOnChainInfo + err := sp.UnmarshalCBOR(bytes.NewReader(val.Raw)) + if err != nil { + return SectorPreCommitOnChainInfo{}, err + } + + return fromV11SectorPreCommitOnChainInfo(sp), nil +} + +func (s *state11) EraseAllUnproven() error { + + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return err + } + + err = dls.ForEach(s.store, func(dindx uint64, dl *miner11.Deadline) error { + ps, err := dl.PartitionsArray(s.store) + if err != nil { + return err + } + + var part miner11.Partition + err = ps.ForEach(&part, func(pindx int64) error { + _ = part.ActivateUnproven() + err = ps.Set(uint64(pindx), &part) + return nil + }) + + if err != nil { + return err + } + + dl.Partitions, err = ps.Root() + if err != nil { + return err + } + + return dls.UpdateDeadline(s.store, dindx, dl) + }) + if err != nil { + return err + } + + return s.State.SaveDeadlines(s.store, dls) + +} + +func (d *deadline11) LoadPartition(idx uint64) (Partition, error) { + p, err := d.Deadline.LoadPartition(d.store, idx) + if err != nil { + return nil, err + } + return &partition11{*p, d.store}, nil +} + +func (d *deadline11) ForEachPartition(cb func(uint64, Partition) error) error { + ps, err := d.Deadline.PartitionsArray(d.store) + if err != nil { + return err + } + var part miner11.Partition + return ps.ForEach(&part, func(i int64) error { + return cb(uint64(i), &partition11{part, d.store}) + }) +} + +func (d *deadline11) PartitionsChanged(other Deadline) (bool, error) { + other11, ok := other.(*deadline11) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + + return !d.Deadline.Partitions.Equals(other11.Deadline.Partitions), nil +} + +func (d *deadline11) PartitionsPoSted() (bitfield.BitField, error) { + return d.Deadline.PartitionsPoSted, nil +} + +func (d *deadline11) DisputableProofCount() (uint64, error) { + + ops, err := d.OptimisticProofsSnapshotArray(d.store) + if err != nil { + return 0, err + } + + return ops.Length(), nil + +} + +func (p *partition11) AllSectors() (bitfield.BitField, error) { + return p.Partition.Sectors, nil +} + +func (p *partition11) FaultySectors() (bitfield.BitField, error) { + return p.Partition.Faults, nil +} + +func (p *partition11) RecoveringSectors() (bitfield.BitField, error) { + return p.Partition.Recoveries, nil +} + +func (p *partition11) UnprovenSectors() (bitfield.BitField, error) { + return p.Partition.Unproven, nil +} + +func fromV11SectorOnChainInfo(v11 miner11.SectorOnChainInfo) SectorOnChainInfo { + info := SectorOnChainInfo{ + SectorNumber: v11.SectorNumber, + SealProof: v11.SealProof, + SealedCID: v11.SealedCID, + DealIDs: v11.DealIDs, + Activation: v11.Activation, + Expiration: v11.Expiration, + DealWeight: v11.DealWeight, + VerifiedDealWeight: v11.VerifiedDealWeight, + InitialPledge: v11.InitialPledge, + ExpectedDayReward: v11.ExpectedDayReward, + ExpectedStoragePledge: v11.ExpectedStoragePledge, + + SectorKeyCID: v11.SectorKeyCID, + } + return info +} + +func fromV11SectorPreCommitOnChainInfo(v11 miner11.SectorPreCommitOnChainInfo) SectorPreCommitOnChainInfo { + ret := SectorPreCommitOnChainInfo{ + Info: SectorPreCommitInfo{ + SealProof: v11.Info.SealProof, + SectorNumber: v11.Info.SectorNumber, + SealedCID: v11.Info.SealedCID, + SealRandEpoch: v11.Info.SealRandEpoch, + DealIDs: v11.Info.DealIDs, + Expiration: v11.Info.Expiration, + UnsealedCid: nil, + }, + PreCommitDeposit: v11.PreCommitDeposit, + PreCommitEpoch: v11.PreCommitEpoch, + } + + ret.Info.UnsealedCid = v11.Info.UnsealedCid + + return ret +} + +func (s *state11) GetState() interface{} { + return &s.State +} + +func (s *state11) ActorKey() string { + return manifest.MinerKey +} + +func (s *state11) ActorVersion() actorstypes.Version { + return actorstypes.Version11 +} + +func (s *state11) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/miner/v2.go b/chain/actors/builtin/miner/v2.go new file mode 100644 index 000000000..14341ae38 --- /dev/null +++ b/chain/actors/builtin/miner/v2.go @@ -0,0 +1,584 @@ +package miner + +import ( + "bytes" + "errors" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-bitfield" + rle "github.com/filecoin-project/go-bitfield/rle" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/dline" + "github.com/filecoin-project/go-state-types/manifest" + miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/miner" + adt2 "github.com/filecoin-project/specs-actors/v2/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state2)(nil) + +func load2(store adt.Store, root cid.Cid) (State, error) { + out := state2{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make2(store adt.Store) (State, error) { + out := state2{store: store} + out.State = miner2.State{} + return &out, nil +} + +type state2 struct { + miner2.State + store adt.Store +} + +type deadline2 struct { + miner2.Deadline + store adt.Store +} + +type partition2 struct { + miner2.Partition + store adt.Store +} + +func (s *state2) AvailableBalance(bal abi.TokenAmount) (available abi.TokenAmount, err error) { + defer func() { + if r := recover(); r != nil { + err = xerrors.Errorf("failed to get available balance: %w", r) + available = abi.NewTokenAmount(0) + } + }() + // this panics if the miner doesnt have enough funds to cover their locked pledge + available, err = s.GetAvailableBalance(bal) + return available, err +} + +func (s *state2) VestedFunds(epoch abi.ChainEpoch) (abi.TokenAmount, error) { + return s.CheckVestedFunds(s.store, epoch) +} + +func (s *state2) LockedFunds() (LockedFunds, error) { + return LockedFunds{ + VestingFunds: s.State.LockedFunds, + InitialPledgeRequirement: s.State.InitialPledge, + PreCommitDeposits: s.State.PreCommitDeposits, + }, nil +} + +func (s *state2) FeeDebt() (abi.TokenAmount, error) { + return s.State.FeeDebt, nil +} + +func (s *state2) InitialPledge() (abi.TokenAmount, error) { + return s.State.InitialPledge, nil +} + +func (s *state2) PreCommitDeposits() (abi.TokenAmount, error) { + return s.State.PreCommitDeposits, nil +} + +// Returns nil, nil if sector is not found +func (s *state2) GetSector(num abi.SectorNumber) (*SectorOnChainInfo, error) { + info, ok, err := s.State.GetSector(s.store, num) + if !ok || err != nil { + return nil, err + } + + ret := fromV2SectorOnChainInfo(*info) + return &ret, nil +} + +func (s *state2) FindSector(num abi.SectorNumber) (*SectorLocation, error) { + dlIdx, partIdx, err := s.State.FindSector(s.store, num) + if err != nil { + return nil, err + } + return &SectorLocation{ + Deadline: dlIdx, + Partition: partIdx, + }, nil +} + +func (s *state2) NumLiveSectors() (uint64, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return 0, err + } + var total uint64 + if err := dls.ForEach(s.store, func(dlIdx uint64, dl *miner2.Deadline) error { + total += dl.LiveSectors + return nil + }); err != nil { + return 0, err + } + return total, nil +} + +// GetSectorExpiration returns the effective expiration of the given sector. +// +// If the sector does not expire early, the Early expiration field is 0. +func (s *state2) GetSectorExpiration(num abi.SectorNumber) (*SectorExpiration, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return nil, err + } + // NOTE: this can be optimized significantly. + // 1. If the sector is non-faulty, it will either expire on-time (can be + // learned from the sector info), or in the next quantized expiration + // epoch (i.e., the first element in the partition's expiration queue. + // 2. If it's faulty, it will expire early within the first 14 entries + // of the expiration queue. + + stopErr := errors.New("stop") + out := SectorExpiration{} + err = dls.ForEach(s.store, func(dlIdx uint64, dl *miner2.Deadline) error { + partitions, err := dl.PartitionsArray(s.store) + if err != nil { + return err + } + quant := s.State.QuantSpecForDeadline(dlIdx) + var part miner2.Partition + return partitions.ForEach(&part, func(partIdx int64) error { + if found, err := part.Sectors.IsSet(uint64(num)); err != nil { + return err + } else if !found { + return nil + } + if found, err := part.Terminated.IsSet(uint64(num)); err != nil { + return err + } else if found { + // already terminated + return stopErr + } + + q, err := miner2.LoadExpirationQueue(s.store, part.ExpirationsEpochs, quant) + if err != nil { + return err + } + var exp miner2.ExpirationSet + return q.ForEach(&exp, func(epoch int64) error { + if early, err := exp.EarlySectors.IsSet(uint64(num)); err != nil { + return err + } else if early { + out.Early = abi.ChainEpoch(epoch) + return nil + } + if onTime, err := exp.OnTimeSectors.IsSet(uint64(num)); err != nil { + return err + } else if onTime { + out.OnTime = abi.ChainEpoch(epoch) + return stopErr + } + return nil + }) + }) + }) + if err == stopErr { + err = nil + } + if err != nil { + return nil, err + } + if out.Early == 0 && out.OnTime == 0 { + return nil, xerrors.Errorf("failed to find sector %d", num) + } + return &out, nil +} + +func (s *state2) GetPrecommittedSector(num abi.SectorNumber) (*SectorPreCommitOnChainInfo, error) { + info, ok, err := s.State.GetPrecommittedSector(s.store, num) + if !ok || err != nil { + return nil, err + } + + ret := fromV2SectorPreCommitOnChainInfo(*info) + + 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 { + return nil, err + } + + // If no sector numbers are specified, load all. + if snos == nil { + infos := make([]*SectorOnChainInfo, 0, sectors.Length()) + var info2 miner2.SectorOnChainInfo + if err := sectors.ForEach(&info2, func(_ int64) error { + info := fromV2SectorOnChainInfo(info2) + infos = append(infos, &info) + return nil + }); err != nil { + return nil, err + } + return infos, nil + } + + // Otherwise, load selected. + infos2, err := sectors.Load(*snos) + if err != nil { + return nil, err + } + infos := make([]*SectorOnChainInfo, len(infos2)) + for i, info2 := range infos2 { + info := fromV2SectorOnChainInfo(*info2) + infos[i] = &info + } + return infos, nil +} + +func (s *state2) loadAllocatedSectorNumbers() (bitfield.BitField, error) { + var allocatedSectors bitfield.BitField + 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 + } + + return allocatedSectors.IsSet(uint64(num)) +} + +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) GetAllocatedSectors() (*bitfield.BitField, error) { + var allocatedSectors bitfield.BitField + if err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors); err != nil { + return nil, err + } + + return &allocatedSectors, nil +} + +func (s *state2) LoadDeadline(idx uint64) (Deadline, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return nil, err + } + dl, err := dls.LoadDeadline(s.store, idx) + if err != nil { + return nil, err + } + return &deadline2{*dl, s.store}, nil +} + +func (s *state2) ForEachDeadline(cb func(uint64, Deadline) error) error { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return err + } + return dls.ForEach(s.store, func(i uint64, dl *miner2.Deadline) error { + return cb(i, &deadline2{*dl, s.store}) + }) +} + +func (s *state2) NumDeadlines() (uint64, error) { + return miner2.WPoStPeriodDeadlines, nil +} + +func (s *state2) DeadlinesChanged(other State) (bool, error) { + other2, ok := other.(*state2) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + + return !s.State.Deadlines.Equals(other2.Deadlines), nil +} + +func (s *state2) MinerInfoChanged(other State) (bool, error) { + other0, ok := other.(*state2) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.Info.Equals(other0.State.Info), nil +} + +func (s *state2) Info() (MinerInfo, error) { + info, err := s.State.GetInfo(s.store) + if err != nil { + return MinerInfo{}, err + } + + wpp, err := info.SealProofType.RegisteredWindowPoStProof() + if err != nil { + return MinerInfo{}, err + } + + mi := MinerInfo{ + Owner: info.Owner, + Worker: info.Worker, + ControlAddresses: info.ControlAddresses, + + PendingWorkerKey: (*WorkerKeyChange)(info.PendingWorkerKey), + + PeerId: info.PeerId, + Multiaddrs: info.Multiaddrs, + WindowPoStProofType: wpp, + SectorSize: info.SectorSize, + WindowPoStPartitionSectors: info.WindowPoStPartitionSectors, + ConsensusFaultElapsed: info.ConsensusFaultElapsed, + } + + return mi, nil +} + +func (s *state2) DeadlineInfo(epoch abi.ChainEpoch) (*dline.Info, error) { + return s.State.DeadlineInfo(epoch), nil +} + +func (s *state2) DeadlineCronActive() (bool, error) { + return true, nil // always active in this version +} + +func (s *state2) sectors() (adt.Array, error) { + return adt2.AsArray(s.store, s.Sectors) +} + +func (s *state2) decodeSectorOnChainInfo(val *cbg.Deferred) (SectorOnChainInfo, error) { + var si miner2.SectorOnChainInfo + err := si.UnmarshalCBOR(bytes.NewReader(val.Raw)) + if err != nil { + return SectorOnChainInfo{}, err + } + + return fromV2SectorOnChainInfo(si), nil +} + +func (s *state2) precommits() (adt.Map, error) { + return adt2.AsMap(s.store, s.PreCommittedSectors) +} + +func (s *state2) decodeSectorPreCommitOnChainInfo(val *cbg.Deferred) (SectorPreCommitOnChainInfo, error) { + var sp miner2.SectorPreCommitOnChainInfo + err := sp.UnmarshalCBOR(bytes.NewReader(val.Raw)) + if err != nil { + return SectorPreCommitOnChainInfo{}, err + } + + return fromV2SectorPreCommitOnChainInfo(sp), nil +} + +func (s *state2) EraseAllUnproven() error { + + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return err + } + + err = dls.ForEach(s.store, func(dindx uint64, dl *miner2.Deadline) error { + ps, err := dl.PartitionsArray(s.store) + if err != nil { + return err + } + + var part miner2.Partition + err = ps.ForEach(&part, func(pindx int64) error { + _ = part.ActivateUnproven() + err = ps.Set(uint64(pindx), &part) + return nil + }) + + if err != nil { + return err + } + + dl.Partitions, err = ps.Root() + if err != nil { + return err + } + + return dls.UpdateDeadline(s.store, dindx, dl) + }) + if err != nil { + return err + } + + return s.State.SaveDeadlines(s.store, dls) + +} + +func (d *deadline2) LoadPartition(idx uint64) (Partition, error) { + p, err := d.Deadline.LoadPartition(d.store, idx) + if err != nil { + return nil, err + } + return &partition2{*p, d.store}, nil +} + +func (d *deadline2) ForEachPartition(cb func(uint64, Partition) error) error { + ps, err := d.Deadline.PartitionsArray(d.store) + if err != nil { + return err + } + var part miner2.Partition + return ps.ForEach(&part, func(i int64) error { + return cb(uint64(i), &partition2{part, d.store}) + }) +} + +func (d *deadline2) PartitionsChanged(other Deadline) (bool, error) { + other2, ok := other.(*deadline2) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + + return !d.Deadline.Partitions.Equals(other2.Deadline.Partitions), nil +} + +func (d *deadline2) PartitionsPoSted() (bitfield.BitField, error) { + return d.Deadline.PostSubmissions, nil +} + +func (d *deadline2) DisputableProofCount() (uint64, error) { + + // field doesn't exist until v3 + return 0, nil + +} + +func (p *partition2) AllSectors() (bitfield.BitField, error) { + return p.Partition.Sectors, nil +} + +func (p *partition2) FaultySectors() (bitfield.BitField, error) { + return p.Partition.Faults, nil +} + +func (p *partition2) RecoveringSectors() (bitfield.BitField, error) { + return p.Partition.Recoveries, nil +} + +func (p *partition2) UnprovenSectors() (bitfield.BitField, error) { + return p.Partition.Unproven, nil +} + +func fromV2SectorOnChainInfo(v2 miner2.SectorOnChainInfo) SectorOnChainInfo { + info := SectorOnChainInfo{ + SectorNumber: v2.SectorNumber, + SealProof: v2.SealProof, + SealedCID: v2.SealedCID, + DealIDs: v2.DealIDs, + Activation: v2.Activation, + Expiration: v2.Expiration, + DealWeight: v2.DealWeight, + VerifiedDealWeight: v2.VerifiedDealWeight, + InitialPledge: v2.InitialPledge, + ExpectedDayReward: v2.ExpectedDayReward, + ExpectedStoragePledge: v2.ExpectedStoragePledge, + } + return info +} + +func fromV2SectorPreCommitOnChainInfo(v2 miner2.SectorPreCommitOnChainInfo) SectorPreCommitOnChainInfo { + ret := SectorPreCommitOnChainInfo{ + Info: SectorPreCommitInfo{ + SealProof: v2.Info.SealProof, + SectorNumber: v2.Info.SectorNumber, + SealedCID: v2.Info.SealedCID, + SealRandEpoch: v2.Info.SealRandEpoch, + DealIDs: v2.Info.DealIDs, + Expiration: v2.Info.Expiration, + UnsealedCid: nil, + }, + PreCommitDeposit: v2.PreCommitDeposit, + PreCommitEpoch: v2.PreCommitEpoch, + } + + return ret +} + +func (s *state2) GetState() interface{} { + return &s.State +} + +func (s *state2) ActorKey() string { + return manifest.MinerKey +} + +func (s *state2) ActorVersion() actorstypes.Version { + return actorstypes.Version2 +} + +func (s *state2) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/miner/v3.go b/chain/actors/builtin/miner/v3.go new file mode 100644 index 000000000..52808da8c --- /dev/null +++ b/chain/actors/builtin/miner/v3.go @@ -0,0 +1,584 @@ +package miner + +import ( + "bytes" + "errors" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-bitfield" + rle "github.com/filecoin-project/go-bitfield/rle" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/dline" + "github.com/filecoin-project/go-state-types/manifest" + builtin3 "github.com/filecoin-project/specs-actors/v3/actors/builtin" + miner3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/miner" + adt3 "github.com/filecoin-project/specs-actors/v3/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state3)(nil) + +func load3(store adt.Store, root cid.Cid) (State, error) { + out := state3{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make3(store adt.Store) (State, error) { + out := state3{store: store} + out.State = miner3.State{} + return &out, nil +} + +type state3 struct { + miner3.State + store adt.Store +} + +type deadline3 struct { + miner3.Deadline + store adt.Store +} + +type partition3 struct { + miner3.Partition + store adt.Store +} + +func (s *state3) AvailableBalance(bal abi.TokenAmount) (available abi.TokenAmount, err error) { + defer func() { + if r := recover(); r != nil { + err = xerrors.Errorf("failed to get available balance: %w", r) + available = abi.NewTokenAmount(0) + } + }() + // this panics if the miner doesnt have enough funds to cover their locked pledge + available, err = s.GetAvailableBalance(bal) + return available, err +} + +func (s *state3) VestedFunds(epoch abi.ChainEpoch) (abi.TokenAmount, error) { + return s.CheckVestedFunds(s.store, epoch) +} + +func (s *state3) LockedFunds() (LockedFunds, error) { + return LockedFunds{ + VestingFunds: s.State.LockedFunds, + InitialPledgeRequirement: s.State.InitialPledge, + PreCommitDeposits: s.State.PreCommitDeposits, + }, nil +} + +func (s *state3) FeeDebt() (abi.TokenAmount, error) { + return s.State.FeeDebt, nil +} + +func (s *state3) InitialPledge() (abi.TokenAmount, error) { + return s.State.InitialPledge, nil +} + +func (s *state3) PreCommitDeposits() (abi.TokenAmount, error) { + return s.State.PreCommitDeposits, nil +} + +// Returns nil, nil if sector is not found +func (s *state3) GetSector(num abi.SectorNumber) (*SectorOnChainInfo, error) { + info, ok, err := s.State.GetSector(s.store, num) + if !ok || err != nil { + return nil, err + } + + ret := fromV3SectorOnChainInfo(*info) + return &ret, nil +} + +func (s *state3) FindSector(num abi.SectorNumber) (*SectorLocation, error) { + dlIdx, partIdx, err := s.State.FindSector(s.store, num) + if err != nil { + return nil, err + } + return &SectorLocation{ + Deadline: dlIdx, + Partition: partIdx, + }, nil +} + +func (s *state3) NumLiveSectors() (uint64, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return 0, err + } + var total uint64 + if err := dls.ForEach(s.store, func(dlIdx uint64, dl *miner3.Deadline) error { + total += dl.LiveSectors + return nil + }); err != nil { + return 0, err + } + return total, nil +} + +// GetSectorExpiration returns the effective expiration of the given sector. +// +// If the sector does not expire early, the Early expiration field is 0. +func (s *state3) GetSectorExpiration(num abi.SectorNumber) (*SectorExpiration, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return nil, err + } + // NOTE: this can be optimized significantly. + // 1. If the sector is non-faulty, it will either expire on-time (can be + // learned from the sector info), or in the next quantized expiration + // epoch (i.e., the first element in the partition's expiration queue. + // 2. If it's faulty, it will expire early within the first 14 entries + // of the expiration queue. + + stopErr := errors.New("stop") + out := SectorExpiration{} + err = dls.ForEach(s.store, func(dlIdx uint64, dl *miner3.Deadline) error { + partitions, err := dl.PartitionsArray(s.store) + if err != nil { + return err + } + quant := s.State.QuantSpecForDeadline(dlIdx) + var part miner3.Partition + return partitions.ForEach(&part, func(partIdx int64) error { + if found, err := part.Sectors.IsSet(uint64(num)); err != nil { + return err + } else if !found { + return nil + } + if found, err := part.Terminated.IsSet(uint64(num)); err != nil { + return err + } else if found { + // already terminated + return stopErr + } + + q, err := miner3.LoadExpirationQueue(s.store, part.ExpirationsEpochs, quant, miner3.PartitionExpirationAmtBitwidth) + if err != nil { + return err + } + var exp miner3.ExpirationSet + return q.ForEach(&exp, func(epoch int64) error { + if early, err := exp.EarlySectors.IsSet(uint64(num)); err != nil { + return err + } else if early { + out.Early = abi.ChainEpoch(epoch) + return nil + } + if onTime, err := exp.OnTimeSectors.IsSet(uint64(num)); err != nil { + return err + } else if onTime { + out.OnTime = abi.ChainEpoch(epoch) + return stopErr + } + return nil + }) + }) + }) + if err == stopErr { + err = nil + } + if err != nil { + return nil, err + } + if out.Early == 0 && out.OnTime == 0 { + return nil, xerrors.Errorf("failed to find sector %d", num) + } + return &out, nil +} + +func (s *state3) GetPrecommittedSector(num abi.SectorNumber) (*SectorPreCommitOnChainInfo, error) { + info, ok, err := s.State.GetPrecommittedSector(s.store, num) + if !ok || err != nil { + return nil, err + } + + ret := fromV3SectorPreCommitOnChainInfo(*info) + + 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 { + return nil, err + } + + // If no sector numbers are specified, load all. + if snos == nil { + infos := make([]*SectorOnChainInfo, 0, sectors.Length()) + var info3 miner3.SectorOnChainInfo + if err := sectors.ForEach(&info3, func(_ int64) error { + info := fromV3SectorOnChainInfo(info3) + infos = append(infos, &info) + return nil + }); err != nil { + return nil, err + } + return infos, nil + } + + // Otherwise, load selected. + infos3, err := sectors.Load(*snos) + if err != nil { + return nil, err + } + infos := make([]*SectorOnChainInfo, len(infos3)) + for i, info3 := range infos3 { + info := fromV3SectorOnChainInfo(*info3) + infos[i] = &info + } + return infos, nil +} + +func (s *state3) loadAllocatedSectorNumbers() (bitfield.BitField, error) { + var allocatedSectors bitfield.BitField + 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 + } + + return allocatedSectors.IsSet(uint64(num)) +} + +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) GetAllocatedSectors() (*bitfield.BitField, error) { + var allocatedSectors bitfield.BitField + if err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors); err != nil { + return nil, err + } + + return &allocatedSectors, nil +} + +func (s *state3) LoadDeadline(idx uint64) (Deadline, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return nil, err + } + dl, err := dls.LoadDeadline(s.store, idx) + if err != nil { + return nil, err + } + return &deadline3{*dl, s.store}, nil +} + +func (s *state3) ForEachDeadline(cb func(uint64, Deadline) error) error { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return err + } + return dls.ForEach(s.store, func(i uint64, dl *miner3.Deadline) error { + return cb(i, &deadline3{*dl, s.store}) + }) +} + +func (s *state3) NumDeadlines() (uint64, error) { + return miner3.WPoStPeriodDeadlines, nil +} + +func (s *state3) DeadlinesChanged(other State) (bool, error) { + other3, ok := other.(*state3) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + + return !s.State.Deadlines.Equals(other3.Deadlines), nil +} + +func (s *state3) MinerInfoChanged(other State) (bool, error) { + other0, ok := other.(*state3) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.Info.Equals(other0.State.Info), nil +} + +func (s *state3) Info() (MinerInfo, error) { + info, err := s.State.GetInfo(s.store) + if err != nil { + return MinerInfo{}, err + } + + mi := MinerInfo{ + Owner: info.Owner, + Worker: info.Worker, + ControlAddresses: info.ControlAddresses, + + PendingWorkerKey: (*WorkerKeyChange)(info.PendingWorkerKey), + + PeerId: info.PeerId, + Multiaddrs: info.Multiaddrs, + WindowPoStProofType: info.WindowPoStProofType, + SectorSize: info.SectorSize, + WindowPoStPartitionSectors: info.WindowPoStPartitionSectors, + ConsensusFaultElapsed: info.ConsensusFaultElapsed, + } + + return mi, nil +} + +func (s *state3) DeadlineInfo(epoch abi.ChainEpoch) (*dline.Info, error) { + return s.State.DeadlineInfo(epoch), nil +} + +func (s *state3) DeadlineCronActive() (bool, error) { + return true, nil // always active in this version +} + +func (s *state3) sectors() (adt.Array, error) { + return adt3.AsArray(s.store, s.Sectors, miner3.SectorsAmtBitwidth) +} + +func (s *state3) decodeSectorOnChainInfo(val *cbg.Deferred) (SectorOnChainInfo, error) { + var si miner3.SectorOnChainInfo + err := si.UnmarshalCBOR(bytes.NewReader(val.Raw)) + if err != nil { + return SectorOnChainInfo{}, err + } + + return fromV3SectorOnChainInfo(si), nil +} + +func (s *state3) precommits() (adt.Map, error) { + return adt3.AsMap(s.store, s.PreCommittedSectors, builtin3.DefaultHamtBitwidth) +} + +func (s *state3) decodeSectorPreCommitOnChainInfo(val *cbg.Deferred) (SectorPreCommitOnChainInfo, error) { + var sp miner3.SectorPreCommitOnChainInfo + err := sp.UnmarshalCBOR(bytes.NewReader(val.Raw)) + if err != nil { + return SectorPreCommitOnChainInfo{}, err + } + + return fromV3SectorPreCommitOnChainInfo(sp), nil +} + +func (s *state3) EraseAllUnproven() error { + + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return err + } + + err = dls.ForEach(s.store, func(dindx uint64, dl *miner3.Deadline) error { + ps, err := dl.PartitionsArray(s.store) + if err != nil { + return err + } + + var part miner3.Partition + err = ps.ForEach(&part, func(pindx int64) error { + _ = part.ActivateUnproven() + err = ps.Set(uint64(pindx), &part) + return nil + }) + + if err != nil { + return err + } + + dl.Partitions, err = ps.Root() + if err != nil { + return err + } + + return dls.UpdateDeadline(s.store, dindx, dl) + }) + if err != nil { + return err + } + + return s.State.SaveDeadlines(s.store, dls) + +} + +func (d *deadline3) LoadPartition(idx uint64) (Partition, error) { + p, err := d.Deadline.LoadPartition(d.store, idx) + if err != nil { + return nil, err + } + return &partition3{*p, d.store}, nil +} + +func (d *deadline3) ForEachPartition(cb func(uint64, Partition) error) error { + ps, err := d.Deadline.PartitionsArray(d.store) + if err != nil { + return err + } + var part miner3.Partition + return ps.ForEach(&part, func(i int64) error { + return cb(uint64(i), &partition3{part, d.store}) + }) +} + +func (d *deadline3) PartitionsChanged(other Deadline) (bool, error) { + other3, ok := other.(*deadline3) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + + return !d.Deadline.Partitions.Equals(other3.Deadline.Partitions), nil +} + +func (d *deadline3) PartitionsPoSted() (bitfield.BitField, error) { + return d.Deadline.PartitionsPoSted, nil +} + +func (d *deadline3) DisputableProofCount() (uint64, error) { + + ops, err := d.OptimisticProofsSnapshotArray(d.store) + if err != nil { + return 0, err + } + + return ops.Length(), nil + +} + +func (p *partition3) AllSectors() (bitfield.BitField, error) { + return p.Partition.Sectors, nil +} + +func (p *partition3) FaultySectors() (bitfield.BitField, error) { + return p.Partition.Faults, nil +} + +func (p *partition3) RecoveringSectors() (bitfield.BitField, error) { + return p.Partition.Recoveries, nil +} + +func (p *partition3) UnprovenSectors() (bitfield.BitField, error) { + return p.Partition.Unproven, nil +} + +func fromV3SectorOnChainInfo(v3 miner3.SectorOnChainInfo) SectorOnChainInfo { + info := SectorOnChainInfo{ + SectorNumber: v3.SectorNumber, + SealProof: v3.SealProof, + SealedCID: v3.SealedCID, + DealIDs: v3.DealIDs, + Activation: v3.Activation, + Expiration: v3.Expiration, + DealWeight: v3.DealWeight, + VerifiedDealWeight: v3.VerifiedDealWeight, + InitialPledge: v3.InitialPledge, + ExpectedDayReward: v3.ExpectedDayReward, + ExpectedStoragePledge: v3.ExpectedStoragePledge, + } + return info +} + +func fromV3SectorPreCommitOnChainInfo(v3 miner3.SectorPreCommitOnChainInfo) SectorPreCommitOnChainInfo { + ret := SectorPreCommitOnChainInfo{ + Info: SectorPreCommitInfo{ + SealProof: v3.Info.SealProof, + SectorNumber: v3.Info.SectorNumber, + SealedCID: v3.Info.SealedCID, + SealRandEpoch: v3.Info.SealRandEpoch, + DealIDs: v3.Info.DealIDs, + Expiration: v3.Info.Expiration, + UnsealedCid: nil, + }, + PreCommitDeposit: v3.PreCommitDeposit, + PreCommitEpoch: v3.PreCommitEpoch, + } + + return ret +} + +func (s *state3) GetState() interface{} { + return &s.State +} + +func (s *state3) ActorKey() string { + return manifest.MinerKey +} + +func (s *state3) ActorVersion() actorstypes.Version { + return actorstypes.Version3 +} + +func (s *state3) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/miner/v4.go b/chain/actors/builtin/miner/v4.go new file mode 100644 index 000000000..5980ef769 --- /dev/null +++ b/chain/actors/builtin/miner/v4.go @@ -0,0 +1,584 @@ +package miner + +import ( + "bytes" + "errors" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-bitfield" + rle "github.com/filecoin-project/go-bitfield/rle" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/dline" + "github.com/filecoin-project/go-state-types/manifest" + builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" + miner4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/miner" + adt4 "github.com/filecoin-project/specs-actors/v4/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state4)(nil) + +func load4(store adt.Store, root cid.Cid) (State, error) { + out := state4{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make4(store adt.Store) (State, error) { + out := state4{store: store} + out.State = miner4.State{} + return &out, nil +} + +type state4 struct { + miner4.State + store adt.Store +} + +type deadline4 struct { + miner4.Deadline + store adt.Store +} + +type partition4 struct { + miner4.Partition + store adt.Store +} + +func (s *state4) AvailableBalance(bal abi.TokenAmount) (available abi.TokenAmount, err error) { + defer func() { + if r := recover(); r != nil { + err = xerrors.Errorf("failed to get available balance: %w", r) + available = abi.NewTokenAmount(0) + } + }() + // this panics if the miner doesnt have enough funds to cover their locked pledge + available, err = s.GetAvailableBalance(bal) + return available, err +} + +func (s *state4) VestedFunds(epoch abi.ChainEpoch) (abi.TokenAmount, error) { + return s.CheckVestedFunds(s.store, epoch) +} + +func (s *state4) LockedFunds() (LockedFunds, error) { + return LockedFunds{ + VestingFunds: s.State.LockedFunds, + InitialPledgeRequirement: s.State.InitialPledge, + PreCommitDeposits: s.State.PreCommitDeposits, + }, nil +} + +func (s *state4) FeeDebt() (abi.TokenAmount, error) { + return s.State.FeeDebt, nil +} + +func (s *state4) InitialPledge() (abi.TokenAmount, error) { + return s.State.InitialPledge, nil +} + +func (s *state4) PreCommitDeposits() (abi.TokenAmount, error) { + return s.State.PreCommitDeposits, nil +} + +// Returns nil, nil if sector is not found +func (s *state4) GetSector(num abi.SectorNumber) (*SectorOnChainInfo, error) { + info, ok, err := s.State.GetSector(s.store, num) + if !ok || err != nil { + return nil, err + } + + ret := fromV4SectorOnChainInfo(*info) + return &ret, nil +} + +func (s *state4) FindSector(num abi.SectorNumber) (*SectorLocation, error) { + dlIdx, partIdx, err := s.State.FindSector(s.store, num) + if err != nil { + return nil, err + } + return &SectorLocation{ + Deadline: dlIdx, + Partition: partIdx, + }, nil +} + +func (s *state4) NumLiveSectors() (uint64, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return 0, err + } + var total uint64 + if err := dls.ForEach(s.store, func(dlIdx uint64, dl *miner4.Deadline) error { + total += dl.LiveSectors + return nil + }); err != nil { + return 0, err + } + return total, nil +} + +// GetSectorExpiration returns the effective expiration of the given sector. +// +// If the sector does not expire early, the Early expiration field is 0. +func (s *state4) GetSectorExpiration(num abi.SectorNumber) (*SectorExpiration, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return nil, err + } + // NOTE: this can be optimized significantly. + // 1. If the sector is non-faulty, it will either expire on-time (can be + // learned from the sector info), or in the next quantized expiration + // epoch (i.e., the first element in the partition's expiration queue. + // 2. If it's faulty, it will expire early within the first 14 entries + // of the expiration queue. + + stopErr := errors.New("stop") + out := SectorExpiration{} + err = dls.ForEach(s.store, func(dlIdx uint64, dl *miner4.Deadline) error { + partitions, err := dl.PartitionsArray(s.store) + if err != nil { + return err + } + quant := s.State.QuantSpecForDeadline(dlIdx) + var part miner4.Partition + return partitions.ForEach(&part, func(partIdx int64) error { + if found, err := part.Sectors.IsSet(uint64(num)); err != nil { + return err + } else if !found { + return nil + } + if found, err := part.Terminated.IsSet(uint64(num)); err != nil { + return err + } else if found { + // already terminated + return stopErr + } + + q, err := miner4.LoadExpirationQueue(s.store, part.ExpirationsEpochs, quant, miner4.PartitionExpirationAmtBitwidth) + if err != nil { + return err + } + var exp miner4.ExpirationSet + return q.ForEach(&exp, func(epoch int64) error { + if early, err := exp.EarlySectors.IsSet(uint64(num)); err != nil { + return err + } else if early { + out.Early = abi.ChainEpoch(epoch) + return nil + } + if onTime, err := exp.OnTimeSectors.IsSet(uint64(num)); err != nil { + return err + } else if onTime { + out.OnTime = abi.ChainEpoch(epoch) + return stopErr + } + return nil + }) + }) + }) + if err == stopErr { + err = nil + } + if err != nil { + return nil, err + } + if out.Early == 0 && out.OnTime == 0 { + return nil, xerrors.Errorf("failed to find sector %d", num) + } + return &out, nil +} + +func (s *state4) GetPrecommittedSector(num abi.SectorNumber) (*SectorPreCommitOnChainInfo, error) { + info, ok, err := s.State.GetPrecommittedSector(s.store, num) + if !ok || err != nil { + return nil, err + } + + ret := fromV4SectorPreCommitOnChainInfo(*info) + + 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 { + return nil, err + } + + // If no sector numbers are specified, load all. + if snos == nil { + infos := make([]*SectorOnChainInfo, 0, sectors.Length()) + var info4 miner4.SectorOnChainInfo + if err := sectors.ForEach(&info4, func(_ int64) error { + info := fromV4SectorOnChainInfo(info4) + infos = append(infos, &info) + return nil + }); err != nil { + return nil, err + } + return infos, nil + } + + // Otherwise, load selected. + infos4, err := sectors.Load(*snos) + if err != nil { + return nil, err + } + infos := make([]*SectorOnChainInfo, len(infos4)) + for i, info4 := range infos4 { + info := fromV4SectorOnChainInfo(*info4) + infos[i] = &info + } + return infos, nil +} + +func (s *state4) loadAllocatedSectorNumbers() (bitfield.BitField, error) { + var allocatedSectors bitfield.BitField + 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 + } + + return allocatedSectors.IsSet(uint64(num)) +} + +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) GetAllocatedSectors() (*bitfield.BitField, error) { + var allocatedSectors bitfield.BitField + if err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors); err != nil { + return nil, err + } + + return &allocatedSectors, nil +} + +func (s *state4) LoadDeadline(idx uint64) (Deadline, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return nil, err + } + dl, err := dls.LoadDeadline(s.store, idx) + if err != nil { + return nil, err + } + return &deadline4{*dl, s.store}, nil +} + +func (s *state4) ForEachDeadline(cb func(uint64, Deadline) error) error { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return err + } + return dls.ForEach(s.store, func(i uint64, dl *miner4.Deadline) error { + return cb(i, &deadline4{*dl, s.store}) + }) +} + +func (s *state4) NumDeadlines() (uint64, error) { + return miner4.WPoStPeriodDeadlines, nil +} + +func (s *state4) DeadlinesChanged(other State) (bool, error) { + other4, ok := other.(*state4) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + + return !s.State.Deadlines.Equals(other4.Deadlines), nil +} + +func (s *state4) MinerInfoChanged(other State) (bool, error) { + other0, ok := other.(*state4) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.Info.Equals(other0.State.Info), nil +} + +func (s *state4) Info() (MinerInfo, error) { + info, err := s.State.GetInfo(s.store) + if err != nil { + return MinerInfo{}, err + } + + mi := MinerInfo{ + Owner: info.Owner, + Worker: info.Worker, + ControlAddresses: info.ControlAddresses, + + PendingWorkerKey: (*WorkerKeyChange)(info.PendingWorkerKey), + + PeerId: info.PeerId, + Multiaddrs: info.Multiaddrs, + WindowPoStProofType: info.WindowPoStProofType, + SectorSize: info.SectorSize, + WindowPoStPartitionSectors: info.WindowPoStPartitionSectors, + ConsensusFaultElapsed: info.ConsensusFaultElapsed, + } + + return mi, nil +} + +func (s *state4) DeadlineInfo(epoch abi.ChainEpoch) (*dline.Info, error) { + return s.State.RecordedDeadlineInfo(epoch), nil +} + +func (s *state4) DeadlineCronActive() (bool, error) { + return s.State.DeadlineCronActive, nil +} + +func (s *state4) sectors() (adt.Array, error) { + return adt4.AsArray(s.store, s.Sectors, miner4.SectorsAmtBitwidth) +} + +func (s *state4) decodeSectorOnChainInfo(val *cbg.Deferred) (SectorOnChainInfo, error) { + var si miner4.SectorOnChainInfo + err := si.UnmarshalCBOR(bytes.NewReader(val.Raw)) + if err != nil { + return SectorOnChainInfo{}, err + } + + return fromV4SectorOnChainInfo(si), nil +} + +func (s *state4) precommits() (adt.Map, error) { + return adt4.AsMap(s.store, s.PreCommittedSectors, builtin4.DefaultHamtBitwidth) +} + +func (s *state4) decodeSectorPreCommitOnChainInfo(val *cbg.Deferred) (SectorPreCommitOnChainInfo, error) { + var sp miner4.SectorPreCommitOnChainInfo + err := sp.UnmarshalCBOR(bytes.NewReader(val.Raw)) + if err != nil { + return SectorPreCommitOnChainInfo{}, err + } + + return fromV4SectorPreCommitOnChainInfo(sp), nil +} + +func (s *state4) EraseAllUnproven() error { + + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return err + } + + err = dls.ForEach(s.store, func(dindx uint64, dl *miner4.Deadline) error { + ps, err := dl.PartitionsArray(s.store) + if err != nil { + return err + } + + var part miner4.Partition + err = ps.ForEach(&part, func(pindx int64) error { + _ = part.ActivateUnproven() + err = ps.Set(uint64(pindx), &part) + return nil + }) + + if err != nil { + return err + } + + dl.Partitions, err = ps.Root() + if err != nil { + return err + } + + return dls.UpdateDeadline(s.store, dindx, dl) + }) + if err != nil { + return err + } + + return s.State.SaveDeadlines(s.store, dls) + +} + +func (d *deadline4) LoadPartition(idx uint64) (Partition, error) { + p, err := d.Deadline.LoadPartition(d.store, idx) + if err != nil { + return nil, err + } + return &partition4{*p, d.store}, nil +} + +func (d *deadline4) ForEachPartition(cb func(uint64, Partition) error) error { + ps, err := d.Deadline.PartitionsArray(d.store) + if err != nil { + return err + } + var part miner4.Partition + return ps.ForEach(&part, func(i int64) error { + return cb(uint64(i), &partition4{part, d.store}) + }) +} + +func (d *deadline4) PartitionsChanged(other Deadline) (bool, error) { + other4, ok := other.(*deadline4) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + + return !d.Deadline.Partitions.Equals(other4.Deadline.Partitions), nil +} + +func (d *deadline4) PartitionsPoSted() (bitfield.BitField, error) { + return d.Deadline.PartitionsPoSted, nil +} + +func (d *deadline4) DisputableProofCount() (uint64, error) { + + ops, err := d.OptimisticProofsSnapshotArray(d.store) + if err != nil { + return 0, err + } + + return ops.Length(), nil + +} + +func (p *partition4) AllSectors() (bitfield.BitField, error) { + return p.Partition.Sectors, nil +} + +func (p *partition4) FaultySectors() (bitfield.BitField, error) { + return p.Partition.Faults, nil +} + +func (p *partition4) RecoveringSectors() (bitfield.BitField, error) { + return p.Partition.Recoveries, nil +} + +func (p *partition4) UnprovenSectors() (bitfield.BitField, error) { + return p.Partition.Unproven, nil +} + +func fromV4SectorOnChainInfo(v4 miner4.SectorOnChainInfo) SectorOnChainInfo { + info := SectorOnChainInfo{ + SectorNumber: v4.SectorNumber, + SealProof: v4.SealProof, + SealedCID: v4.SealedCID, + DealIDs: v4.DealIDs, + Activation: v4.Activation, + Expiration: v4.Expiration, + DealWeight: v4.DealWeight, + VerifiedDealWeight: v4.VerifiedDealWeight, + InitialPledge: v4.InitialPledge, + ExpectedDayReward: v4.ExpectedDayReward, + ExpectedStoragePledge: v4.ExpectedStoragePledge, + } + return info +} + +func fromV4SectorPreCommitOnChainInfo(v4 miner4.SectorPreCommitOnChainInfo) SectorPreCommitOnChainInfo { + ret := SectorPreCommitOnChainInfo{ + Info: SectorPreCommitInfo{ + SealProof: v4.Info.SealProof, + SectorNumber: v4.Info.SectorNumber, + SealedCID: v4.Info.SealedCID, + SealRandEpoch: v4.Info.SealRandEpoch, + DealIDs: v4.Info.DealIDs, + Expiration: v4.Info.Expiration, + UnsealedCid: nil, + }, + PreCommitDeposit: v4.PreCommitDeposit, + PreCommitEpoch: v4.PreCommitEpoch, + } + + return ret +} + +func (s *state4) GetState() interface{} { + return &s.State +} + +func (s *state4) ActorKey() string { + return manifest.MinerKey +} + +func (s *state4) ActorVersion() actorstypes.Version { + return actorstypes.Version4 +} + +func (s *state4) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/miner/v5.go b/chain/actors/builtin/miner/v5.go new file mode 100644 index 000000000..886300ea3 --- /dev/null +++ b/chain/actors/builtin/miner/v5.go @@ -0,0 +1,584 @@ +package miner + +import ( + "bytes" + "errors" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-bitfield" + rle "github.com/filecoin-project/go-bitfield/rle" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/dline" + "github.com/filecoin-project/go-state-types/manifest" + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" + adt5 "github.com/filecoin-project/specs-actors/v5/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state5)(nil) + +func load5(store adt.Store, root cid.Cid) (State, error) { + out := state5{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make5(store adt.Store) (State, error) { + out := state5{store: store} + out.State = miner5.State{} + return &out, nil +} + +type state5 struct { + miner5.State + store adt.Store +} + +type deadline5 struct { + miner5.Deadline + store adt.Store +} + +type partition5 struct { + miner5.Partition + store adt.Store +} + +func (s *state5) AvailableBalance(bal abi.TokenAmount) (available abi.TokenAmount, err error) { + defer func() { + if r := recover(); r != nil { + err = xerrors.Errorf("failed to get available balance: %w", r) + available = abi.NewTokenAmount(0) + } + }() + // this panics if the miner doesnt have enough funds to cover their locked pledge + available, err = s.GetAvailableBalance(bal) + return available, err +} + +func (s *state5) VestedFunds(epoch abi.ChainEpoch) (abi.TokenAmount, error) { + return s.CheckVestedFunds(s.store, epoch) +} + +func (s *state5) LockedFunds() (LockedFunds, error) { + return LockedFunds{ + VestingFunds: s.State.LockedFunds, + InitialPledgeRequirement: s.State.InitialPledge, + PreCommitDeposits: s.State.PreCommitDeposits, + }, nil +} + +func (s *state5) FeeDebt() (abi.TokenAmount, error) { + return s.State.FeeDebt, nil +} + +func (s *state5) InitialPledge() (abi.TokenAmount, error) { + return s.State.InitialPledge, nil +} + +func (s *state5) PreCommitDeposits() (abi.TokenAmount, error) { + return s.State.PreCommitDeposits, nil +} + +// Returns nil, nil if sector is not found +func (s *state5) GetSector(num abi.SectorNumber) (*SectorOnChainInfo, error) { + info, ok, err := s.State.GetSector(s.store, num) + if !ok || err != nil { + return nil, err + } + + ret := fromV5SectorOnChainInfo(*info) + return &ret, nil +} + +func (s *state5) FindSector(num abi.SectorNumber) (*SectorLocation, error) { + dlIdx, partIdx, err := s.State.FindSector(s.store, num) + if err != nil { + return nil, err + } + return &SectorLocation{ + Deadline: dlIdx, + Partition: partIdx, + }, nil +} + +func (s *state5) NumLiveSectors() (uint64, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return 0, err + } + var total uint64 + if err := dls.ForEach(s.store, func(dlIdx uint64, dl *miner5.Deadline) error { + total += dl.LiveSectors + return nil + }); err != nil { + return 0, err + } + return total, nil +} + +// GetSectorExpiration returns the effective expiration of the given sector. +// +// If the sector does not expire early, the Early expiration field is 0. +func (s *state5) GetSectorExpiration(num abi.SectorNumber) (*SectorExpiration, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return nil, err + } + // NOTE: this can be optimized significantly. + // 1. If the sector is non-faulty, it will either expire on-time (can be + // learned from the sector info), or in the next quantized expiration + // epoch (i.e., the first element in the partition's expiration queue. + // 2. If it's faulty, it will expire early within the first 14 entries + // of the expiration queue. + + stopErr := errors.New("stop") + out := SectorExpiration{} + err = dls.ForEach(s.store, func(dlIdx uint64, dl *miner5.Deadline) error { + partitions, err := dl.PartitionsArray(s.store) + if err != nil { + return err + } + quant := s.State.QuantSpecForDeadline(dlIdx) + var part miner5.Partition + return partitions.ForEach(&part, func(partIdx int64) error { + if found, err := part.Sectors.IsSet(uint64(num)); err != nil { + return err + } else if !found { + return nil + } + if found, err := part.Terminated.IsSet(uint64(num)); err != nil { + return err + } else if found { + // already terminated + return stopErr + } + + q, err := miner5.LoadExpirationQueue(s.store, part.ExpirationsEpochs, quant, miner5.PartitionExpirationAmtBitwidth) + if err != nil { + return err + } + var exp miner5.ExpirationSet + return q.ForEach(&exp, func(epoch int64) error { + if early, err := exp.EarlySectors.IsSet(uint64(num)); err != nil { + return err + } else if early { + out.Early = abi.ChainEpoch(epoch) + return nil + } + if onTime, err := exp.OnTimeSectors.IsSet(uint64(num)); err != nil { + return err + } else if onTime { + out.OnTime = abi.ChainEpoch(epoch) + return stopErr + } + return nil + }) + }) + }) + if err == stopErr { + err = nil + } + if err != nil { + return nil, err + } + if out.Early == 0 && out.OnTime == 0 { + return nil, xerrors.Errorf("failed to find sector %d", num) + } + return &out, nil +} + +func (s *state5) GetPrecommittedSector(num abi.SectorNumber) (*SectorPreCommitOnChainInfo, error) { + info, ok, err := s.State.GetPrecommittedSector(s.store, num) + if !ok || err != nil { + return nil, err + } + + ret := fromV5SectorPreCommitOnChainInfo(*info) + + 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 { + return nil, err + } + + // If no sector numbers are specified, load all. + if snos == nil { + infos := make([]*SectorOnChainInfo, 0, sectors.Length()) + var info5 miner5.SectorOnChainInfo + if err := sectors.ForEach(&info5, func(_ int64) error { + info := fromV5SectorOnChainInfo(info5) + infos = append(infos, &info) + return nil + }); err != nil { + return nil, err + } + return infos, nil + } + + // Otherwise, load selected. + infos5, err := sectors.Load(*snos) + if err != nil { + return nil, err + } + infos := make([]*SectorOnChainInfo, len(infos5)) + for i, info5 := range infos5 { + info := fromV5SectorOnChainInfo(*info5) + infos[i] = &info + } + return infos, nil +} + +func (s *state5) loadAllocatedSectorNumbers() (bitfield.BitField, error) { + var allocatedSectors bitfield.BitField + 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 + } + + return allocatedSectors.IsSet(uint64(num)) +} + +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) GetAllocatedSectors() (*bitfield.BitField, error) { + var allocatedSectors bitfield.BitField + if err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors); err != nil { + return nil, err + } + + return &allocatedSectors, nil +} + +func (s *state5) LoadDeadline(idx uint64) (Deadline, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return nil, err + } + dl, err := dls.LoadDeadline(s.store, idx) + if err != nil { + return nil, err + } + return &deadline5{*dl, s.store}, nil +} + +func (s *state5) ForEachDeadline(cb func(uint64, Deadline) error) error { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return err + } + return dls.ForEach(s.store, func(i uint64, dl *miner5.Deadline) error { + return cb(i, &deadline5{*dl, s.store}) + }) +} + +func (s *state5) NumDeadlines() (uint64, error) { + return miner5.WPoStPeriodDeadlines, nil +} + +func (s *state5) DeadlinesChanged(other State) (bool, error) { + other5, ok := other.(*state5) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + + return !s.State.Deadlines.Equals(other5.Deadlines), nil +} + +func (s *state5) MinerInfoChanged(other State) (bool, error) { + other0, ok := other.(*state5) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.Info.Equals(other0.State.Info), nil +} + +func (s *state5) Info() (MinerInfo, error) { + info, err := s.State.GetInfo(s.store) + if err != nil { + return MinerInfo{}, err + } + + mi := MinerInfo{ + Owner: info.Owner, + Worker: info.Worker, + ControlAddresses: info.ControlAddresses, + + PendingWorkerKey: (*WorkerKeyChange)(info.PendingWorkerKey), + + PeerId: info.PeerId, + Multiaddrs: info.Multiaddrs, + WindowPoStProofType: info.WindowPoStProofType, + SectorSize: info.SectorSize, + WindowPoStPartitionSectors: info.WindowPoStPartitionSectors, + ConsensusFaultElapsed: info.ConsensusFaultElapsed, + } + + return mi, nil +} + +func (s *state5) DeadlineInfo(epoch abi.ChainEpoch) (*dline.Info, error) { + return s.State.RecordedDeadlineInfo(epoch), nil +} + +func (s *state5) DeadlineCronActive() (bool, error) { + return s.State.DeadlineCronActive, nil +} + +func (s *state5) sectors() (adt.Array, error) { + return adt5.AsArray(s.store, s.Sectors, miner5.SectorsAmtBitwidth) +} + +func (s *state5) decodeSectorOnChainInfo(val *cbg.Deferred) (SectorOnChainInfo, error) { + var si miner5.SectorOnChainInfo + err := si.UnmarshalCBOR(bytes.NewReader(val.Raw)) + if err != nil { + return SectorOnChainInfo{}, err + } + + return fromV5SectorOnChainInfo(si), nil +} + +func (s *state5) precommits() (adt.Map, error) { + return adt5.AsMap(s.store, s.PreCommittedSectors, builtin5.DefaultHamtBitwidth) +} + +func (s *state5) decodeSectorPreCommitOnChainInfo(val *cbg.Deferred) (SectorPreCommitOnChainInfo, error) { + var sp miner5.SectorPreCommitOnChainInfo + err := sp.UnmarshalCBOR(bytes.NewReader(val.Raw)) + if err != nil { + return SectorPreCommitOnChainInfo{}, err + } + + return fromV5SectorPreCommitOnChainInfo(sp), nil +} + +func (s *state5) EraseAllUnproven() error { + + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return err + } + + err = dls.ForEach(s.store, func(dindx uint64, dl *miner5.Deadline) error { + ps, err := dl.PartitionsArray(s.store) + if err != nil { + return err + } + + var part miner5.Partition + err = ps.ForEach(&part, func(pindx int64) error { + _ = part.ActivateUnproven() + err = ps.Set(uint64(pindx), &part) + return nil + }) + + if err != nil { + return err + } + + dl.Partitions, err = ps.Root() + if err != nil { + return err + } + + return dls.UpdateDeadline(s.store, dindx, dl) + }) + if err != nil { + return err + } + + return s.State.SaveDeadlines(s.store, dls) + +} + +func (d *deadline5) LoadPartition(idx uint64) (Partition, error) { + p, err := d.Deadline.LoadPartition(d.store, idx) + if err != nil { + return nil, err + } + return &partition5{*p, d.store}, nil +} + +func (d *deadline5) ForEachPartition(cb func(uint64, Partition) error) error { + ps, err := d.Deadline.PartitionsArray(d.store) + if err != nil { + return err + } + var part miner5.Partition + return ps.ForEach(&part, func(i int64) error { + return cb(uint64(i), &partition5{part, d.store}) + }) +} + +func (d *deadline5) PartitionsChanged(other Deadline) (bool, error) { + other5, ok := other.(*deadline5) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + + return !d.Deadline.Partitions.Equals(other5.Deadline.Partitions), nil +} + +func (d *deadline5) PartitionsPoSted() (bitfield.BitField, error) { + return d.Deadline.PartitionsPoSted, nil +} + +func (d *deadline5) DisputableProofCount() (uint64, error) { + + ops, err := d.OptimisticProofsSnapshotArray(d.store) + if err != nil { + return 0, err + } + + return ops.Length(), nil + +} + +func (p *partition5) AllSectors() (bitfield.BitField, error) { + return p.Partition.Sectors, nil +} + +func (p *partition5) FaultySectors() (bitfield.BitField, error) { + return p.Partition.Faults, nil +} + +func (p *partition5) RecoveringSectors() (bitfield.BitField, error) { + return p.Partition.Recoveries, nil +} + +func (p *partition5) UnprovenSectors() (bitfield.BitField, error) { + return p.Partition.Unproven, nil +} + +func fromV5SectorOnChainInfo(v5 miner5.SectorOnChainInfo) SectorOnChainInfo { + info := SectorOnChainInfo{ + SectorNumber: v5.SectorNumber, + SealProof: v5.SealProof, + SealedCID: v5.SealedCID, + DealIDs: v5.DealIDs, + Activation: v5.Activation, + Expiration: v5.Expiration, + DealWeight: v5.DealWeight, + VerifiedDealWeight: v5.VerifiedDealWeight, + InitialPledge: v5.InitialPledge, + ExpectedDayReward: v5.ExpectedDayReward, + ExpectedStoragePledge: v5.ExpectedStoragePledge, + } + return info +} + +func fromV5SectorPreCommitOnChainInfo(v5 miner5.SectorPreCommitOnChainInfo) SectorPreCommitOnChainInfo { + ret := SectorPreCommitOnChainInfo{ + Info: SectorPreCommitInfo{ + SealProof: v5.Info.SealProof, + SectorNumber: v5.Info.SectorNumber, + SealedCID: v5.Info.SealedCID, + SealRandEpoch: v5.Info.SealRandEpoch, + DealIDs: v5.Info.DealIDs, + Expiration: v5.Info.Expiration, + UnsealedCid: nil, + }, + PreCommitDeposit: v5.PreCommitDeposit, + PreCommitEpoch: v5.PreCommitEpoch, + } + + return ret +} + +func (s *state5) GetState() interface{} { + return &s.State +} + +func (s *state5) ActorKey() string { + return manifest.MinerKey +} + +func (s *state5) ActorVersion() actorstypes.Version { + return actorstypes.Version5 +} + +func (s *state5) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/miner/v6.go b/chain/actors/builtin/miner/v6.go new file mode 100644 index 000000000..4737b0ee2 --- /dev/null +++ b/chain/actors/builtin/miner/v6.go @@ -0,0 +1,584 @@ +package miner + +import ( + "bytes" + "errors" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-bitfield" + rle "github.com/filecoin-project/go-bitfield/rle" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/dline" + "github.com/filecoin-project/go-state-types/manifest" + builtin6 "github.com/filecoin-project/specs-actors/v6/actors/builtin" + miner6 "github.com/filecoin-project/specs-actors/v6/actors/builtin/miner" + adt6 "github.com/filecoin-project/specs-actors/v6/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state6)(nil) + +func load6(store adt.Store, root cid.Cid) (State, error) { + out := state6{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make6(store adt.Store) (State, error) { + out := state6{store: store} + out.State = miner6.State{} + return &out, nil +} + +type state6 struct { + miner6.State + store adt.Store +} + +type deadline6 struct { + miner6.Deadline + store adt.Store +} + +type partition6 struct { + miner6.Partition + store adt.Store +} + +func (s *state6) AvailableBalance(bal abi.TokenAmount) (available abi.TokenAmount, err error) { + defer func() { + if r := recover(); r != nil { + err = xerrors.Errorf("failed to get available balance: %w", r) + available = abi.NewTokenAmount(0) + } + }() + // this panics if the miner doesnt have enough funds to cover their locked pledge + available, err = s.GetAvailableBalance(bal) + return available, err +} + +func (s *state6) VestedFunds(epoch abi.ChainEpoch) (abi.TokenAmount, error) { + return s.CheckVestedFunds(s.store, epoch) +} + +func (s *state6) LockedFunds() (LockedFunds, error) { + return LockedFunds{ + VestingFunds: s.State.LockedFunds, + InitialPledgeRequirement: s.State.InitialPledge, + PreCommitDeposits: s.State.PreCommitDeposits, + }, nil +} + +func (s *state6) FeeDebt() (abi.TokenAmount, error) { + return s.State.FeeDebt, nil +} + +func (s *state6) InitialPledge() (abi.TokenAmount, error) { + return s.State.InitialPledge, nil +} + +func (s *state6) PreCommitDeposits() (abi.TokenAmount, error) { + return s.State.PreCommitDeposits, nil +} + +// Returns nil, nil if sector is not found +func (s *state6) GetSector(num abi.SectorNumber) (*SectorOnChainInfo, error) { + info, ok, err := s.State.GetSector(s.store, num) + if !ok || err != nil { + return nil, err + } + + ret := fromV6SectorOnChainInfo(*info) + return &ret, nil +} + +func (s *state6) FindSector(num abi.SectorNumber) (*SectorLocation, error) { + dlIdx, partIdx, err := s.State.FindSector(s.store, num) + if err != nil { + return nil, err + } + return &SectorLocation{ + Deadline: dlIdx, + Partition: partIdx, + }, nil +} + +func (s *state6) NumLiveSectors() (uint64, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return 0, err + } + var total uint64 + if err := dls.ForEach(s.store, func(dlIdx uint64, dl *miner6.Deadline) error { + total += dl.LiveSectors + return nil + }); err != nil { + return 0, err + } + return total, nil +} + +// GetSectorExpiration returns the effective expiration of the given sector. +// +// If the sector does not expire early, the Early expiration field is 0. +func (s *state6) GetSectorExpiration(num abi.SectorNumber) (*SectorExpiration, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return nil, err + } + // NOTE: this can be optimized significantly. + // 1. If the sector is non-faulty, it will either expire on-time (can be + // learned from the sector info), or in the next quantized expiration + // epoch (i.e., the first element in the partition's expiration queue. + // 2. If it's faulty, it will expire early within the first 42 entries + // of the expiration queue. + + stopErr := errors.New("stop") + out := SectorExpiration{} + err = dls.ForEach(s.store, func(dlIdx uint64, dl *miner6.Deadline) error { + partitions, err := dl.PartitionsArray(s.store) + if err != nil { + return err + } + quant := s.State.QuantSpecForDeadline(dlIdx) + var part miner6.Partition + return partitions.ForEach(&part, func(partIdx int64) error { + if found, err := part.Sectors.IsSet(uint64(num)); err != nil { + return err + } else if !found { + return nil + } + if found, err := part.Terminated.IsSet(uint64(num)); err != nil { + return err + } else if found { + // already terminated + return stopErr + } + + q, err := miner6.LoadExpirationQueue(s.store, part.ExpirationsEpochs, quant, miner6.PartitionExpirationAmtBitwidth) + if err != nil { + return err + } + var exp miner6.ExpirationSet + return q.ForEach(&exp, func(epoch int64) error { + if early, err := exp.EarlySectors.IsSet(uint64(num)); err != nil { + return err + } else if early { + out.Early = abi.ChainEpoch(epoch) + return nil + } + if onTime, err := exp.OnTimeSectors.IsSet(uint64(num)); err != nil { + return err + } else if onTime { + out.OnTime = abi.ChainEpoch(epoch) + return stopErr + } + return nil + }) + }) + }) + if err == stopErr { + err = nil + } + if err != nil { + return nil, err + } + if out.Early == 0 && out.OnTime == 0 { + return nil, xerrors.Errorf("failed to find sector %d", num) + } + return &out, nil +} + +func (s *state6) GetPrecommittedSector(num abi.SectorNumber) (*SectorPreCommitOnChainInfo, error) { + info, ok, err := s.State.GetPrecommittedSector(s.store, num) + if !ok || err != nil { + return nil, err + } + + ret := fromV6SectorPreCommitOnChainInfo(*info) + + return &ret, nil +} + +func (s *state6) ForEachPrecommittedSector(cb func(SectorPreCommitOnChainInfo) error) error { + precommitted, err := adt6.AsMap(s.store, s.State.PreCommittedSectors, builtin6.DefaultHamtBitwidth) + if err != nil { + return err + } + + var info miner6.SectorPreCommitOnChainInfo + if err := precommitted.ForEach(&info, func(_ string) error { + return cb(fromV6SectorPreCommitOnChainInfo(info)) + }); err != nil { + return err + } + + return nil +} + +func (s *state6) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, error) { + sectors, err := miner6.LoadSectors(s.store, s.State.Sectors) + if err != nil { + return nil, err + } + + // If no sector numbers are specified, load all. + if snos == nil { + infos := make([]*SectorOnChainInfo, 0, sectors.Length()) + var info6 miner6.SectorOnChainInfo + if err := sectors.ForEach(&info6, func(_ int64) error { + info := fromV6SectorOnChainInfo(info6) + infos = append(infos, &info) + return nil + }); err != nil { + return nil, err + } + return infos, nil + } + + // Otherwise, load selected. + infos6, err := sectors.Load(*snos) + if err != nil { + return nil, err + } + infos := make([]*SectorOnChainInfo, len(infos6)) + for i, info6 := range infos6 { + info := fromV6SectorOnChainInfo(*info6) + infos[i] = &info + } + return infos, nil +} + +func (s *state6) loadAllocatedSectorNumbers() (bitfield.BitField, error) { + var allocatedSectors bitfield.BitField + err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors) + return allocatedSectors, err +} + +func (s *state6) IsAllocated(num abi.SectorNumber) (bool, error) { + allocatedSectors, err := s.loadAllocatedSectorNumbers() + if err != nil { + return false, err + } + + return allocatedSectors.IsSet(uint64(num)) +} + +func (s *state6) GetProvingPeriodStart() (abi.ChainEpoch, error) { + return s.State.ProvingPeriodStart, nil +} + +func (s *state6) 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 *state6) GetAllocatedSectors() (*bitfield.BitField, error) { + var allocatedSectors bitfield.BitField + if err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors); err != nil { + return nil, err + } + + return &allocatedSectors, nil +} + +func (s *state6) LoadDeadline(idx uint64) (Deadline, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return nil, err + } + dl, err := dls.LoadDeadline(s.store, idx) + if err != nil { + return nil, err + } + return &deadline6{*dl, s.store}, nil +} + +func (s *state6) ForEachDeadline(cb func(uint64, Deadline) error) error { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return err + } + return dls.ForEach(s.store, func(i uint64, dl *miner6.Deadline) error { + return cb(i, &deadline6{*dl, s.store}) + }) +} + +func (s *state6) NumDeadlines() (uint64, error) { + return miner6.WPoStPeriodDeadlines, nil +} + +func (s *state6) DeadlinesChanged(other State) (bool, error) { + other6, ok := other.(*state6) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + + return !s.State.Deadlines.Equals(other6.Deadlines), nil +} + +func (s *state6) MinerInfoChanged(other State) (bool, error) { + other0, ok := other.(*state6) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.Info.Equals(other0.State.Info), nil +} + +func (s *state6) Info() (MinerInfo, error) { + info, err := s.State.GetInfo(s.store) + if err != nil { + return MinerInfo{}, err + } + + mi := MinerInfo{ + Owner: info.Owner, + Worker: info.Worker, + ControlAddresses: info.ControlAddresses, + + PendingWorkerKey: (*WorkerKeyChange)(info.PendingWorkerKey), + + PeerId: info.PeerId, + Multiaddrs: info.Multiaddrs, + WindowPoStProofType: info.WindowPoStProofType, + SectorSize: info.SectorSize, + WindowPoStPartitionSectors: info.WindowPoStPartitionSectors, + ConsensusFaultElapsed: info.ConsensusFaultElapsed, + } + + return mi, nil +} + +func (s *state6) DeadlineInfo(epoch abi.ChainEpoch) (*dline.Info, error) { + return s.State.RecordedDeadlineInfo(epoch), nil +} + +func (s *state6) DeadlineCronActive() (bool, error) { + return s.State.DeadlineCronActive, nil +} + +func (s *state6) sectors() (adt.Array, error) { + return adt6.AsArray(s.store, s.Sectors, miner6.SectorsAmtBitwidth) +} + +func (s *state6) decodeSectorOnChainInfo(val *cbg.Deferred) (SectorOnChainInfo, error) { + var si miner6.SectorOnChainInfo + err := si.UnmarshalCBOR(bytes.NewReader(val.Raw)) + if err != nil { + return SectorOnChainInfo{}, err + } + + return fromV6SectorOnChainInfo(si), nil +} + +func (s *state6) precommits() (adt.Map, error) { + return adt6.AsMap(s.store, s.PreCommittedSectors, builtin6.DefaultHamtBitwidth) +} + +func (s *state6) decodeSectorPreCommitOnChainInfo(val *cbg.Deferred) (SectorPreCommitOnChainInfo, error) { + var sp miner6.SectorPreCommitOnChainInfo + err := sp.UnmarshalCBOR(bytes.NewReader(val.Raw)) + if err != nil { + return SectorPreCommitOnChainInfo{}, err + } + + return fromV6SectorPreCommitOnChainInfo(sp), nil +} + +func (s *state6) EraseAllUnproven() error { + + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return err + } + + err = dls.ForEach(s.store, func(dindx uint64, dl *miner6.Deadline) error { + ps, err := dl.PartitionsArray(s.store) + if err != nil { + return err + } + + var part miner6.Partition + err = ps.ForEach(&part, func(pindx int64) error { + _ = part.ActivateUnproven() + err = ps.Set(uint64(pindx), &part) + return nil + }) + + if err != nil { + return err + } + + dl.Partitions, err = ps.Root() + if err != nil { + return err + } + + return dls.UpdateDeadline(s.store, dindx, dl) + }) + if err != nil { + return err + } + + return s.State.SaveDeadlines(s.store, dls) + +} + +func (d *deadline6) LoadPartition(idx uint64) (Partition, error) { + p, err := d.Deadline.LoadPartition(d.store, idx) + if err != nil { + return nil, err + } + return &partition6{*p, d.store}, nil +} + +func (d *deadline6) ForEachPartition(cb func(uint64, Partition) error) error { + ps, err := d.Deadline.PartitionsArray(d.store) + if err != nil { + return err + } + var part miner6.Partition + return ps.ForEach(&part, func(i int64) error { + return cb(uint64(i), &partition6{part, d.store}) + }) +} + +func (d *deadline6) PartitionsChanged(other Deadline) (bool, error) { + other6, ok := other.(*deadline6) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + + return !d.Deadline.Partitions.Equals(other6.Deadline.Partitions), nil +} + +func (d *deadline6) PartitionsPoSted() (bitfield.BitField, error) { + return d.Deadline.PartitionsPoSted, nil +} + +func (d *deadline6) DisputableProofCount() (uint64, error) { + + ops, err := d.OptimisticProofsSnapshotArray(d.store) + if err != nil { + return 0, err + } + + return ops.Length(), nil + +} + +func (p *partition6) AllSectors() (bitfield.BitField, error) { + return p.Partition.Sectors, nil +} + +func (p *partition6) FaultySectors() (bitfield.BitField, error) { + return p.Partition.Faults, nil +} + +func (p *partition6) RecoveringSectors() (bitfield.BitField, error) { + return p.Partition.Recoveries, nil +} + +func (p *partition6) UnprovenSectors() (bitfield.BitField, error) { + return p.Partition.Unproven, nil +} + +func fromV6SectorOnChainInfo(v6 miner6.SectorOnChainInfo) SectorOnChainInfo { + info := SectorOnChainInfo{ + SectorNumber: v6.SectorNumber, + SealProof: v6.SealProof, + SealedCID: v6.SealedCID, + DealIDs: v6.DealIDs, + Activation: v6.Activation, + Expiration: v6.Expiration, + DealWeight: v6.DealWeight, + VerifiedDealWeight: v6.VerifiedDealWeight, + InitialPledge: v6.InitialPledge, + ExpectedDayReward: v6.ExpectedDayReward, + ExpectedStoragePledge: v6.ExpectedStoragePledge, + } + return info +} + +func fromV6SectorPreCommitOnChainInfo(v6 miner6.SectorPreCommitOnChainInfo) SectorPreCommitOnChainInfo { + ret := SectorPreCommitOnChainInfo{ + Info: SectorPreCommitInfo{ + SealProof: v6.Info.SealProof, + SectorNumber: v6.Info.SectorNumber, + SealedCID: v6.Info.SealedCID, + SealRandEpoch: v6.Info.SealRandEpoch, + DealIDs: v6.Info.DealIDs, + Expiration: v6.Info.Expiration, + UnsealedCid: nil, + }, + PreCommitDeposit: v6.PreCommitDeposit, + PreCommitEpoch: v6.PreCommitEpoch, + } + + return ret +} + +func (s *state6) GetState() interface{} { + return &s.State +} + +func (s *state6) ActorKey() string { + return manifest.MinerKey +} + +func (s *state6) ActorVersion() actorstypes.Version { + return actorstypes.Version6 +} + +func (s *state6) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/miner/v7.go b/chain/actors/builtin/miner/v7.go new file mode 100644 index 000000000..72803eb75 --- /dev/null +++ b/chain/actors/builtin/miner/v7.go @@ -0,0 +1,585 @@ +package miner + +import ( + "bytes" + "errors" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-bitfield" + rle "github.com/filecoin-project/go-bitfield/rle" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/dline" + "github.com/filecoin-project/go-state-types/manifest" + builtin7 "github.com/filecoin-project/specs-actors/v7/actors/builtin" + miner7 "github.com/filecoin-project/specs-actors/v7/actors/builtin/miner" + adt7 "github.com/filecoin-project/specs-actors/v7/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state7)(nil) + +func load7(store adt.Store, root cid.Cid) (State, error) { + out := state7{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make7(store adt.Store) (State, error) { + out := state7{store: store} + out.State = miner7.State{} + return &out, nil +} + +type state7 struct { + miner7.State + store adt.Store +} + +type deadline7 struct { + miner7.Deadline + store adt.Store +} + +type partition7 struct { + miner7.Partition + store adt.Store +} + +func (s *state7) AvailableBalance(bal abi.TokenAmount) (available abi.TokenAmount, err error) { + defer func() { + if r := recover(); r != nil { + err = xerrors.Errorf("failed to get available balance: %w", r) + available = abi.NewTokenAmount(0) + } + }() + // this panics if the miner doesnt have enough funds to cover their locked pledge + available, err = s.GetAvailableBalance(bal) + return available, err +} + +func (s *state7) VestedFunds(epoch abi.ChainEpoch) (abi.TokenAmount, error) { + return s.CheckVestedFunds(s.store, epoch) +} + +func (s *state7) LockedFunds() (LockedFunds, error) { + return LockedFunds{ + VestingFunds: s.State.LockedFunds, + InitialPledgeRequirement: s.State.InitialPledge, + PreCommitDeposits: s.State.PreCommitDeposits, + }, nil +} + +func (s *state7) FeeDebt() (abi.TokenAmount, error) { + return s.State.FeeDebt, nil +} + +func (s *state7) InitialPledge() (abi.TokenAmount, error) { + return s.State.InitialPledge, nil +} + +func (s *state7) PreCommitDeposits() (abi.TokenAmount, error) { + return s.State.PreCommitDeposits, nil +} + +// Returns nil, nil if sector is not found +func (s *state7) GetSector(num abi.SectorNumber) (*SectorOnChainInfo, error) { + info, ok, err := s.State.GetSector(s.store, num) + if !ok || err != nil { + return nil, err + } + + ret := fromV7SectorOnChainInfo(*info) + return &ret, nil +} + +func (s *state7) FindSector(num abi.SectorNumber) (*SectorLocation, error) { + dlIdx, partIdx, err := s.State.FindSector(s.store, num) + if err != nil { + return nil, err + } + return &SectorLocation{ + Deadline: dlIdx, + Partition: partIdx, + }, nil +} + +func (s *state7) NumLiveSectors() (uint64, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return 0, err + } + var total uint64 + if err := dls.ForEach(s.store, func(dlIdx uint64, dl *miner7.Deadline) error { + total += dl.LiveSectors + return nil + }); err != nil { + return 0, err + } + return total, nil +} + +// GetSectorExpiration returns the effective expiration of the given sector. +// +// If the sector does not expire early, the Early expiration field is 0. +func (s *state7) GetSectorExpiration(num abi.SectorNumber) (*SectorExpiration, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return nil, err + } + // NOTE: this can be optimized significantly. + // 1. If the sector is non-faulty, it will expire on-time (can be + // learned from the sector info). + // 2. If it's faulty, it will expire early within the first 42 entries + // of the expiration queue. + + stopErr := errors.New("stop") + out := SectorExpiration{} + err = dls.ForEach(s.store, func(dlIdx uint64, dl *miner7.Deadline) error { + partitions, err := dl.PartitionsArray(s.store) + if err != nil { + return err + } + quant := s.State.QuantSpecForDeadline(dlIdx) + var part miner7.Partition + return partitions.ForEach(&part, func(partIdx int64) error { + if found, err := part.Sectors.IsSet(uint64(num)); err != nil { + return err + } else if !found { + return nil + } + if found, err := part.Terminated.IsSet(uint64(num)); err != nil { + return err + } else if found { + // already terminated + return stopErr + } + + q, err := miner7.LoadExpirationQueue(s.store, part.ExpirationsEpochs, quant, miner7.PartitionExpirationAmtBitwidth) + if err != nil { + return err + } + var exp miner7.ExpirationSet + return q.ForEach(&exp, func(epoch int64) error { + if early, err := exp.EarlySectors.IsSet(uint64(num)); err != nil { + return err + } else if early { + out.Early = abi.ChainEpoch(epoch) + return nil + } + if onTime, err := exp.OnTimeSectors.IsSet(uint64(num)); err != nil { + return err + } else if onTime { + out.OnTime = abi.ChainEpoch(epoch) + return stopErr + } + return nil + }) + }) + }) + if err == stopErr { + err = nil + } + if err != nil { + return nil, err + } + if out.Early == 0 && out.OnTime == 0 { + return nil, xerrors.Errorf("failed to find sector %d", num) + } + return &out, nil +} + +func (s *state7) GetPrecommittedSector(num abi.SectorNumber) (*SectorPreCommitOnChainInfo, error) { + info, ok, err := s.State.GetPrecommittedSector(s.store, num) + if !ok || err != nil { + return nil, err + } + + ret := fromV7SectorPreCommitOnChainInfo(*info) + + return &ret, nil +} + +func (s *state7) ForEachPrecommittedSector(cb func(SectorPreCommitOnChainInfo) error) error { + precommitted, err := adt7.AsMap(s.store, s.State.PreCommittedSectors, builtin7.DefaultHamtBitwidth) + if err != nil { + return err + } + + var info miner7.SectorPreCommitOnChainInfo + if err := precommitted.ForEach(&info, func(_ string) error { + return cb(fromV7SectorPreCommitOnChainInfo(info)) + }); err != nil { + return err + } + + return nil +} + +func (s *state7) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, error) { + sectors, err := miner7.LoadSectors(s.store, s.State.Sectors) + if err != nil { + return nil, err + } + + // If no sector numbers are specified, load all. + if snos == nil { + infos := make([]*SectorOnChainInfo, 0, sectors.Length()) + var info7 miner7.SectorOnChainInfo + if err := sectors.ForEach(&info7, func(_ int64) error { + info := fromV7SectorOnChainInfo(info7) + infos = append(infos, &info) + return nil + }); err != nil { + return nil, err + } + return infos, nil + } + + // Otherwise, load selected. + infos7, err := sectors.Load(*snos) + if err != nil { + return nil, err + } + infos := make([]*SectorOnChainInfo, len(infos7)) + for i, info7 := range infos7 { + info := fromV7SectorOnChainInfo(*info7) + infos[i] = &info + } + return infos, nil +} + +func (s *state7) loadAllocatedSectorNumbers() (bitfield.BitField, error) { + var allocatedSectors bitfield.BitField + err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors) + return allocatedSectors, err +} + +func (s *state7) IsAllocated(num abi.SectorNumber) (bool, error) { + allocatedSectors, err := s.loadAllocatedSectorNumbers() + if err != nil { + return false, err + } + + return allocatedSectors.IsSet(uint64(num)) +} + +func (s *state7) GetProvingPeriodStart() (abi.ChainEpoch, error) { + return s.State.ProvingPeriodStart, nil +} + +func (s *state7) 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 *state7) GetAllocatedSectors() (*bitfield.BitField, error) { + var allocatedSectors bitfield.BitField + if err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors); err != nil { + return nil, err + } + + return &allocatedSectors, nil +} + +func (s *state7) LoadDeadline(idx uint64) (Deadline, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return nil, err + } + dl, err := dls.LoadDeadline(s.store, idx) + if err != nil { + return nil, err + } + return &deadline7{*dl, s.store}, nil +} + +func (s *state7) ForEachDeadline(cb func(uint64, Deadline) error) error { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return err + } + return dls.ForEach(s.store, func(i uint64, dl *miner7.Deadline) error { + return cb(i, &deadline7{*dl, s.store}) + }) +} + +func (s *state7) NumDeadlines() (uint64, error) { + return miner7.WPoStPeriodDeadlines, nil +} + +func (s *state7) DeadlinesChanged(other State) (bool, error) { + other7, ok := other.(*state7) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + + return !s.State.Deadlines.Equals(other7.Deadlines), nil +} + +func (s *state7) MinerInfoChanged(other State) (bool, error) { + other0, ok := other.(*state7) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.Info.Equals(other0.State.Info), nil +} + +func (s *state7) Info() (MinerInfo, error) { + info, err := s.State.GetInfo(s.store) + if err != nil { + return MinerInfo{}, err + } + + mi := MinerInfo{ + Owner: info.Owner, + Worker: info.Worker, + ControlAddresses: info.ControlAddresses, + + PendingWorkerKey: (*WorkerKeyChange)(info.PendingWorkerKey), + + PeerId: info.PeerId, + Multiaddrs: info.Multiaddrs, + WindowPoStProofType: info.WindowPoStProofType, + SectorSize: info.SectorSize, + WindowPoStPartitionSectors: info.WindowPoStPartitionSectors, + ConsensusFaultElapsed: info.ConsensusFaultElapsed, + } + + return mi, nil +} + +func (s *state7) DeadlineInfo(epoch abi.ChainEpoch) (*dline.Info, error) { + return s.State.RecordedDeadlineInfo(epoch), nil +} + +func (s *state7) DeadlineCronActive() (bool, error) { + return s.State.DeadlineCronActive, nil +} + +func (s *state7) sectors() (adt.Array, error) { + return adt7.AsArray(s.store, s.Sectors, miner7.SectorsAmtBitwidth) +} + +func (s *state7) decodeSectorOnChainInfo(val *cbg.Deferred) (SectorOnChainInfo, error) { + var si miner7.SectorOnChainInfo + err := si.UnmarshalCBOR(bytes.NewReader(val.Raw)) + if err != nil { + return SectorOnChainInfo{}, err + } + + return fromV7SectorOnChainInfo(si), nil +} + +func (s *state7) precommits() (adt.Map, error) { + return adt7.AsMap(s.store, s.PreCommittedSectors, builtin7.DefaultHamtBitwidth) +} + +func (s *state7) decodeSectorPreCommitOnChainInfo(val *cbg.Deferred) (SectorPreCommitOnChainInfo, error) { + var sp miner7.SectorPreCommitOnChainInfo + err := sp.UnmarshalCBOR(bytes.NewReader(val.Raw)) + if err != nil { + return SectorPreCommitOnChainInfo{}, err + } + + return fromV7SectorPreCommitOnChainInfo(sp), nil +} + +func (s *state7) EraseAllUnproven() error { + + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return err + } + + err = dls.ForEach(s.store, func(dindx uint64, dl *miner7.Deadline) error { + ps, err := dl.PartitionsArray(s.store) + if err != nil { + return err + } + + var part miner7.Partition + err = ps.ForEach(&part, func(pindx int64) error { + _ = part.ActivateUnproven() + err = ps.Set(uint64(pindx), &part) + return nil + }) + + if err != nil { + return err + } + + dl.Partitions, err = ps.Root() + if err != nil { + return err + } + + return dls.UpdateDeadline(s.store, dindx, dl) + }) + if err != nil { + return err + } + + return s.State.SaveDeadlines(s.store, dls) + +} + +func (d *deadline7) LoadPartition(idx uint64) (Partition, error) { + p, err := d.Deadline.LoadPartition(d.store, idx) + if err != nil { + return nil, err + } + return &partition7{*p, d.store}, nil +} + +func (d *deadline7) ForEachPartition(cb func(uint64, Partition) error) error { + ps, err := d.Deadline.PartitionsArray(d.store) + if err != nil { + return err + } + var part miner7.Partition + return ps.ForEach(&part, func(i int64) error { + return cb(uint64(i), &partition7{part, d.store}) + }) +} + +func (d *deadline7) PartitionsChanged(other Deadline) (bool, error) { + other7, ok := other.(*deadline7) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + + return !d.Deadline.Partitions.Equals(other7.Deadline.Partitions), nil +} + +func (d *deadline7) PartitionsPoSted() (bitfield.BitField, error) { + return d.Deadline.PartitionsPoSted, nil +} + +func (d *deadline7) DisputableProofCount() (uint64, error) { + + ops, err := d.OptimisticProofsSnapshotArray(d.store) + if err != nil { + return 0, err + } + + return ops.Length(), nil + +} + +func (p *partition7) AllSectors() (bitfield.BitField, error) { + return p.Partition.Sectors, nil +} + +func (p *partition7) FaultySectors() (bitfield.BitField, error) { + return p.Partition.Faults, nil +} + +func (p *partition7) RecoveringSectors() (bitfield.BitField, error) { + return p.Partition.Recoveries, nil +} + +func (p *partition7) UnprovenSectors() (bitfield.BitField, error) { + return p.Partition.Unproven, nil +} + +func fromV7SectorOnChainInfo(v7 miner7.SectorOnChainInfo) SectorOnChainInfo { + info := SectorOnChainInfo{ + SectorNumber: v7.SectorNumber, + SealProof: v7.SealProof, + SealedCID: v7.SealedCID, + DealIDs: v7.DealIDs, + Activation: v7.Activation, + Expiration: v7.Expiration, + DealWeight: v7.DealWeight, + VerifiedDealWeight: v7.VerifiedDealWeight, + InitialPledge: v7.InitialPledge, + ExpectedDayReward: v7.ExpectedDayReward, + ExpectedStoragePledge: v7.ExpectedStoragePledge, + + SectorKeyCID: v7.SectorKeyCID, + } + return info +} + +func fromV7SectorPreCommitOnChainInfo(v7 miner7.SectorPreCommitOnChainInfo) SectorPreCommitOnChainInfo { + ret := SectorPreCommitOnChainInfo{ + Info: SectorPreCommitInfo{ + SealProof: v7.Info.SealProof, + SectorNumber: v7.Info.SectorNumber, + SealedCID: v7.Info.SealedCID, + SealRandEpoch: v7.Info.SealRandEpoch, + DealIDs: v7.Info.DealIDs, + Expiration: v7.Info.Expiration, + UnsealedCid: nil, + }, + PreCommitDeposit: v7.PreCommitDeposit, + PreCommitEpoch: v7.PreCommitEpoch, + } + + return ret +} + +func (s *state7) GetState() interface{} { + return &s.State +} + +func (s *state7) ActorKey() string { + return manifest.MinerKey +} + +func (s *state7) ActorVersion() actorstypes.Version { + return actorstypes.Version7 +} + +func (s *state7) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/miner/v8.go b/chain/actors/builtin/miner/v8.go new file mode 100644 index 000000000..3e3739591 --- /dev/null +++ b/chain/actors/builtin/miner/v8.go @@ -0,0 +1,585 @@ +package miner + +import ( + "bytes" + "errors" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-bitfield" + rle "github.com/filecoin-project/go-bitfield/rle" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtin8 "github.com/filecoin-project/go-state-types/builtin" + miner8 "github.com/filecoin-project/go-state-types/builtin/v8/miner" + adt8 "github.com/filecoin-project/go-state-types/builtin/v8/util/adt" + "github.com/filecoin-project/go-state-types/dline" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state8)(nil) + +func load8(store adt.Store, root cid.Cid) (State, error) { + out := state8{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make8(store adt.Store) (State, error) { + out := state8{store: store} + out.State = miner8.State{} + return &out, nil +} + +type state8 struct { + miner8.State + store adt.Store +} + +type deadline8 struct { + miner8.Deadline + store adt.Store +} + +type partition8 struct { + miner8.Partition + store adt.Store +} + +func (s *state8) AvailableBalance(bal abi.TokenAmount) (available abi.TokenAmount, err error) { + defer func() { + if r := recover(); r != nil { + err = xerrors.Errorf("failed to get available balance: %w", r) + available = abi.NewTokenAmount(0) + } + }() + // this panics if the miner doesnt have enough funds to cover their locked pledge + available, err = s.GetAvailableBalance(bal) + return available, err +} + +func (s *state8) VestedFunds(epoch abi.ChainEpoch) (abi.TokenAmount, error) { + return s.CheckVestedFunds(s.store, epoch) +} + +func (s *state8) LockedFunds() (LockedFunds, error) { + return LockedFunds{ + VestingFunds: s.State.LockedFunds, + InitialPledgeRequirement: s.State.InitialPledge, + PreCommitDeposits: s.State.PreCommitDeposits, + }, nil +} + +func (s *state8) FeeDebt() (abi.TokenAmount, error) { + return s.State.FeeDebt, nil +} + +func (s *state8) InitialPledge() (abi.TokenAmount, error) { + return s.State.InitialPledge, nil +} + +func (s *state8) PreCommitDeposits() (abi.TokenAmount, error) { + return s.State.PreCommitDeposits, nil +} + +// Returns nil, nil if sector is not found +func (s *state8) GetSector(num abi.SectorNumber) (*SectorOnChainInfo, error) { + info, ok, err := s.State.GetSector(s.store, num) + if !ok || err != nil { + return nil, err + } + + ret := fromV8SectorOnChainInfo(*info) + return &ret, nil +} + +func (s *state8) FindSector(num abi.SectorNumber) (*SectorLocation, error) { + dlIdx, partIdx, err := s.State.FindSector(s.store, num) + if err != nil { + return nil, err + } + return &SectorLocation{ + Deadline: dlIdx, + Partition: partIdx, + }, nil +} + +func (s *state8) NumLiveSectors() (uint64, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return 0, err + } + var total uint64 + if err := dls.ForEach(s.store, func(dlIdx uint64, dl *miner8.Deadline) error { + total += dl.LiveSectors + return nil + }); err != nil { + return 0, err + } + return total, nil +} + +// GetSectorExpiration returns the effective expiration of the given sector. +// +// If the sector does not expire early, the Early expiration field is 0. +func (s *state8) GetSectorExpiration(num abi.SectorNumber) (*SectorExpiration, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return nil, err + } + // NOTE: this can be optimized significantly. + // 1. If the sector is non-faulty, it will expire on-time (can be + // learned from the sector info). + // 2. If it's faulty, it will expire early within the first 42 entries + // of the expiration queue. + + stopErr := errors.New("stop") + out := SectorExpiration{} + err = dls.ForEach(s.store, func(dlIdx uint64, dl *miner8.Deadline) error { + partitions, err := dl.PartitionsArray(s.store) + if err != nil { + return err + } + quant := s.State.QuantSpecForDeadline(dlIdx) + var part miner8.Partition + return partitions.ForEach(&part, func(partIdx int64) error { + if found, err := part.Sectors.IsSet(uint64(num)); err != nil { + return err + } else if !found { + return nil + } + if found, err := part.Terminated.IsSet(uint64(num)); err != nil { + return err + } else if found { + // already terminated + return stopErr + } + + q, err := miner8.LoadExpirationQueue(s.store, part.ExpirationsEpochs, quant, miner8.PartitionExpirationAmtBitwidth) + if err != nil { + return err + } + var exp miner8.ExpirationSet + return q.ForEach(&exp, func(epoch int64) error { + if early, err := exp.EarlySectors.IsSet(uint64(num)); err != nil { + return err + } else if early { + out.Early = abi.ChainEpoch(epoch) + return nil + } + if onTime, err := exp.OnTimeSectors.IsSet(uint64(num)); err != nil { + return err + } else if onTime { + out.OnTime = abi.ChainEpoch(epoch) + return stopErr + } + return nil + }) + }) + }) + if err == stopErr { + err = nil + } + if err != nil { + return nil, err + } + if out.Early == 0 && out.OnTime == 0 { + return nil, xerrors.Errorf("failed to find sector %d", num) + } + return &out, nil +} + +func (s *state8) GetPrecommittedSector(num abi.SectorNumber) (*SectorPreCommitOnChainInfo, error) { + info, ok, err := s.State.GetPrecommittedSector(s.store, num) + if !ok || err != nil { + return nil, err + } + + ret := fromV8SectorPreCommitOnChainInfo(*info) + + return &ret, nil +} + +func (s *state8) ForEachPrecommittedSector(cb func(SectorPreCommitOnChainInfo) error) error { + precommitted, err := adt8.AsMap(s.store, s.State.PreCommittedSectors, builtin8.DefaultHamtBitwidth) + if err != nil { + return err + } + + var info miner8.SectorPreCommitOnChainInfo + if err := precommitted.ForEach(&info, func(_ string) error { + return cb(fromV8SectorPreCommitOnChainInfo(info)) + }); err != nil { + return err + } + + return nil +} + +func (s *state8) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, error) { + sectors, err := miner8.LoadSectors(s.store, s.State.Sectors) + if err != nil { + return nil, err + } + + // If no sector numbers are specified, load all. + if snos == nil { + infos := make([]*SectorOnChainInfo, 0, sectors.Length()) + var info8 miner8.SectorOnChainInfo + if err := sectors.ForEach(&info8, func(_ int64) error { + info := fromV8SectorOnChainInfo(info8) + infos = append(infos, &info) + return nil + }); err != nil { + return nil, err + } + return infos, nil + } + + // Otherwise, load selected. + infos8, err := sectors.Load(*snos) + if err != nil { + return nil, err + } + infos := make([]*SectorOnChainInfo, len(infos8)) + for i, info8 := range infos8 { + info := fromV8SectorOnChainInfo(*info8) + infos[i] = &info + } + return infos, nil +} + +func (s *state8) loadAllocatedSectorNumbers() (bitfield.BitField, error) { + var allocatedSectors bitfield.BitField + err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors) + return allocatedSectors, err +} + +func (s *state8) IsAllocated(num abi.SectorNumber) (bool, error) { + allocatedSectors, err := s.loadAllocatedSectorNumbers() + if err != nil { + return false, err + } + + return allocatedSectors.IsSet(uint64(num)) +} + +func (s *state8) GetProvingPeriodStart() (abi.ChainEpoch, error) { + return s.State.ProvingPeriodStart, nil +} + +func (s *state8) 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 *state8) GetAllocatedSectors() (*bitfield.BitField, error) { + var allocatedSectors bitfield.BitField + if err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors); err != nil { + return nil, err + } + + return &allocatedSectors, nil +} + +func (s *state8) LoadDeadline(idx uint64) (Deadline, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return nil, err + } + dl, err := dls.LoadDeadline(s.store, idx) + if err != nil { + return nil, err + } + return &deadline8{*dl, s.store}, nil +} + +func (s *state8) ForEachDeadline(cb func(uint64, Deadline) error) error { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return err + } + return dls.ForEach(s.store, func(i uint64, dl *miner8.Deadline) error { + return cb(i, &deadline8{*dl, s.store}) + }) +} + +func (s *state8) NumDeadlines() (uint64, error) { + return miner8.WPoStPeriodDeadlines, nil +} + +func (s *state8) DeadlinesChanged(other State) (bool, error) { + other8, ok := other.(*state8) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + + return !s.State.Deadlines.Equals(other8.Deadlines), nil +} + +func (s *state8) MinerInfoChanged(other State) (bool, error) { + other0, ok := other.(*state8) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.Info.Equals(other0.State.Info), nil +} + +func (s *state8) Info() (MinerInfo, error) { + info, err := s.State.GetInfo(s.store) + if err != nil { + return MinerInfo{}, err + } + + mi := MinerInfo{ + Owner: info.Owner, + Worker: info.Worker, + ControlAddresses: info.ControlAddresses, + + PendingWorkerKey: (*WorkerKeyChange)(info.PendingWorkerKey), + + PeerId: info.PeerId, + Multiaddrs: info.Multiaddrs, + WindowPoStProofType: info.WindowPoStProofType, + SectorSize: info.SectorSize, + WindowPoStPartitionSectors: info.WindowPoStPartitionSectors, + ConsensusFaultElapsed: info.ConsensusFaultElapsed, + } + + return mi, nil +} + +func (s *state8) DeadlineInfo(epoch abi.ChainEpoch) (*dline.Info, error) { + return s.State.RecordedDeadlineInfo(epoch), nil +} + +func (s *state8) DeadlineCronActive() (bool, error) { + return s.State.DeadlineCronActive, nil +} + +func (s *state8) sectors() (adt.Array, error) { + return adt8.AsArray(s.store, s.Sectors, miner8.SectorsAmtBitwidth) +} + +func (s *state8) decodeSectorOnChainInfo(val *cbg.Deferred) (SectorOnChainInfo, error) { + var si miner8.SectorOnChainInfo + err := si.UnmarshalCBOR(bytes.NewReader(val.Raw)) + if err != nil { + return SectorOnChainInfo{}, err + } + + return fromV8SectorOnChainInfo(si), nil +} + +func (s *state8) precommits() (adt.Map, error) { + return adt8.AsMap(s.store, s.PreCommittedSectors, builtin8.DefaultHamtBitwidth) +} + +func (s *state8) decodeSectorPreCommitOnChainInfo(val *cbg.Deferred) (SectorPreCommitOnChainInfo, error) { + var sp miner8.SectorPreCommitOnChainInfo + err := sp.UnmarshalCBOR(bytes.NewReader(val.Raw)) + if err != nil { + return SectorPreCommitOnChainInfo{}, err + } + + return fromV8SectorPreCommitOnChainInfo(sp), nil +} + +func (s *state8) EraseAllUnproven() error { + + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return err + } + + err = dls.ForEach(s.store, func(dindx uint64, dl *miner8.Deadline) error { + ps, err := dl.PartitionsArray(s.store) + if err != nil { + return err + } + + var part miner8.Partition + err = ps.ForEach(&part, func(pindx int64) error { + _ = part.ActivateUnproven() + err = ps.Set(uint64(pindx), &part) + return nil + }) + + if err != nil { + return err + } + + dl.Partitions, err = ps.Root() + if err != nil { + return err + } + + return dls.UpdateDeadline(s.store, dindx, dl) + }) + if err != nil { + return err + } + + return s.State.SaveDeadlines(s.store, dls) + +} + +func (d *deadline8) LoadPartition(idx uint64) (Partition, error) { + p, err := d.Deadline.LoadPartition(d.store, idx) + if err != nil { + return nil, err + } + return &partition8{*p, d.store}, nil +} + +func (d *deadline8) ForEachPartition(cb func(uint64, Partition) error) error { + ps, err := d.Deadline.PartitionsArray(d.store) + if err != nil { + return err + } + var part miner8.Partition + return ps.ForEach(&part, func(i int64) error { + return cb(uint64(i), &partition8{part, d.store}) + }) +} + +func (d *deadline8) PartitionsChanged(other Deadline) (bool, error) { + other8, ok := other.(*deadline8) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + + return !d.Deadline.Partitions.Equals(other8.Deadline.Partitions), nil +} + +func (d *deadline8) PartitionsPoSted() (bitfield.BitField, error) { + return d.Deadline.PartitionsPoSted, nil +} + +func (d *deadline8) DisputableProofCount() (uint64, error) { + + ops, err := d.OptimisticProofsSnapshotArray(d.store) + if err != nil { + return 0, err + } + + return ops.Length(), nil + +} + +func (p *partition8) AllSectors() (bitfield.BitField, error) { + return p.Partition.Sectors, nil +} + +func (p *partition8) FaultySectors() (bitfield.BitField, error) { + return p.Partition.Faults, nil +} + +func (p *partition8) RecoveringSectors() (bitfield.BitField, error) { + return p.Partition.Recoveries, nil +} + +func (p *partition8) UnprovenSectors() (bitfield.BitField, error) { + return p.Partition.Unproven, nil +} + +func fromV8SectorOnChainInfo(v8 miner8.SectorOnChainInfo) SectorOnChainInfo { + info := SectorOnChainInfo{ + SectorNumber: v8.SectorNumber, + SealProof: v8.SealProof, + SealedCID: v8.SealedCID, + DealIDs: v8.DealIDs, + Activation: v8.Activation, + Expiration: v8.Expiration, + DealWeight: v8.DealWeight, + VerifiedDealWeight: v8.VerifiedDealWeight, + InitialPledge: v8.InitialPledge, + ExpectedDayReward: v8.ExpectedDayReward, + ExpectedStoragePledge: v8.ExpectedStoragePledge, + + SectorKeyCID: v8.SectorKeyCID, + } + return info +} + +func fromV8SectorPreCommitOnChainInfo(v8 miner8.SectorPreCommitOnChainInfo) SectorPreCommitOnChainInfo { + ret := SectorPreCommitOnChainInfo{ + Info: SectorPreCommitInfo{ + SealProof: v8.Info.SealProof, + SectorNumber: v8.Info.SectorNumber, + SealedCID: v8.Info.SealedCID, + SealRandEpoch: v8.Info.SealRandEpoch, + DealIDs: v8.Info.DealIDs, + Expiration: v8.Info.Expiration, + UnsealedCid: nil, + }, + PreCommitDeposit: v8.PreCommitDeposit, + PreCommitEpoch: v8.PreCommitEpoch, + } + + return ret +} + +func (s *state8) GetState() interface{} { + return &s.State +} + +func (s *state8) ActorKey() string { + return manifest.MinerKey +} + +func (s *state8) ActorVersion() actorstypes.Version { + return actorstypes.Version8 +} + +func (s *state8) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/miner/v9.go b/chain/actors/builtin/miner/v9.go new file mode 100644 index 000000000..72d9dbd59 --- /dev/null +++ b/chain/actors/builtin/miner/v9.go @@ -0,0 +1,591 @@ +package miner + +import ( + "bytes" + "errors" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-bitfield" + rle "github.com/filecoin-project/go-bitfield/rle" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtin9 "github.com/filecoin-project/go-state-types/builtin" + miner9 "github.com/filecoin-project/go-state-types/builtin/v9/miner" + adt9 "github.com/filecoin-project/go-state-types/builtin/v9/util/adt" + "github.com/filecoin-project/go-state-types/dline" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state9)(nil) + +func load9(store adt.Store, root cid.Cid) (State, error) { + out := state9{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make9(store adt.Store) (State, error) { + out := state9{store: store} + out.State = miner9.State{} + return &out, nil +} + +type state9 struct { + miner9.State + store adt.Store +} + +type deadline9 struct { + miner9.Deadline + store adt.Store +} + +type partition9 struct { + miner9.Partition + store adt.Store +} + +func (s *state9) AvailableBalance(bal abi.TokenAmount) (available abi.TokenAmount, err error) { + defer func() { + if r := recover(); r != nil { + err = xerrors.Errorf("failed to get available balance: %w", r) + available = abi.NewTokenAmount(0) + } + }() + // this panics if the miner doesnt have enough funds to cover their locked pledge + available, err = s.GetAvailableBalance(bal) + return available, err +} + +func (s *state9) VestedFunds(epoch abi.ChainEpoch) (abi.TokenAmount, error) { + return s.CheckVestedFunds(s.store, epoch) +} + +func (s *state9) LockedFunds() (LockedFunds, error) { + return LockedFunds{ + VestingFunds: s.State.LockedFunds, + InitialPledgeRequirement: s.State.InitialPledge, + PreCommitDeposits: s.State.PreCommitDeposits, + }, nil +} + +func (s *state9) FeeDebt() (abi.TokenAmount, error) { + return s.State.FeeDebt, nil +} + +func (s *state9) InitialPledge() (abi.TokenAmount, error) { + return s.State.InitialPledge, nil +} + +func (s *state9) PreCommitDeposits() (abi.TokenAmount, error) { + return s.State.PreCommitDeposits, nil +} + +// Returns nil, nil if sector is not found +func (s *state9) GetSector(num abi.SectorNumber) (*SectorOnChainInfo, error) { + info, ok, err := s.State.GetSector(s.store, num) + if !ok || err != nil { + return nil, err + } + + ret := fromV9SectorOnChainInfo(*info) + return &ret, nil +} + +func (s *state9) FindSector(num abi.SectorNumber) (*SectorLocation, error) { + dlIdx, partIdx, err := s.State.FindSector(s.store, num) + if err != nil { + return nil, err + } + return &SectorLocation{ + Deadline: dlIdx, + Partition: partIdx, + }, nil +} + +func (s *state9) NumLiveSectors() (uint64, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return 0, err + } + var total uint64 + if err := dls.ForEach(s.store, func(dlIdx uint64, dl *miner9.Deadline) error { + total += dl.LiveSectors + return nil + }); err != nil { + return 0, err + } + return total, nil +} + +// GetSectorExpiration returns the effective expiration of the given sector. +// +// If the sector does not expire early, the Early expiration field is 0. +func (s *state9) GetSectorExpiration(num abi.SectorNumber) (*SectorExpiration, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return nil, err + } + // NOTE: this can be optimized significantly. + // 1. If the sector is non-faulty, it will expire on-time (can be + // learned from the sector info). + // 2. If it's faulty, it will expire early within the first 42 entries + // of the expiration queue. + + stopErr := errors.New("stop") + out := SectorExpiration{} + err = dls.ForEach(s.store, func(dlIdx uint64, dl *miner9.Deadline) error { + partitions, err := dl.PartitionsArray(s.store) + if err != nil { + return err + } + quant := s.State.QuantSpecForDeadline(dlIdx) + var part miner9.Partition + return partitions.ForEach(&part, func(partIdx int64) error { + if found, err := part.Sectors.IsSet(uint64(num)); err != nil { + return err + } else if !found { + return nil + } + if found, err := part.Terminated.IsSet(uint64(num)); err != nil { + return err + } else if found { + // already terminated + return stopErr + } + + q, err := miner9.LoadExpirationQueue(s.store, part.ExpirationsEpochs, quant, miner9.PartitionExpirationAmtBitwidth) + if err != nil { + return err + } + var exp miner9.ExpirationSet + return q.ForEach(&exp, func(epoch int64) error { + if early, err := exp.EarlySectors.IsSet(uint64(num)); err != nil { + return err + } else if early { + out.Early = abi.ChainEpoch(epoch) + return nil + } + if onTime, err := exp.OnTimeSectors.IsSet(uint64(num)); err != nil { + return err + } else if onTime { + out.OnTime = abi.ChainEpoch(epoch) + return stopErr + } + return nil + }) + }) + }) + if err == stopErr { + err = nil + } + if err != nil { + return nil, err + } + if out.Early == 0 && out.OnTime == 0 { + return nil, xerrors.Errorf("failed to find sector %d", num) + } + return &out, nil +} + +func (s *state9) GetPrecommittedSector(num abi.SectorNumber) (*SectorPreCommitOnChainInfo, error) { + info, ok, err := s.State.GetPrecommittedSector(s.store, num) + if !ok || err != nil { + return nil, err + } + + ret := fromV9SectorPreCommitOnChainInfo(*info) + + return &ret, nil +} + +func (s *state9) ForEachPrecommittedSector(cb func(SectorPreCommitOnChainInfo) error) error { + precommitted, err := adt9.AsMap(s.store, s.State.PreCommittedSectors, builtin9.DefaultHamtBitwidth) + if err != nil { + return err + } + + var info miner9.SectorPreCommitOnChainInfo + if err := precommitted.ForEach(&info, func(_ string) error { + return cb(fromV9SectorPreCommitOnChainInfo(info)) + }); err != nil { + return err + } + + return nil +} + +func (s *state9) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, error) { + sectors, err := miner9.LoadSectors(s.store, s.State.Sectors) + if err != nil { + return nil, err + } + + // If no sector numbers are specified, load all. + if snos == nil { + infos := make([]*SectorOnChainInfo, 0, sectors.Length()) + var info9 miner9.SectorOnChainInfo + if err := sectors.ForEach(&info9, func(_ int64) error { + info := fromV9SectorOnChainInfo(info9) + infos = append(infos, &info) + return nil + }); err != nil { + return nil, err + } + return infos, nil + } + + // Otherwise, load selected. + infos9, err := sectors.Load(*snos) + if err != nil { + return nil, err + } + infos := make([]*SectorOnChainInfo, len(infos9)) + for i, info9 := range infos9 { + info := fromV9SectorOnChainInfo(*info9) + infos[i] = &info + } + return infos, nil +} + +func (s *state9) loadAllocatedSectorNumbers() (bitfield.BitField, error) { + var allocatedSectors bitfield.BitField + err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors) + return allocatedSectors, err +} + +func (s *state9) IsAllocated(num abi.SectorNumber) (bool, error) { + allocatedSectors, err := s.loadAllocatedSectorNumbers() + if err != nil { + return false, err + } + + return allocatedSectors.IsSet(uint64(num)) +} + +func (s *state9) GetProvingPeriodStart() (abi.ChainEpoch, error) { + return s.State.ProvingPeriodStart, nil +} + +func (s *state9) 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 *state9) GetAllocatedSectors() (*bitfield.BitField, error) { + var allocatedSectors bitfield.BitField + if err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors); err != nil { + return nil, err + } + + return &allocatedSectors, nil +} + +func (s *state9) LoadDeadline(idx uint64) (Deadline, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return nil, err + } + dl, err := dls.LoadDeadline(s.store, idx) + if err != nil { + return nil, err + } + return &deadline9{*dl, s.store}, nil +} + +func (s *state9) ForEachDeadline(cb func(uint64, Deadline) error) error { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return err + } + return dls.ForEach(s.store, func(i uint64, dl *miner9.Deadline) error { + return cb(i, &deadline9{*dl, s.store}) + }) +} + +func (s *state9) NumDeadlines() (uint64, error) { + return miner9.WPoStPeriodDeadlines, nil +} + +func (s *state9) DeadlinesChanged(other State) (bool, error) { + other9, ok := other.(*state9) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + + return !s.State.Deadlines.Equals(other9.Deadlines), nil +} + +func (s *state9) MinerInfoChanged(other State) (bool, error) { + other0, ok := other.(*state9) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.Info.Equals(other0.State.Info), nil +} + +func (s *state9) Info() (MinerInfo, error) { + info, err := s.State.GetInfo(s.store) + if err != nil { + return MinerInfo{}, err + } + + mi := MinerInfo{ + Owner: info.Owner, + Worker: info.Worker, + ControlAddresses: info.ControlAddresses, + + PendingWorkerKey: (*WorkerKeyChange)(info.PendingWorkerKey), + + PeerId: info.PeerId, + Multiaddrs: info.Multiaddrs, + WindowPoStProofType: info.WindowPoStProofType, + SectorSize: info.SectorSize, + WindowPoStPartitionSectors: info.WindowPoStPartitionSectors, + ConsensusFaultElapsed: info.ConsensusFaultElapsed, + + Beneficiary: info.Beneficiary, + BeneficiaryTerm: BeneficiaryTerm(info.BeneficiaryTerm), + PendingBeneficiaryTerm: (*PendingBeneficiaryChange)(info.PendingBeneficiaryTerm), + } + + return mi, nil +} + +func (s *state9) DeadlineInfo(epoch abi.ChainEpoch) (*dline.Info, error) { + return s.State.RecordedDeadlineInfo(epoch), nil +} + +func (s *state9) DeadlineCronActive() (bool, error) { + return s.State.DeadlineCronActive, nil +} + +func (s *state9) sectors() (adt.Array, error) { + return adt9.AsArray(s.store, s.Sectors, miner9.SectorsAmtBitwidth) +} + +func (s *state9) decodeSectorOnChainInfo(val *cbg.Deferred) (SectorOnChainInfo, error) { + var si miner9.SectorOnChainInfo + err := si.UnmarshalCBOR(bytes.NewReader(val.Raw)) + if err != nil { + return SectorOnChainInfo{}, err + } + + return fromV9SectorOnChainInfo(si), nil +} + +func (s *state9) precommits() (adt.Map, error) { + return adt9.AsMap(s.store, s.PreCommittedSectors, builtin9.DefaultHamtBitwidth) +} + +func (s *state9) decodeSectorPreCommitOnChainInfo(val *cbg.Deferred) (SectorPreCommitOnChainInfo, error) { + var sp miner9.SectorPreCommitOnChainInfo + err := sp.UnmarshalCBOR(bytes.NewReader(val.Raw)) + if err != nil { + return SectorPreCommitOnChainInfo{}, err + } + + return fromV9SectorPreCommitOnChainInfo(sp), nil +} + +func (s *state9) EraseAllUnproven() error { + + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return err + } + + err = dls.ForEach(s.store, func(dindx uint64, dl *miner9.Deadline) error { + ps, err := dl.PartitionsArray(s.store) + if err != nil { + return err + } + + var part miner9.Partition + err = ps.ForEach(&part, func(pindx int64) error { + _ = part.ActivateUnproven() + err = ps.Set(uint64(pindx), &part) + return nil + }) + + if err != nil { + return err + } + + dl.Partitions, err = ps.Root() + if err != nil { + return err + } + + return dls.UpdateDeadline(s.store, dindx, dl) + }) + if err != nil { + return err + } + + return s.State.SaveDeadlines(s.store, dls) + +} + +func (d *deadline9) LoadPartition(idx uint64) (Partition, error) { + p, err := d.Deadline.LoadPartition(d.store, idx) + if err != nil { + return nil, err + } + return &partition9{*p, d.store}, nil +} + +func (d *deadline9) ForEachPartition(cb func(uint64, Partition) error) error { + ps, err := d.Deadline.PartitionsArray(d.store) + if err != nil { + return err + } + var part miner9.Partition + return ps.ForEach(&part, func(i int64) error { + return cb(uint64(i), &partition9{part, d.store}) + }) +} + +func (d *deadline9) PartitionsChanged(other Deadline) (bool, error) { + other9, ok := other.(*deadline9) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + + return !d.Deadline.Partitions.Equals(other9.Deadline.Partitions), nil +} + +func (d *deadline9) PartitionsPoSted() (bitfield.BitField, error) { + return d.Deadline.PartitionsPoSted, nil +} + +func (d *deadline9) DisputableProofCount() (uint64, error) { + + ops, err := d.OptimisticProofsSnapshotArray(d.store) + if err != nil { + return 0, err + } + + return ops.Length(), nil + +} + +func (p *partition9) AllSectors() (bitfield.BitField, error) { + return p.Partition.Sectors, nil +} + +func (p *partition9) FaultySectors() (bitfield.BitField, error) { + return p.Partition.Faults, nil +} + +func (p *partition9) RecoveringSectors() (bitfield.BitField, error) { + return p.Partition.Recoveries, nil +} + +func (p *partition9) UnprovenSectors() (bitfield.BitField, error) { + return p.Partition.Unproven, nil +} + +func fromV9SectorOnChainInfo(v9 miner9.SectorOnChainInfo) SectorOnChainInfo { + info := SectorOnChainInfo{ + SectorNumber: v9.SectorNumber, + SealProof: v9.SealProof, + SealedCID: v9.SealedCID, + DealIDs: v9.DealIDs, + Activation: v9.Activation, + Expiration: v9.Expiration, + DealWeight: v9.DealWeight, + VerifiedDealWeight: v9.VerifiedDealWeight, + InitialPledge: v9.InitialPledge, + ExpectedDayReward: v9.ExpectedDayReward, + ExpectedStoragePledge: v9.ExpectedStoragePledge, + + SectorKeyCID: v9.SectorKeyCID, + } + return info +} + +func fromV9SectorPreCommitOnChainInfo(v9 miner9.SectorPreCommitOnChainInfo) SectorPreCommitOnChainInfo { + ret := SectorPreCommitOnChainInfo{ + Info: SectorPreCommitInfo{ + SealProof: v9.Info.SealProof, + SectorNumber: v9.Info.SectorNumber, + SealedCID: v9.Info.SealedCID, + SealRandEpoch: v9.Info.SealRandEpoch, + DealIDs: v9.Info.DealIDs, + Expiration: v9.Info.Expiration, + UnsealedCid: nil, + }, + PreCommitDeposit: v9.PreCommitDeposit, + PreCommitEpoch: v9.PreCommitEpoch, + } + + ret.Info.UnsealedCid = v9.Info.UnsealedCid + + return ret +} + +func (s *state9) GetState() interface{} { + return &s.State +} + +func (s *state9) ActorKey() string { + return manifest.MinerKey +} + +func (s *state9) ActorVersion() actorstypes.Version { + return actorstypes.Version9 +} + +func (s *state9) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/multisig/actor.go.template b/chain/actors/builtin/multisig/actor.go.template new file mode 100644 index 000000000..9bb69a048 --- /dev/null +++ b/chain/actors/builtin/multisig/actor.go.template @@ -0,0 +1,156 @@ +package multisig + +import ( + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/ipfs/go-cid" + "fmt" + + "github.com/minio/blake2b-simd" + cbg "github.com/whyrusleeping/cbor-gen" + "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/cbor" + + msig{{.latestVersion}} "github.com/filecoin-project/go-state-types/builtin{{import .latestVersion}}multisig" +{{range .versions}} + {{if (le . 7)}} + builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin" + {{end}} +{{end}} + + builtintypes "github.com/filecoin-project/go-state-types/builtin" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/types" +) + +func Load(store adt.Store, act *types.Actor) (State, error) { + if name, av, ok := actors.GetActorMetaByCode(act.Code); ok { + if name != manifest.MultisigKey { + return nil, xerrors.Errorf("actor code is not multisig: %s", name) + } + + switch av { + {{range .versions}} + {{if (ge . 8)}} + case actorstypes.Version{{.}}: + return load{{.}}(store, act.Head) + {{end}} + {{end}} + } + } + + switch act.Code { +{{range .versions}} + {{if (le . 7)}} + case builtin{{.}}.MultisigActorCodeID: + return load{{.}}(store, act.Head) + {{end}} +{{end}} + } + + return nil, xerrors.Errorf("unknown actor code %s", act.Code) +} + +func MakeState(store adt.Store, av actorstypes.Version, signers []address.Address, threshold uint64, startEpoch abi.ChainEpoch, unlockDuration abi.ChainEpoch, initialBalance abi.TokenAmount) (State, error) { + switch av { +{{range .versions}} + case actorstypes.Version{{.}}: + return make{{.}}(store, signers, threshold, startEpoch, unlockDuration, initialBalance) +{{end}} +} + return nil, xerrors.Errorf("unknown actor version %d", av) +} + +type State interface { + cbor.Marshaler + + Code() cid.Cid + ActorKey() string + ActorVersion() actorstypes.Version + + LockedBalance(epoch abi.ChainEpoch) (abi.TokenAmount, error) + StartEpoch() (abi.ChainEpoch, error) + UnlockDuration() (abi.ChainEpoch, error) + InitialBalance() (abi.TokenAmount, error) + Threshold() (uint64, error) + Signers() ([]address.Address, error) + + ForEachPendingTxn(func(id int64, txn Transaction) error) error + PendingTxnChanged(State) (bool, error) + + transactions() (adt.Map, error) + decodeTransaction(val *cbg.Deferred) (Transaction, error) + GetState() interface{} +} + +type Transaction = msig{{.latestVersion}}.Transaction + +var Methods = builtintypes.MethodsMultisig + +func Message(version actorstypes.Version, from address.Address) MessageBuilder { + switch version { +{{range .versions}} + case actorstypes.Version{{.}}: + return message{{.}}{{"{"}}{{if (ge . 2)}}message0{from}{{else}}from{{end}}} +{{end}} default: + panic(fmt.Sprintf("unsupported actors version: %d", version)) + } +} + +type MessageBuilder interface { + // Create a new multisig with the specified parameters. + Create(signers []address.Address, threshold uint64, + vestingStart, vestingDuration abi.ChainEpoch, + initialAmount abi.TokenAmount) (*types.Message, error) + + // Propose a transaction to the given multisig. + Propose(msig, target address.Address, amt abi.TokenAmount, + method abi.MethodNum, params []byte) (*types.Message, error) + + // Approve a multisig transaction. The "hash" is optional. + Approve(msig address.Address, txID uint64, hash *ProposalHashData) (*types.Message, error) + + // Cancel a multisig transaction. The "hash" is optional. + Cancel(msig address.Address, txID uint64, hash *ProposalHashData) (*types.Message, error) +} + +// this type is the same between v0 and v2 +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)} + if data != nil { + if data.Requester.Protocol() != address.ID { + return nil, xerrors.Errorf("proposer address must be an ID address, was %s", data.Requester) + } + if data.Value.Sign() == -1 { + return nil, xerrors.Errorf("proposal value must be non-negative, was %s", data.Value) + } + if data.To == address.Undef { + return nil, xerrors.Errorf("proposed destination address must be set") + } + pser, err := data.Serialize() + if err != nil { + return nil, err + } + hash := blake2b.Sum256(pser) + params.ProposalHash = hash[:] + } + + return actors.SerializeParams(¶ms) +} + +func AllCodes() []cid.Cid { + return []cid.Cid{ {{range .versions}} + (&state{{.}}{}).Code(), + {{- end}} + } +} diff --git a/chain/actors/builtin/multisig/diff.go b/chain/actors/builtin/multisig/diff.go new file mode 100644 index 000000000..f24931fb8 --- /dev/null +++ b/chain/actors/builtin/multisig/diff.go @@ -0,0 +1,135 @@ +package multisig + +import ( + cbg "github.com/whyrusleeping/cbor-gen" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +type PendingTransactionChanges struct { + Added []TransactionChange + Modified []TransactionModification + Removed []TransactionChange +} + +type TransactionChange struct { + TxID int64 + Tx Transaction +} + +type TransactionModification struct { + TxID int64 + From Transaction + To Transaction +} + +func DiffPendingTransactions(pre, cur State) (*PendingTransactionChanges, error) { + results := new(PendingTransactionChanges) + if changed, err := pre.PendingTxnChanged(cur); err != nil { + return nil, err + } else if !changed { // if nothing has changed then return an empty result and bail. + return results, nil + } + + pret, err := pre.transactions() + if err != nil { + return nil, err + } + + curt, err := cur.transactions() + if err != nil { + return nil, err + } + + if err := adt.DiffAdtMap(pret, curt, &transactionDiffer{results, pre, cur}); err != nil { + return nil, err + } + return results, nil +} + +type transactionDiffer struct { + Results *PendingTransactionChanges + pre, after State +} + +func (t *transactionDiffer) AsKey(key string) (abi.Keyer, error) { + txID, err := abi.ParseIntKey(key) + if err != nil { + return nil, err + } + return abi.IntKey(txID), nil +} + +func (t *transactionDiffer) Add(key string, val *cbg.Deferred) error { + txID, err := abi.ParseIntKey(key) + if err != nil { + return err + } + tx, err := t.after.decodeTransaction(val) + if err != nil { + return err + } + t.Results.Added = append(t.Results.Added, TransactionChange{ + TxID: txID, + Tx: tx, + }) + return nil +} + +func (t *transactionDiffer) Modify(key string, from, to *cbg.Deferred) error { + txID, err := abi.ParseIntKey(key) + if err != nil { + return err + } + + txFrom, err := t.pre.decodeTransaction(from) + if err != nil { + return err + } + + txTo, err := t.after.decodeTransaction(to) + if err != nil { + return err + } + + if approvalsChanged(txFrom.Approved, txTo.Approved) { + t.Results.Modified = append(t.Results.Modified, TransactionModification{ + TxID: txID, + From: txFrom, + To: txTo, + }) + } + + return nil +} + +func approvalsChanged(from, to []address.Address) bool { + if len(from) != len(to) { + return true + } + for idx := range from { + if from[idx] != to[idx] { + return true + } + } + return false +} + +func (t *transactionDiffer) Remove(key string, val *cbg.Deferred) error { + txID, err := abi.ParseIntKey(key) + if err != nil { + return err + } + tx, err := t.pre.decodeTransaction(val) + if err != nil { + return err + } + t.Results.Removed = append(t.Results.Removed, TransactionChange{ + TxID: txID, + Tx: tx, + }) + return nil +} diff --git a/chain/actors/builtin/multisig/message.go.template b/chain/actors/builtin/multisig/message.go.template new file mode 100644 index 000000000..b5bc6924f --- /dev/null +++ b/chain/actors/builtin/multisig/message.go.template @@ -0,0 +1,167 @@ +package multisig + +import ( + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + + {{if (le .v 7)}} + builtin{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/builtin" + init{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/builtin/init" + multisig{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/builtin/multisig" + {{else}} + actorstypes "github.com/filecoin-project/go-state-types/actors" + multisig{{.v}} "github.com/filecoin-project/go-state-types/builtin{{.import}}multisig" + init{{.latestVersion}} "github.com/filecoin-project/go-state-types/builtin/v{{.latestVersion}}/init" + "github.com/filecoin-project/go-state-types/manifest" + {{end}} + + builtintypes "github.com/filecoin-project/go-state-types/builtin" + "github.com/filecoin-project/lotus/chain/actors" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/types" +) + +type message{{.v}} struct{ {{if (ge .v 2)}}message0{{else}}from address.Address{{end}} } + +func (m message{{.v}}) Create( + signers []address.Address, threshold uint64, + unlockStart, unlockDuration abi.ChainEpoch, + initialAmount abi.TokenAmount, +) (*types.Message, error) { + + lenAddrs := uint64(len(signers)) + + if lenAddrs < threshold { + return nil, xerrors.Errorf("cannot require signing of more addresses than provided for multisig") + } + + if threshold == 0 { + threshold = lenAddrs + } + + if m.from == address.Undef { + return nil, xerrors.Errorf("must provide source address") + } +{{if (le .v 1)}} + if unlockStart != 0 { + return nil, xerrors.Errorf("actors v0 does not support a non-zero vesting start time") + } +{{end}} + // Set up constructor parameters for multisig + msigParams := &multisig{{.v}}.ConstructorParams{ + Signers: signers, + NumApprovalsThreshold: threshold, + UnlockDuration: unlockDuration,{{if (ge .v 2)}} + StartEpoch: unlockStart,{{end}} + } + + enc, actErr := actors.SerializeParams(msigParams) + if actErr != nil { + return nil, actErr + } + + {{if (le .v 7)}} + // new actors are created by invoking 'exec' on the init actor with the constructor params + execParams := &init{{.v}}.ExecParams{ + CodeCID: builtin{{.v}}.MultisigActorCodeID, + ConstructorParams: enc, + } + {{else}} + code, ok := actors.GetActorCodeID(actorstypes.Version{{.v}}, manifest.MultisigKey) + if !ok { + return nil, xerrors.Errorf("failed to get multisig code ID") + } + + // new actors are created by invoking 'exec' on the init actor with the constructor params + execParams := &init{{.latestVersion}}.ExecParams{ + CodeCID: code, + ConstructorParams: enc, + } + {{end}} + + enc, actErr = actors.SerializeParams(execParams) + if actErr != nil { + return nil, actErr + } + + return &types.Message{ + To: init_.Address, + From: m.from, + Method: builtintypes.MethodsInit.Exec, + Params: enc, + Value: initialAmount, + }, nil +} + +{{if (le .v 1)}} + +func (m message0) Propose(msig, to address.Address, amt abi.TokenAmount, + method abi.MethodNum, params []byte) (*types.Message, error) { + + if msig == address.Undef { + return nil, xerrors.Errorf("must provide a multisig address for proposal") + } + + if to == address.Undef { + return nil, xerrors.Errorf("must provide a target address for proposal") + } + + if amt.Sign() == -1 { + return nil, xerrors.Errorf("must provide a non-negative amount for proposed send") + } + + if m.from == address.Undef { + return nil, xerrors.Errorf("must provide source address") + } + + enc, actErr := actors.SerializeParams(&multisig0.ProposeParams{ + To: to, + Value: amt, + Method: method, + Params: params, + }) + if actErr != nil { + return nil, xerrors.Errorf("failed to serialize parameters: %w", actErr) + } + + return &types.Message{ + To: msig, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin0.MethodsMultisig.Propose, + Params: enc, + }, nil +} + +func (m message0) Approve(msig address.Address, txID uint64, hashData *ProposalHashData) (*types.Message, error) { + enc, err := txnParams(txID, hashData) + if err != nil { + return nil, err + } + + return &types.Message{ + To: msig, + From: m.from, + Value: types.NewInt(0), + Method: builtin0.MethodsMultisig.Approve, + Params: enc, + }, nil +} + +func (m message0) Cancel(msig address.Address, txID uint64, hashData *ProposalHashData) (*types.Message, error) { + enc, err := txnParams(txID, hashData) + if err != nil { + return nil, err + } + + return &types.Message{ + To: msig, + From: m.from, + Value: types.NewInt(0), + Method: builtin0.MethodsMultisig.Cancel, + Params: enc, + }, nil +} +{{end}} diff --git a/chain/actors/builtin/multisig/message0.go b/chain/actors/builtin/multisig/message0.go new file mode 100644 index 000000000..7dbdf444c --- /dev/null +++ b/chain/actors/builtin/multisig/message0.go @@ -0,0 +1,142 @@ +package multisig + +import ( + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + builtintypes "github.com/filecoin-project/go-state-types/builtin" + builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" + init0 "github.com/filecoin-project/specs-actors/actors/builtin/init" + multisig0 "github.com/filecoin-project/specs-actors/actors/builtin/multisig" + + "github.com/filecoin-project/lotus/chain/actors" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/types" +) + +type message0 struct{ from address.Address } + +func (m message0) Create( + signers []address.Address, threshold uint64, + unlockStart, unlockDuration abi.ChainEpoch, + initialAmount abi.TokenAmount, +) (*types.Message, error) { + + lenAddrs := uint64(len(signers)) + + if lenAddrs < threshold { + return nil, xerrors.Errorf("cannot require signing of more addresses than provided for multisig") + } + + if threshold == 0 { + threshold = lenAddrs + } + + if m.from == address.Undef { + return nil, xerrors.Errorf("must provide source address") + } + + if unlockStart != 0 { + return nil, xerrors.Errorf("actors v0 does not support a non-zero vesting start time") + } + + // Set up constructor parameters for multisig + msigParams := &multisig0.ConstructorParams{ + Signers: signers, + NumApprovalsThreshold: threshold, + UnlockDuration: unlockDuration, + } + + enc, actErr := actors.SerializeParams(msigParams) + if actErr != nil { + return nil, actErr + } + + // new actors are created by invoking 'exec' on the init actor with the constructor params + execParams := &init0.ExecParams{ + CodeCID: builtin0.MultisigActorCodeID, + ConstructorParams: enc, + } + + enc, actErr = actors.SerializeParams(execParams) + if actErr != nil { + return nil, actErr + } + + return &types.Message{ + To: init_.Address, + From: m.from, + Method: builtintypes.MethodsInit.Exec, + Params: enc, + Value: initialAmount, + }, nil +} + +func (m message0) Propose(msig, to address.Address, amt abi.TokenAmount, + method abi.MethodNum, params []byte) (*types.Message, error) { + + if msig == address.Undef { + return nil, xerrors.Errorf("must provide a multisig address for proposal") + } + + if to == address.Undef { + return nil, xerrors.Errorf("must provide a target address for proposal") + } + + if amt.Sign() == -1 { + return nil, xerrors.Errorf("must provide a non-negative amount for proposed send") + } + + if m.from == address.Undef { + return nil, xerrors.Errorf("must provide source address") + } + + enc, actErr := actors.SerializeParams(&multisig0.ProposeParams{ + To: to, + Value: amt, + Method: method, + Params: params, + }) + if actErr != nil { + return nil, xerrors.Errorf("failed to serialize parameters: %w", actErr) + } + + return &types.Message{ + To: msig, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin0.MethodsMultisig.Propose, + Params: enc, + }, nil +} + +func (m message0) Approve(msig address.Address, txID uint64, hashData *ProposalHashData) (*types.Message, error) { + enc, err := txnParams(txID, hashData) + if err != nil { + return nil, err + } + + return &types.Message{ + To: msig, + From: m.from, + Value: types.NewInt(0), + Method: builtin0.MethodsMultisig.Approve, + Params: enc, + }, nil +} + +func (m message0) Cancel(msig address.Address, txID uint64, hashData *ProposalHashData) (*types.Message, error) { + enc, err := txnParams(txID, hashData) + if err != nil { + return nil, err + } + + return &types.Message{ + To: msig, + From: m.from, + Value: types.NewInt(0), + Method: builtin0.MethodsMultisig.Cancel, + Params: enc, + }, nil +} diff --git a/chain/actors/builtin/multisig/message10.go b/chain/actors/builtin/multisig/message10.go new file mode 100644 index 000000000..5f70ea3c1 --- /dev/null +++ b/chain/actors/builtin/multisig/message10.go @@ -0,0 +1,77 @@ +package multisig + +import ( + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtintypes "github.com/filecoin-project/go-state-types/builtin" + multisig10 "github.com/filecoin-project/go-state-types/builtin/v10/multisig" + init11 "github.com/filecoin-project/go-state-types/builtin/v11/init" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/types" +) + +type message10 struct{ message0 } + +func (m message10) Create( + signers []address.Address, threshold uint64, + unlockStart, unlockDuration abi.ChainEpoch, + initialAmount abi.TokenAmount, +) (*types.Message, error) { + + lenAddrs := uint64(len(signers)) + + if lenAddrs < threshold { + return nil, xerrors.Errorf("cannot require signing of more addresses than provided for multisig") + } + + if threshold == 0 { + threshold = lenAddrs + } + + if m.from == address.Undef { + return nil, xerrors.Errorf("must provide source address") + } + + // Set up constructor parameters for multisig + msigParams := &multisig10.ConstructorParams{ + Signers: signers, + NumApprovalsThreshold: threshold, + UnlockDuration: unlockDuration, + StartEpoch: unlockStart, + } + + enc, actErr := actors.SerializeParams(msigParams) + if actErr != nil { + return nil, actErr + } + + code, ok := actors.GetActorCodeID(actorstypes.Version10, manifest.MultisigKey) + if !ok { + return nil, xerrors.Errorf("failed to get multisig code ID") + } + + // new actors are created by invoking 'exec' on the init actor with the constructor params + execParams := &init11.ExecParams{ + CodeCID: code, + ConstructorParams: enc, + } + + enc, actErr = actors.SerializeParams(execParams) + if actErr != nil { + return nil, actErr + } + + return &types.Message{ + To: init_.Address, + From: m.from, + Method: builtintypes.MethodsInit.Exec, + Params: enc, + Value: initialAmount, + }, nil +} diff --git a/chain/actors/builtin/multisig/message11.go b/chain/actors/builtin/multisig/message11.go new file mode 100644 index 000000000..a2c086614 --- /dev/null +++ b/chain/actors/builtin/multisig/message11.go @@ -0,0 +1,77 @@ +package multisig + +import ( + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtintypes "github.com/filecoin-project/go-state-types/builtin" + init11 "github.com/filecoin-project/go-state-types/builtin/v11/init" + multisig11 "github.com/filecoin-project/go-state-types/builtin/v11/multisig" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/types" +) + +type message11 struct{ message0 } + +func (m message11) Create( + signers []address.Address, threshold uint64, + unlockStart, unlockDuration abi.ChainEpoch, + initialAmount abi.TokenAmount, +) (*types.Message, error) { + + lenAddrs := uint64(len(signers)) + + if lenAddrs < threshold { + return nil, xerrors.Errorf("cannot require signing of more addresses than provided for multisig") + } + + if threshold == 0 { + threshold = lenAddrs + } + + if m.from == address.Undef { + return nil, xerrors.Errorf("must provide source address") + } + + // Set up constructor parameters for multisig + msigParams := &multisig11.ConstructorParams{ + Signers: signers, + NumApprovalsThreshold: threshold, + UnlockDuration: unlockDuration, + StartEpoch: unlockStart, + } + + enc, actErr := actors.SerializeParams(msigParams) + if actErr != nil { + return nil, actErr + } + + code, ok := actors.GetActorCodeID(actorstypes.Version11, manifest.MultisigKey) + if !ok { + return nil, xerrors.Errorf("failed to get multisig code ID") + } + + // new actors are created by invoking 'exec' on the init actor with the constructor params + execParams := &init11.ExecParams{ + CodeCID: code, + ConstructorParams: enc, + } + + enc, actErr = actors.SerializeParams(execParams) + if actErr != nil { + return nil, actErr + } + + return &types.Message{ + To: init_.Address, + From: m.from, + Method: builtintypes.MethodsInit.Exec, + Params: enc, + Value: initialAmount, + }, nil +} diff --git a/chain/actors/builtin/multisig/message2.go b/chain/actors/builtin/multisig/message2.go new file mode 100644 index 000000000..91ad17b84 --- /dev/null +++ b/chain/actors/builtin/multisig/message2.go @@ -0,0 +1,71 @@ +package multisig + +import ( + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + builtintypes "github.com/filecoin-project/go-state-types/builtin" + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + init2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/init" + multisig2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/multisig" + + "github.com/filecoin-project/lotus/chain/actors" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/types" +) + +type message2 struct{ message0 } + +func (m message2) Create( + signers []address.Address, threshold uint64, + unlockStart, unlockDuration abi.ChainEpoch, + initialAmount abi.TokenAmount, +) (*types.Message, error) { + + lenAddrs := uint64(len(signers)) + + if lenAddrs < threshold { + return nil, xerrors.Errorf("cannot require signing of more addresses than provided for multisig") + } + + if threshold == 0 { + threshold = lenAddrs + } + + if m.from == address.Undef { + return nil, xerrors.Errorf("must provide source address") + } + + // Set up constructor parameters for multisig + msigParams := &multisig2.ConstructorParams{ + Signers: signers, + NumApprovalsThreshold: threshold, + UnlockDuration: unlockDuration, + StartEpoch: unlockStart, + } + + enc, actErr := actors.SerializeParams(msigParams) + if actErr != nil { + return nil, actErr + } + + // new actors are created by invoking 'exec' on the init actor with the constructor params + execParams := &init2.ExecParams{ + CodeCID: builtin2.MultisigActorCodeID, + ConstructorParams: enc, + } + + enc, actErr = actors.SerializeParams(execParams) + if actErr != nil { + return nil, actErr + } + + return &types.Message{ + To: init_.Address, + From: m.from, + Method: builtintypes.MethodsInit.Exec, + Params: enc, + Value: initialAmount, + }, nil +} diff --git a/chain/actors/builtin/multisig/message3.go b/chain/actors/builtin/multisig/message3.go new file mode 100644 index 000000000..4124e00c8 --- /dev/null +++ b/chain/actors/builtin/multisig/message3.go @@ -0,0 +1,71 @@ +package multisig + +import ( + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + builtintypes "github.com/filecoin-project/go-state-types/builtin" + builtin3 "github.com/filecoin-project/specs-actors/v3/actors/builtin" + init3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/init" + multisig3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/multisig" + + "github.com/filecoin-project/lotus/chain/actors" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/types" +) + +type message3 struct{ message0 } + +func (m message3) Create( + signers []address.Address, threshold uint64, + unlockStart, unlockDuration abi.ChainEpoch, + initialAmount abi.TokenAmount, +) (*types.Message, error) { + + lenAddrs := uint64(len(signers)) + + if lenAddrs < threshold { + return nil, xerrors.Errorf("cannot require signing of more addresses than provided for multisig") + } + + if threshold == 0 { + threshold = lenAddrs + } + + if m.from == address.Undef { + return nil, xerrors.Errorf("must provide source address") + } + + // Set up constructor parameters for multisig + msigParams := &multisig3.ConstructorParams{ + Signers: signers, + NumApprovalsThreshold: threshold, + UnlockDuration: unlockDuration, + StartEpoch: unlockStart, + } + + enc, actErr := actors.SerializeParams(msigParams) + if actErr != nil { + return nil, actErr + } + + // new actors are created by invoking 'exec' on the init actor with the constructor params + execParams := &init3.ExecParams{ + CodeCID: builtin3.MultisigActorCodeID, + ConstructorParams: enc, + } + + enc, actErr = actors.SerializeParams(execParams) + if actErr != nil { + return nil, actErr + } + + return &types.Message{ + To: init_.Address, + From: m.from, + Method: builtintypes.MethodsInit.Exec, + Params: enc, + Value: initialAmount, + }, nil +} diff --git a/chain/actors/builtin/multisig/message4.go b/chain/actors/builtin/multisig/message4.go new file mode 100644 index 000000000..33449df75 --- /dev/null +++ b/chain/actors/builtin/multisig/message4.go @@ -0,0 +1,71 @@ +package multisig + +import ( + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + builtintypes "github.com/filecoin-project/go-state-types/builtin" + builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" + init4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/init" + multisig4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/multisig" + + "github.com/filecoin-project/lotus/chain/actors" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/types" +) + +type message4 struct{ message0 } + +func (m message4) Create( + signers []address.Address, threshold uint64, + unlockStart, unlockDuration abi.ChainEpoch, + initialAmount abi.TokenAmount, +) (*types.Message, error) { + + lenAddrs := uint64(len(signers)) + + if lenAddrs < threshold { + return nil, xerrors.Errorf("cannot require signing of more addresses than provided for multisig") + } + + if threshold == 0 { + threshold = lenAddrs + } + + if m.from == address.Undef { + return nil, xerrors.Errorf("must provide source address") + } + + // Set up constructor parameters for multisig + msigParams := &multisig4.ConstructorParams{ + Signers: signers, + NumApprovalsThreshold: threshold, + UnlockDuration: unlockDuration, + StartEpoch: unlockStart, + } + + enc, actErr := actors.SerializeParams(msigParams) + if actErr != nil { + return nil, actErr + } + + // new actors are created by invoking 'exec' on the init actor with the constructor params + execParams := &init4.ExecParams{ + CodeCID: builtin4.MultisigActorCodeID, + ConstructorParams: enc, + } + + enc, actErr = actors.SerializeParams(execParams) + if actErr != nil { + return nil, actErr + } + + return &types.Message{ + To: init_.Address, + From: m.from, + Method: builtintypes.MethodsInit.Exec, + Params: enc, + Value: initialAmount, + }, nil +} diff --git a/chain/actors/builtin/multisig/message5.go b/chain/actors/builtin/multisig/message5.go new file mode 100644 index 000000000..46c35dabc --- /dev/null +++ b/chain/actors/builtin/multisig/message5.go @@ -0,0 +1,71 @@ +package multisig + +import ( + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + builtintypes "github.com/filecoin-project/go-state-types/builtin" + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" + init5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/init" + multisig5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/multisig" + + "github.com/filecoin-project/lotus/chain/actors" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/types" +) + +type message5 struct{ message0 } + +func (m message5) Create( + signers []address.Address, threshold uint64, + unlockStart, unlockDuration abi.ChainEpoch, + initialAmount abi.TokenAmount, +) (*types.Message, error) { + + lenAddrs := uint64(len(signers)) + + if lenAddrs < threshold { + return nil, xerrors.Errorf("cannot require signing of more addresses than provided for multisig") + } + + if threshold == 0 { + threshold = lenAddrs + } + + if m.from == address.Undef { + return nil, xerrors.Errorf("must provide source address") + } + + // Set up constructor parameters for multisig + msigParams := &multisig5.ConstructorParams{ + Signers: signers, + NumApprovalsThreshold: threshold, + UnlockDuration: unlockDuration, + StartEpoch: unlockStart, + } + + enc, actErr := actors.SerializeParams(msigParams) + if actErr != nil { + return nil, actErr + } + + // new actors are created by invoking 'exec' on the init actor with the constructor params + execParams := &init5.ExecParams{ + CodeCID: builtin5.MultisigActorCodeID, + ConstructorParams: enc, + } + + enc, actErr = actors.SerializeParams(execParams) + if actErr != nil { + return nil, actErr + } + + return &types.Message{ + To: init_.Address, + From: m.from, + Method: builtintypes.MethodsInit.Exec, + Params: enc, + Value: initialAmount, + }, nil +} diff --git a/chain/actors/builtin/multisig/message6.go b/chain/actors/builtin/multisig/message6.go new file mode 100644 index 000000000..f528cfbb5 --- /dev/null +++ b/chain/actors/builtin/multisig/message6.go @@ -0,0 +1,71 @@ +package multisig + +import ( + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + builtintypes "github.com/filecoin-project/go-state-types/builtin" + builtin6 "github.com/filecoin-project/specs-actors/v6/actors/builtin" + init6 "github.com/filecoin-project/specs-actors/v6/actors/builtin/init" + multisig6 "github.com/filecoin-project/specs-actors/v6/actors/builtin/multisig" + + "github.com/filecoin-project/lotus/chain/actors" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/types" +) + +type message6 struct{ message0 } + +func (m message6) Create( + signers []address.Address, threshold uint64, + unlockStart, unlockDuration abi.ChainEpoch, + initialAmount abi.TokenAmount, +) (*types.Message, error) { + + lenAddrs := uint64(len(signers)) + + if lenAddrs < threshold { + return nil, xerrors.Errorf("cannot require signing of more addresses than provided for multisig") + } + + if threshold == 0 { + threshold = lenAddrs + } + + if m.from == address.Undef { + return nil, xerrors.Errorf("must provide source address") + } + + // Set up constructor parameters for multisig + msigParams := &multisig6.ConstructorParams{ + Signers: signers, + NumApprovalsThreshold: threshold, + UnlockDuration: unlockDuration, + StartEpoch: unlockStart, + } + + enc, actErr := actors.SerializeParams(msigParams) + if actErr != nil { + return nil, actErr + } + + // new actors are created by invoking 'exec' on the init actor with the constructor params + execParams := &init6.ExecParams{ + CodeCID: builtin6.MultisigActorCodeID, + ConstructorParams: enc, + } + + enc, actErr = actors.SerializeParams(execParams) + if actErr != nil { + return nil, actErr + } + + return &types.Message{ + To: init_.Address, + From: m.from, + Method: builtintypes.MethodsInit.Exec, + Params: enc, + Value: initialAmount, + }, nil +} diff --git a/chain/actors/builtin/multisig/message7.go b/chain/actors/builtin/multisig/message7.go new file mode 100644 index 000000000..6e62dad13 --- /dev/null +++ b/chain/actors/builtin/multisig/message7.go @@ -0,0 +1,71 @@ +package multisig + +import ( + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + builtintypes "github.com/filecoin-project/go-state-types/builtin" + builtin7 "github.com/filecoin-project/specs-actors/v7/actors/builtin" + init7 "github.com/filecoin-project/specs-actors/v7/actors/builtin/init" + multisig7 "github.com/filecoin-project/specs-actors/v7/actors/builtin/multisig" + + "github.com/filecoin-project/lotus/chain/actors" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/types" +) + +type message7 struct{ message0 } + +func (m message7) Create( + signers []address.Address, threshold uint64, + unlockStart, unlockDuration abi.ChainEpoch, + initialAmount abi.TokenAmount, +) (*types.Message, error) { + + lenAddrs := uint64(len(signers)) + + if lenAddrs < threshold { + return nil, xerrors.Errorf("cannot require signing of more addresses than provided for multisig") + } + + if threshold == 0 { + threshold = lenAddrs + } + + if m.from == address.Undef { + return nil, xerrors.Errorf("must provide source address") + } + + // Set up constructor parameters for multisig + msigParams := &multisig7.ConstructorParams{ + Signers: signers, + NumApprovalsThreshold: threshold, + UnlockDuration: unlockDuration, + StartEpoch: unlockStart, + } + + enc, actErr := actors.SerializeParams(msigParams) + if actErr != nil { + return nil, actErr + } + + // new actors are created by invoking 'exec' on the init actor with the constructor params + execParams := &init7.ExecParams{ + CodeCID: builtin7.MultisigActorCodeID, + ConstructorParams: enc, + } + + enc, actErr = actors.SerializeParams(execParams) + if actErr != nil { + return nil, actErr + } + + return &types.Message{ + To: init_.Address, + From: m.from, + Method: builtintypes.MethodsInit.Exec, + Params: enc, + Value: initialAmount, + }, nil +} diff --git a/chain/actors/builtin/multisig/message8.go b/chain/actors/builtin/multisig/message8.go new file mode 100644 index 000000000..817d66726 --- /dev/null +++ b/chain/actors/builtin/multisig/message8.go @@ -0,0 +1,77 @@ +package multisig + +import ( + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtintypes "github.com/filecoin-project/go-state-types/builtin" + init11 "github.com/filecoin-project/go-state-types/builtin/v11/init" + multisig8 "github.com/filecoin-project/go-state-types/builtin/v8/multisig" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/types" +) + +type message8 struct{ message0 } + +func (m message8) Create( + signers []address.Address, threshold uint64, + unlockStart, unlockDuration abi.ChainEpoch, + initialAmount abi.TokenAmount, +) (*types.Message, error) { + + lenAddrs := uint64(len(signers)) + + if lenAddrs < threshold { + return nil, xerrors.Errorf("cannot require signing of more addresses than provided for multisig") + } + + if threshold == 0 { + threshold = lenAddrs + } + + if m.from == address.Undef { + return nil, xerrors.Errorf("must provide source address") + } + + // Set up constructor parameters for multisig + msigParams := &multisig8.ConstructorParams{ + Signers: signers, + NumApprovalsThreshold: threshold, + UnlockDuration: unlockDuration, + StartEpoch: unlockStart, + } + + enc, actErr := actors.SerializeParams(msigParams) + if actErr != nil { + return nil, actErr + } + + code, ok := actors.GetActorCodeID(actorstypes.Version8, manifest.MultisigKey) + if !ok { + return nil, xerrors.Errorf("failed to get multisig code ID") + } + + // new actors are created by invoking 'exec' on the init actor with the constructor params + execParams := &init11.ExecParams{ + CodeCID: code, + ConstructorParams: enc, + } + + enc, actErr = actors.SerializeParams(execParams) + if actErr != nil { + return nil, actErr + } + + return &types.Message{ + To: init_.Address, + From: m.from, + Method: builtintypes.MethodsInit.Exec, + Params: enc, + Value: initialAmount, + }, nil +} diff --git a/chain/actors/builtin/multisig/message9.go b/chain/actors/builtin/multisig/message9.go new file mode 100644 index 000000000..1472c4e66 --- /dev/null +++ b/chain/actors/builtin/multisig/message9.go @@ -0,0 +1,77 @@ +package multisig + +import ( + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtintypes "github.com/filecoin-project/go-state-types/builtin" + init11 "github.com/filecoin-project/go-state-types/builtin/v11/init" + multisig9 "github.com/filecoin-project/go-state-types/builtin/v9/multisig" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/types" +) + +type message9 struct{ message0 } + +func (m message9) Create( + signers []address.Address, threshold uint64, + unlockStart, unlockDuration abi.ChainEpoch, + initialAmount abi.TokenAmount, +) (*types.Message, error) { + + lenAddrs := uint64(len(signers)) + + if lenAddrs < threshold { + return nil, xerrors.Errorf("cannot require signing of more addresses than provided for multisig") + } + + if threshold == 0 { + threshold = lenAddrs + } + + if m.from == address.Undef { + return nil, xerrors.Errorf("must provide source address") + } + + // Set up constructor parameters for multisig + msigParams := &multisig9.ConstructorParams{ + Signers: signers, + NumApprovalsThreshold: threshold, + UnlockDuration: unlockDuration, + StartEpoch: unlockStart, + } + + enc, actErr := actors.SerializeParams(msigParams) + if actErr != nil { + return nil, actErr + } + + code, ok := actors.GetActorCodeID(actorstypes.Version9, manifest.MultisigKey) + if !ok { + return nil, xerrors.Errorf("failed to get multisig code ID") + } + + // new actors are created by invoking 'exec' on the init actor with the constructor params + execParams := &init11.ExecParams{ + CodeCID: code, + ConstructorParams: enc, + } + + enc, actErr = actors.SerializeParams(execParams) + if actErr != nil { + return nil, actErr + } + + return &types.Message{ + To: init_.Address, + From: m.from, + Method: builtintypes.MethodsInit.Exec, + Params: enc, + Value: initialAmount, + }, nil +} diff --git a/chain/actors/builtin/multisig/multisig.go b/chain/actors/builtin/multisig/multisig.go new file mode 100644 index 000000000..9ab8fffb5 --- /dev/null +++ b/chain/actors/builtin/multisig/multisig.go @@ -0,0 +1,248 @@ +package multisig + +import ( + "fmt" + + "github.com/ipfs/go-cid" + "github.com/minio/blake2b-simd" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtintypes "github.com/filecoin-project/go-state-types/builtin" + msig11 "github.com/filecoin-project/go-state-types/builtin/v11/multisig" + "github.com/filecoin-project/go-state-types/cbor" + "github.com/filecoin-project/go-state-types/manifest" + builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + builtin3 "github.com/filecoin-project/specs-actors/v3/actors/builtin" + builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" + builtin6 "github.com/filecoin-project/specs-actors/v6/actors/builtin" + builtin7 "github.com/filecoin-project/specs-actors/v7/actors/builtin" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/types" +) + +func Load(store adt.Store, act *types.Actor) (State, error) { + if name, av, ok := actors.GetActorMetaByCode(act.Code); ok { + if name != manifest.MultisigKey { + return nil, xerrors.Errorf("actor code is not multisig: %s", name) + } + + switch av { + + case actorstypes.Version8: + return load8(store, act.Head) + + case actorstypes.Version9: + return load9(store, act.Head) + + case actorstypes.Version10: + return load10(store, act.Head) + + case actorstypes.Version11: + return load11(store, act.Head) + + } + } + + switch act.Code { + + case builtin0.MultisigActorCodeID: + return load0(store, act.Head) + + case builtin2.MultisigActorCodeID: + return load2(store, act.Head) + + case builtin3.MultisigActorCodeID: + return load3(store, act.Head) + + case builtin4.MultisigActorCodeID: + return load4(store, act.Head) + + case builtin5.MultisigActorCodeID: + return load5(store, act.Head) + + case builtin6.MultisigActorCodeID: + return load6(store, act.Head) + + case builtin7.MultisigActorCodeID: + return load7(store, act.Head) + + } + + return nil, xerrors.Errorf("unknown actor code %s", act.Code) +} + +func MakeState(store adt.Store, av actorstypes.Version, signers []address.Address, threshold uint64, startEpoch abi.ChainEpoch, unlockDuration abi.ChainEpoch, initialBalance abi.TokenAmount) (State, error) { + switch av { + + case actorstypes.Version0: + return make0(store, signers, threshold, startEpoch, unlockDuration, initialBalance) + + case actorstypes.Version2: + return make2(store, signers, threshold, startEpoch, unlockDuration, initialBalance) + + case actorstypes.Version3: + return make3(store, signers, threshold, startEpoch, unlockDuration, initialBalance) + + case actorstypes.Version4: + return make4(store, signers, threshold, startEpoch, unlockDuration, initialBalance) + + case actorstypes.Version5: + return make5(store, signers, threshold, startEpoch, unlockDuration, initialBalance) + + case actorstypes.Version6: + return make6(store, signers, threshold, startEpoch, unlockDuration, initialBalance) + + case actorstypes.Version7: + return make7(store, signers, threshold, startEpoch, unlockDuration, initialBalance) + + case actorstypes.Version8: + return make8(store, signers, threshold, startEpoch, unlockDuration, initialBalance) + + case actorstypes.Version9: + return make9(store, signers, threshold, startEpoch, unlockDuration, initialBalance) + + case actorstypes.Version10: + return make10(store, signers, threshold, startEpoch, unlockDuration, initialBalance) + + case actorstypes.Version11: + return make11(store, signers, threshold, startEpoch, unlockDuration, initialBalance) + + } + return nil, xerrors.Errorf("unknown actor version %d", av) +} + +type State interface { + cbor.Marshaler + + Code() cid.Cid + ActorKey() string + ActorVersion() actorstypes.Version + + LockedBalance(epoch abi.ChainEpoch) (abi.TokenAmount, error) + StartEpoch() (abi.ChainEpoch, error) + UnlockDuration() (abi.ChainEpoch, error) + InitialBalance() (abi.TokenAmount, error) + Threshold() (uint64, error) + Signers() ([]address.Address, error) + + ForEachPendingTxn(func(id int64, txn Transaction) error) error + PendingTxnChanged(State) (bool, error) + + transactions() (adt.Map, error) + decodeTransaction(val *cbg.Deferred) (Transaction, error) + GetState() interface{} +} + +type Transaction = msig11.Transaction + +var Methods = builtintypes.MethodsMultisig + +func Message(version actorstypes.Version, from address.Address) MessageBuilder { + switch version { + + case actorstypes.Version0: + return message0{from} + + case actorstypes.Version2: + return message2{message0{from}} + + case actorstypes.Version3: + return message3{message0{from}} + + case actorstypes.Version4: + return message4{message0{from}} + + case actorstypes.Version5: + return message5{message0{from}} + + case actorstypes.Version6: + return message6{message0{from}} + + case actorstypes.Version7: + return message7{message0{from}} + + case actorstypes.Version8: + return message8{message0{from}} + + case actorstypes.Version9: + return message9{message0{from}} + + case actorstypes.Version10: + return message10{message0{from}} + + case actorstypes.Version11: + return message11{message0{from}} + default: + panic(fmt.Sprintf("unsupported actors version: %d", version)) + } +} + +type MessageBuilder interface { + // Create a new multisig with the specified parameters. + Create(signers []address.Address, threshold uint64, + vestingStart, vestingDuration abi.ChainEpoch, + initialAmount abi.TokenAmount) (*types.Message, error) + + // Propose a transaction to the given multisig. + Propose(msig, target address.Address, amt abi.TokenAmount, + method abi.MethodNum, params []byte) (*types.Message, error) + + // Approve a multisig transaction. The "hash" is optional. + Approve(msig address.Address, txID uint64, hash *ProposalHashData) (*types.Message, error) + + // Cancel a multisig transaction. The "hash" is optional. + Cancel(msig address.Address, txID uint64, hash *ProposalHashData) (*types.Message, error) +} + +// this type is the same between v0 and v2 +type ProposalHashData = msig11.ProposalHashData +type ProposeReturn = msig11.ProposeReturn +type ProposeParams = msig11.ProposeParams +type ApproveReturn = msig11.ApproveReturn + +func txnParams(id uint64, data *ProposalHashData) ([]byte, error) { + params := msig11.TxnIDParams{ID: msig11.TxnID(id)} + if data != nil { + if data.Requester.Protocol() != address.ID { + return nil, xerrors.Errorf("proposer address must be an ID address, was %s", data.Requester) + } + if data.Value.Sign() == -1 { + return nil, xerrors.Errorf("proposal value must be non-negative, was %s", data.Value) + } + if data.To == address.Undef { + return nil, xerrors.Errorf("proposed destination address must be set") + } + pser, err := data.Serialize() + if err != nil { + return nil, err + } + hash := blake2b.Sum256(pser) + params.ProposalHash = hash[:] + } + + return actors.SerializeParams(¶ms) +} + +func AllCodes() []cid.Cid { + return []cid.Cid{ + (&state0{}).Code(), + (&state2{}).Code(), + (&state3{}).Code(), + (&state4{}).Code(), + (&state5{}).Code(), + (&state6{}).Code(), + (&state7{}).Code(), + (&state8{}).Code(), + (&state9{}).Code(), + (&state10{}).Code(), + (&state11{}).Code(), + } +} diff --git a/chain/actors/builtin/multisig/state.go.template b/chain/actors/builtin/multisig/state.go.template new file mode 100644 index 000000000..5d04b94be --- /dev/null +++ b/chain/actors/builtin/multisig/state.go.template @@ -0,0 +1,154 @@ +package multisig + +import ( + "fmt" + "bytes" + "encoding/binary" + + actorstypes "github.com/filecoin-project/go-state-types/actors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/go-state-types/manifest" + +{{if (le .v 7)}} + {{if (ge .v 3)}} + builtin{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/builtin" + {{end}} + msig{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/builtin/multisig" + adt{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/util/adt" +{{else}} + msig{{.v}} "github.com/filecoin-project/go-state-types/builtin{{.import}}multisig" + adt{{.v}} "github.com/filecoin-project/go-state-types/builtin{{.import}}util/adt" + builtin{{.v}} "github.com/filecoin-project/go-state-types/builtin" +{{end}} +) + +var _ State = (*state{{.v}})(nil) + +func load{{.v}}(store adt.Store, root cid.Cid) (State, error) { + out := state{{.v}}{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make{{.v}}(store adt.Store, signers []address.Address, threshold uint64, startEpoch abi.ChainEpoch, unlockDuration abi.ChainEpoch, initialBalance abi.TokenAmount) (State, error) { + out := state{{.v}}{store: store} + out.State = msig{{.v}}.State{} + out.State.Signers = signers + out.State.NumApprovalsThreshold = threshold + out.State.StartEpoch = startEpoch + out.State.UnlockDuration = unlockDuration + out.State.InitialBalance = initialBalance + {{if (le .v 2)}} + em, err := adt{{.v}}.MakeEmptyMap(store).Root() + if err != nil { + return nil, err + } + + out.State.PendingTxns = em + {{else}} + em, err := adt{{.v}}.StoreEmptyMap(store, builtin{{.v}}.DefaultHamtBitwidth) + if err != nil { + return nil, err + } + + out.State.PendingTxns = em + {{end}} + return &out, nil +} + +type state{{.v}} struct { + msig{{.v}}.State + store adt.Store +} + +func (s *state{{.v}}) LockedBalance(currEpoch abi.ChainEpoch) (abi.TokenAmount, error) { + return s.State.AmountLocked(currEpoch - s.State.StartEpoch), nil +} + +func (s *state{{.v}}) StartEpoch() (abi.ChainEpoch, error) { + return s.State.StartEpoch, nil +} + +func (s *state{{.v}}) UnlockDuration() (abi.ChainEpoch, error) { + return s.State.UnlockDuration, nil +} + +func (s *state{{.v}}) InitialBalance() (abi.TokenAmount, error) { + return s.State.InitialBalance, nil +} + +func (s *state{{.v}}) Threshold() (uint64, error) { + return s.State.NumApprovalsThreshold, nil +} + +func (s *state{{.v}}) Signers() ([]address.Address, error) { + return s.State.Signers, nil +} + +func (s *state{{.v}}) ForEachPendingTxn(cb func(id int64, txn Transaction) error) error { + arr, err := adt{{.v}}.AsMap(s.store, s.State.PendingTxns{{if (ge .v 3)}}, builtin{{.v}}.DefaultHamtBitwidth{{end}}) + if err != nil { + return err + } + var out msig{{.v}}.Transaction + return arr.ForEach(&out, func(key string) error { + txid, n := binary.Varint([]byte(key)) + if n <= 0 { + return xerrors.Errorf("invalid pending transaction key: %v", key) + } + return cb(txid, (Transaction)(out)) //nolint:unconvert + }) +} + +func (s *state{{.v}}) PendingTxnChanged(other State) (bool, error) { + other{{.v}}, ok := other.(*state{{.v}}) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.PendingTxns.Equals(other{{.v}}.PendingTxns), nil +} + +func (s *state{{.v}}) transactions() (adt.Map, error) { + return adt{{.v}}.AsMap(s.store, s.PendingTxns{{if (ge .v 3)}}, builtin{{.v}}.DefaultHamtBitwidth{{end}}) +} + +func (s *state{{.v}}) decodeTransaction(val *cbg.Deferred) (Transaction, error) { + var tx msig{{.v}}.Transaction + if err := tx.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return Transaction{}, err + } + return Transaction(tx), nil +} + +func (s *state{{.v}}) GetState() interface{} { + return &s.State +} + +func (s *state{{.v}}) ActorKey() string { + return manifest.MultisigKey +} + +func (s *state{{.v}}) ActorVersion() actorstypes.Version { + return actorstypes.Version{{.v}} +} + +func (s *state{{.v}}) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/multisig/v0.go b/chain/actors/builtin/multisig/v0.go new file mode 100644 index 000000000..86bfdaaf3 --- /dev/null +++ b/chain/actors/builtin/multisig/v0.go @@ -0,0 +1,137 @@ +package multisig + +import ( + "bytes" + "encoding/binary" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + msig0 "github.com/filecoin-project/specs-actors/actors/builtin/multisig" + adt0 "github.com/filecoin-project/specs-actors/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state0)(nil) + +func load0(store adt.Store, root cid.Cid) (State, error) { + out := state0{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make0(store adt.Store, signers []address.Address, threshold uint64, startEpoch abi.ChainEpoch, unlockDuration abi.ChainEpoch, initialBalance abi.TokenAmount) (State, error) { + out := state0{store: store} + out.State = msig0.State{} + out.State.Signers = signers + out.State.NumApprovalsThreshold = threshold + out.State.StartEpoch = startEpoch + out.State.UnlockDuration = unlockDuration + out.State.InitialBalance = initialBalance + + em, err := adt0.MakeEmptyMap(store).Root() + if err != nil { + return nil, err + } + + out.State.PendingTxns = em + + return &out, nil +} + +type state0 struct { + msig0.State + store adt.Store +} + +func (s *state0) LockedBalance(currEpoch abi.ChainEpoch) (abi.TokenAmount, error) { + return s.State.AmountLocked(currEpoch - s.State.StartEpoch), nil +} + +func (s *state0) StartEpoch() (abi.ChainEpoch, error) { + return s.State.StartEpoch, nil +} + +func (s *state0) UnlockDuration() (abi.ChainEpoch, error) { + return s.State.UnlockDuration, nil +} + +func (s *state0) InitialBalance() (abi.TokenAmount, error) { + return s.State.InitialBalance, nil +} + +func (s *state0) Threshold() (uint64, error) { + return s.State.NumApprovalsThreshold, nil +} + +func (s *state0) Signers() ([]address.Address, error) { + return s.State.Signers, nil +} + +func (s *state0) ForEachPendingTxn(cb func(id int64, txn Transaction) error) error { + arr, err := adt0.AsMap(s.store, s.State.PendingTxns) + if err != nil { + return err + } + var out msig0.Transaction + return arr.ForEach(&out, func(key string) error { + txid, n := binary.Varint([]byte(key)) + if n <= 0 { + return xerrors.Errorf("invalid pending transaction key: %v", key) + } + return cb(txid, (Transaction)(out)) //nolint:unconvert + }) +} + +func (s *state0) PendingTxnChanged(other State) (bool, error) { + other0, ok := other.(*state0) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.PendingTxns.Equals(other0.PendingTxns), nil +} + +func (s *state0) transactions() (adt.Map, error) { + return adt0.AsMap(s.store, s.PendingTxns) +} + +func (s *state0) decodeTransaction(val *cbg.Deferred) (Transaction, error) { + var tx msig0.Transaction + if err := tx.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return Transaction{}, err + } + return Transaction(tx), nil +} + +func (s *state0) GetState() interface{} { + return &s.State +} + +func (s *state0) ActorKey() string { + return manifest.MultisigKey +} + +func (s *state0) ActorVersion() actorstypes.Version { + return actorstypes.Version0 +} + +func (s *state0) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/multisig/v10.go b/chain/actors/builtin/multisig/v10.go new file mode 100644 index 000000000..d87fc5807 --- /dev/null +++ b/chain/actors/builtin/multisig/v10.go @@ -0,0 +1,138 @@ +package multisig + +import ( + "bytes" + "encoding/binary" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtin10 "github.com/filecoin-project/go-state-types/builtin" + msig10 "github.com/filecoin-project/go-state-types/builtin/v10/multisig" + adt10 "github.com/filecoin-project/go-state-types/builtin/v10/util/adt" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state10)(nil) + +func load10(store adt.Store, root cid.Cid) (State, error) { + out := state10{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make10(store adt.Store, signers []address.Address, threshold uint64, startEpoch abi.ChainEpoch, unlockDuration abi.ChainEpoch, initialBalance abi.TokenAmount) (State, error) { + out := state10{store: store} + out.State = msig10.State{} + out.State.Signers = signers + out.State.NumApprovalsThreshold = threshold + out.State.StartEpoch = startEpoch + out.State.UnlockDuration = unlockDuration + out.State.InitialBalance = initialBalance + + em, err := adt10.StoreEmptyMap(store, builtin10.DefaultHamtBitwidth) + if err != nil { + return nil, err + } + + out.State.PendingTxns = em + + return &out, nil +} + +type state10 struct { + msig10.State + store adt.Store +} + +func (s *state10) LockedBalance(currEpoch abi.ChainEpoch) (abi.TokenAmount, error) { + return s.State.AmountLocked(currEpoch - s.State.StartEpoch), nil +} + +func (s *state10) StartEpoch() (abi.ChainEpoch, error) { + return s.State.StartEpoch, nil +} + +func (s *state10) UnlockDuration() (abi.ChainEpoch, error) { + return s.State.UnlockDuration, nil +} + +func (s *state10) InitialBalance() (abi.TokenAmount, error) { + return s.State.InitialBalance, nil +} + +func (s *state10) Threshold() (uint64, error) { + return s.State.NumApprovalsThreshold, nil +} + +func (s *state10) Signers() ([]address.Address, error) { + return s.State.Signers, nil +} + +func (s *state10) ForEachPendingTxn(cb func(id int64, txn Transaction) error) error { + arr, err := adt10.AsMap(s.store, s.State.PendingTxns, builtin10.DefaultHamtBitwidth) + if err != nil { + return err + } + var out msig10.Transaction + return arr.ForEach(&out, func(key string) error { + txid, n := binary.Varint([]byte(key)) + if n <= 0 { + return xerrors.Errorf("invalid pending transaction key: %v", key) + } + return cb(txid, (Transaction)(out)) //nolint:unconvert + }) +} + +func (s *state10) PendingTxnChanged(other State) (bool, error) { + other10, ok := other.(*state10) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.PendingTxns.Equals(other10.PendingTxns), nil +} + +func (s *state10) transactions() (adt.Map, error) { + return adt10.AsMap(s.store, s.PendingTxns, builtin10.DefaultHamtBitwidth) +} + +func (s *state10) decodeTransaction(val *cbg.Deferred) (Transaction, error) { + var tx msig10.Transaction + if err := tx.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return Transaction{}, err + } + return Transaction(tx), nil +} + +func (s *state10) GetState() interface{} { + return &s.State +} + +func (s *state10) ActorKey() string { + return manifest.MultisigKey +} + +func (s *state10) ActorVersion() actorstypes.Version { + return actorstypes.Version10 +} + +func (s *state10) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/multisig/v11.go b/chain/actors/builtin/multisig/v11.go new file mode 100644 index 000000000..3627dc959 --- /dev/null +++ b/chain/actors/builtin/multisig/v11.go @@ -0,0 +1,138 @@ +package multisig + +import ( + "bytes" + "encoding/binary" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtin11 "github.com/filecoin-project/go-state-types/builtin" + msig11 "github.com/filecoin-project/go-state-types/builtin/v11/multisig" + adt11 "github.com/filecoin-project/go-state-types/builtin/v11/util/adt" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state11)(nil) + +func load11(store adt.Store, root cid.Cid) (State, error) { + out := state11{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make11(store adt.Store, signers []address.Address, threshold uint64, startEpoch abi.ChainEpoch, unlockDuration abi.ChainEpoch, initialBalance abi.TokenAmount) (State, error) { + out := state11{store: store} + out.State = msig11.State{} + out.State.Signers = signers + out.State.NumApprovalsThreshold = threshold + out.State.StartEpoch = startEpoch + out.State.UnlockDuration = unlockDuration + out.State.InitialBalance = initialBalance + + em, err := adt11.StoreEmptyMap(store, builtin11.DefaultHamtBitwidth) + if err != nil { + return nil, err + } + + out.State.PendingTxns = em + + return &out, nil +} + +type state11 struct { + msig11.State + store adt.Store +} + +func (s *state11) LockedBalance(currEpoch abi.ChainEpoch) (abi.TokenAmount, error) { + return s.State.AmountLocked(currEpoch - s.State.StartEpoch), nil +} + +func (s *state11) StartEpoch() (abi.ChainEpoch, error) { + return s.State.StartEpoch, nil +} + +func (s *state11) UnlockDuration() (abi.ChainEpoch, error) { + return s.State.UnlockDuration, nil +} + +func (s *state11) InitialBalance() (abi.TokenAmount, error) { + return s.State.InitialBalance, nil +} + +func (s *state11) Threshold() (uint64, error) { + return s.State.NumApprovalsThreshold, nil +} + +func (s *state11) Signers() ([]address.Address, error) { + return s.State.Signers, nil +} + +func (s *state11) ForEachPendingTxn(cb func(id int64, txn Transaction) error) error { + arr, err := adt11.AsMap(s.store, s.State.PendingTxns, builtin11.DefaultHamtBitwidth) + if err != nil { + return err + } + var out msig11.Transaction + return arr.ForEach(&out, func(key string) error { + txid, n := binary.Varint([]byte(key)) + if n <= 0 { + return xerrors.Errorf("invalid pending transaction key: %v", key) + } + return cb(txid, (Transaction)(out)) //nolint:unconvert + }) +} + +func (s *state11) PendingTxnChanged(other State) (bool, error) { + other11, ok := other.(*state11) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.PendingTxns.Equals(other11.PendingTxns), nil +} + +func (s *state11) transactions() (adt.Map, error) { + return adt11.AsMap(s.store, s.PendingTxns, builtin11.DefaultHamtBitwidth) +} + +func (s *state11) decodeTransaction(val *cbg.Deferred) (Transaction, error) { + var tx msig11.Transaction + if err := tx.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return Transaction{}, err + } + return Transaction(tx), nil +} + +func (s *state11) GetState() interface{} { + return &s.State +} + +func (s *state11) ActorKey() string { + return manifest.MultisigKey +} + +func (s *state11) ActorVersion() actorstypes.Version { + return actorstypes.Version11 +} + +func (s *state11) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/multisig/v2.go b/chain/actors/builtin/multisig/v2.go new file mode 100644 index 000000000..77330d513 --- /dev/null +++ b/chain/actors/builtin/multisig/v2.go @@ -0,0 +1,137 @@ +package multisig + +import ( + "bytes" + "encoding/binary" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + msig2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/multisig" + adt2 "github.com/filecoin-project/specs-actors/v2/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state2)(nil) + +func load2(store adt.Store, root cid.Cid) (State, error) { + out := state2{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make2(store adt.Store, signers []address.Address, threshold uint64, startEpoch abi.ChainEpoch, unlockDuration abi.ChainEpoch, initialBalance abi.TokenAmount) (State, error) { + out := state2{store: store} + out.State = msig2.State{} + out.State.Signers = signers + out.State.NumApprovalsThreshold = threshold + out.State.StartEpoch = startEpoch + out.State.UnlockDuration = unlockDuration + out.State.InitialBalance = initialBalance + + em, err := adt2.MakeEmptyMap(store).Root() + if err != nil { + return nil, err + } + + out.State.PendingTxns = em + + return &out, nil +} + +type state2 struct { + msig2.State + store adt.Store +} + +func (s *state2) LockedBalance(currEpoch abi.ChainEpoch) (abi.TokenAmount, error) { + return s.State.AmountLocked(currEpoch - s.State.StartEpoch), nil +} + +func (s *state2) StartEpoch() (abi.ChainEpoch, error) { + return s.State.StartEpoch, nil +} + +func (s *state2) UnlockDuration() (abi.ChainEpoch, error) { + return s.State.UnlockDuration, nil +} + +func (s *state2) InitialBalance() (abi.TokenAmount, error) { + return s.State.InitialBalance, nil +} + +func (s *state2) Threshold() (uint64, error) { + return s.State.NumApprovalsThreshold, nil +} + +func (s *state2) Signers() ([]address.Address, error) { + return s.State.Signers, nil +} + +func (s *state2) ForEachPendingTxn(cb func(id int64, txn Transaction) error) error { + arr, err := adt2.AsMap(s.store, s.State.PendingTxns) + if err != nil { + return err + } + var out msig2.Transaction + return arr.ForEach(&out, func(key string) error { + txid, n := binary.Varint([]byte(key)) + if n <= 0 { + return xerrors.Errorf("invalid pending transaction key: %v", key) + } + return cb(txid, (Transaction)(out)) //nolint:unconvert + }) +} + +func (s *state2) PendingTxnChanged(other State) (bool, error) { + other2, ok := other.(*state2) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.PendingTxns.Equals(other2.PendingTxns), nil +} + +func (s *state2) transactions() (adt.Map, error) { + return adt2.AsMap(s.store, s.PendingTxns) +} + +func (s *state2) decodeTransaction(val *cbg.Deferred) (Transaction, error) { + var tx msig2.Transaction + if err := tx.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return Transaction{}, err + } + return Transaction(tx), nil +} + +func (s *state2) GetState() interface{} { + return &s.State +} + +func (s *state2) ActorKey() string { + return manifest.MultisigKey +} + +func (s *state2) ActorVersion() actorstypes.Version { + return actorstypes.Version2 +} + +func (s *state2) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/multisig/v3.go b/chain/actors/builtin/multisig/v3.go new file mode 100644 index 000000000..e8659093f --- /dev/null +++ b/chain/actors/builtin/multisig/v3.go @@ -0,0 +1,138 @@ +package multisig + +import ( + "bytes" + "encoding/binary" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + builtin3 "github.com/filecoin-project/specs-actors/v3/actors/builtin" + msig3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/multisig" + adt3 "github.com/filecoin-project/specs-actors/v3/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state3)(nil) + +func load3(store adt.Store, root cid.Cid) (State, error) { + out := state3{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make3(store adt.Store, signers []address.Address, threshold uint64, startEpoch abi.ChainEpoch, unlockDuration abi.ChainEpoch, initialBalance abi.TokenAmount) (State, error) { + out := state3{store: store} + out.State = msig3.State{} + out.State.Signers = signers + out.State.NumApprovalsThreshold = threshold + out.State.StartEpoch = startEpoch + out.State.UnlockDuration = unlockDuration + out.State.InitialBalance = initialBalance + + em, err := adt3.StoreEmptyMap(store, builtin3.DefaultHamtBitwidth) + if err != nil { + return nil, err + } + + out.State.PendingTxns = em + + return &out, nil +} + +type state3 struct { + msig3.State + store adt.Store +} + +func (s *state3) LockedBalance(currEpoch abi.ChainEpoch) (abi.TokenAmount, error) { + return s.State.AmountLocked(currEpoch - s.State.StartEpoch), nil +} + +func (s *state3) StartEpoch() (abi.ChainEpoch, error) { + return s.State.StartEpoch, nil +} + +func (s *state3) UnlockDuration() (abi.ChainEpoch, error) { + return s.State.UnlockDuration, nil +} + +func (s *state3) InitialBalance() (abi.TokenAmount, error) { + return s.State.InitialBalance, nil +} + +func (s *state3) Threshold() (uint64, error) { + return s.State.NumApprovalsThreshold, nil +} + +func (s *state3) Signers() ([]address.Address, error) { + return s.State.Signers, nil +} + +func (s *state3) ForEachPendingTxn(cb func(id int64, txn Transaction) error) error { + arr, err := adt3.AsMap(s.store, s.State.PendingTxns, builtin3.DefaultHamtBitwidth) + if err != nil { + return err + } + var out msig3.Transaction + return arr.ForEach(&out, func(key string) error { + txid, n := binary.Varint([]byte(key)) + if n <= 0 { + return xerrors.Errorf("invalid pending transaction key: %v", key) + } + return cb(txid, (Transaction)(out)) //nolint:unconvert + }) +} + +func (s *state3) PendingTxnChanged(other State) (bool, error) { + other3, ok := other.(*state3) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.PendingTxns.Equals(other3.PendingTxns), nil +} + +func (s *state3) transactions() (adt.Map, error) { + return adt3.AsMap(s.store, s.PendingTxns, builtin3.DefaultHamtBitwidth) +} + +func (s *state3) decodeTransaction(val *cbg.Deferred) (Transaction, error) { + var tx msig3.Transaction + if err := tx.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return Transaction{}, err + } + return Transaction(tx), nil +} + +func (s *state3) GetState() interface{} { + return &s.State +} + +func (s *state3) ActorKey() string { + return manifest.MultisigKey +} + +func (s *state3) ActorVersion() actorstypes.Version { + return actorstypes.Version3 +} + +func (s *state3) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/multisig/v4.go b/chain/actors/builtin/multisig/v4.go new file mode 100644 index 000000000..ddaac5470 --- /dev/null +++ b/chain/actors/builtin/multisig/v4.go @@ -0,0 +1,138 @@ +package multisig + +import ( + "bytes" + "encoding/binary" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" + msig4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/multisig" + adt4 "github.com/filecoin-project/specs-actors/v4/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state4)(nil) + +func load4(store adt.Store, root cid.Cid) (State, error) { + out := state4{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make4(store adt.Store, signers []address.Address, threshold uint64, startEpoch abi.ChainEpoch, unlockDuration abi.ChainEpoch, initialBalance abi.TokenAmount) (State, error) { + out := state4{store: store} + out.State = msig4.State{} + out.State.Signers = signers + out.State.NumApprovalsThreshold = threshold + out.State.StartEpoch = startEpoch + out.State.UnlockDuration = unlockDuration + out.State.InitialBalance = initialBalance + + em, err := adt4.StoreEmptyMap(store, builtin4.DefaultHamtBitwidth) + if err != nil { + return nil, err + } + + out.State.PendingTxns = em + + return &out, nil +} + +type state4 struct { + msig4.State + store adt.Store +} + +func (s *state4) LockedBalance(currEpoch abi.ChainEpoch) (abi.TokenAmount, error) { + return s.State.AmountLocked(currEpoch - s.State.StartEpoch), nil +} + +func (s *state4) StartEpoch() (abi.ChainEpoch, error) { + return s.State.StartEpoch, nil +} + +func (s *state4) UnlockDuration() (abi.ChainEpoch, error) { + return s.State.UnlockDuration, nil +} + +func (s *state4) InitialBalance() (abi.TokenAmount, error) { + return s.State.InitialBalance, nil +} + +func (s *state4) Threshold() (uint64, error) { + return s.State.NumApprovalsThreshold, nil +} + +func (s *state4) Signers() ([]address.Address, error) { + return s.State.Signers, nil +} + +func (s *state4) ForEachPendingTxn(cb func(id int64, txn Transaction) error) error { + arr, err := adt4.AsMap(s.store, s.State.PendingTxns, builtin4.DefaultHamtBitwidth) + if err != nil { + return err + } + var out msig4.Transaction + return arr.ForEach(&out, func(key string) error { + txid, n := binary.Varint([]byte(key)) + if n <= 0 { + return xerrors.Errorf("invalid pending transaction key: %v", key) + } + return cb(txid, (Transaction)(out)) //nolint:unconvert + }) +} + +func (s *state4) PendingTxnChanged(other State) (bool, error) { + other4, ok := other.(*state4) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.PendingTxns.Equals(other4.PendingTxns), nil +} + +func (s *state4) transactions() (adt.Map, error) { + return adt4.AsMap(s.store, s.PendingTxns, builtin4.DefaultHamtBitwidth) +} + +func (s *state4) decodeTransaction(val *cbg.Deferred) (Transaction, error) { + var tx msig4.Transaction + if err := tx.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return Transaction{}, err + } + return Transaction(tx), nil +} + +func (s *state4) GetState() interface{} { + return &s.State +} + +func (s *state4) ActorKey() string { + return manifest.MultisigKey +} + +func (s *state4) ActorVersion() actorstypes.Version { + return actorstypes.Version4 +} + +func (s *state4) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/multisig/v5.go b/chain/actors/builtin/multisig/v5.go new file mode 100644 index 000000000..50474d5fd --- /dev/null +++ b/chain/actors/builtin/multisig/v5.go @@ -0,0 +1,138 @@ +package multisig + +import ( + "bytes" + "encoding/binary" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" + msig5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/multisig" + adt5 "github.com/filecoin-project/specs-actors/v5/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state5)(nil) + +func load5(store adt.Store, root cid.Cid) (State, error) { + out := state5{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make5(store adt.Store, signers []address.Address, threshold uint64, startEpoch abi.ChainEpoch, unlockDuration abi.ChainEpoch, initialBalance abi.TokenAmount) (State, error) { + out := state5{store: store} + out.State = msig5.State{} + out.State.Signers = signers + out.State.NumApprovalsThreshold = threshold + out.State.StartEpoch = startEpoch + out.State.UnlockDuration = unlockDuration + out.State.InitialBalance = initialBalance + + em, err := adt5.StoreEmptyMap(store, builtin5.DefaultHamtBitwidth) + if err != nil { + return nil, err + } + + out.State.PendingTxns = em + + return &out, nil +} + +type state5 struct { + msig5.State + store adt.Store +} + +func (s *state5) LockedBalance(currEpoch abi.ChainEpoch) (abi.TokenAmount, error) { + return s.State.AmountLocked(currEpoch - s.State.StartEpoch), nil +} + +func (s *state5) StartEpoch() (abi.ChainEpoch, error) { + return s.State.StartEpoch, nil +} + +func (s *state5) UnlockDuration() (abi.ChainEpoch, error) { + return s.State.UnlockDuration, nil +} + +func (s *state5) InitialBalance() (abi.TokenAmount, error) { + return s.State.InitialBalance, nil +} + +func (s *state5) Threshold() (uint64, error) { + return s.State.NumApprovalsThreshold, nil +} + +func (s *state5) Signers() ([]address.Address, error) { + return s.State.Signers, nil +} + +func (s *state5) ForEachPendingTxn(cb func(id int64, txn Transaction) error) error { + arr, err := adt5.AsMap(s.store, s.State.PendingTxns, builtin5.DefaultHamtBitwidth) + if err != nil { + return err + } + var out msig5.Transaction + return arr.ForEach(&out, func(key string) error { + txid, n := binary.Varint([]byte(key)) + if n <= 0 { + return xerrors.Errorf("invalid pending transaction key: %v", key) + } + return cb(txid, (Transaction)(out)) //nolint:unconvert + }) +} + +func (s *state5) PendingTxnChanged(other State) (bool, error) { + other5, ok := other.(*state5) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.PendingTxns.Equals(other5.PendingTxns), nil +} + +func (s *state5) transactions() (adt.Map, error) { + return adt5.AsMap(s.store, s.PendingTxns, builtin5.DefaultHamtBitwidth) +} + +func (s *state5) decodeTransaction(val *cbg.Deferred) (Transaction, error) { + var tx msig5.Transaction + if err := tx.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return Transaction{}, err + } + return Transaction(tx), nil +} + +func (s *state5) GetState() interface{} { + return &s.State +} + +func (s *state5) ActorKey() string { + return manifest.MultisigKey +} + +func (s *state5) ActorVersion() actorstypes.Version { + return actorstypes.Version5 +} + +func (s *state5) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/multisig/v6.go b/chain/actors/builtin/multisig/v6.go new file mode 100644 index 000000000..c51404dc4 --- /dev/null +++ b/chain/actors/builtin/multisig/v6.go @@ -0,0 +1,138 @@ +package multisig + +import ( + "bytes" + "encoding/binary" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + builtin6 "github.com/filecoin-project/specs-actors/v6/actors/builtin" + msig6 "github.com/filecoin-project/specs-actors/v6/actors/builtin/multisig" + adt6 "github.com/filecoin-project/specs-actors/v6/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state6)(nil) + +func load6(store adt.Store, root cid.Cid) (State, error) { + out := state6{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make6(store adt.Store, signers []address.Address, threshold uint64, startEpoch abi.ChainEpoch, unlockDuration abi.ChainEpoch, initialBalance abi.TokenAmount) (State, error) { + out := state6{store: store} + out.State = msig6.State{} + out.State.Signers = signers + out.State.NumApprovalsThreshold = threshold + out.State.StartEpoch = startEpoch + out.State.UnlockDuration = unlockDuration + out.State.InitialBalance = initialBalance + + em, err := adt6.StoreEmptyMap(store, builtin6.DefaultHamtBitwidth) + if err != nil { + return nil, err + } + + out.State.PendingTxns = em + + return &out, nil +} + +type state6 struct { + msig6.State + store adt.Store +} + +func (s *state6) LockedBalance(currEpoch abi.ChainEpoch) (abi.TokenAmount, error) { + return s.State.AmountLocked(currEpoch - s.State.StartEpoch), nil +} + +func (s *state6) StartEpoch() (abi.ChainEpoch, error) { + return s.State.StartEpoch, nil +} + +func (s *state6) UnlockDuration() (abi.ChainEpoch, error) { + return s.State.UnlockDuration, nil +} + +func (s *state6) InitialBalance() (abi.TokenAmount, error) { + return s.State.InitialBalance, nil +} + +func (s *state6) Threshold() (uint64, error) { + return s.State.NumApprovalsThreshold, nil +} + +func (s *state6) Signers() ([]address.Address, error) { + return s.State.Signers, nil +} + +func (s *state6) ForEachPendingTxn(cb func(id int64, txn Transaction) error) error { + arr, err := adt6.AsMap(s.store, s.State.PendingTxns, builtin6.DefaultHamtBitwidth) + if err != nil { + return err + } + var out msig6.Transaction + return arr.ForEach(&out, func(key string) error { + txid, n := binary.Varint([]byte(key)) + if n <= 0 { + return xerrors.Errorf("invalid pending transaction key: %v", key) + } + return cb(txid, (Transaction)(out)) //nolint:unconvert + }) +} + +func (s *state6) PendingTxnChanged(other State) (bool, error) { + other6, ok := other.(*state6) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.PendingTxns.Equals(other6.PendingTxns), nil +} + +func (s *state6) transactions() (adt.Map, error) { + return adt6.AsMap(s.store, s.PendingTxns, builtin6.DefaultHamtBitwidth) +} + +func (s *state6) decodeTransaction(val *cbg.Deferred) (Transaction, error) { + var tx msig6.Transaction + if err := tx.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return Transaction{}, err + } + return Transaction(tx), nil +} + +func (s *state6) GetState() interface{} { + return &s.State +} + +func (s *state6) ActorKey() string { + return manifest.MultisigKey +} + +func (s *state6) ActorVersion() actorstypes.Version { + return actorstypes.Version6 +} + +func (s *state6) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/multisig/v7.go b/chain/actors/builtin/multisig/v7.go new file mode 100644 index 000000000..9ddce167a --- /dev/null +++ b/chain/actors/builtin/multisig/v7.go @@ -0,0 +1,138 @@ +package multisig + +import ( + "bytes" + "encoding/binary" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + builtin7 "github.com/filecoin-project/specs-actors/v7/actors/builtin" + msig7 "github.com/filecoin-project/specs-actors/v7/actors/builtin/multisig" + adt7 "github.com/filecoin-project/specs-actors/v7/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state7)(nil) + +func load7(store adt.Store, root cid.Cid) (State, error) { + out := state7{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make7(store adt.Store, signers []address.Address, threshold uint64, startEpoch abi.ChainEpoch, unlockDuration abi.ChainEpoch, initialBalance abi.TokenAmount) (State, error) { + out := state7{store: store} + out.State = msig7.State{} + out.State.Signers = signers + out.State.NumApprovalsThreshold = threshold + out.State.StartEpoch = startEpoch + out.State.UnlockDuration = unlockDuration + out.State.InitialBalance = initialBalance + + em, err := adt7.StoreEmptyMap(store, builtin7.DefaultHamtBitwidth) + if err != nil { + return nil, err + } + + out.State.PendingTxns = em + + return &out, nil +} + +type state7 struct { + msig7.State + store adt.Store +} + +func (s *state7) LockedBalance(currEpoch abi.ChainEpoch) (abi.TokenAmount, error) { + return s.State.AmountLocked(currEpoch - s.State.StartEpoch), nil +} + +func (s *state7) StartEpoch() (abi.ChainEpoch, error) { + return s.State.StartEpoch, nil +} + +func (s *state7) UnlockDuration() (abi.ChainEpoch, error) { + return s.State.UnlockDuration, nil +} + +func (s *state7) InitialBalance() (abi.TokenAmount, error) { + return s.State.InitialBalance, nil +} + +func (s *state7) Threshold() (uint64, error) { + return s.State.NumApprovalsThreshold, nil +} + +func (s *state7) Signers() ([]address.Address, error) { + return s.State.Signers, nil +} + +func (s *state7) ForEachPendingTxn(cb func(id int64, txn Transaction) error) error { + arr, err := adt7.AsMap(s.store, s.State.PendingTxns, builtin7.DefaultHamtBitwidth) + if err != nil { + return err + } + var out msig7.Transaction + return arr.ForEach(&out, func(key string) error { + txid, n := binary.Varint([]byte(key)) + if n <= 0 { + return xerrors.Errorf("invalid pending transaction key: %v", key) + } + return cb(txid, (Transaction)(out)) //nolint:unconvert + }) +} + +func (s *state7) PendingTxnChanged(other State) (bool, error) { + other7, ok := other.(*state7) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.PendingTxns.Equals(other7.PendingTxns), nil +} + +func (s *state7) transactions() (adt.Map, error) { + return adt7.AsMap(s.store, s.PendingTxns, builtin7.DefaultHamtBitwidth) +} + +func (s *state7) decodeTransaction(val *cbg.Deferred) (Transaction, error) { + var tx msig7.Transaction + if err := tx.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return Transaction{}, err + } + return Transaction(tx), nil +} + +func (s *state7) GetState() interface{} { + return &s.State +} + +func (s *state7) ActorKey() string { + return manifest.MultisigKey +} + +func (s *state7) ActorVersion() actorstypes.Version { + return actorstypes.Version7 +} + +func (s *state7) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/multisig/v8.go b/chain/actors/builtin/multisig/v8.go new file mode 100644 index 000000000..b28ec5684 --- /dev/null +++ b/chain/actors/builtin/multisig/v8.go @@ -0,0 +1,138 @@ +package multisig + +import ( + "bytes" + "encoding/binary" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtin8 "github.com/filecoin-project/go-state-types/builtin" + msig8 "github.com/filecoin-project/go-state-types/builtin/v8/multisig" + adt8 "github.com/filecoin-project/go-state-types/builtin/v8/util/adt" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state8)(nil) + +func load8(store adt.Store, root cid.Cid) (State, error) { + out := state8{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make8(store adt.Store, signers []address.Address, threshold uint64, startEpoch abi.ChainEpoch, unlockDuration abi.ChainEpoch, initialBalance abi.TokenAmount) (State, error) { + out := state8{store: store} + out.State = msig8.State{} + out.State.Signers = signers + out.State.NumApprovalsThreshold = threshold + out.State.StartEpoch = startEpoch + out.State.UnlockDuration = unlockDuration + out.State.InitialBalance = initialBalance + + em, err := adt8.StoreEmptyMap(store, builtin8.DefaultHamtBitwidth) + if err != nil { + return nil, err + } + + out.State.PendingTxns = em + + return &out, nil +} + +type state8 struct { + msig8.State + store adt.Store +} + +func (s *state8) LockedBalance(currEpoch abi.ChainEpoch) (abi.TokenAmount, error) { + return s.State.AmountLocked(currEpoch - s.State.StartEpoch), nil +} + +func (s *state8) StartEpoch() (abi.ChainEpoch, error) { + return s.State.StartEpoch, nil +} + +func (s *state8) UnlockDuration() (abi.ChainEpoch, error) { + return s.State.UnlockDuration, nil +} + +func (s *state8) InitialBalance() (abi.TokenAmount, error) { + return s.State.InitialBalance, nil +} + +func (s *state8) Threshold() (uint64, error) { + return s.State.NumApprovalsThreshold, nil +} + +func (s *state8) Signers() ([]address.Address, error) { + return s.State.Signers, nil +} + +func (s *state8) ForEachPendingTxn(cb func(id int64, txn Transaction) error) error { + arr, err := adt8.AsMap(s.store, s.State.PendingTxns, builtin8.DefaultHamtBitwidth) + if err != nil { + return err + } + var out msig8.Transaction + return arr.ForEach(&out, func(key string) error { + txid, n := binary.Varint([]byte(key)) + if n <= 0 { + return xerrors.Errorf("invalid pending transaction key: %v", key) + } + return cb(txid, (Transaction)(out)) //nolint:unconvert + }) +} + +func (s *state8) PendingTxnChanged(other State) (bool, error) { + other8, ok := other.(*state8) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.PendingTxns.Equals(other8.PendingTxns), nil +} + +func (s *state8) transactions() (adt.Map, error) { + return adt8.AsMap(s.store, s.PendingTxns, builtin8.DefaultHamtBitwidth) +} + +func (s *state8) decodeTransaction(val *cbg.Deferred) (Transaction, error) { + var tx msig8.Transaction + if err := tx.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return Transaction{}, err + } + return Transaction(tx), nil +} + +func (s *state8) GetState() interface{} { + return &s.State +} + +func (s *state8) ActorKey() string { + return manifest.MultisigKey +} + +func (s *state8) ActorVersion() actorstypes.Version { + return actorstypes.Version8 +} + +func (s *state8) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/multisig/v9.go b/chain/actors/builtin/multisig/v9.go new file mode 100644 index 000000000..faa3b7d37 --- /dev/null +++ b/chain/actors/builtin/multisig/v9.go @@ -0,0 +1,138 @@ +package multisig + +import ( + "bytes" + "encoding/binary" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtin9 "github.com/filecoin-project/go-state-types/builtin" + msig9 "github.com/filecoin-project/go-state-types/builtin/v9/multisig" + adt9 "github.com/filecoin-project/go-state-types/builtin/v9/util/adt" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state9)(nil) + +func load9(store adt.Store, root cid.Cid) (State, error) { + out := state9{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make9(store adt.Store, signers []address.Address, threshold uint64, startEpoch abi.ChainEpoch, unlockDuration abi.ChainEpoch, initialBalance abi.TokenAmount) (State, error) { + out := state9{store: store} + out.State = msig9.State{} + out.State.Signers = signers + out.State.NumApprovalsThreshold = threshold + out.State.StartEpoch = startEpoch + out.State.UnlockDuration = unlockDuration + out.State.InitialBalance = initialBalance + + em, err := adt9.StoreEmptyMap(store, builtin9.DefaultHamtBitwidth) + if err != nil { + return nil, err + } + + out.State.PendingTxns = em + + return &out, nil +} + +type state9 struct { + msig9.State + store adt.Store +} + +func (s *state9) LockedBalance(currEpoch abi.ChainEpoch) (abi.TokenAmount, error) { + return s.State.AmountLocked(currEpoch - s.State.StartEpoch), nil +} + +func (s *state9) StartEpoch() (abi.ChainEpoch, error) { + return s.State.StartEpoch, nil +} + +func (s *state9) UnlockDuration() (abi.ChainEpoch, error) { + return s.State.UnlockDuration, nil +} + +func (s *state9) InitialBalance() (abi.TokenAmount, error) { + return s.State.InitialBalance, nil +} + +func (s *state9) Threshold() (uint64, error) { + return s.State.NumApprovalsThreshold, nil +} + +func (s *state9) Signers() ([]address.Address, error) { + return s.State.Signers, nil +} + +func (s *state9) ForEachPendingTxn(cb func(id int64, txn Transaction) error) error { + arr, err := adt9.AsMap(s.store, s.State.PendingTxns, builtin9.DefaultHamtBitwidth) + if err != nil { + return err + } + var out msig9.Transaction + return arr.ForEach(&out, func(key string) error { + txid, n := binary.Varint([]byte(key)) + if n <= 0 { + return xerrors.Errorf("invalid pending transaction key: %v", key) + } + return cb(txid, (Transaction)(out)) //nolint:unconvert + }) +} + +func (s *state9) PendingTxnChanged(other State) (bool, error) { + other9, ok := other.(*state9) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.PendingTxns.Equals(other9.PendingTxns), nil +} + +func (s *state9) transactions() (adt.Map, error) { + return adt9.AsMap(s.store, s.PendingTxns, builtin9.DefaultHamtBitwidth) +} + +func (s *state9) decodeTransaction(val *cbg.Deferred) (Transaction, error) { + var tx msig9.Transaction + if err := tx.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return Transaction{}, err + } + return Transaction(tx), nil +} + +func (s *state9) GetState() interface{} { + return &s.State +} + +func (s *state9) ActorKey() string { + return manifest.MultisigKey +} + +func (s *state9) ActorVersion() actorstypes.Version { + return actorstypes.Version9 +} + +func (s *state9) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/paych/actor.go.template b/chain/actors/builtin/paych/actor.go.template new file mode 100644 index 000000000..e19ac5e29 --- /dev/null +++ b/chain/actors/builtin/paych/actor.go.template @@ -0,0 +1,150 @@ +package paych + +import ( + "github.com/ipfs/go-cid" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/ipfs/go-cid" + "encoding/base64" + "fmt" + + "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/cbor" + ipldcbor "github.com/ipfs/go-ipld-cbor" + "github.com/filecoin-project/go-state-types/manifest" + + paych0 "github.com/filecoin-project/specs-actors/actors/builtin/paych" + paychtypes "github.com/filecoin-project/go-state-types/builtin/v8/paych" +{{range .versions}} + {{if (le . 7)}} + builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin" + {{end}} +{{end}} + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/types" +) + +// Load returns an abstract copy of payment channel state, irregardless of actor version +func Load(store adt.Store, act *types.Actor) (State, error) { + if name, av, ok := actors.GetActorMetaByCode(act.Code); ok { + if name != manifest.PaychKey { + return nil, xerrors.Errorf("actor code is not paych: %s", name) + } + + switch av { + {{range .versions}} + {{if (ge . 8)}} + case actorstypes.Version{{.}}: + return load{{.}}(store, act.Head) + {{end}} + {{end}} + } + } + + switch act.Code { +{{range .versions}} + {{if (le . 7)}} + case builtin{{.}}.PaymentChannelActorCodeID: + return load{{.}}(store, act.Head) + {{end}} +{{end}} + } + + return nil, xerrors.Errorf("unknown actor code %s", act.Code) +} + +// State is an abstract version of payment channel state that works across +// versions +type State interface { + cbor.Marshaler + + Code() cid.Cid + ActorKey() string + ActorVersion() actorstypes.Version + + // Channel owner, who has funded the actor + From() (address.Address, error) + // Recipient of payouts from channel + To() (address.Address, error) + + // Height at which the channel can be `Collected` + SettlingAt() (abi.ChainEpoch, error) + + // Amount successfully redeemed through the payment channel, paid out on `Collect()` + ToSend() (abi.TokenAmount, error) + + // Get total number of lanes + LaneCount() (uint64, error) + + // Iterate lane states + ForEachLaneState(cb func(idx uint64, dl LaneState) error) error + + GetState() interface{} +} + +// LaneState is an abstract copy of the state of a single lane +type LaneState interface { + Redeemed() (big.Int, error) + Nonce() (uint64, error) +} + +// DecodeSignedVoucher decodes base64 encoded signed voucher. +func DecodeSignedVoucher(s string) (*paychtypes.SignedVoucher, error) { + data, err := base64.RawURLEncoding.DecodeString(s) + if err != nil { + return nil, err + } + + var sv paychtypes.SignedVoucher + if err := ipldcbor.DecodeInto(data, &sv); err != nil { + return nil, err + } + + return &sv, nil +} + +func Message(version actorstypes.Version, from address.Address) MessageBuilder { + switch version { +{{range .versions}} + case actorstypes.Version{{.}}: + return message{{.}}{from} +{{end}} + default: + panic(fmt.Sprintf("unsupported actors version: %d", version)) + } +} + +type MessageBuilder interface { + Create(to address.Address, initialAmount abi.TokenAmount) (*types.Message, error) + Update(paych address.Address, voucher *paychtypes.SignedVoucher, secret []byte) (*types.Message, error) + Settle(paych address.Address) (*types.Message, error) + Collect(paych address.Address) (*types.Message, error) +} + +func toV0SignedVoucher(sv paychtypes.SignedVoucher) paych0.SignedVoucher { + return paych0.SignedVoucher{ + ChannelAddr: sv.ChannelAddr, + TimeLockMin: sv.TimeLockMin, + TimeLockMax: sv.TimeLockMax, + SecretPreimage: sv.SecretHash, + Extra: (*paych0.ModVerifyParams)(sv.Extra), + Lane: sv.Lane, + Nonce: sv.Nonce, + Amount: sv.Amount, + MinSettleHeight: sv.MinSettleHeight, + Merges: nil, + Signature: sv.Signature, + } +} + +func AllCodes() []cid.Cid { + return []cid.Cid{ {{range .versions}} + (&state{{.}}{}).Code(), + {{- end}} + } +} diff --git a/chain/actors/builtin/paych/message.go.template b/chain/actors/builtin/paych/message.go.template new file mode 100644 index 000000000..79aed1692 --- /dev/null +++ b/chain/actors/builtin/paych/message.go.template @@ -0,0 +1,128 @@ +package paych + +import ( + {{if (ge .v 8)}} + "golang.org/x/xerrors" + {{end}} + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + + paychtypes "github.com/filecoin-project/go-state-types/builtin/v8/paych" + {{if (le .v 7)}} + builtin{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/builtin" + init{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/builtin/init" + paych{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/builtin/paych" + {{else}} + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtin{{.v}} "github.com/filecoin-project/go-state-types/builtin" + paych{{.v}} "github.com/filecoin-project/go-state-types/builtin/v{{.v}}/paych" + init{{.v}} "github.com/filecoin-project/go-state-types/builtin/v{{.v}}/init" + {{end}} + + + "github.com/filecoin-project/lotus/chain/actors" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/types" +) + +type message{{.v}} struct{ from address.Address } + +func (m message{{.v}}) Create(to address.Address, initialAmount abi.TokenAmount) (*types.Message, error) { + {{if (le .v 7)}} + actorCodeID := builtin{{.v}}.PaymentChannelActorCodeID + {{else}} + actorCodeID, ok := actors.GetActorCodeID(actorstypes.Version{{.v}}, "paymentchannel") + if !ok { + return nil, xerrors.Errorf("error getting actor paymentchannel code id for actor version %d", {{.v}}) + } + {{end}} + + params, aerr := actors.SerializeParams(&paych{{.v}}.ConstructorParams{From: m.from, To: to}) + if aerr != nil { + return nil, aerr + } + enc, aerr := actors.SerializeParams(&init{{.v}}.ExecParams{ + CodeCID: actorCodeID, + ConstructorParams: params, + }) + if aerr != nil { + return nil, aerr + } + + return &types.Message{ + To: init_.Address, + From: m.from, + Value: initialAmount, + Method: builtin{{.v}}.MethodsInit.Exec, + Params: enc, + }, nil +} + +func (m message{{.v}}) Update(paych address.Address, sv *paychtypes.SignedVoucher, secret []byte) (*types.Message, error) { + params, aerr := actors.SerializeParams(&paych{{.v}}.UpdateChannelStateParams{ + {{if (le .v 6)}} + Sv: toV0SignedVoucher(*sv), + {{else if (le .v 8)}} + Sv: *sv, + {{else}} + Sv: toV{{.v}}SignedVoucher(*sv), + {{end}} + Secret: secret, + }) + if aerr != nil { + return nil, aerr + } + + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin{{.v}}.MethodsPaych.UpdateChannelState, + Params: params, + }, nil +} + +{{if (ge .v 9)}} + func toV{{.v}}SignedVoucher(sv paychtypes.SignedVoucher) paych{{.v}}.SignedVoucher { + merges := make([]paych{{.v}}.Merge, len(sv.Merges)) + for i := range sv.Merges { + merges[i] = paych{{.v}}.Merge{ + Lane: sv.Merges[i].Lane, + Nonce: sv.Merges[i].Nonce, + } + } + + return paych{{.v}}.SignedVoucher{ + ChannelAddr: sv.ChannelAddr, + TimeLockMin: sv.TimeLockMin, + TimeLockMax: sv.TimeLockMax, + SecretHash: sv.SecretHash, + Extra: (*paych{{.v}}.ModVerifyParams)(sv.Extra), + Lane: sv.Lane, + Nonce: sv.Nonce, + Amount: sv.Amount, + MinSettleHeight: sv.MinSettleHeight, + Merges: merges, + Signature: sv.Signature, + } + } +{{end}} + +func (m message{{.v}}) Settle(paych address.Address) (*types.Message, error) { + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin{{.v}}.MethodsPaych.Settle, + }, nil +} + +func (m message{{.v}}) Collect(paych address.Address) (*types.Message, error) { + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin{{.v}}.MethodsPaych.Collect, + }, nil +} diff --git a/chain/actors/builtin/paych/message0.go b/chain/actors/builtin/paych/message0.go new file mode 100644 index 000000000..d5a112d43 --- /dev/null +++ b/chain/actors/builtin/paych/message0.go @@ -0,0 +1,79 @@ +package paych + +import ( + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + paychtypes "github.com/filecoin-project/go-state-types/builtin/v8/paych" + builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" + init0 "github.com/filecoin-project/specs-actors/actors/builtin/init" + paych0 "github.com/filecoin-project/specs-actors/actors/builtin/paych" + + "github.com/filecoin-project/lotus/chain/actors" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/types" +) + +type message0 struct{ from address.Address } + +func (m message0) Create(to address.Address, initialAmount abi.TokenAmount) (*types.Message, error) { + + actorCodeID := builtin0.PaymentChannelActorCodeID + + params, aerr := actors.SerializeParams(&paych0.ConstructorParams{From: m.from, To: to}) + if aerr != nil { + return nil, aerr + } + enc, aerr := actors.SerializeParams(&init0.ExecParams{ + CodeCID: actorCodeID, + ConstructorParams: params, + }) + if aerr != nil { + return nil, aerr + } + + return &types.Message{ + To: init_.Address, + From: m.from, + Value: initialAmount, + Method: builtin0.MethodsInit.Exec, + Params: enc, + }, nil +} + +func (m message0) Update(paych address.Address, sv *paychtypes.SignedVoucher, secret []byte) (*types.Message, error) { + params, aerr := actors.SerializeParams(&paych0.UpdateChannelStateParams{ + + Sv: toV0SignedVoucher(*sv), + + Secret: secret, + }) + if aerr != nil { + return nil, aerr + } + + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin0.MethodsPaych.UpdateChannelState, + Params: params, + }, nil +} + +func (m message0) Settle(paych address.Address) (*types.Message, error) { + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin0.MethodsPaych.Settle, + }, nil +} + +func (m message0) Collect(paych address.Address) (*types.Message, error) { + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin0.MethodsPaych.Collect, + }, nil +} diff --git a/chain/actors/builtin/paych/message10.go b/chain/actors/builtin/paych/message10.go new file mode 100644 index 000000000..03daa7bcf --- /dev/null +++ b/chain/actors/builtin/paych/message10.go @@ -0,0 +1,109 @@ +package paych + +import ( + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtin10 "github.com/filecoin-project/go-state-types/builtin" + init10 "github.com/filecoin-project/go-state-types/builtin/v10/init" + paych10 "github.com/filecoin-project/go-state-types/builtin/v10/paych" + paychtypes "github.com/filecoin-project/go-state-types/builtin/v8/paych" + + "github.com/filecoin-project/lotus/chain/actors" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/types" +) + +type message10 struct{ from address.Address } + +func (m message10) Create(to address.Address, initialAmount abi.TokenAmount) (*types.Message, error) { + + actorCodeID, ok := actors.GetActorCodeID(actorstypes.Version10, "paymentchannel") + if !ok { + return nil, xerrors.Errorf("error getting actor paymentchannel code id for actor version %d", 10) + } + + params, aerr := actors.SerializeParams(&paych10.ConstructorParams{From: m.from, To: to}) + if aerr != nil { + return nil, aerr + } + enc, aerr := actors.SerializeParams(&init10.ExecParams{ + CodeCID: actorCodeID, + ConstructorParams: params, + }) + if aerr != nil { + return nil, aerr + } + + return &types.Message{ + To: init_.Address, + From: m.from, + Value: initialAmount, + Method: builtin10.MethodsInit.Exec, + Params: enc, + }, nil +} + +func (m message10) Update(paych address.Address, sv *paychtypes.SignedVoucher, secret []byte) (*types.Message, error) { + params, aerr := actors.SerializeParams(&paych10.UpdateChannelStateParams{ + + Sv: toV10SignedVoucher(*sv), + + Secret: secret, + }) + if aerr != nil { + return nil, aerr + } + + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin10.MethodsPaych.UpdateChannelState, + Params: params, + }, nil +} + +func toV10SignedVoucher(sv paychtypes.SignedVoucher) paych10.SignedVoucher { + merges := make([]paych10.Merge, len(sv.Merges)) + for i := range sv.Merges { + merges[i] = paych10.Merge{ + Lane: sv.Merges[i].Lane, + Nonce: sv.Merges[i].Nonce, + } + } + + return paych10.SignedVoucher{ + ChannelAddr: sv.ChannelAddr, + TimeLockMin: sv.TimeLockMin, + TimeLockMax: sv.TimeLockMax, + SecretHash: sv.SecretHash, + Extra: (*paych10.ModVerifyParams)(sv.Extra), + Lane: sv.Lane, + Nonce: sv.Nonce, + Amount: sv.Amount, + MinSettleHeight: sv.MinSettleHeight, + Merges: merges, + Signature: sv.Signature, + } +} + +func (m message10) Settle(paych address.Address) (*types.Message, error) { + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin10.MethodsPaych.Settle, + }, nil +} + +func (m message10) Collect(paych address.Address) (*types.Message, error) { + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin10.MethodsPaych.Collect, + }, nil +} diff --git a/chain/actors/builtin/paych/message11.go b/chain/actors/builtin/paych/message11.go new file mode 100644 index 000000000..7a9ec1c8e --- /dev/null +++ b/chain/actors/builtin/paych/message11.go @@ -0,0 +1,109 @@ +package paych + +import ( + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtin11 "github.com/filecoin-project/go-state-types/builtin" + init11 "github.com/filecoin-project/go-state-types/builtin/v11/init" + paych11 "github.com/filecoin-project/go-state-types/builtin/v11/paych" + paychtypes "github.com/filecoin-project/go-state-types/builtin/v8/paych" + + "github.com/filecoin-project/lotus/chain/actors" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/types" +) + +type message11 struct{ from address.Address } + +func (m message11) Create(to address.Address, initialAmount abi.TokenAmount) (*types.Message, error) { + + actorCodeID, ok := actors.GetActorCodeID(actorstypes.Version11, "paymentchannel") + if !ok { + return nil, xerrors.Errorf("error getting actor paymentchannel code id for actor version %d", 11) + } + + params, aerr := actors.SerializeParams(&paych11.ConstructorParams{From: m.from, To: to}) + if aerr != nil { + return nil, aerr + } + enc, aerr := actors.SerializeParams(&init11.ExecParams{ + CodeCID: actorCodeID, + ConstructorParams: params, + }) + if aerr != nil { + return nil, aerr + } + + return &types.Message{ + To: init_.Address, + From: m.from, + Value: initialAmount, + Method: builtin11.MethodsInit.Exec, + Params: enc, + }, nil +} + +func (m message11) Update(paych address.Address, sv *paychtypes.SignedVoucher, secret []byte) (*types.Message, error) { + params, aerr := actors.SerializeParams(&paych11.UpdateChannelStateParams{ + + Sv: toV11SignedVoucher(*sv), + + Secret: secret, + }) + if aerr != nil { + return nil, aerr + } + + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin11.MethodsPaych.UpdateChannelState, + Params: params, + }, nil +} + +func toV11SignedVoucher(sv paychtypes.SignedVoucher) paych11.SignedVoucher { + merges := make([]paych11.Merge, len(sv.Merges)) + for i := range sv.Merges { + merges[i] = paych11.Merge{ + Lane: sv.Merges[i].Lane, + Nonce: sv.Merges[i].Nonce, + } + } + + return paych11.SignedVoucher{ + ChannelAddr: sv.ChannelAddr, + TimeLockMin: sv.TimeLockMin, + TimeLockMax: sv.TimeLockMax, + SecretHash: sv.SecretHash, + Extra: (*paych11.ModVerifyParams)(sv.Extra), + Lane: sv.Lane, + Nonce: sv.Nonce, + Amount: sv.Amount, + MinSettleHeight: sv.MinSettleHeight, + Merges: merges, + Signature: sv.Signature, + } +} + +func (m message11) Settle(paych address.Address) (*types.Message, error) { + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin11.MethodsPaych.Settle, + }, nil +} + +func (m message11) Collect(paych address.Address) (*types.Message, error) { + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin11.MethodsPaych.Collect, + }, nil +} diff --git a/chain/actors/builtin/paych/message2.go b/chain/actors/builtin/paych/message2.go new file mode 100644 index 000000000..89a6e74ce --- /dev/null +++ b/chain/actors/builtin/paych/message2.go @@ -0,0 +1,79 @@ +package paych + +import ( + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + paychtypes "github.com/filecoin-project/go-state-types/builtin/v8/paych" + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + init2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/init" + paych2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/paych" + + "github.com/filecoin-project/lotus/chain/actors" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/types" +) + +type message2 struct{ from address.Address } + +func (m message2) Create(to address.Address, initialAmount abi.TokenAmount) (*types.Message, error) { + + actorCodeID := builtin2.PaymentChannelActorCodeID + + params, aerr := actors.SerializeParams(&paych2.ConstructorParams{From: m.from, To: to}) + if aerr != nil { + return nil, aerr + } + enc, aerr := actors.SerializeParams(&init2.ExecParams{ + CodeCID: actorCodeID, + ConstructorParams: params, + }) + if aerr != nil { + return nil, aerr + } + + return &types.Message{ + To: init_.Address, + From: m.from, + Value: initialAmount, + Method: builtin2.MethodsInit.Exec, + Params: enc, + }, nil +} + +func (m message2) Update(paych address.Address, sv *paychtypes.SignedVoucher, secret []byte) (*types.Message, error) { + params, aerr := actors.SerializeParams(&paych2.UpdateChannelStateParams{ + + Sv: toV0SignedVoucher(*sv), + + Secret: secret, + }) + if aerr != nil { + return nil, aerr + } + + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin2.MethodsPaych.UpdateChannelState, + Params: params, + }, nil +} + +func (m message2) Settle(paych address.Address) (*types.Message, error) { + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin2.MethodsPaych.Settle, + }, nil +} + +func (m message2) Collect(paych address.Address) (*types.Message, error) { + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin2.MethodsPaych.Collect, + }, nil +} diff --git a/chain/actors/builtin/paych/message3.go b/chain/actors/builtin/paych/message3.go new file mode 100644 index 000000000..47f8e7e9e --- /dev/null +++ b/chain/actors/builtin/paych/message3.go @@ -0,0 +1,79 @@ +package paych + +import ( + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + paychtypes "github.com/filecoin-project/go-state-types/builtin/v8/paych" + builtin3 "github.com/filecoin-project/specs-actors/v3/actors/builtin" + init3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/init" + paych3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/paych" + + "github.com/filecoin-project/lotus/chain/actors" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/types" +) + +type message3 struct{ from address.Address } + +func (m message3) Create(to address.Address, initialAmount abi.TokenAmount) (*types.Message, error) { + + actorCodeID := builtin3.PaymentChannelActorCodeID + + params, aerr := actors.SerializeParams(&paych3.ConstructorParams{From: m.from, To: to}) + if aerr != nil { + return nil, aerr + } + enc, aerr := actors.SerializeParams(&init3.ExecParams{ + CodeCID: actorCodeID, + ConstructorParams: params, + }) + if aerr != nil { + return nil, aerr + } + + return &types.Message{ + To: init_.Address, + From: m.from, + Value: initialAmount, + Method: builtin3.MethodsInit.Exec, + Params: enc, + }, nil +} + +func (m message3) Update(paych address.Address, sv *paychtypes.SignedVoucher, secret []byte) (*types.Message, error) { + params, aerr := actors.SerializeParams(&paych3.UpdateChannelStateParams{ + + Sv: toV0SignedVoucher(*sv), + + Secret: secret, + }) + if aerr != nil { + return nil, aerr + } + + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin3.MethodsPaych.UpdateChannelState, + Params: params, + }, nil +} + +func (m message3) Settle(paych address.Address) (*types.Message, error) { + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin3.MethodsPaych.Settle, + }, nil +} + +func (m message3) Collect(paych address.Address) (*types.Message, error) { + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin3.MethodsPaych.Collect, + }, nil +} diff --git a/chain/actors/builtin/paych/message4.go b/chain/actors/builtin/paych/message4.go new file mode 100644 index 000000000..65e63149d --- /dev/null +++ b/chain/actors/builtin/paych/message4.go @@ -0,0 +1,79 @@ +package paych + +import ( + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + paychtypes "github.com/filecoin-project/go-state-types/builtin/v8/paych" + builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" + init4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/init" + paych4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/paych" + + "github.com/filecoin-project/lotus/chain/actors" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/types" +) + +type message4 struct{ from address.Address } + +func (m message4) Create(to address.Address, initialAmount abi.TokenAmount) (*types.Message, error) { + + actorCodeID := builtin4.PaymentChannelActorCodeID + + params, aerr := actors.SerializeParams(&paych4.ConstructorParams{From: m.from, To: to}) + if aerr != nil { + return nil, aerr + } + enc, aerr := actors.SerializeParams(&init4.ExecParams{ + CodeCID: actorCodeID, + ConstructorParams: params, + }) + if aerr != nil { + return nil, aerr + } + + return &types.Message{ + To: init_.Address, + From: m.from, + Value: initialAmount, + Method: builtin4.MethodsInit.Exec, + Params: enc, + }, nil +} + +func (m message4) Update(paych address.Address, sv *paychtypes.SignedVoucher, secret []byte) (*types.Message, error) { + params, aerr := actors.SerializeParams(&paych4.UpdateChannelStateParams{ + + Sv: toV0SignedVoucher(*sv), + + Secret: secret, + }) + if aerr != nil { + return nil, aerr + } + + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin4.MethodsPaych.UpdateChannelState, + Params: params, + }, nil +} + +func (m message4) Settle(paych address.Address) (*types.Message, error) { + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin4.MethodsPaych.Settle, + }, nil +} + +func (m message4) Collect(paych address.Address) (*types.Message, error) { + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin4.MethodsPaych.Collect, + }, nil +} diff --git a/chain/actors/builtin/paych/message5.go b/chain/actors/builtin/paych/message5.go new file mode 100644 index 000000000..ede1c8ceb --- /dev/null +++ b/chain/actors/builtin/paych/message5.go @@ -0,0 +1,79 @@ +package paych + +import ( + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + paychtypes "github.com/filecoin-project/go-state-types/builtin/v8/paych" + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" + init5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/init" + paych5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/paych" + + "github.com/filecoin-project/lotus/chain/actors" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/types" +) + +type message5 struct{ from address.Address } + +func (m message5) Create(to address.Address, initialAmount abi.TokenAmount) (*types.Message, error) { + + actorCodeID := builtin5.PaymentChannelActorCodeID + + params, aerr := actors.SerializeParams(&paych5.ConstructorParams{From: m.from, To: to}) + if aerr != nil { + return nil, aerr + } + enc, aerr := actors.SerializeParams(&init5.ExecParams{ + CodeCID: actorCodeID, + ConstructorParams: params, + }) + if aerr != nil { + return nil, aerr + } + + return &types.Message{ + To: init_.Address, + From: m.from, + Value: initialAmount, + Method: builtin5.MethodsInit.Exec, + Params: enc, + }, nil +} + +func (m message5) Update(paych address.Address, sv *paychtypes.SignedVoucher, secret []byte) (*types.Message, error) { + params, aerr := actors.SerializeParams(&paych5.UpdateChannelStateParams{ + + Sv: toV0SignedVoucher(*sv), + + Secret: secret, + }) + if aerr != nil { + return nil, aerr + } + + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin5.MethodsPaych.UpdateChannelState, + Params: params, + }, nil +} + +func (m message5) Settle(paych address.Address) (*types.Message, error) { + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin5.MethodsPaych.Settle, + }, nil +} + +func (m message5) Collect(paych address.Address) (*types.Message, error) { + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin5.MethodsPaych.Collect, + }, nil +} diff --git a/chain/actors/builtin/paych/message6.go b/chain/actors/builtin/paych/message6.go new file mode 100644 index 000000000..009e8641f --- /dev/null +++ b/chain/actors/builtin/paych/message6.go @@ -0,0 +1,79 @@ +package paych + +import ( + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + paychtypes "github.com/filecoin-project/go-state-types/builtin/v8/paych" + builtin6 "github.com/filecoin-project/specs-actors/v6/actors/builtin" + init6 "github.com/filecoin-project/specs-actors/v6/actors/builtin/init" + paych6 "github.com/filecoin-project/specs-actors/v6/actors/builtin/paych" + + "github.com/filecoin-project/lotus/chain/actors" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/types" +) + +type message6 struct{ from address.Address } + +func (m message6) Create(to address.Address, initialAmount abi.TokenAmount) (*types.Message, error) { + + actorCodeID := builtin6.PaymentChannelActorCodeID + + params, aerr := actors.SerializeParams(&paych6.ConstructorParams{From: m.from, To: to}) + if aerr != nil { + return nil, aerr + } + enc, aerr := actors.SerializeParams(&init6.ExecParams{ + CodeCID: actorCodeID, + ConstructorParams: params, + }) + if aerr != nil { + return nil, aerr + } + + return &types.Message{ + To: init_.Address, + From: m.from, + Value: initialAmount, + Method: builtin6.MethodsInit.Exec, + Params: enc, + }, nil +} + +func (m message6) Update(paych address.Address, sv *paychtypes.SignedVoucher, secret []byte) (*types.Message, error) { + params, aerr := actors.SerializeParams(&paych6.UpdateChannelStateParams{ + + Sv: toV0SignedVoucher(*sv), + + Secret: secret, + }) + if aerr != nil { + return nil, aerr + } + + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin6.MethodsPaych.UpdateChannelState, + Params: params, + }, nil +} + +func (m message6) Settle(paych address.Address) (*types.Message, error) { + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin6.MethodsPaych.Settle, + }, nil +} + +func (m message6) Collect(paych address.Address) (*types.Message, error) { + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin6.MethodsPaych.Collect, + }, nil +} diff --git a/chain/actors/builtin/paych/message7.go b/chain/actors/builtin/paych/message7.go new file mode 100644 index 000000000..430fb9341 --- /dev/null +++ b/chain/actors/builtin/paych/message7.go @@ -0,0 +1,79 @@ +package paych + +import ( + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + paychtypes "github.com/filecoin-project/go-state-types/builtin/v8/paych" + builtin7 "github.com/filecoin-project/specs-actors/v7/actors/builtin" + init7 "github.com/filecoin-project/specs-actors/v7/actors/builtin/init" + paych7 "github.com/filecoin-project/specs-actors/v7/actors/builtin/paych" + + "github.com/filecoin-project/lotus/chain/actors" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/types" +) + +type message7 struct{ from address.Address } + +func (m message7) Create(to address.Address, initialAmount abi.TokenAmount) (*types.Message, error) { + + actorCodeID := builtin7.PaymentChannelActorCodeID + + params, aerr := actors.SerializeParams(&paych7.ConstructorParams{From: m.from, To: to}) + if aerr != nil { + return nil, aerr + } + enc, aerr := actors.SerializeParams(&init7.ExecParams{ + CodeCID: actorCodeID, + ConstructorParams: params, + }) + if aerr != nil { + return nil, aerr + } + + return &types.Message{ + To: init_.Address, + From: m.from, + Value: initialAmount, + Method: builtin7.MethodsInit.Exec, + Params: enc, + }, nil +} + +func (m message7) Update(paych address.Address, sv *paychtypes.SignedVoucher, secret []byte) (*types.Message, error) { + params, aerr := actors.SerializeParams(&paych7.UpdateChannelStateParams{ + + Sv: *sv, + + Secret: secret, + }) + if aerr != nil { + return nil, aerr + } + + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin7.MethodsPaych.UpdateChannelState, + Params: params, + }, nil +} + +func (m message7) Settle(paych address.Address) (*types.Message, error) { + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin7.MethodsPaych.Settle, + }, nil +} + +func (m message7) Collect(paych address.Address) (*types.Message, error) { + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin7.MethodsPaych.Collect, + }, nil +} diff --git a/chain/actors/builtin/paych/message8.go b/chain/actors/builtin/paych/message8.go new file mode 100644 index 000000000..a72c5fbb3 --- /dev/null +++ b/chain/actors/builtin/paych/message8.go @@ -0,0 +1,85 @@ +package paych + +import ( + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtin8 "github.com/filecoin-project/go-state-types/builtin" + init8 "github.com/filecoin-project/go-state-types/builtin/v8/init" + paych8 "github.com/filecoin-project/go-state-types/builtin/v8/paych" + paychtypes "github.com/filecoin-project/go-state-types/builtin/v8/paych" + + "github.com/filecoin-project/lotus/chain/actors" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/types" +) + +type message8 struct{ from address.Address } + +func (m message8) Create(to address.Address, initialAmount abi.TokenAmount) (*types.Message, error) { + + actorCodeID, ok := actors.GetActorCodeID(actorstypes.Version8, "paymentchannel") + if !ok { + return nil, xerrors.Errorf("error getting actor paymentchannel code id for actor version %d", 8) + } + + params, aerr := actors.SerializeParams(&paych8.ConstructorParams{From: m.from, To: to}) + if aerr != nil { + return nil, aerr + } + enc, aerr := actors.SerializeParams(&init8.ExecParams{ + CodeCID: actorCodeID, + ConstructorParams: params, + }) + if aerr != nil { + return nil, aerr + } + + return &types.Message{ + To: init_.Address, + From: m.from, + Value: initialAmount, + Method: builtin8.MethodsInit.Exec, + Params: enc, + }, nil +} + +func (m message8) Update(paych address.Address, sv *paychtypes.SignedVoucher, secret []byte) (*types.Message, error) { + params, aerr := actors.SerializeParams(&paych8.UpdateChannelStateParams{ + + Sv: *sv, + + Secret: secret, + }) + if aerr != nil { + return nil, aerr + } + + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin8.MethodsPaych.UpdateChannelState, + Params: params, + }, nil +} + +func (m message8) Settle(paych address.Address) (*types.Message, error) { + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin8.MethodsPaych.Settle, + }, nil +} + +func (m message8) Collect(paych address.Address) (*types.Message, error) { + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin8.MethodsPaych.Collect, + }, nil +} diff --git a/chain/actors/builtin/paych/message9.go b/chain/actors/builtin/paych/message9.go new file mode 100644 index 000000000..350c760ea --- /dev/null +++ b/chain/actors/builtin/paych/message9.go @@ -0,0 +1,109 @@ +package paych + +import ( + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtin9 "github.com/filecoin-project/go-state-types/builtin" + paychtypes "github.com/filecoin-project/go-state-types/builtin/v8/paych" + init9 "github.com/filecoin-project/go-state-types/builtin/v9/init" + paych9 "github.com/filecoin-project/go-state-types/builtin/v9/paych" + + "github.com/filecoin-project/lotus/chain/actors" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/types" +) + +type message9 struct{ from address.Address } + +func (m message9) Create(to address.Address, initialAmount abi.TokenAmount) (*types.Message, error) { + + actorCodeID, ok := actors.GetActorCodeID(actorstypes.Version9, "paymentchannel") + if !ok { + return nil, xerrors.Errorf("error getting actor paymentchannel code id for actor version %d", 9) + } + + params, aerr := actors.SerializeParams(&paych9.ConstructorParams{From: m.from, To: to}) + if aerr != nil { + return nil, aerr + } + enc, aerr := actors.SerializeParams(&init9.ExecParams{ + CodeCID: actorCodeID, + ConstructorParams: params, + }) + if aerr != nil { + return nil, aerr + } + + return &types.Message{ + To: init_.Address, + From: m.from, + Value: initialAmount, + Method: builtin9.MethodsInit.Exec, + Params: enc, + }, nil +} + +func (m message9) Update(paych address.Address, sv *paychtypes.SignedVoucher, secret []byte) (*types.Message, error) { + params, aerr := actors.SerializeParams(&paych9.UpdateChannelStateParams{ + + Sv: toV9SignedVoucher(*sv), + + Secret: secret, + }) + if aerr != nil { + return nil, aerr + } + + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin9.MethodsPaych.UpdateChannelState, + Params: params, + }, nil +} + +func toV9SignedVoucher(sv paychtypes.SignedVoucher) paych9.SignedVoucher { + merges := make([]paych9.Merge, len(sv.Merges)) + for i := range sv.Merges { + merges[i] = paych9.Merge{ + Lane: sv.Merges[i].Lane, + Nonce: sv.Merges[i].Nonce, + } + } + + return paych9.SignedVoucher{ + ChannelAddr: sv.ChannelAddr, + TimeLockMin: sv.TimeLockMin, + TimeLockMax: sv.TimeLockMax, + SecretHash: sv.SecretHash, + Extra: (*paych9.ModVerifyParams)(sv.Extra), + Lane: sv.Lane, + Nonce: sv.Nonce, + Amount: sv.Amount, + MinSettleHeight: sv.MinSettleHeight, + Merges: merges, + Signature: sv.Signature, + } +} + +func (m message9) Settle(paych address.Address) (*types.Message, error) { + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin9.MethodsPaych.Settle, + }, nil +} + +func (m message9) Collect(paych address.Address) (*types.Message, error) { + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin9.MethodsPaych.Collect, + }, nil +} diff --git a/chain/actors/builtin/paych/mock/mock.go b/chain/actors/builtin/paych/mock/mock.go new file mode 100644 index 000000000..3b8881319 --- /dev/null +++ b/chain/actors/builtin/paych/mock/mock.go @@ -0,0 +1,109 @@ +package mock + +import ( + "io" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors/builtin/paych" +) + +type mockState struct { + from address.Address + to address.Address + settlingAt abi.ChainEpoch + toSend abi.TokenAmount + lanes map[uint64]paych.LaneState +} + +func (ms *mockState) Code() cid.Cid { + panic("paych mock does not have CID") +} + +func (ms *mockState) ActorKey() string { + return manifest.PaychKey +} + +func (ms *mockState) ActorVersion() actorstypes.Version { + panic("paych mock is unversioned") +} + +func (ms *mockState) GetState() interface{} { + panic("implement me") +} + +type mockLaneState struct { + redeemed big.Int + nonce uint64 +} + +// NewMockPayChState constructs a state for a payment channel with the set fixed values +// that satisfies the paych.State interface. +func NewMockPayChState(from address.Address, + to address.Address, + settlingAt abi.ChainEpoch, + lanes map[uint64]paych.LaneState, +) paych.State { + return &mockState{from: from, to: to, settlingAt: settlingAt, toSend: big.NewInt(0), lanes: lanes} +} + +// NewMockLaneState constructs a state for a payment channel lane with the set fixed values +// that satisfies the paych.LaneState interface. Useful for populating lanes when +// calling NewMockPayChState +func NewMockLaneState(redeemed big.Int, nonce uint64) paych.LaneState { + return &mockLaneState{redeemed, nonce} +} + +func (ms *mockState) MarshalCBOR(io.Writer) error { + panic("not implemented") +} + +// Channel owner, who has funded the actor +func (ms *mockState) From() (address.Address, error) { + return ms.from, nil +} + +// Recipient of payouts from channel +func (ms *mockState) To() (address.Address, error) { + return ms.to, nil +} + +// Height at which the channel can be `Collected` +func (ms *mockState) SettlingAt() (abi.ChainEpoch, error) { + return ms.settlingAt, nil +} + +// Amount successfully redeemed through the payment channel, paid out on `Collect()` +func (ms *mockState) ToSend() (abi.TokenAmount, error) { + return ms.toSend, nil +} + +// Get total number of lanes +func (ms *mockState) LaneCount() (uint64, error) { + return uint64(len(ms.lanes)), nil +} + +// Iterate lane states +func (ms *mockState) ForEachLaneState(cb func(idx uint64, dl paych.LaneState) error) error { + var lastErr error + for lane, state := range ms.lanes { + if err := cb(lane, state); err != nil { + lastErr = err + } + } + return lastErr +} + +func (mls *mockLaneState) Redeemed() (big.Int, error) { + return mls.redeemed, nil +} + +func (mls *mockLaneState) Nonce() (uint64, error) { + return mls.nonce, nil +} diff --git a/chain/actors/builtin/paych/paych.go b/chain/actors/builtin/paych/paych.go new file mode 100644 index 000000000..ccf48dbce --- /dev/null +++ b/chain/actors/builtin/paych/paych.go @@ -0,0 +1,212 @@ +package paych + +import ( + "encoding/base64" + "fmt" + + "github.com/ipfs/go-cid" + ipldcbor "github.com/ipfs/go-ipld-cbor" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + paychtypes "github.com/filecoin-project/go-state-types/builtin/v8/paych" + "github.com/filecoin-project/go-state-types/cbor" + "github.com/filecoin-project/go-state-types/manifest" + builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" + paych0 "github.com/filecoin-project/specs-actors/actors/builtin/paych" + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + builtin3 "github.com/filecoin-project/specs-actors/v3/actors/builtin" + builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" + builtin6 "github.com/filecoin-project/specs-actors/v6/actors/builtin" + builtin7 "github.com/filecoin-project/specs-actors/v7/actors/builtin" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/types" +) + +// Load returns an abstract copy of payment channel state, irregardless of actor version +func Load(store adt.Store, act *types.Actor) (State, error) { + if name, av, ok := actors.GetActorMetaByCode(act.Code); ok { + if name != manifest.PaychKey { + return nil, xerrors.Errorf("actor code is not paych: %s", name) + } + + switch av { + + case actorstypes.Version8: + return load8(store, act.Head) + + case actorstypes.Version9: + return load9(store, act.Head) + + case actorstypes.Version10: + return load10(store, act.Head) + + case actorstypes.Version11: + return load11(store, act.Head) + + } + } + + switch act.Code { + + case builtin0.PaymentChannelActorCodeID: + return load0(store, act.Head) + + case builtin2.PaymentChannelActorCodeID: + return load2(store, act.Head) + + case builtin3.PaymentChannelActorCodeID: + return load3(store, act.Head) + + case builtin4.PaymentChannelActorCodeID: + return load4(store, act.Head) + + case builtin5.PaymentChannelActorCodeID: + return load5(store, act.Head) + + case builtin6.PaymentChannelActorCodeID: + return load6(store, act.Head) + + case builtin7.PaymentChannelActorCodeID: + return load7(store, act.Head) + + } + + return nil, xerrors.Errorf("unknown actor code %s", act.Code) +} + +// State is an abstract version of payment channel state that works across +// versions +type State interface { + cbor.Marshaler + + Code() cid.Cid + ActorKey() string + ActorVersion() actorstypes.Version + + // Channel owner, who has funded the actor + From() (address.Address, error) + // Recipient of payouts from channel + To() (address.Address, error) + + // Height at which the channel can be `Collected` + SettlingAt() (abi.ChainEpoch, error) + + // Amount successfully redeemed through the payment channel, paid out on `Collect()` + ToSend() (abi.TokenAmount, error) + + // Get total number of lanes + LaneCount() (uint64, error) + + // Iterate lane states + ForEachLaneState(cb func(idx uint64, dl LaneState) error) error + + GetState() interface{} +} + +// LaneState is an abstract copy of the state of a single lane +type LaneState interface { + Redeemed() (big.Int, error) + Nonce() (uint64, error) +} + +// DecodeSignedVoucher decodes base64 encoded signed voucher. +func DecodeSignedVoucher(s string) (*paychtypes.SignedVoucher, error) { + data, err := base64.RawURLEncoding.DecodeString(s) + if err != nil { + return nil, err + } + + var sv paychtypes.SignedVoucher + if err := ipldcbor.DecodeInto(data, &sv); err != nil { + return nil, err + } + + return &sv, nil +} + +func Message(version actorstypes.Version, from address.Address) MessageBuilder { + switch version { + + case actorstypes.Version0: + return message0{from} + + case actorstypes.Version2: + return message2{from} + + case actorstypes.Version3: + return message3{from} + + case actorstypes.Version4: + return message4{from} + + case actorstypes.Version5: + return message5{from} + + case actorstypes.Version6: + return message6{from} + + case actorstypes.Version7: + return message7{from} + + case actorstypes.Version8: + return message8{from} + + case actorstypes.Version9: + return message9{from} + + case actorstypes.Version10: + return message10{from} + + case actorstypes.Version11: + return message11{from} + + default: + panic(fmt.Sprintf("unsupported actors version: %d", version)) + } +} + +type MessageBuilder interface { + Create(to address.Address, initialAmount abi.TokenAmount) (*types.Message, error) + Update(paych address.Address, voucher *paychtypes.SignedVoucher, secret []byte) (*types.Message, error) + Settle(paych address.Address) (*types.Message, error) + Collect(paych address.Address) (*types.Message, error) +} + +func toV0SignedVoucher(sv paychtypes.SignedVoucher) paych0.SignedVoucher { + return paych0.SignedVoucher{ + ChannelAddr: sv.ChannelAddr, + TimeLockMin: sv.TimeLockMin, + TimeLockMax: sv.TimeLockMax, + SecretPreimage: sv.SecretHash, + Extra: (*paych0.ModVerifyParams)(sv.Extra), + Lane: sv.Lane, + Nonce: sv.Nonce, + Amount: sv.Amount, + MinSettleHeight: sv.MinSettleHeight, + Merges: nil, + Signature: sv.Signature, + } +} + +func AllCodes() []cid.Cid { + return []cid.Cid{ + (&state0{}).Code(), + (&state2{}).Code(), + (&state3{}).Code(), + (&state4{}).Code(), + (&state5{}).Code(), + (&state6{}).Code(), + (&state7{}).Code(), + (&state8{}).Code(), + (&state9{}).Code(), + (&state10{}).Code(), + (&state11{}).Code(), + } +} diff --git a/chain/actors/builtin/paych/state.go.template b/chain/actors/builtin/paych/state.go.template new file mode 100644 index 000000000..0b0f9f9a1 --- /dev/null +++ b/chain/actors/builtin/paych/state.go.template @@ -0,0 +1,140 @@ +package paych + +import ( + "fmt" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/ipfs/go-cid" + + "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/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/go-state-types/manifest" + +{{if (le .v 7)}} + paych{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/builtin/paych" + adt{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/util/adt" +{{else}} + paych{{.v}} "github.com/filecoin-project/go-state-types/builtin{{.import}}paych" + adt{{.v}} "github.com/filecoin-project/go-state-types/builtin{{.import}}util/adt" +{{end}} +) + +var _ State = (*state{{.v}})(nil) + +func load{{.v}}(store adt.Store, root cid.Cid) (State, error) { + out := state{{.v}}{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make{{.v}}(store adt.Store) (State, error) { + out := state{{.v}}{store: store} + out.State = paych{{.v}}.State{} + return &out, nil +} + +type state{{.v}} struct { + paych{{.v}}.State + store adt.Store + lsAmt *adt{{.v}}.Array +} + +// Channel owner, who has funded the actor +func (s *state{{.v}}) From() (address.Address, error) { + return s.State.From, nil +} + +// Recipient of payouts from channel +func (s *state{{.v}}) To() (address.Address, error) { + return s.State.To, nil +} + +// Height at which the channel can be `Collected` +func (s *state{{.v}}) SettlingAt() (abi.ChainEpoch, error) { + return s.State.SettlingAt, nil +} + +// Amount successfully redeemed through the payment channel, paid out on `Collect()` +func (s *state{{.v}}) ToSend() (abi.TokenAmount, error) { + return s.State.ToSend, nil +} + +func (s *state{{.v}}) getOrLoadLsAmt() (*adt{{.v}}.Array, error) { + if s.lsAmt != nil { + return s.lsAmt, nil + } + + // Get the lane state from the chain + lsamt, err := adt{{.v}}.AsArray(s.store, s.State.LaneStates{{if (ge .v 3)}}, paych{{.v}}.LaneStatesAmtBitwidth{{end}}) + if err != nil { + return nil, err + } + + s.lsAmt = lsamt + return lsamt, nil +} + +// Get total number of lanes +func (s *state{{.v}}) LaneCount() (uint64, error) { + lsamt, err := s.getOrLoadLsAmt() + if err != nil { + return 0, err + } + return lsamt.Length(), nil +} + +func (s *state{{.v}}) GetState() interface{} { + return &s.State +} + +// Iterate lane states +func (s *state{{.v}}) ForEachLaneState(cb func(idx uint64, dl LaneState) error) error { + // Get the lane state from the chain + lsamt, err := s.getOrLoadLsAmt() + if err != nil { + return err + } + + // Note: we use a map instead of an array to store laneStates because the + // client sets the lane ID (the index) and potentially they could use a + // very large index. + var ls paych{{.v}}.LaneState + return lsamt.ForEach(&ls, func(i int64) error { + return cb(uint64(i), &laneState{{.v}}{ls}) + }) +} + +type laneState{{.v}} struct { + paych{{.v}}.LaneState +} + +func (ls *laneState{{.v}}) Redeemed() (big.Int, error) { + return ls.LaneState.Redeemed, nil +} + +func (ls *laneState{{.v}}) Nonce() (uint64, error) { + return ls.LaneState.Nonce, nil +} + +func (s *state{{.v}}) ActorKey() string { + return manifest.PaychKey +} + +func (s *state{{.v}}) ActorVersion() actorstypes.Version { + return actorstypes.Version{{.v}} +} + +func (s *state{{.v}}) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/paych/v0.go b/chain/actors/builtin/paych/v0.go new file mode 100644 index 000000000..facc7f656 --- /dev/null +++ b/chain/actors/builtin/paych/v0.go @@ -0,0 +1,135 @@ +package paych + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/manifest" + paych0 "github.com/filecoin-project/specs-actors/actors/builtin/paych" + adt0 "github.com/filecoin-project/specs-actors/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state0)(nil) + +func load0(store adt.Store, root cid.Cid) (State, error) { + out := state0{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make0(store adt.Store) (State, error) { + out := state0{store: store} + out.State = paych0.State{} + return &out, nil +} + +type state0 struct { + paych0.State + store adt.Store + lsAmt *adt0.Array +} + +// Channel owner, who has funded the actor +func (s *state0) From() (address.Address, error) { + return s.State.From, nil +} + +// Recipient of payouts from channel +func (s *state0) To() (address.Address, error) { + return s.State.To, nil +} + +// Height at which the channel can be `Collected` +func (s *state0) SettlingAt() (abi.ChainEpoch, error) { + return s.State.SettlingAt, nil +} + +// Amount successfully redeemed through the payment channel, paid out on `Collect()` +func (s *state0) ToSend() (abi.TokenAmount, error) { + return s.State.ToSend, nil +} + +func (s *state0) getOrLoadLsAmt() (*adt0.Array, error) { + if s.lsAmt != nil { + return s.lsAmt, nil + } + + // Get the lane state from the chain + lsamt, err := adt0.AsArray(s.store, s.State.LaneStates) + if err != nil { + return nil, err + } + + s.lsAmt = lsamt + return lsamt, nil +} + +// Get total number of lanes +func (s *state0) LaneCount() (uint64, error) { + lsamt, err := s.getOrLoadLsAmt() + if err != nil { + return 0, err + } + return lsamt.Length(), nil +} + +func (s *state0) GetState() interface{} { + return &s.State +} + +// Iterate lane states +func (s *state0) ForEachLaneState(cb func(idx uint64, dl LaneState) error) error { + // Get the lane state from the chain + lsamt, err := s.getOrLoadLsAmt() + if err != nil { + return err + } + + // Note: we use a map instead of an array to store laneStates because the + // client sets the lane ID (the index) and potentially they could use a + // very large index. + var ls paych0.LaneState + return lsamt.ForEach(&ls, func(i int64) error { + return cb(uint64(i), &laneState0{ls}) + }) +} + +type laneState0 struct { + paych0.LaneState +} + +func (ls *laneState0) Redeemed() (big.Int, error) { + return ls.LaneState.Redeemed, nil +} + +func (ls *laneState0) Nonce() (uint64, error) { + return ls.LaneState.Nonce, nil +} + +func (s *state0) ActorKey() string { + return manifest.PaychKey +} + +func (s *state0) ActorVersion() actorstypes.Version { + return actorstypes.Version0 +} + +func (s *state0) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/paych/v10.go b/chain/actors/builtin/paych/v10.go new file mode 100644 index 000000000..edc6c96b6 --- /dev/null +++ b/chain/actors/builtin/paych/v10.go @@ -0,0 +1,135 @@ +package paych + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + paych10 "github.com/filecoin-project/go-state-types/builtin/v10/paych" + adt10 "github.com/filecoin-project/go-state-types/builtin/v10/util/adt" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state10)(nil) + +func load10(store adt.Store, root cid.Cid) (State, error) { + out := state10{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make10(store adt.Store) (State, error) { + out := state10{store: store} + out.State = paych10.State{} + return &out, nil +} + +type state10 struct { + paych10.State + store adt.Store + lsAmt *adt10.Array +} + +// Channel owner, who has funded the actor +func (s *state10) From() (address.Address, error) { + return s.State.From, nil +} + +// Recipient of payouts from channel +func (s *state10) To() (address.Address, error) { + return s.State.To, nil +} + +// Height at which the channel can be `Collected` +func (s *state10) SettlingAt() (abi.ChainEpoch, error) { + return s.State.SettlingAt, nil +} + +// Amount successfully redeemed through the payment channel, paid out on `Collect()` +func (s *state10) ToSend() (abi.TokenAmount, error) { + return s.State.ToSend, nil +} + +func (s *state10) getOrLoadLsAmt() (*adt10.Array, error) { + if s.lsAmt != nil { + return s.lsAmt, nil + } + + // Get the lane state from the chain + lsamt, err := adt10.AsArray(s.store, s.State.LaneStates, paych10.LaneStatesAmtBitwidth) + if err != nil { + return nil, err + } + + s.lsAmt = lsamt + return lsamt, nil +} + +// Get total number of lanes +func (s *state10) LaneCount() (uint64, error) { + lsamt, err := s.getOrLoadLsAmt() + if err != nil { + return 0, err + } + return lsamt.Length(), nil +} + +func (s *state10) GetState() interface{} { + return &s.State +} + +// Iterate lane states +func (s *state10) ForEachLaneState(cb func(idx uint64, dl LaneState) error) error { + // Get the lane state from the chain + lsamt, err := s.getOrLoadLsAmt() + if err != nil { + return err + } + + // Note: we use a map instead of an array to store laneStates because the + // client sets the lane ID (the index) and potentially they could use a + // very large index. + var ls paych10.LaneState + return lsamt.ForEach(&ls, func(i int64) error { + return cb(uint64(i), &laneState10{ls}) + }) +} + +type laneState10 struct { + paych10.LaneState +} + +func (ls *laneState10) Redeemed() (big.Int, error) { + return ls.LaneState.Redeemed, nil +} + +func (ls *laneState10) Nonce() (uint64, error) { + return ls.LaneState.Nonce, nil +} + +func (s *state10) ActorKey() string { + return manifest.PaychKey +} + +func (s *state10) ActorVersion() actorstypes.Version { + return actorstypes.Version10 +} + +func (s *state10) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/paych/v11.go b/chain/actors/builtin/paych/v11.go new file mode 100644 index 000000000..977a013f1 --- /dev/null +++ b/chain/actors/builtin/paych/v11.go @@ -0,0 +1,135 @@ +package paych + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + paych11 "github.com/filecoin-project/go-state-types/builtin/v11/paych" + adt11 "github.com/filecoin-project/go-state-types/builtin/v11/util/adt" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state11)(nil) + +func load11(store adt.Store, root cid.Cid) (State, error) { + out := state11{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make11(store adt.Store) (State, error) { + out := state11{store: store} + out.State = paych11.State{} + return &out, nil +} + +type state11 struct { + paych11.State + store adt.Store + lsAmt *adt11.Array +} + +// Channel owner, who has funded the actor +func (s *state11) From() (address.Address, error) { + return s.State.From, nil +} + +// Recipient of payouts from channel +func (s *state11) To() (address.Address, error) { + return s.State.To, nil +} + +// Height at which the channel can be `Collected` +func (s *state11) SettlingAt() (abi.ChainEpoch, error) { + return s.State.SettlingAt, nil +} + +// Amount successfully redeemed through the payment channel, paid out on `Collect()` +func (s *state11) ToSend() (abi.TokenAmount, error) { + return s.State.ToSend, nil +} + +func (s *state11) getOrLoadLsAmt() (*adt11.Array, error) { + if s.lsAmt != nil { + return s.lsAmt, nil + } + + // Get the lane state from the chain + lsamt, err := adt11.AsArray(s.store, s.State.LaneStates, paych11.LaneStatesAmtBitwidth) + if err != nil { + return nil, err + } + + s.lsAmt = lsamt + return lsamt, nil +} + +// Get total number of lanes +func (s *state11) LaneCount() (uint64, error) { + lsamt, err := s.getOrLoadLsAmt() + if err != nil { + return 0, err + } + return lsamt.Length(), nil +} + +func (s *state11) GetState() interface{} { + return &s.State +} + +// Iterate lane states +func (s *state11) ForEachLaneState(cb func(idx uint64, dl LaneState) error) error { + // Get the lane state from the chain + lsamt, err := s.getOrLoadLsAmt() + if err != nil { + return err + } + + // Note: we use a map instead of an array to store laneStates because the + // client sets the lane ID (the index) and potentially they could use a + // very large index. + var ls paych11.LaneState + return lsamt.ForEach(&ls, func(i int64) error { + return cb(uint64(i), &laneState11{ls}) + }) +} + +type laneState11 struct { + paych11.LaneState +} + +func (ls *laneState11) Redeemed() (big.Int, error) { + return ls.LaneState.Redeemed, nil +} + +func (ls *laneState11) Nonce() (uint64, error) { + return ls.LaneState.Nonce, nil +} + +func (s *state11) ActorKey() string { + return manifest.PaychKey +} + +func (s *state11) ActorVersion() actorstypes.Version { + return actorstypes.Version11 +} + +func (s *state11) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/paych/v2.go b/chain/actors/builtin/paych/v2.go new file mode 100644 index 000000000..63a3cc75e --- /dev/null +++ b/chain/actors/builtin/paych/v2.go @@ -0,0 +1,135 @@ +package paych + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/manifest" + paych2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/paych" + adt2 "github.com/filecoin-project/specs-actors/v2/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state2)(nil) + +func load2(store adt.Store, root cid.Cid) (State, error) { + out := state2{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make2(store adt.Store) (State, error) { + out := state2{store: store} + out.State = paych2.State{} + return &out, nil +} + +type state2 struct { + paych2.State + store adt.Store + lsAmt *adt2.Array +} + +// Channel owner, who has funded the actor +func (s *state2) From() (address.Address, error) { + return s.State.From, nil +} + +// Recipient of payouts from channel +func (s *state2) To() (address.Address, error) { + return s.State.To, nil +} + +// Height at which the channel can be `Collected` +func (s *state2) SettlingAt() (abi.ChainEpoch, error) { + return s.State.SettlingAt, nil +} + +// Amount successfully redeemed through the payment channel, paid out on `Collect()` +func (s *state2) ToSend() (abi.TokenAmount, error) { + return s.State.ToSend, nil +} + +func (s *state2) getOrLoadLsAmt() (*adt2.Array, error) { + if s.lsAmt != nil { + return s.lsAmt, nil + } + + // Get the lane state from the chain + lsamt, err := adt2.AsArray(s.store, s.State.LaneStates) + if err != nil { + return nil, err + } + + s.lsAmt = lsamt + return lsamt, nil +} + +// Get total number of lanes +func (s *state2) LaneCount() (uint64, error) { + lsamt, err := s.getOrLoadLsAmt() + if err != nil { + return 0, err + } + return lsamt.Length(), nil +} + +func (s *state2) GetState() interface{} { + return &s.State +} + +// Iterate lane states +func (s *state2) ForEachLaneState(cb func(idx uint64, dl LaneState) error) error { + // Get the lane state from the chain + lsamt, err := s.getOrLoadLsAmt() + if err != nil { + return err + } + + // Note: we use a map instead of an array to store laneStates because the + // client sets the lane ID (the index) and potentially they could use a + // very large index. + var ls paych2.LaneState + return lsamt.ForEach(&ls, func(i int64) error { + return cb(uint64(i), &laneState2{ls}) + }) +} + +type laneState2 struct { + paych2.LaneState +} + +func (ls *laneState2) Redeemed() (big.Int, error) { + return ls.LaneState.Redeemed, nil +} + +func (ls *laneState2) Nonce() (uint64, error) { + return ls.LaneState.Nonce, nil +} + +func (s *state2) ActorKey() string { + return manifest.PaychKey +} + +func (s *state2) ActorVersion() actorstypes.Version { + return actorstypes.Version2 +} + +func (s *state2) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/paych/v3.go b/chain/actors/builtin/paych/v3.go new file mode 100644 index 000000000..c672c0027 --- /dev/null +++ b/chain/actors/builtin/paych/v3.go @@ -0,0 +1,135 @@ +package paych + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/manifest" + paych3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/paych" + adt3 "github.com/filecoin-project/specs-actors/v3/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state3)(nil) + +func load3(store adt.Store, root cid.Cid) (State, error) { + out := state3{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make3(store adt.Store) (State, error) { + out := state3{store: store} + out.State = paych3.State{} + return &out, nil +} + +type state3 struct { + paych3.State + store adt.Store + lsAmt *adt3.Array +} + +// Channel owner, who has funded the actor +func (s *state3) From() (address.Address, error) { + return s.State.From, nil +} + +// Recipient of payouts from channel +func (s *state3) To() (address.Address, error) { + return s.State.To, nil +} + +// Height at which the channel can be `Collected` +func (s *state3) SettlingAt() (abi.ChainEpoch, error) { + return s.State.SettlingAt, nil +} + +// Amount successfully redeemed through the payment channel, paid out on `Collect()` +func (s *state3) ToSend() (abi.TokenAmount, error) { + return s.State.ToSend, nil +} + +func (s *state3) getOrLoadLsAmt() (*adt3.Array, error) { + if s.lsAmt != nil { + return s.lsAmt, nil + } + + // Get the lane state from the chain + lsamt, err := adt3.AsArray(s.store, s.State.LaneStates, paych3.LaneStatesAmtBitwidth) + if err != nil { + return nil, err + } + + s.lsAmt = lsamt + return lsamt, nil +} + +// Get total number of lanes +func (s *state3) LaneCount() (uint64, error) { + lsamt, err := s.getOrLoadLsAmt() + if err != nil { + return 0, err + } + return lsamt.Length(), nil +} + +func (s *state3) GetState() interface{} { + return &s.State +} + +// Iterate lane states +func (s *state3) ForEachLaneState(cb func(idx uint64, dl LaneState) error) error { + // Get the lane state from the chain + lsamt, err := s.getOrLoadLsAmt() + if err != nil { + return err + } + + // Note: we use a map instead of an array to store laneStates because the + // client sets the lane ID (the index) and potentially they could use a + // very large index. + var ls paych3.LaneState + return lsamt.ForEach(&ls, func(i int64) error { + return cb(uint64(i), &laneState3{ls}) + }) +} + +type laneState3 struct { + paych3.LaneState +} + +func (ls *laneState3) Redeemed() (big.Int, error) { + return ls.LaneState.Redeemed, nil +} + +func (ls *laneState3) Nonce() (uint64, error) { + return ls.LaneState.Nonce, nil +} + +func (s *state3) ActorKey() string { + return manifest.PaychKey +} + +func (s *state3) ActorVersion() actorstypes.Version { + return actorstypes.Version3 +} + +func (s *state3) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/paych/v4.go b/chain/actors/builtin/paych/v4.go new file mode 100644 index 000000000..842e52093 --- /dev/null +++ b/chain/actors/builtin/paych/v4.go @@ -0,0 +1,135 @@ +package paych + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/manifest" + paych4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/paych" + adt4 "github.com/filecoin-project/specs-actors/v4/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state4)(nil) + +func load4(store adt.Store, root cid.Cid) (State, error) { + out := state4{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make4(store adt.Store) (State, error) { + out := state4{store: store} + out.State = paych4.State{} + return &out, nil +} + +type state4 struct { + paych4.State + store adt.Store + lsAmt *adt4.Array +} + +// Channel owner, who has funded the actor +func (s *state4) From() (address.Address, error) { + return s.State.From, nil +} + +// Recipient of payouts from channel +func (s *state4) To() (address.Address, error) { + return s.State.To, nil +} + +// Height at which the channel can be `Collected` +func (s *state4) SettlingAt() (abi.ChainEpoch, error) { + return s.State.SettlingAt, nil +} + +// Amount successfully redeemed through the payment channel, paid out on `Collect()` +func (s *state4) ToSend() (abi.TokenAmount, error) { + return s.State.ToSend, nil +} + +func (s *state4) getOrLoadLsAmt() (*adt4.Array, error) { + if s.lsAmt != nil { + return s.lsAmt, nil + } + + // Get the lane state from the chain + lsamt, err := adt4.AsArray(s.store, s.State.LaneStates, paych4.LaneStatesAmtBitwidth) + if err != nil { + return nil, err + } + + s.lsAmt = lsamt + return lsamt, nil +} + +// Get total number of lanes +func (s *state4) LaneCount() (uint64, error) { + lsamt, err := s.getOrLoadLsAmt() + if err != nil { + return 0, err + } + return lsamt.Length(), nil +} + +func (s *state4) GetState() interface{} { + return &s.State +} + +// Iterate lane states +func (s *state4) ForEachLaneState(cb func(idx uint64, dl LaneState) error) error { + // Get the lane state from the chain + lsamt, err := s.getOrLoadLsAmt() + if err != nil { + return err + } + + // Note: we use a map instead of an array to store laneStates because the + // client sets the lane ID (the index) and potentially they could use a + // very large index. + var ls paych4.LaneState + return lsamt.ForEach(&ls, func(i int64) error { + return cb(uint64(i), &laneState4{ls}) + }) +} + +type laneState4 struct { + paych4.LaneState +} + +func (ls *laneState4) Redeemed() (big.Int, error) { + return ls.LaneState.Redeemed, nil +} + +func (ls *laneState4) Nonce() (uint64, error) { + return ls.LaneState.Nonce, nil +} + +func (s *state4) ActorKey() string { + return manifest.PaychKey +} + +func (s *state4) ActorVersion() actorstypes.Version { + return actorstypes.Version4 +} + +func (s *state4) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/paych/v5.go b/chain/actors/builtin/paych/v5.go new file mode 100644 index 000000000..8f53fe43f --- /dev/null +++ b/chain/actors/builtin/paych/v5.go @@ -0,0 +1,135 @@ +package paych + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/manifest" + paych5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/paych" + adt5 "github.com/filecoin-project/specs-actors/v5/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state5)(nil) + +func load5(store adt.Store, root cid.Cid) (State, error) { + out := state5{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make5(store adt.Store) (State, error) { + out := state5{store: store} + out.State = paych5.State{} + return &out, nil +} + +type state5 struct { + paych5.State + store adt.Store + lsAmt *adt5.Array +} + +// Channel owner, who has funded the actor +func (s *state5) From() (address.Address, error) { + return s.State.From, nil +} + +// Recipient of payouts from channel +func (s *state5) To() (address.Address, error) { + return s.State.To, nil +} + +// Height at which the channel can be `Collected` +func (s *state5) SettlingAt() (abi.ChainEpoch, error) { + return s.State.SettlingAt, nil +} + +// Amount successfully redeemed through the payment channel, paid out on `Collect()` +func (s *state5) ToSend() (abi.TokenAmount, error) { + return s.State.ToSend, nil +} + +func (s *state5) getOrLoadLsAmt() (*adt5.Array, error) { + if s.lsAmt != nil { + return s.lsAmt, nil + } + + // Get the lane state from the chain + lsamt, err := adt5.AsArray(s.store, s.State.LaneStates, paych5.LaneStatesAmtBitwidth) + if err != nil { + return nil, err + } + + s.lsAmt = lsamt + return lsamt, nil +} + +// Get total number of lanes +func (s *state5) LaneCount() (uint64, error) { + lsamt, err := s.getOrLoadLsAmt() + if err != nil { + return 0, err + } + return lsamt.Length(), nil +} + +func (s *state5) GetState() interface{} { + return &s.State +} + +// Iterate lane states +func (s *state5) ForEachLaneState(cb func(idx uint64, dl LaneState) error) error { + // Get the lane state from the chain + lsamt, err := s.getOrLoadLsAmt() + if err != nil { + return err + } + + // Note: we use a map instead of an array to store laneStates because the + // client sets the lane ID (the index) and potentially they could use a + // very large index. + var ls paych5.LaneState + return lsamt.ForEach(&ls, func(i int64) error { + return cb(uint64(i), &laneState5{ls}) + }) +} + +type laneState5 struct { + paych5.LaneState +} + +func (ls *laneState5) Redeemed() (big.Int, error) { + return ls.LaneState.Redeemed, nil +} + +func (ls *laneState5) Nonce() (uint64, error) { + return ls.LaneState.Nonce, nil +} + +func (s *state5) ActorKey() string { + return manifest.PaychKey +} + +func (s *state5) ActorVersion() actorstypes.Version { + return actorstypes.Version5 +} + +func (s *state5) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/paych/v6.go b/chain/actors/builtin/paych/v6.go new file mode 100644 index 000000000..0a8a93896 --- /dev/null +++ b/chain/actors/builtin/paych/v6.go @@ -0,0 +1,135 @@ +package paych + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/manifest" + paych6 "github.com/filecoin-project/specs-actors/v6/actors/builtin/paych" + adt6 "github.com/filecoin-project/specs-actors/v6/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state6)(nil) + +func load6(store adt.Store, root cid.Cid) (State, error) { + out := state6{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make6(store adt.Store) (State, error) { + out := state6{store: store} + out.State = paych6.State{} + return &out, nil +} + +type state6 struct { + paych6.State + store adt.Store + lsAmt *adt6.Array +} + +// Channel owner, who has funded the actor +func (s *state6) From() (address.Address, error) { + return s.State.From, nil +} + +// Recipient of payouts from channel +func (s *state6) To() (address.Address, error) { + return s.State.To, nil +} + +// Height at which the channel can be `Collected` +func (s *state6) SettlingAt() (abi.ChainEpoch, error) { + return s.State.SettlingAt, nil +} + +// Amount successfully redeemed through the payment channel, paid out on `Collect()` +func (s *state6) ToSend() (abi.TokenAmount, error) { + return s.State.ToSend, nil +} + +func (s *state6) getOrLoadLsAmt() (*adt6.Array, error) { + if s.lsAmt != nil { + return s.lsAmt, nil + } + + // Get the lane state from the chain + lsamt, err := adt6.AsArray(s.store, s.State.LaneStates, paych6.LaneStatesAmtBitwidth) + if err != nil { + return nil, err + } + + s.lsAmt = lsamt + return lsamt, nil +} + +// Get total number of lanes +func (s *state6) LaneCount() (uint64, error) { + lsamt, err := s.getOrLoadLsAmt() + if err != nil { + return 0, err + } + return lsamt.Length(), nil +} + +func (s *state6) GetState() interface{} { + return &s.State +} + +// Iterate lane states +func (s *state6) ForEachLaneState(cb func(idx uint64, dl LaneState) error) error { + // Get the lane state from the chain + lsamt, err := s.getOrLoadLsAmt() + if err != nil { + return err + } + + // Note: we use a map instead of an array to store laneStates because the + // client sets the lane ID (the index) and potentially they could use a + // very large index. + var ls paych6.LaneState + return lsamt.ForEach(&ls, func(i int64) error { + return cb(uint64(i), &laneState6{ls}) + }) +} + +type laneState6 struct { + paych6.LaneState +} + +func (ls *laneState6) Redeemed() (big.Int, error) { + return ls.LaneState.Redeemed, nil +} + +func (ls *laneState6) Nonce() (uint64, error) { + return ls.LaneState.Nonce, nil +} + +func (s *state6) ActorKey() string { + return manifest.PaychKey +} + +func (s *state6) ActorVersion() actorstypes.Version { + return actorstypes.Version6 +} + +func (s *state6) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/paych/v7.go b/chain/actors/builtin/paych/v7.go new file mode 100644 index 000000000..ce0dcba0a --- /dev/null +++ b/chain/actors/builtin/paych/v7.go @@ -0,0 +1,135 @@ +package paych + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/manifest" + paych7 "github.com/filecoin-project/specs-actors/v7/actors/builtin/paych" + adt7 "github.com/filecoin-project/specs-actors/v7/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state7)(nil) + +func load7(store adt.Store, root cid.Cid) (State, error) { + out := state7{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make7(store adt.Store) (State, error) { + out := state7{store: store} + out.State = paych7.State{} + return &out, nil +} + +type state7 struct { + paych7.State + store adt.Store + lsAmt *adt7.Array +} + +// Channel owner, who has funded the actor +func (s *state7) From() (address.Address, error) { + return s.State.From, nil +} + +// Recipient of payouts from channel +func (s *state7) To() (address.Address, error) { + return s.State.To, nil +} + +// Height at which the channel can be `Collected` +func (s *state7) SettlingAt() (abi.ChainEpoch, error) { + return s.State.SettlingAt, nil +} + +// Amount successfully redeemed through the payment channel, paid out on `Collect()` +func (s *state7) ToSend() (abi.TokenAmount, error) { + return s.State.ToSend, nil +} + +func (s *state7) getOrLoadLsAmt() (*adt7.Array, error) { + if s.lsAmt != nil { + return s.lsAmt, nil + } + + // Get the lane state from the chain + lsamt, err := adt7.AsArray(s.store, s.State.LaneStates, paych7.LaneStatesAmtBitwidth) + if err != nil { + return nil, err + } + + s.lsAmt = lsamt + return lsamt, nil +} + +// Get total number of lanes +func (s *state7) LaneCount() (uint64, error) { + lsamt, err := s.getOrLoadLsAmt() + if err != nil { + return 0, err + } + return lsamt.Length(), nil +} + +func (s *state7) GetState() interface{} { + return &s.State +} + +// Iterate lane states +func (s *state7) ForEachLaneState(cb func(idx uint64, dl LaneState) error) error { + // Get the lane state from the chain + lsamt, err := s.getOrLoadLsAmt() + if err != nil { + return err + } + + // Note: we use a map instead of an array to store laneStates because the + // client sets the lane ID (the index) and potentially they could use a + // very large index. + var ls paych7.LaneState + return lsamt.ForEach(&ls, func(i int64) error { + return cb(uint64(i), &laneState7{ls}) + }) +} + +type laneState7 struct { + paych7.LaneState +} + +func (ls *laneState7) Redeemed() (big.Int, error) { + return ls.LaneState.Redeemed, nil +} + +func (ls *laneState7) Nonce() (uint64, error) { + return ls.LaneState.Nonce, nil +} + +func (s *state7) ActorKey() string { + return manifest.PaychKey +} + +func (s *state7) ActorVersion() actorstypes.Version { + return actorstypes.Version7 +} + +func (s *state7) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/paych/v8.go b/chain/actors/builtin/paych/v8.go new file mode 100644 index 000000000..51067e87b --- /dev/null +++ b/chain/actors/builtin/paych/v8.go @@ -0,0 +1,135 @@ +package paych + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + paych8 "github.com/filecoin-project/go-state-types/builtin/v8/paych" + adt8 "github.com/filecoin-project/go-state-types/builtin/v8/util/adt" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state8)(nil) + +func load8(store adt.Store, root cid.Cid) (State, error) { + out := state8{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make8(store adt.Store) (State, error) { + out := state8{store: store} + out.State = paych8.State{} + return &out, nil +} + +type state8 struct { + paych8.State + store adt.Store + lsAmt *adt8.Array +} + +// Channel owner, who has funded the actor +func (s *state8) From() (address.Address, error) { + return s.State.From, nil +} + +// Recipient of payouts from channel +func (s *state8) To() (address.Address, error) { + return s.State.To, nil +} + +// Height at which the channel can be `Collected` +func (s *state8) SettlingAt() (abi.ChainEpoch, error) { + return s.State.SettlingAt, nil +} + +// Amount successfully redeemed through the payment channel, paid out on `Collect()` +func (s *state8) ToSend() (abi.TokenAmount, error) { + return s.State.ToSend, nil +} + +func (s *state8) getOrLoadLsAmt() (*adt8.Array, error) { + if s.lsAmt != nil { + return s.lsAmt, nil + } + + // Get the lane state from the chain + lsamt, err := adt8.AsArray(s.store, s.State.LaneStates, paych8.LaneStatesAmtBitwidth) + if err != nil { + return nil, err + } + + s.lsAmt = lsamt + return lsamt, nil +} + +// Get total number of lanes +func (s *state8) LaneCount() (uint64, error) { + lsamt, err := s.getOrLoadLsAmt() + if err != nil { + return 0, err + } + return lsamt.Length(), nil +} + +func (s *state8) GetState() interface{} { + return &s.State +} + +// Iterate lane states +func (s *state8) ForEachLaneState(cb func(idx uint64, dl LaneState) error) error { + // Get the lane state from the chain + lsamt, err := s.getOrLoadLsAmt() + if err != nil { + return err + } + + // Note: we use a map instead of an array to store laneStates because the + // client sets the lane ID (the index) and potentially they could use a + // very large index. + var ls paych8.LaneState + return lsamt.ForEach(&ls, func(i int64) error { + return cb(uint64(i), &laneState8{ls}) + }) +} + +type laneState8 struct { + paych8.LaneState +} + +func (ls *laneState8) Redeemed() (big.Int, error) { + return ls.LaneState.Redeemed, nil +} + +func (ls *laneState8) Nonce() (uint64, error) { + return ls.LaneState.Nonce, nil +} + +func (s *state8) ActorKey() string { + return manifest.PaychKey +} + +func (s *state8) ActorVersion() actorstypes.Version { + return actorstypes.Version8 +} + +func (s *state8) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/paych/v9.go b/chain/actors/builtin/paych/v9.go new file mode 100644 index 000000000..8a69e16d4 --- /dev/null +++ b/chain/actors/builtin/paych/v9.go @@ -0,0 +1,135 @@ +package paych + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + paych9 "github.com/filecoin-project/go-state-types/builtin/v9/paych" + adt9 "github.com/filecoin-project/go-state-types/builtin/v9/util/adt" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state9)(nil) + +func load9(store adt.Store, root cid.Cid) (State, error) { + out := state9{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make9(store adt.Store) (State, error) { + out := state9{store: store} + out.State = paych9.State{} + return &out, nil +} + +type state9 struct { + paych9.State + store adt.Store + lsAmt *adt9.Array +} + +// Channel owner, who has funded the actor +func (s *state9) From() (address.Address, error) { + return s.State.From, nil +} + +// Recipient of payouts from channel +func (s *state9) To() (address.Address, error) { + return s.State.To, nil +} + +// Height at which the channel can be `Collected` +func (s *state9) SettlingAt() (abi.ChainEpoch, error) { + return s.State.SettlingAt, nil +} + +// Amount successfully redeemed through the payment channel, paid out on `Collect()` +func (s *state9) ToSend() (abi.TokenAmount, error) { + return s.State.ToSend, nil +} + +func (s *state9) getOrLoadLsAmt() (*adt9.Array, error) { + if s.lsAmt != nil { + return s.lsAmt, nil + } + + // Get the lane state from the chain + lsamt, err := adt9.AsArray(s.store, s.State.LaneStates, paych9.LaneStatesAmtBitwidth) + if err != nil { + return nil, err + } + + s.lsAmt = lsamt + return lsamt, nil +} + +// Get total number of lanes +func (s *state9) LaneCount() (uint64, error) { + lsamt, err := s.getOrLoadLsAmt() + if err != nil { + return 0, err + } + return lsamt.Length(), nil +} + +func (s *state9) GetState() interface{} { + return &s.State +} + +// Iterate lane states +func (s *state9) ForEachLaneState(cb func(idx uint64, dl LaneState) error) error { + // Get the lane state from the chain + lsamt, err := s.getOrLoadLsAmt() + if err != nil { + return err + } + + // Note: we use a map instead of an array to store laneStates because the + // client sets the lane ID (the index) and potentially they could use a + // very large index. + var ls paych9.LaneState + return lsamt.ForEach(&ls, func(i int64) error { + return cb(uint64(i), &laneState9{ls}) + }) +} + +type laneState9 struct { + paych9.LaneState +} + +func (ls *laneState9) Redeemed() (big.Int, error) { + return ls.LaneState.Redeemed, nil +} + +func (ls *laneState9) Nonce() (uint64, error) { + return ls.LaneState.Nonce, nil +} + +func (s *state9) ActorKey() string { + return manifest.PaychKey +} + +func (s *state9) ActorVersion() actorstypes.Version { + return actorstypes.Version9 +} + +func (s *state9) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/power/actor.go.template b/chain/actors/builtin/power/actor.go.template new file mode 100644 index 000000000..ed2d3fa77 --- /dev/null +++ b/chain/actors/builtin/power/actor.go.template @@ -0,0 +1,124 @@ +package power + +import ( + "github.com/ipfs/go-cid" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/lotus/chain/actors" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/cbor" + + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/go-state-types/manifest" + +{{range .versions}} + {{if (le . 7)}} + builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin" + {{end}} +{{end}} + builtin{{.latestVersion}} "github.com/filecoin-project/go-state-types/builtin" +) + +var ( + Address = builtin{{.latestVersion}}.StoragePowerActorAddr + Methods = builtin{{.latestVersion}}.MethodsPower +) + +func Load(store adt.Store, act *types.Actor) (State, error) { + if name, av, ok := actors.GetActorMetaByCode(act.Code); ok { + if name != manifest.PowerKey { + return nil, xerrors.Errorf("actor code is not power: %s", name) + } + + switch av { + {{range .versions}} + {{if (ge . 8)}} + case actorstypes.Version{{.}}: + return load{{.}}(store, act.Head) + {{end}} + {{end}} + } + } + + switch act.Code { +{{range .versions}} + {{if (le . 7)}} + case builtin{{.}}.StoragePowerActorCodeID: + return load{{.}}(store, act.Head) + {{end}} +{{end}} + } + + return nil, xerrors.Errorf("unknown actor code %s", act.Code) +} + +func MakeState(store adt.Store, av actorstypes.Version) (State, error) { + switch av { +{{range .versions}} + case actorstypes.Version{{.}}: + return make{{.}}(store) +{{end}} +} + return nil, xerrors.Errorf("unknown actor version %d", av) +} + +type State interface { + cbor.Marshaler + + Code() cid.Cid + ActorKey() string + ActorVersion() actorstypes.Version + + TotalLocked() (abi.TokenAmount, error) + TotalPower() (Claim, error) + TotalCommitted() (Claim, error) + TotalPowerSmoothed() (builtin.FilterEstimate, error) + GetState() interface{} + + // MinerCounts returns the number of miners. Participating is the number + // with power above the minimum miner threshold. + MinerCounts() (participating, total uint64, err error) + MinerPower(address.Address) (Claim, bool, error) + MinerNominalPowerMeetsConsensusMinimum(address.Address) (bool, error) + ListAllMiners() ([]address.Address, error) + ForEachClaim(func(miner address.Address, claim Claim) error) error + ClaimsChanged(State) (bool, error) + + // Testing or genesis setup only + SetTotalQualityAdjPower(abi.StoragePower) error + SetTotalRawBytePower(abi.StoragePower) error + SetThisEpochQualityAdjPower(abi.StoragePower) error + SetThisEpochRawBytePower(abi.StoragePower) error + + // Diff helpers. Used by Diff* functions internally. + claims() (adt.Map, error) + decodeClaim(*cbg.Deferred) (Claim, error) +} + +type Claim struct { + // Sum of raw byte power for a miner's sectors. + RawBytePower abi.StoragePower + + // Sum of quality adjusted power for a miner's sectors. + QualityAdjPower abi.StoragePower +} + +func AddClaims(a Claim, b Claim) Claim { + return Claim{ + RawBytePower: big.Add(a.RawBytePower, b.RawBytePower), + QualityAdjPower: big.Add(a.QualityAdjPower, b.QualityAdjPower), + } +} + +func AllCodes() []cid.Cid { + return []cid.Cid{ {{range .versions}} + (&state{{.}}{}).Code(), + {{- end}} + } +} diff --git a/chain/actors/builtin/power/diff.go b/chain/actors/builtin/power/diff.go new file mode 100644 index 000000000..bb2c354ac --- /dev/null +++ b/chain/actors/builtin/power/diff.go @@ -0,0 +1,118 @@ +package power + +import ( + cbg "github.com/whyrusleeping/cbor-gen" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +type ClaimChanges struct { + Added []ClaimInfo + Modified []ClaimModification + Removed []ClaimInfo +} + +type ClaimModification struct { + Miner address.Address + From Claim + To Claim +} + +type ClaimInfo struct { + Miner address.Address + Claim Claim +} + +func DiffClaims(pre, cur State) (*ClaimChanges, error) { + results := new(ClaimChanges) + + prec, err := pre.claims() + if err != nil { + return nil, err + } + + curc, err := cur.claims() + if err != nil { + return nil, err + } + + if err := adt.DiffAdtMap(prec, curc, &claimDiffer{results, pre, cur}); err != nil { + return nil, err + } + + return results, nil +} + +type claimDiffer struct { + Results *ClaimChanges + pre, after State +} + +func (c *claimDiffer) AsKey(key string) (abi.Keyer, error) { + addr, err := address.NewFromBytes([]byte(key)) + if err != nil { + return nil, err + } + return abi.AddrKey(addr), nil +} + +func (c *claimDiffer) Add(key string, val *cbg.Deferred) error { + ci, err := c.after.decodeClaim(val) + if err != nil { + return err + } + addr, err := address.NewFromBytes([]byte(key)) + if err != nil { + return err + } + c.Results.Added = append(c.Results.Added, ClaimInfo{ + Miner: addr, + Claim: ci, + }) + return nil +} + +func (c *claimDiffer) Modify(key string, from, to *cbg.Deferred) error { + ciFrom, err := c.pre.decodeClaim(from) + if err != nil { + return err + } + + ciTo, err := c.after.decodeClaim(to) + if err != nil { + return err + } + + addr, err := address.NewFromBytes([]byte(key)) + if err != nil { + return err + } + + if ciFrom != ciTo { + c.Results.Modified = append(c.Results.Modified, ClaimModification{ + Miner: addr, + From: ciFrom, + To: ciTo, + }) + } + return nil +} + +func (c *claimDiffer) Remove(key string, val *cbg.Deferred) error { + ci, err := c.after.decodeClaim(val) + if err != nil { + return err + } + addr, err := address.NewFromBytes([]byte(key)) + if err != nil { + return err + } + c.Results.Removed = append(c.Results.Removed, ClaimInfo{ + Miner: addr, + Claim: ci, + }) + return nil +} diff --git a/chain/actors/builtin/power/power.go b/chain/actors/builtin/power/power.go new file mode 100644 index 000000000..f3bcef5bb --- /dev/null +++ b/chain/actors/builtin/power/power.go @@ -0,0 +1,187 @@ +package power + +import ( + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + builtin11 "github.com/filecoin-project/go-state-types/builtin" + "github.com/filecoin-project/go-state-types/cbor" + "github.com/filecoin-project/go-state-types/manifest" + builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + builtin3 "github.com/filecoin-project/specs-actors/v3/actors/builtin" + builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" + builtin6 "github.com/filecoin-project/specs-actors/v6/actors/builtin" + builtin7 "github.com/filecoin-project/specs-actors/v7/actors/builtin" + + "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/types" +) + +var ( + Address = builtin11.StoragePowerActorAddr + Methods = builtin11.MethodsPower +) + +func Load(store adt.Store, act *types.Actor) (State, error) { + if name, av, ok := actors.GetActorMetaByCode(act.Code); ok { + if name != manifest.PowerKey { + return nil, xerrors.Errorf("actor code is not power: %s", name) + } + + switch av { + + case actorstypes.Version8: + return load8(store, act.Head) + + case actorstypes.Version9: + return load9(store, act.Head) + + case actorstypes.Version10: + return load10(store, act.Head) + + case actorstypes.Version11: + return load11(store, act.Head) + + } + } + + switch act.Code { + + case builtin0.StoragePowerActorCodeID: + return load0(store, act.Head) + + case builtin2.StoragePowerActorCodeID: + return load2(store, act.Head) + + case builtin3.StoragePowerActorCodeID: + return load3(store, act.Head) + + case builtin4.StoragePowerActorCodeID: + return load4(store, act.Head) + + case builtin5.StoragePowerActorCodeID: + return load5(store, act.Head) + + case builtin6.StoragePowerActorCodeID: + return load6(store, act.Head) + + case builtin7.StoragePowerActorCodeID: + return load7(store, act.Head) + + } + + return nil, xerrors.Errorf("unknown actor code %s", act.Code) +} + +func MakeState(store adt.Store, av actorstypes.Version) (State, error) { + switch av { + + case actorstypes.Version0: + return make0(store) + + case actorstypes.Version2: + return make2(store) + + case actorstypes.Version3: + return make3(store) + + case actorstypes.Version4: + return make4(store) + + case actorstypes.Version5: + return make5(store) + + case actorstypes.Version6: + return make6(store) + + case actorstypes.Version7: + return make7(store) + + case actorstypes.Version8: + return make8(store) + + case actorstypes.Version9: + return make9(store) + + case actorstypes.Version10: + return make10(store) + + case actorstypes.Version11: + return make11(store) + + } + return nil, xerrors.Errorf("unknown actor version %d", av) +} + +type State interface { + cbor.Marshaler + + Code() cid.Cid + ActorKey() string + ActorVersion() actorstypes.Version + + TotalLocked() (abi.TokenAmount, error) + TotalPower() (Claim, error) + TotalCommitted() (Claim, error) + TotalPowerSmoothed() (builtin.FilterEstimate, error) + GetState() interface{} + + // MinerCounts returns the number of miners. Participating is the number + // with power above the minimum miner threshold. + MinerCounts() (participating, total uint64, err error) + MinerPower(address.Address) (Claim, bool, error) + MinerNominalPowerMeetsConsensusMinimum(address.Address) (bool, error) + ListAllMiners() ([]address.Address, error) + ForEachClaim(func(miner address.Address, claim Claim) error) error + ClaimsChanged(State) (bool, error) + + // Testing or genesis setup only + SetTotalQualityAdjPower(abi.StoragePower) error + SetTotalRawBytePower(abi.StoragePower) error + SetThisEpochQualityAdjPower(abi.StoragePower) error + SetThisEpochRawBytePower(abi.StoragePower) error + + // Diff helpers. Used by Diff* functions internally. + claims() (adt.Map, error) + decodeClaim(*cbg.Deferred) (Claim, error) +} + +type Claim struct { + // Sum of raw byte power for a miner's sectors. + RawBytePower abi.StoragePower + + // Sum of quality adjusted power for a miner's sectors. + QualityAdjPower abi.StoragePower +} + +func AddClaims(a Claim, b Claim) Claim { + return Claim{ + RawBytePower: big.Add(a.RawBytePower, b.RawBytePower), + QualityAdjPower: big.Add(a.QualityAdjPower, b.QualityAdjPower), + } +} + +func AllCodes() []cid.Cid { + return []cid.Cid{ + (&state0{}).Code(), + (&state2{}).Code(), + (&state3{}).Code(), + (&state4{}).Code(), + (&state5{}).Code(), + (&state6{}).Code(), + (&state7{}).Code(), + (&state8{}).Code(), + (&state9{}).Code(), + (&state10{}).Code(), + (&state11{}).Code(), + } +} diff --git a/chain/actors/builtin/power/state.go.template b/chain/actors/builtin/power/state.go.template new file mode 100644 index 000000000..d37a8b98a --- /dev/null +++ b/chain/actors/builtin/power/state.go.template @@ -0,0 +1,228 @@ +package power + +import ( + "fmt" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "bytes" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + + "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/go-state-types/manifest" + +{{if (le .v 7)}} + {{if (ge .v 3)}} + builtin{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/builtin" + {{end}} + power{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/builtin/power" + adt{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/util/adt" +{{else}} + builtin{{.v}} "github.com/filecoin-project/go-state-types/builtin" + power{{.v}} "github.com/filecoin-project/go-state-types/builtin{{.import}}power" + adt{{.v}} "github.com/filecoin-project/go-state-types/builtin{{.import}}util/adt" +{{end}} +) + +var _ State = (*state{{.v}})(nil) + +func load{{.v}}(store adt.Store, root cid.Cid) (State, error) { + out := state{{.v}}{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make{{.v}}(store adt.Store) (State, error) { + out := state{{.v}}{store: store} + {{if (le .v 2)}} + em, err := adt{{.v}}.MakeEmptyMap(store).Root() + if err != nil { + return nil, err + } + + emm, err := adt{{.v}}.MakeEmptyMultimap(store).Root() + if err != nil { + return nil, err + } + + out.State = *power{{.v}}.ConstructState(em, emm) + {{else}} + s, err := power{{.v}}.ConstructState(store) + if err != nil { + return nil, err + } + + out.State = *s + {{end}} + + return &out, nil +} + +type state{{.v}} struct { + power{{.v}}.State + store adt.Store +} + +func (s *state{{.v}}) TotalLocked() (abi.TokenAmount, error) { + return s.TotalPledgeCollateral, nil +} + +func (s *state{{.v}}) TotalPower() (Claim, error) { + return Claim{ + RawBytePower: s.TotalRawBytePower, + QualityAdjPower: s.TotalQualityAdjPower, + }, nil +} + +// Committed power to the network. Includes miners below the minimum threshold. +func (s *state{{.v}}) TotalCommitted() (Claim, error) { + return Claim{ + RawBytePower: s.TotalBytesCommitted, + QualityAdjPower: s.TotalQABytesCommitted, + }, nil +} + +func (s *state{{.v}}) MinerPower(addr address.Address) (Claim, bool, error) { + claims, err := s.claims() + if err != nil { + return Claim{}, false, err + } + var claim power{{.v}}.Claim + ok, err := claims.Get(abi.AddrKey(addr), &claim) + if err != nil { + return Claim{}, false, err + } + return Claim{ + RawBytePower: claim.RawBytePower, + QualityAdjPower: claim.QualityAdjPower, + }, ok, nil +} + +func (s *state{{.v}}) MinerNominalPowerMeetsConsensusMinimum(a address.Address) (bool, error) { + return s.State.MinerNominalPowerMeetsConsensusMinimum(s.store, a) +} + +func (s *state{{.v}}) TotalPowerSmoothed() (builtin.FilterEstimate, error) { + return builtin.FilterEstimate({{if (le .v 1)}}*{{end}}s.State.ThisEpochQAPowerSmoothed), nil +} + +func (s *state{{.v}}) MinerCounts() (uint64, uint64, error) { + return uint64(s.State.MinerAboveMinPowerCount), uint64(s.State.MinerCount), nil +} + +func (s *state{{.v}}) ListAllMiners() ([]address.Address, error) { + claims, err := s.claims() + if err != nil { + return nil, err + } + + var miners []address.Address + err = claims.ForEach(nil, func(k string) error { + a, err := address.NewFromBytes([]byte(k)) + if err != nil { + return err + } + miners = append(miners, a) + return nil + }) + if err != nil { + return nil, err + } + + return miners, nil +} + +func (s *state{{.v}}) ForEachClaim(cb func(miner address.Address, claim Claim) error) error { + claims, err := s.claims() + if err != nil { + return err + } + + var claim power{{.v}}.Claim + return claims.ForEach(&claim, func(k string) error { + a, err := address.NewFromBytes([]byte(k)) + if err != nil { + return err + } + return cb(a, Claim{ + RawBytePower: claim.RawBytePower, + QualityAdjPower: claim.QualityAdjPower, + }) + }) +} + +func (s *state{{.v}}) ClaimsChanged(other State) (bool, error) { + other{{.v}}, ok := other.(*state{{.v}}) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.Claims.Equals(other{{.v}}.State.Claims), nil +} + +func (s *state{{.v}}) SetTotalQualityAdjPower(p abi.StoragePower) error { + s.State.TotalQualityAdjPower = p + return nil +} + +func (s *state{{.v}}) SetTotalRawBytePower(p abi.StoragePower) error { + s.State.TotalRawBytePower = p + return nil +} + +func (s *state{{.v}}) SetThisEpochQualityAdjPower(p abi.StoragePower) error { + s.State.ThisEpochQualityAdjPower = p + return nil +} + +func (s *state{{.v}}) SetThisEpochRawBytePower(p abi.StoragePower) error { + s.State.ThisEpochRawBytePower = p + return nil +} + +func (s *state{{.v}}) GetState() interface{} { + return &s.State +} + +func (s *state{{.v}}) claims() (adt.Map, error) { + return adt{{.v}}.AsMap(s.store, s.Claims{{if (ge .v 3)}}, builtin{{.v}}.DefaultHamtBitwidth{{end}}) +} + +func (s *state{{.v}}) decodeClaim(val *cbg.Deferred) (Claim, error) { + var ci power{{.v}}.Claim + if err := ci.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return Claim{}, err + } + return fromV{{.v}}Claim(ci), nil +} + +func fromV{{.v}}Claim(v{{.v}} power{{.v}}.Claim) Claim { + return Claim{ + RawBytePower: v{{.v}}.RawBytePower, + QualityAdjPower: v{{.v}}.QualityAdjPower, + } +} + +func (s *state{{.v}}) ActorKey() string { + return manifest.PowerKey +} + +func (s *state{{.v}}) ActorVersion() actorstypes.Version { + return actorstypes.Version{{.v}} +} + +func (s *state{{.v}}) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/power/v0.go b/chain/actors/builtin/power/v0.go new file mode 100644 index 000000000..4cf550616 --- /dev/null +++ b/chain/actors/builtin/power/v0.go @@ -0,0 +1,211 @@ +package power + +import ( + "bytes" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + power0 "github.com/filecoin-project/specs-actors/actors/builtin/power" + adt0 "github.com/filecoin-project/specs-actors/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" +) + +var _ State = (*state0)(nil) + +func load0(store adt.Store, root cid.Cid) (State, error) { + out := state0{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make0(store adt.Store) (State, error) { + out := state0{store: store} + + em, err := adt0.MakeEmptyMap(store).Root() + if err != nil { + return nil, err + } + + emm, err := adt0.MakeEmptyMultimap(store).Root() + if err != nil { + return nil, err + } + + out.State = *power0.ConstructState(em, emm) + + return &out, nil +} + +type state0 struct { + power0.State + store adt.Store +} + +func (s *state0) TotalLocked() (abi.TokenAmount, error) { + return s.TotalPledgeCollateral, nil +} + +func (s *state0) TotalPower() (Claim, error) { + return Claim{ + RawBytePower: s.TotalRawBytePower, + QualityAdjPower: s.TotalQualityAdjPower, + }, nil +} + +// Committed power to the network. Includes miners below the minimum threshold. +func (s *state0) TotalCommitted() (Claim, error) { + return Claim{ + RawBytePower: s.TotalBytesCommitted, + QualityAdjPower: s.TotalQABytesCommitted, + }, nil +} + +func (s *state0) MinerPower(addr address.Address) (Claim, bool, error) { + claims, err := s.claims() + if err != nil { + return Claim{}, false, err + } + var claim power0.Claim + ok, err := claims.Get(abi.AddrKey(addr), &claim) + if err != nil { + return Claim{}, false, err + } + return Claim{ + RawBytePower: claim.RawBytePower, + QualityAdjPower: claim.QualityAdjPower, + }, ok, nil +} + +func (s *state0) MinerNominalPowerMeetsConsensusMinimum(a address.Address) (bool, error) { + return s.State.MinerNominalPowerMeetsConsensusMinimum(s.store, a) +} + +func (s *state0) TotalPowerSmoothed() (builtin.FilterEstimate, error) { + return builtin.FilterEstimate(*s.State.ThisEpochQAPowerSmoothed), nil +} + +func (s *state0) MinerCounts() (uint64, uint64, error) { + return uint64(s.State.MinerAboveMinPowerCount), uint64(s.State.MinerCount), nil +} + +func (s *state0) ListAllMiners() ([]address.Address, error) { + claims, err := s.claims() + if err != nil { + return nil, err + } + + var miners []address.Address + err = claims.ForEach(nil, func(k string) error { + a, err := address.NewFromBytes([]byte(k)) + if err != nil { + return err + } + miners = append(miners, a) + return nil + }) + if err != nil { + return nil, err + } + + return miners, nil +} + +func (s *state0) ForEachClaim(cb func(miner address.Address, claim Claim) error) error { + claims, err := s.claims() + if err != nil { + return err + } + + var claim power0.Claim + return claims.ForEach(&claim, func(k string) error { + a, err := address.NewFromBytes([]byte(k)) + if err != nil { + return err + } + return cb(a, Claim{ + RawBytePower: claim.RawBytePower, + QualityAdjPower: claim.QualityAdjPower, + }) + }) +} + +func (s *state0) ClaimsChanged(other State) (bool, error) { + other0, ok := other.(*state0) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.Claims.Equals(other0.State.Claims), nil +} + +func (s *state0) SetTotalQualityAdjPower(p abi.StoragePower) error { + s.State.TotalQualityAdjPower = p + return nil +} + +func (s *state0) SetTotalRawBytePower(p abi.StoragePower) error { + s.State.TotalRawBytePower = p + return nil +} + +func (s *state0) SetThisEpochQualityAdjPower(p abi.StoragePower) error { + s.State.ThisEpochQualityAdjPower = p + return nil +} + +func (s *state0) SetThisEpochRawBytePower(p abi.StoragePower) error { + s.State.ThisEpochRawBytePower = p + return nil +} + +func (s *state0) GetState() interface{} { + return &s.State +} + +func (s *state0) claims() (adt.Map, error) { + return adt0.AsMap(s.store, s.Claims) +} + +func (s *state0) decodeClaim(val *cbg.Deferred) (Claim, error) { + var ci power0.Claim + if err := ci.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return Claim{}, err + } + return fromV0Claim(ci), nil +} + +func fromV0Claim(v0 power0.Claim) Claim { + return Claim{ + RawBytePower: v0.RawBytePower, + QualityAdjPower: v0.QualityAdjPower, + } +} + +func (s *state0) ActorKey() string { + return manifest.PowerKey +} + +func (s *state0) ActorVersion() actorstypes.Version { + return actorstypes.Version0 +} + +func (s *state0) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/power/v10.go b/chain/actors/builtin/power/v10.go new file mode 100644 index 000000000..dd7a9decf --- /dev/null +++ b/chain/actors/builtin/power/v10.go @@ -0,0 +1,207 @@ +package power + +import ( + "bytes" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtin10 "github.com/filecoin-project/go-state-types/builtin" + power10 "github.com/filecoin-project/go-state-types/builtin/v10/power" + adt10 "github.com/filecoin-project/go-state-types/builtin/v10/util/adt" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" +) + +var _ State = (*state10)(nil) + +func load10(store adt.Store, root cid.Cid) (State, error) { + out := state10{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make10(store adt.Store) (State, error) { + out := state10{store: store} + + s, err := power10.ConstructState(store) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state10 struct { + power10.State + store adt.Store +} + +func (s *state10) TotalLocked() (abi.TokenAmount, error) { + return s.TotalPledgeCollateral, nil +} + +func (s *state10) TotalPower() (Claim, error) { + return Claim{ + RawBytePower: s.TotalRawBytePower, + QualityAdjPower: s.TotalQualityAdjPower, + }, nil +} + +// Committed power to the network. Includes miners below the minimum threshold. +func (s *state10) TotalCommitted() (Claim, error) { + return Claim{ + RawBytePower: s.TotalBytesCommitted, + QualityAdjPower: s.TotalQABytesCommitted, + }, nil +} + +func (s *state10) MinerPower(addr address.Address) (Claim, bool, error) { + claims, err := s.claims() + if err != nil { + return Claim{}, false, err + } + var claim power10.Claim + ok, err := claims.Get(abi.AddrKey(addr), &claim) + if err != nil { + return Claim{}, false, err + } + return Claim{ + RawBytePower: claim.RawBytePower, + QualityAdjPower: claim.QualityAdjPower, + }, ok, nil +} + +func (s *state10) MinerNominalPowerMeetsConsensusMinimum(a address.Address) (bool, error) { + return s.State.MinerNominalPowerMeetsConsensusMinimum(s.store, a) +} + +func (s *state10) TotalPowerSmoothed() (builtin.FilterEstimate, error) { + return builtin.FilterEstimate(s.State.ThisEpochQAPowerSmoothed), nil +} + +func (s *state10) MinerCounts() (uint64, uint64, error) { + return uint64(s.State.MinerAboveMinPowerCount), uint64(s.State.MinerCount), nil +} + +func (s *state10) ListAllMiners() ([]address.Address, error) { + claims, err := s.claims() + if err != nil { + return nil, err + } + + var miners []address.Address + err = claims.ForEach(nil, func(k string) error { + a, err := address.NewFromBytes([]byte(k)) + if err != nil { + return err + } + miners = append(miners, a) + return nil + }) + if err != nil { + return nil, err + } + + return miners, nil +} + +func (s *state10) ForEachClaim(cb func(miner address.Address, claim Claim) error) error { + claims, err := s.claims() + if err != nil { + return err + } + + var claim power10.Claim + return claims.ForEach(&claim, func(k string) error { + a, err := address.NewFromBytes([]byte(k)) + if err != nil { + return err + } + return cb(a, Claim{ + RawBytePower: claim.RawBytePower, + QualityAdjPower: claim.QualityAdjPower, + }) + }) +} + +func (s *state10) ClaimsChanged(other State) (bool, error) { + other10, ok := other.(*state10) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.Claims.Equals(other10.State.Claims), nil +} + +func (s *state10) SetTotalQualityAdjPower(p abi.StoragePower) error { + s.State.TotalQualityAdjPower = p + return nil +} + +func (s *state10) SetTotalRawBytePower(p abi.StoragePower) error { + s.State.TotalRawBytePower = p + return nil +} + +func (s *state10) SetThisEpochQualityAdjPower(p abi.StoragePower) error { + s.State.ThisEpochQualityAdjPower = p + return nil +} + +func (s *state10) SetThisEpochRawBytePower(p abi.StoragePower) error { + s.State.ThisEpochRawBytePower = p + return nil +} + +func (s *state10) GetState() interface{} { + return &s.State +} + +func (s *state10) claims() (adt.Map, error) { + return adt10.AsMap(s.store, s.Claims, builtin10.DefaultHamtBitwidth) +} + +func (s *state10) decodeClaim(val *cbg.Deferred) (Claim, error) { + var ci power10.Claim + if err := ci.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return Claim{}, err + } + return fromV10Claim(ci), nil +} + +func fromV10Claim(v10 power10.Claim) Claim { + return Claim{ + RawBytePower: v10.RawBytePower, + QualityAdjPower: v10.QualityAdjPower, + } +} + +func (s *state10) ActorKey() string { + return manifest.PowerKey +} + +func (s *state10) ActorVersion() actorstypes.Version { + return actorstypes.Version10 +} + +func (s *state10) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/power/v11.go b/chain/actors/builtin/power/v11.go new file mode 100644 index 000000000..0ec1e2bdc --- /dev/null +++ b/chain/actors/builtin/power/v11.go @@ -0,0 +1,207 @@ +package power + +import ( + "bytes" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtin11 "github.com/filecoin-project/go-state-types/builtin" + power11 "github.com/filecoin-project/go-state-types/builtin/v11/power" + adt11 "github.com/filecoin-project/go-state-types/builtin/v11/util/adt" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" +) + +var _ State = (*state11)(nil) + +func load11(store adt.Store, root cid.Cid) (State, error) { + out := state11{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make11(store adt.Store) (State, error) { + out := state11{store: store} + + s, err := power11.ConstructState(store) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state11 struct { + power11.State + store adt.Store +} + +func (s *state11) TotalLocked() (abi.TokenAmount, error) { + return s.TotalPledgeCollateral, nil +} + +func (s *state11) TotalPower() (Claim, error) { + return Claim{ + RawBytePower: s.TotalRawBytePower, + QualityAdjPower: s.TotalQualityAdjPower, + }, nil +} + +// Committed power to the network. Includes miners below the minimum threshold. +func (s *state11) TotalCommitted() (Claim, error) { + return Claim{ + RawBytePower: s.TotalBytesCommitted, + QualityAdjPower: s.TotalQABytesCommitted, + }, nil +} + +func (s *state11) MinerPower(addr address.Address) (Claim, bool, error) { + claims, err := s.claims() + if err != nil { + return Claim{}, false, err + } + var claim power11.Claim + ok, err := claims.Get(abi.AddrKey(addr), &claim) + if err != nil { + return Claim{}, false, err + } + return Claim{ + RawBytePower: claim.RawBytePower, + QualityAdjPower: claim.QualityAdjPower, + }, ok, nil +} + +func (s *state11) MinerNominalPowerMeetsConsensusMinimum(a address.Address) (bool, error) { + return s.State.MinerNominalPowerMeetsConsensusMinimum(s.store, a) +} + +func (s *state11) TotalPowerSmoothed() (builtin.FilterEstimate, error) { + return builtin.FilterEstimate(s.State.ThisEpochQAPowerSmoothed), nil +} + +func (s *state11) MinerCounts() (uint64, uint64, error) { + return uint64(s.State.MinerAboveMinPowerCount), uint64(s.State.MinerCount), nil +} + +func (s *state11) ListAllMiners() ([]address.Address, error) { + claims, err := s.claims() + if err != nil { + return nil, err + } + + var miners []address.Address + err = claims.ForEach(nil, func(k string) error { + a, err := address.NewFromBytes([]byte(k)) + if err != nil { + return err + } + miners = append(miners, a) + return nil + }) + if err != nil { + return nil, err + } + + return miners, nil +} + +func (s *state11) ForEachClaim(cb func(miner address.Address, claim Claim) error) error { + claims, err := s.claims() + if err != nil { + return err + } + + var claim power11.Claim + return claims.ForEach(&claim, func(k string) error { + a, err := address.NewFromBytes([]byte(k)) + if err != nil { + return err + } + return cb(a, Claim{ + RawBytePower: claim.RawBytePower, + QualityAdjPower: claim.QualityAdjPower, + }) + }) +} + +func (s *state11) ClaimsChanged(other State) (bool, error) { + other11, ok := other.(*state11) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.Claims.Equals(other11.State.Claims), nil +} + +func (s *state11) SetTotalQualityAdjPower(p abi.StoragePower) error { + s.State.TotalQualityAdjPower = p + return nil +} + +func (s *state11) SetTotalRawBytePower(p abi.StoragePower) error { + s.State.TotalRawBytePower = p + return nil +} + +func (s *state11) SetThisEpochQualityAdjPower(p abi.StoragePower) error { + s.State.ThisEpochQualityAdjPower = p + return nil +} + +func (s *state11) SetThisEpochRawBytePower(p abi.StoragePower) error { + s.State.ThisEpochRawBytePower = p + return nil +} + +func (s *state11) GetState() interface{} { + return &s.State +} + +func (s *state11) claims() (adt.Map, error) { + return adt11.AsMap(s.store, s.Claims, builtin11.DefaultHamtBitwidth) +} + +func (s *state11) decodeClaim(val *cbg.Deferred) (Claim, error) { + var ci power11.Claim + if err := ci.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return Claim{}, err + } + return fromV11Claim(ci), nil +} + +func fromV11Claim(v11 power11.Claim) Claim { + return Claim{ + RawBytePower: v11.RawBytePower, + QualityAdjPower: v11.QualityAdjPower, + } +} + +func (s *state11) ActorKey() string { + return manifest.PowerKey +} + +func (s *state11) ActorVersion() actorstypes.Version { + return actorstypes.Version11 +} + +func (s *state11) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/power/v2.go b/chain/actors/builtin/power/v2.go new file mode 100644 index 000000000..bac0fa179 --- /dev/null +++ b/chain/actors/builtin/power/v2.go @@ -0,0 +1,211 @@ +package power + +import ( + "bytes" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + power2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/power" + adt2 "github.com/filecoin-project/specs-actors/v2/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" +) + +var _ State = (*state2)(nil) + +func load2(store adt.Store, root cid.Cid) (State, error) { + out := state2{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make2(store adt.Store) (State, error) { + out := state2{store: store} + + em, err := adt2.MakeEmptyMap(store).Root() + if err != nil { + return nil, err + } + + emm, err := adt2.MakeEmptyMultimap(store).Root() + if err != nil { + return nil, err + } + + out.State = *power2.ConstructState(em, emm) + + return &out, nil +} + +type state2 struct { + power2.State + store adt.Store +} + +func (s *state2) TotalLocked() (abi.TokenAmount, error) { + return s.TotalPledgeCollateral, nil +} + +func (s *state2) TotalPower() (Claim, error) { + return Claim{ + RawBytePower: s.TotalRawBytePower, + QualityAdjPower: s.TotalQualityAdjPower, + }, nil +} + +// Committed power to the network. Includes miners below the minimum threshold. +func (s *state2) TotalCommitted() (Claim, error) { + return Claim{ + RawBytePower: s.TotalBytesCommitted, + QualityAdjPower: s.TotalQABytesCommitted, + }, nil +} + +func (s *state2) MinerPower(addr address.Address) (Claim, bool, error) { + claims, err := s.claims() + if err != nil { + return Claim{}, false, err + } + var claim power2.Claim + ok, err := claims.Get(abi.AddrKey(addr), &claim) + if err != nil { + return Claim{}, false, err + } + return Claim{ + RawBytePower: claim.RawBytePower, + QualityAdjPower: claim.QualityAdjPower, + }, ok, nil +} + +func (s *state2) MinerNominalPowerMeetsConsensusMinimum(a address.Address) (bool, error) { + return s.State.MinerNominalPowerMeetsConsensusMinimum(s.store, a) +} + +func (s *state2) TotalPowerSmoothed() (builtin.FilterEstimate, error) { + return builtin.FilterEstimate(s.State.ThisEpochQAPowerSmoothed), nil +} + +func (s *state2) MinerCounts() (uint64, uint64, error) { + return uint64(s.State.MinerAboveMinPowerCount), uint64(s.State.MinerCount), nil +} + +func (s *state2) ListAllMiners() ([]address.Address, error) { + claims, err := s.claims() + if err != nil { + return nil, err + } + + var miners []address.Address + err = claims.ForEach(nil, func(k string) error { + a, err := address.NewFromBytes([]byte(k)) + if err != nil { + return err + } + miners = append(miners, a) + return nil + }) + if err != nil { + return nil, err + } + + return miners, nil +} + +func (s *state2) ForEachClaim(cb func(miner address.Address, claim Claim) error) error { + claims, err := s.claims() + if err != nil { + return err + } + + var claim power2.Claim + return claims.ForEach(&claim, func(k string) error { + a, err := address.NewFromBytes([]byte(k)) + if err != nil { + return err + } + return cb(a, Claim{ + RawBytePower: claim.RawBytePower, + QualityAdjPower: claim.QualityAdjPower, + }) + }) +} + +func (s *state2) ClaimsChanged(other State) (bool, error) { + other2, ok := other.(*state2) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.Claims.Equals(other2.State.Claims), nil +} + +func (s *state2) SetTotalQualityAdjPower(p abi.StoragePower) error { + s.State.TotalQualityAdjPower = p + return nil +} + +func (s *state2) SetTotalRawBytePower(p abi.StoragePower) error { + s.State.TotalRawBytePower = p + return nil +} + +func (s *state2) SetThisEpochQualityAdjPower(p abi.StoragePower) error { + s.State.ThisEpochQualityAdjPower = p + return nil +} + +func (s *state2) SetThisEpochRawBytePower(p abi.StoragePower) error { + s.State.ThisEpochRawBytePower = p + return nil +} + +func (s *state2) GetState() interface{} { + return &s.State +} + +func (s *state2) claims() (adt.Map, error) { + return adt2.AsMap(s.store, s.Claims) +} + +func (s *state2) decodeClaim(val *cbg.Deferred) (Claim, error) { + var ci power2.Claim + if err := ci.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return Claim{}, err + } + return fromV2Claim(ci), nil +} + +func fromV2Claim(v2 power2.Claim) Claim { + return Claim{ + RawBytePower: v2.RawBytePower, + QualityAdjPower: v2.QualityAdjPower, + } +} + +func (s *state2) ActorKey() string { + return manifest.PowerKey +} + +func (s *state2) ActorVersion() actorstypes.Version { + return actorstypes.Version2 +} + +func (s *state2) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/power/v3.go b/chain/actors/builtin/power/v3.go new file mode 100644 index 000000000..bdb66e384 --- /dev/null +++ b/chain/actors/builtin/power/v3.go @@ -0,0 +1,207 @@ +package power + +import ( + "bytes" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + builtin3 "github.com/filecoin-project/specs-actors/v3/actors/builtin" + power3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/power" + adt3 "github.com/filecoin-project/specs-actors/v3/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" +) + +var _ State = (*state3)(nil) + +func load3(store adt.Store, root cid.Cid) (State, error) { + out := state3{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make3(store adt.Store) (State, error) { + out := state3{store: store} + + s, err := power3.ConstructState(store) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state3 struct { + power3.State + store adt.Store +} + +func (s *state3) TotalLocked() (abi.TokenAmount, error) { + return s.TotalPledgeCollateral, nil +} + +func (s *state3) TotalPower() (Claim, error) { + return Claim{ + RawBytePower: s.TotalRawBytePower, + QualityAdjPower: s.TotalQualityAdjPower, + }, nil +} + +// Committed power to the network. Includes miners below the minimum threshold. +func (s *state3) TotalCommitted() (Claim, error) { + return Claim{ + RawBytePower: s.TotalBytesCommitted, + QualityAdjPower: s.TotalQABytesCommitted, + }, nil +} + +func (s *state3) MinerPower(addr address.Address) (Claim, bool, error) { + claims, err := s.claims() + if err != nil { + return Claim{}, false, err + } + var claim power3.Claim + ok, err := claims.Get(abi.AddrKey(addr), &claim) + if err != nil { + return Claim{}, false, err + } + return Claim{ + RawBytePower: claim.RawBytePower, + QualityAdjPower: claim.QualityAdjPower, + }, ok, nil +} + +func (s *state3) MinerNominalPowerMeetsConsensusMinimum(a address.Address) (bool, error) { + return s.State.MinerNominalPowerMeetsConsensusMinimum(s.store, a) +} + +func (s *state3) TotalPowerSmoothed() (builtin.FilterEstimate, error) { + return builtin.FilterEstimate(s.State.ThisEpochQAPowerSmoothed), nil +} + +func (s *state3) MinerCounts() (uint64, uint64, error) { + return uint64(s.State.MinerAboveMinPowerCount), uint64(s.State.MinerCount), nil +} + +func (s *state3) ListAllMiners() ([]address.Address, error) { + claims, err := s.claims() + if err != nil { + return nil, err + } + + var miners []address.Address + err = claims.ForEach(nil, func(k string) error { + a, err := address.NewFromBytes([]byte(k)) + if err != nil { + return err + } + miners = append(miners, a) + return nil + }) + if err != nil { + return nil, err + } + + return miners, nil +} + +func (s *state3) ForEachClaim(cb func(miner address.Address, claim Claim) error) error { + claims, err := s.claims() + if err != nil { + return err + } + + var claim power3.Claim + return claims.ForEach(&claim, func(k string) error { + a, err := address.NewFromBytes([]byte(k)) + if err != nil { + return err + } + return cb(a, Claim{ + RawBytePower: claim.RawBytePower, + QualityAdjPower: claim.QualityAdjPower, + }) + }) +} + +func (s *state3) ClaimsChanged(other State) (bool, error) { + other3, ok := other.(*state3) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.Claims.Equals(other3.State.Claims), nil +} + +func (s *state3) SetTotalQualityAdjPower(p abi.StoragePower) error { + s.State.TotalQualityAdjPower = p + return nil +} + +func (s *state3) SetTotalRawBytePower(p abi.StoragePower) error { + s.State.TotalRawBytePower = p + return nil +} + +func (s *state3) SetThisEpochQualityAdjPower(p abi.StoragePower) error { + s.State.ThisEpochQualityAdjPower = p + return nil +} + +func (s *state3) SetThisEpochRawBytePower(p abi.StoragePower) error { + s.State.ThisEpochRawBytePower = p + return nil +} + +func (s *state3) GetState() interface{} { + return &s.State +} + +func (s *state3) claims() (adt.Map, error) { + return adt3.AsMap(s.store, s.Claims, builtin3.DefaultHamtBitwidth) +} + +func (s *state3) decodeClaim(val *cbg.Deferred) (Claim, error) { + var ci power3.Claim + if err := ci.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return Claim{}, err + } + return fromV3Claim(ci), nil +} + +func fromV3Claim(v3 power3.Claim) Claim { + return Claim{ + RawBytePower: v3.RawBytePower, + QualityAdjPower: v3.QualityAdjPower, + } +} + +func (s *state3) ActorKey() string { + return manifest.PowerKey +} + +func (s *state3) ActorVersion() actorstypes.Version { + return actorstypes.Version3 +} + +func (s *state3) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/power/v4.go b/chain/actors/builtin/power/v4.go new file mode 100644 index 000000000..b2dc95347 --- /dev/null +++ b/chain/actors/builtin/power/v4.go @@ -0,0 +1,207 @@ +package power + +import ( + "bytes" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" + power4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/power" + adt4 "github.com/filecoin-project/specs-actors/v4/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" +) + +var _ State = (*state4)(nil) + +func load4(store adt.Store, root cid.Cid) (State, error) { + out := state4{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make4(store adt.Store) (State, error) { + out := state4{store: store} + + s, err := power4.ConstructState(store) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state4 struct { + power4.State + store adt.Store +} + +func (s *state4) TotalLocked() (abi.TokenAmount, error) { + return s.TotalPledgeCollateral, nil +} + +func (s *state4) TotalPower() (Claim, error) { + return Claim{ + RawBytePower: s.TotalRawBytePower, + QualityAdjPower: s.TotalQualityAdjPower, + }, nil +} + +// Committed power to the network. Includes miners below the minimum threshold. +func (s *state4) TotalCommitted() (Claim, error) { + return Claim{ + RawBytePower: s.TotalBytesCommitted, + QualityAdjPower: s.TotalQABytesCommitted, + }, nil +} + +func (s *state4) MinerPower(addr address.Address) (Claim, bool, error) { + claims, err := s.claims() + if err != nil { + return Claim{}, false, err + } + var claim power4.Claim + ok, err := claims.Get(abi.AddrKey(addr), &claim) + if err != nil { + return Claim{}, false, err + } + return Claim{ + RawBytePower: claim.RawBytePower, + QualityAdjPower: claim.QualityAdjPower, + }, ok, nil +} + +func (s *state4) MinerNominalPowerMeetsConsensusMinimum(a address.Address) (bool, error) { + return s.State.MinerNominalPowerMeetsConsensusMinimum(s.store, a) +} + +func (s *state4) TotalPowerSmoothed() (builtin.FilterEstimate, error) { + return builtin.FilterEstimate(s.State.ThisEpochQAPowerSmoothed), nil +} + +func (s *state4) MinerCounts() (uint64, uint64, error) { + return uint64(s.State.MinerAboveMinPowerCount), uint64(s.State.MinerCount), nil +} + +func (s *state4) ListAllMiners() ([]address.Address, error) { + claims, err := s.claims() + if err != nil { + return nil, err + } + + var miners []address.Address + err = claims.ForEach(nil, func(k string) error { + a, err := address.NewFromBytes([]byte(k)) + if err != nil { + return err + } + miners = append(miners, a) + return nil + }) + if err != nil { + return nil, err + } + + return miners, nil +} + +func (s *state4) ForEachClaim(cb func(miner address.Address, claim Claim) error) error { + claims, err := s.claims() + if err != nil { + return err + } + + var claim power4.Claim + return claims.ForEach(&claim, func(k string) error { + a, err := address.NewFromBytes([]byte(k)) + if err != nil { + return err + } + return cb(a, Claim{ + RawBytePower: claim.RawBytePower, + QualityAdjPower: claim.QualityAdjPower, + }) + }) +} + +func (s *state4) ClaimsChanged(other State) (bool, error) { + other4, ok := other.(*state4) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.Claims.Equals(other4.State.Claims), nil +} + +func (s *state4) SetTotalQualityAdjPower(p abi.StoragePower) error { + s.State.TotalQualityAdjPower = p + return nil +} + +func (s *state4) SetTotalRawBytePower(p abi.StoragePower) error { + s.State.TotalRawBytePower = p + return nil +} + +func (s *state4) SetThisEpochQualityAdjPower(p abi.StoragePower) error { + s.State.ThisEpochQualityAdjPower = p + return nil +} + +func (s *state4) SetThisEpochRawBytePower(p abi.StoragePower) error { + s.State.ThisEpochRawBytePower = p + return nil +} + +func (s *state4) GetState() interface{} { + return &s.State +} + +func (s *state4) claims() (adt.Map, error) { + return adt4.AsMap(s.store, s.Claims, builtin4.DefaultHamtBitwidth) +} + +func (s *state4) decodeClaim(val *cbg.Deferred) (Claim, error) { + var ci power4.Claim + if err := ci.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return Claim{}, err + } + return fromV4Claim(ci), nil +} + +func fromV4Claim(v4 power4.Claim) Claim { + return Claim{ + RawBytePower: v4.RawBytePower, + QualityAdjPower: v4.QualityAdjPower, + } +} + +func (s *state4) ActorKey() string { + return manifest.PowerKey +} + +func (s *state4) ActorVersion() actorstypes.Version { + return actorstypes.Version4 +} + +func (s *state4) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/power/v5.go b/chain/actors/builtin/power/v5.go new file mode 100644 index 000000000..3a3adaf32 --- /dev/null +++ b/chain/actors/builtin/power/v5.go @@ -0,0 +1,207 @@ +package power + +import ( + "bytes" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" + power5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/power" + adt5 "github.com/filecoin-project/specs-actors/v5/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" +) + +var _ State = (*state5)(nil) + +func load5(store adt.Store, root cid.Cid) (State, error) { + out := state5{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make5(store adt.Store) (State, error) { + out := state5{store: store} + + s, err := power5.ConstructState(store) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state5 struct { + power5.State + store adt.Store +} + +func (s *state5) TotalLocked() (abi.TokenAmount, error) { + return s.TotalPledgeCollateral, nil +} + +func (s *state5) TotalPower() (Claim, error) { + return Claim{ + RawBytePower: s.TotalRawBytePower, + QualityAdjPower: s.TotalQualityAdjPower, + }, nil +} + +// Committed power to the network. Includes miners below the minimum threshold. +func (s *state5) TotalCommitted() (Claim, error) { + return Claim{ + RawBytePower: s.TotalBytesCommitted, + QualityAdjPower: s.TotalQABytesCommitted, + }, nil +} + +func (s *state5) MinerPower(addr address.Address) (Claim, bool, error) { + claims, err := s.claims() + if err != nil { + return Claim{}, false, err + } + var claim power5.Claim + ok, err := claims.Get(abi.AddrKey(addr), &claim) + if err != nil { + return Claim{}, false, err + } + return Claim{ + RawBytePower: claim.RawBytePower, + QualityAdjPower: claim.QualityAdjPower, + }, ok, nil +} + +func (s *state5) MinerNominalPowerMeetsConsensusMinimum(a address.Address) (bool, error) { + return s.State.MinerNominalPowerMeetsConsensusMinimum(s.store, a) +} + +func (s *state5) TotalPowerSmoothed() (builtin.FilterEstimate, error) { + return builtin.FilterEstimate(s.State.ThisEpochQAPowerSmoothed), nil +} + +func (s *state5) MinerCounts() (uint64, uint64, error) { + return uint64(s.State.MinerAboveMinPowerCount), uint64(s.State.MinerCount), nil +} + +func (s *state5) ListAllMiners() ([]address.Address, error) { + claims, err := s.claims() + if err != nil { + return nil, err + } + + var miners []address.Address + err = claims.ForEach(nil, func(k string) error { + a, err := address.NewFromBytes([]byte(k)) + if err != nil { + return err + } + miners = append(miners, a) + return nil + }) + if err != nil { + return nil, err + } + + return miners, nil +} + +func (s *state5) ForEachClaim(cb func(miner address.Address, claim Claim) error) error { + claims, err := s.claims() + if err != nil { + return err + } + + var claim power5.Claim + return claims.ForEach(&claim, func(k string) error { + a, err := address.NewFromBytes([]byte(k)) + if err != nil { + return err + } + return cb(a, Claim{ + RawBytePower: claim.RawBytePower, + QualityAdjPower: claim.QualityAdjPower, + }) + }) +} + +func (s *state5) ClaimsChanged(other State) (bool, error) { + other5, ok := other.(*state5) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.Claims.Equals(other5.State.Claims), nil +} + +func (s *state5) SetTotalQualityAdjPower(p abi.StoragePower) error { + s.State.TotalQualityAdjPower = p + return nil +} + +func (s *state5) SetTotalRawBytePower(p abi.StoragePower) error { + s.State.TotalRawBytePower = p + return nil +} + +func (s *state5) SetThisEpochQualityAdjPower(p abi.StoragePower) error { + s.State.ThisEpochQualityAdjPower = p + return nil +} + +func (s *state5) SetThisEpochRawBytePower(p abi.StoragePower) error { + s.State.ThisEpochRawBytePower = p + return nil +} + +func (s *state5) GetState() interface{} { + return &s.State +} + +func (s *state5) claims() (adt.Map, error) { + return adt5.AsMap(s.store, s.Claims, builtin5.DefaultHamtBitwidth) +} + +func (s *state5) decodeClaim(val *cbg.Deferred) (Claim, error) { + var ci power5.Claim + if err := ci.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return Claim{}, err + } + return fromV5Claim(ci), nil +} + +func fromV5Claim(v5 power5.Claim) Claim { + return Claim{ + RawBytePower: v5.RawBytePower, + QualityAdjPower: v5.QualityAdjPower, + } +} + +func (s *state5) ActorKey() string { + return manifest.PowerKey +} + +func (s *state5) ActorVersion() actorstypes.Version { + return actorstypes.Version5 +} + +func (s *state5) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/power/v6.go b/chain/actors/builtin/power/v6.go new file mode 100644 index 000000000..7e8eb3654 --- /dev/null +++ b/chain/actors/builtin/power/v6.go @@ -0,0 +1,207 @@ +package power + +import ( + "bytes" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + builtin6 "github.com/filecoin-project/specs-actors/v6/actors/builtin" + power6 "github.com/filecoin-project/specs-actors/v6/actors/builtin/power" + adt6 "github.com/filecoin-project/specs-actors/v6/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" +) + +var _ State = (*state6)(nil) + +func load6(store adt.Store, root cid.Cid) (State, error) { + out := state6{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make6(store adt.Store) (State, error) { + out := state6{store: store} + + s, err := power6.ConstructState(store) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state6 struct { + power6.State + store adt.Store +} + +func (s *state6) TotalLocked() (abi.TokenAmount, error) { + return s.TotalPledgeCollateral, nil +} + +func (s *state6) TotalPower() (Claim, error) { + return Claim{ + RawBytePower: s.TotalRawBytePower, + QualityAdjPower: s.TotalQualityAdjPower, + }, nil +} + +// Committed power to the network. Includes miners below the minimum threshold. +func (s *state6) TotalCommitted() (Claim, error) { + return Claim{ + RawBytePower: s.TotalBytesCommitted, + QualityAdjPower: s.TotalQABytesCommitted, + }, nil +} + +func (s *state6) MinerPower(addr address.Address) (Claim, bool, error) { + claims, err := s.claims() + if err != nil { + return Claim{}, false, err + } + var claim power6.Claim + ok, err := claims.Get(abi.AddrKey(addr), &claim) + if err != nil { + return Claim{}, false, err + } + return Claim{ + RawBytePower: claim.RawBytePower, + QualityAdjPower: claim.QualityAdjPower, + }, ok, nil +} + +func (s *state6) MinerNominalPowerMeetsConsensusMinimum(a address.Address) (bool, error) { + return s.State.MinerNominalPowerMeetsConsensusMinimum(s.store, a) +} + +func (s *state6) TotalPowerSmoothed() (builtin.FilterEstimate, error) { + return builtin.FilterEstimate(s.State.ThisEpochQAPowerSmoothed), nil +} + +func (s *state6) MinerCounts() (uint64, uint64, error) { + return uint64(s.State.MinerAboveMinPowerCount), uint64(s.State.MinerCount), nil +} + +func (s *state6) ListAllMiners() ([]address.Address, error) { + claims, err := s.claims() + if err != nil { + return nil, err + } + + var miners []address.Address + err = claims.ForEach(nil, func(k string) error { + a, err := address.NewFromBytes([]byte(k)) + if err != nil { + return err + } + miners = append(miners, a) + return nil + }) + if err != nil { + return nil, err + } + + return miners, nil +} + +func (s *state6) ForEachClaim(cb func(miner address.Address, claim Claim) error) error { + claims, err := s.claims() + if err != nil { + return err + } + + var claim power6.Claim + return claims.ForEach(&claim, func(k string) error { + a, err := address.NewFromBytes([]byte(k)) + if err != nil { + return err + } + return cb(a, Claim{ + RawBytePower: claim.RawBytePower, + QualityAdjPower: claim.QualityAdjPower, + }) + }) +} + +func (s *state6) ClaimsChanged(other State) (bool, error) { + other6, ok := other.(*state6) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.Claims.Equals(other6.State.Claims), nil +} + +func (s *state6) SetTotalQualityAdjPower(p abi.StoragePower) error { + s.State.TotalQualityAdjPower = p + return nil +} + +func (s *state6) SetTotalRawBytePower(p abi.StoragePower) error { + s.State.TotalRawBytePower = p + return nil +} + +func (s *state6) SetThisEpochQualityAdjPower(p abi.StoragePower) error { + s.State.ThisEpochQualityAdjPower = p + return nil +} + +func (s *state6) SetThisEpochRawBytePower(p abi.StoragePower) error { + s.State.ThisEpochRawBytePower = p + return nil +} + +func (s *state6) GetState() interface{} { + return &s.State +} + +func (s *state6) claims() (adt.Map, error) { + return adt6.AsMap(s.store, s.Claims, builtin6.DefaultHamtBitwidth) +} + +func (s *state6) decodeClaim(val *cbg.Deferred) (Claim, error) { + var ci power6.Claim + if err := ci.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return Claim{}, err + } + return fromV6Claim(ci), nil +} + +func fromV6Claim(v6 power6.Claim) Claim { + return Claim{ + RawBytePower: v6.RawBytePower, + QualityAdjPower: v6.QualityAdjPower, + } +} + +func (s *state6) ActorKey() string { + return manifest.PowerKey +} + +func (s *state6) ActorVersion() actorstypes.Version { + return actorstypes.Version6 +} + +func (s *state6) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/power/v7.go b/chain/actors/builtin/power/v7.go new file mode 100644 index 000000000..893c58667 --- /dev/null +++ b/chain/actors/builtin/power/v7.go @@ -0,0 +1,207 @@ +package power + +import ( + "bytes" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + builtin7 "github.com/filecoin-project/specs-actors/v7/actors/builtin" + power7 "github.com/filecoin-project/specs-actors/v7/actors/builtin/power" + adt7 "github.com/filecoin-project/specs-actors/v7/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" +) + +var _ State = (*state7)(nil) + +func load7(store adt.Store, root cid.Cid) (State, error) { + out := state7{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make7(store adt.Store) (State, error) { + out := state7{store: store} + + s, err := power7.ConstructState(store) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state7 struct { + power7.State + store adt.Store +} + +func (s *state7) TotalLocked() (abi.TokenAmount, error) { + return s.TotalPledgeCollateral, nil +} + +func (s *state7) TotalPower() (Claim, error) { + return Claim{ + RawBytePower: s.TotalRawBytePower, + QualityAdjPower: s.TotalQualityAdjPower, + }, nil +} + +// Committed power to the network. Includes miners below the minimum threshold. +func (s *state7) TotalCommitted() (Claim, error) { + return Claim{ + RawBytePower: s.TotalBytesCommitted, + QualityAdjPower: s.TotalQABytesCommitted, + }, nil +} + +func (s *state7) MinerPower(addr address.Address) (Claim, bool, error) { + claims, err := s.claims() + if err != nil { + return Claim{}, false, err + } + var claim power7.Claim + ok, err := claims.Get(abi.AddrKey(addr), &claim) + if err != nil { + return Claim{}, false, err + } + return Claim{ + RawBytePower: claim.RawBytePower, + QualityAdjPower: claim.QualityAdjPower, + }, ok, nil +} + +func (s *state7) MinerNominalPowerMeetsConsensusMinimum(a address.Address) (bool, error) { + return s.State.MinerNominalPowerMeetsConsensusMinimum(s.store, a) +} + +func (s *state7) TotalPowerSmoothed() (builtin.FilterEstimate, error) { + return builtin.FilterEstimate(s.State.ThisEpochQAPowerSmoothed), nil +} + +func (s *state7) MinerCounts() (uint64, uint64, error) { + return uint64(s.State.MinerAboveMinPowerCount), uint64(s.State.MinerCount), nil +} + +func (s *state7) ListAllMiners() ([]address.Address, error) { + claims, err := s.claims() + if err != nil { + return nil, err + } + + var miners []address.Address + err = claims.ForEach(nil, func(k string) error { + a, err := address.NewFromBytes([]byte(k)) + if err != nil { + return err + } + miners = append(miners, a) + return nil + }) + if err != nil { + return nil, err + } + + return miners, nil +} + +func (s *state7) ForEachClaim(cb func(miner address.Address, claim Claim) error) error { + claims, err := s.claims() + if err != nil { + return err + } + + var claim power7.Claim + return claims.ForEach(&claim, func(k string) error { + a, err := address.NewFromBytes([]byte(k)) + if err != nil { + return err + } + return cb(a, Claim{ + RawBytePower: claim.RawBytePower, + QualityAdjPower: claim.QualityAdjPower, + }) + }) +} + +func (s *state7) ClaimsChanged(other State) (bool, error) { + other7, ok := other.(*state7) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.Claims.Equals(other7.State.Claims), nil +} + +func (s *state7) SetTotalQualityAdjPower(p abi.StoragePower) error { + s.State.TotalQualityAdjPower = p + return nil +} + +func (s *state7) SetTotalRawBytePower(p abi.StoragePower) error { + s.State.TotalRawBytePower = p + return nil +} + +func (s *state7) SetThisEpochQualityAdjPower(p abi.StoragePower) error { + s.State.ThisEpochQualityAdjPower = p + return nil +} + +func (s *state7) SetThisEpochRawBytePower(p abi.StoragePower) error { + s.State.ThisEpochRawBytePower = p + return nil +} + +func (s *state7) GetState() interface{} { + return &s.State +} + +func (s *state7) claims() (adt.Map, error) { + return adt7.AsMap(s.store, s.Claims, builtin7.DefaultHamtBitwidth) +} + +func (s *state7) decodeClaim(val *cbg.Deferred) (Claim, error) { + var ci power7.Claim + if err := ci.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return Claim{}, err + } + return fromV7Claim(ci), nil +} + +func fromV7Claim(v7 power7.Claim) Claim { + return Claim{ + RawBytePower: v7.RawBytePower, + QualityAdjPower: v7.QualityAdjPower, + } +} + +func (s *state7) ActorKey() string { + return manifest.PowerKey +} + +func (s *state7) ActorVersion() actorstypes.Version { + return actorstypes.Version7 +} + +func (s *state7) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/power/v8.go b/chain/actors/builtin/power/v8.go new file mode 100644 index 000000000..b15fe4355 --- /dev/null +++ b/chain/actors/builtin/power/v8.go @@ -0,0 +1,207 @@ +package power + +import ( + "bytes" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtin8 "github.com/filecoin-project/go-state-types/builtin" + power8 "github.com/filecoin-project/go-state-types/builtin/v8/power" + adt8 "github.com/filecoin-project/go-state-types/builtin/v8/util/adt" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" +) + +var _ State = (*state8)(nil) + +func load8(store adt.Store, root cid.Cid) (State, error) { + out := state8{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make8(store adt.Store) (State, error) { + out := state8{store: store} + + s, err := power8.ConstructState(store) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state8 struct { + power8.State + store adt.Store +} + +func (s *state8) TotalLocked() (abi.TokenAmount, error) { + return s.TotalPledgeCollateral, nil +} + +func (s *state8) TotalPower() (Claim, error) { + return Claim{ + RawBytePower: s.TotalRawBytePower, + QualityAdjPower: s.TotalQualityAdjPower, + }, nil +} + +// Committed power to the network. Includes miners below the minimum threshold. +func (s *state8) TotalCommitted() (Claim, error) { + return Claim{ + RawBytePower: s.TotalBytesCommitted, + QualityAdjPower: s.TotalQABytesCommitted, + }, nil +} + +func (s *state8) MinerPower(addr address.Address) (Claim, bool, error) { + claims, err := s.claims() + if err != nil { + return Claim{}, false, err + } + var claim power8.Claim + ok, err := claims.Get(abi.AddrKey(addr), &claim) + if err != nil { + return Claim{}, false, err + } + return Claim{ + RawBytePower: claim.RawBytePower, + QualityAdjPower: claim.QualityAdjPower, + }, ok, nil +} + +func (s *state8) MinerNominalPowerMeetsConsensusMinimum(a address.Address) (bool, error) { + return s.State.MinerNominalPowerMeetsConsensusMinimum(s.store, a) +} + +func (s *state8) TotalPowerSmoothed() (builtin.FilterEstimate, error) { + return builtin.FilterEstimate(s.State.ThisEpochQAPowerSmoothed), nil +} + +func (s *state8) MinerCounts() (uint64, uint64, error) { + return uint64(s.State.MinerAboveMinPowerCount), uint64(s.State.MinerCount), nil +} + +func (s *state8) ListAllMiners() ([]address.Address, error) { + claims, err := s.claims() + if err != nil { + return nil, err + } + + var miners []address.Address + err = claims.ForEach(nil, func(k string) error { + a, err := address.NewFromBytes([]byte(k)) + if err != nil { + return err + } + miners = append(miners, a) + return nil + }) + if err != nil { + return nil, err + } + + return miners, nil +} + +func (s *state8) ForEachClaim(cb func(miner address.Address, claim Claim) error) error { + claims, err := s.claims() + if err != nil { + return err + } + + var claim power8.Claim + return claims.ForEach(&claim, func(k string) error { + a, err := address.NewFromBytes([]byte(k)) + if err != nil { + return err + } + return cb(a, Claim{ + RawBytePower: claim.RawBytePower, + QualityAdjPower: claim.QualityAdjPower, + }) + }) +} + +func (s *state8) ClaimsChanged(other State) (bool, error) { + other8, ok := other.(*state8) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.Claims.Equals(other8.State.Claims), nil +} + +func (s *state8) SetTotalQualityAdjPower(p abi.StoragePower) error { + s.State.TotalQualityAdjPower = p + return nil +} + +func (s *state8) SetTotalRawBytePower(p abi.StoragePower) error { + s.State.TotalRawBytePower = p + return nil +} + +func (s *state8) SetThisEpochQualityAdjPower(p abi.StoragePower) error { + s.State.ThisEpochQualityAdjPower = p + return nil +} + +func (s *state8) SetThisEpochRawBytePower(p abi.StoragePower) error { + s.State.ThisEpochRawBytePower = p + return nil +} + +func (s *state8) GetState() interface{} { + return &s.State +} + +func (s *state8) claims() (adt.Map, error) { + return adt8.AsMap(s.store, s.Claims, builtin8.DefaultHamtBitwidth) +} + +func (s *state8) decodeClaim(val *cbg.Deferred) (Claim, error) { + var ci power8.Claim + if err := ci.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return Claim{}, err + } + return fromV8Claim(ci), nil +} + +func fromV8Claim(v8 power8.Claim) Claim { + return Claim{ + RawBytePower: v8.RawBytePower, + QualityAdjPower: v8.QualityAdjPower, + } +} + +func (s *state8) ActorKey() string { + return manifest.PowerKey +} + +func (s *state8) ActorVersion() actorstypes.Version { + return actorstypes.Version8 +} + +func (s *state8) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/power/v9.go b/chain/actors/builtin/power/v9.go new file mode 100644 index 000000000..126fbbfbf --- /dev/null +++ b/chain/actors/builtin/power/v9.go @@ -0,0 +1,207 @@ +package power + +import ( + "bytes" + "fmt" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtin9 "github.com/filecoin-project/go-state-types/builtin" + power9 "github.com/filecoin-project/go-state-types/builtin/v9/power" + adt9 "github.com/filecoin-project/go-state-types/builtin/v9/util/adt" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" +) + +var _ State = (*state9)(nil) + +func load9(store adt.Store, root cid.Cid) (State, error) { + out := state9{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make9(store adt.Store) (State, error) { + out := state9{store: store} + + s, err := power9.ConstructState(store) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state9 struct { + power9.State + store adt.Store +} + +func (s *state9) TotalLocked() (abi.TokenAmount, error) { + return s.TotalPledgeCollateral, nil +} + +func (s *state9) TotalPower() (Claim, error) { + return Claim{ + RawBytePower: s.TotalRawBytePower, + QualityAdjPower: s.TotalQualityAdjPower, + }, nil +} + +// Committed power to the network. Includes miners below the minimum threshold. +func (s *state9) TotalCommitted() (Claim, error) { + return Claim{ + RawBytePower: s.TotalBytesCommitted, + QualityAdjPower: s.TotalQABytesCommitted, + }, nil +} + +func (s *state9) MinerPower(addr address.Address) (Claim, bool, error) { + claims, err := s.claims() + if err != nil { + return Claim{}, false, err + } + var claim power9.Claim + ok, err := claims.Get(abi.AddrKey(addr), &claim) + if err != nil { + return Claim{}, false, err + } + return Claim{ + RawBytePower: claim.RawBytePower, + QualityAdjPower: claim.QualityAdjPower, + }, ok, nil +} + +func (s *state9) MinerNominalPowerMeetsConsensusMinimum(a address.Address) (bool, error) { + return s.State.MinerNominalPowerMeetsConsensusMinimum(s.store, a) +} + +func (s *state9) TotalPowerSmoothed() (builtin.FilterEstimate, error) { + return builtin.FilterEstimate(s.State.ThisEpochQAPowerSmoothed), nil +} + +func (s *state9) MinerCounts() (uint64, uint64, error) { + return uint64(s.State.MinerAboveMinPowerCount), uint64(s.State.MinerCount), nil +} + +func (s *state9) ListAllMiners() ([]address.Address, error) { + claims, err := s.claims() + if err != nil { + return nil, err + } + + var miners []address.Address + err = claims.ForEach(nil, func(k string) error { + a, err := address.NewFromBytes([]byte(k)) + if err != nil { + return err + } + miners = append(miners, a) + return nil + }) + if err != nil { + return nil, err + } + + return miners, nil +} + +func (s *state9) ForEachClaim(cb func(miner address.Address, claim Claim) error) error { + claims, err := s.claims() + if err != nil { + return err + } + + var claim power9.Claim + return claims.ForEach(&claim, func(k string) error { + a, err := address.NewFromBytes([]byte(k)) + if err != nil { + return err + } + return cb(a, Claim{ + RawBytePower: claim.RawBytePower, + QualityAdjPower: claim.QualityAdjPower, + }) + }) +} + +func (s *state9) ClaimsChanged(other State) (bool, error) { + other9, ok := other.(*state9) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.Claims.Equals(other9.State.Claims), nil +} + +func (s *state9) SetTotalQualityAdjPower(p abi.StoragePower) error { + s.State.TotalQualityAdjPower = p + return nil +} + +func (s *state9) SetTotalRawBytePower(p abi.StoragePower) error { + s.State.TotalRawBytePower = p + return nil +} + +func (s *state9) SetThisEpochQualityAdjPower(p abi.StoragePower) error { + s.State.ThisEpochQualityAdjPower = p + return nil +} + +func (s *state9) SetThisEpochRawBytePower(p abi.StoragePower) error { + s.State.ThisEpochRawBytePower = p + return nil +} + +func (s *state9) GetState() interface{} { + return &s.State +} + +func (s *state9) claims() (adt.Map, error) { + return adt9.AsMap(s.store, s.Claims, builtin9.DefaultHamtBitwidth) +} + +func (s *state9) decodeClaim(val *cbg.Deferred) (Claim, error) { + var ci power9.Claim + if err := ci.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return Claim{}, err + } + return fromV9Claim(ci), nil +} + +func fromV9Claim(v9 power9.Claim) Claim { + return Claim{ + RawBytePower: v9.RawBytePower, + QualityAdjPower: v9.QualityAdjPower, + } +} + +func (s *state9) ActorKey() string { + return manifest.PowerKey +} + +func (s *state9) ActorVersion() actorstypes.Version { + return actorstypes.Version9 +} + +func (s *state9) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/registry.go b/chain/actors/builtin/registry.go new file mode 100644 index 000000000..4addbd451 --- /dev/null +++ b/chain/actors/builtin/registry.go @@ -0,0 +1,505 @@ +package builtin + +import ( + "reflect" + "runtime" + "strings" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/builtin" + account10 "github.com/filecoin-project/go-state-types/builtin/v10/account" + cron10 "github.com/filecoin-project/go-state-types/builtin/v10/cron" + datacap10 "github.com/filecoin-project/go-state-types/builtin/v10/datacap" + eam10 "github.com/filecoin-project/go-state-types/builtin/v10/eam" + ethaccount10 "github.com/filecoin-project/go-state-types/builtin/v10/ethaccount" + evm10 "github.com/filecoin-project/go-state-types/builtin/v10/evm" + _init10 "github.com/filecoin-project/go-state-types/builtin/v10/init" + market10 "github.com/filecoin-project/go-state-types/builtin/v10/market" + miner10 "github.com/filecoin-project/go-state-types/builtin/v10/miner" + multisig10 "github.com/filecoin-project/go-state-types/builtin/v10/multisig" + paych10 "github.com/filecoin-project/go-state-types/builtin/v10/paych" + placeholder10 "github.com/filecoin-project/go-state-types/builtin/v10/placeholder" + power10 "github.com/filecoin-project/go-state-types/builtin/v10/power" + reward10 "github.com/filecoin-project/go-state-types/builtin/v10/reward" + system10 "github.com/filecoin-project/go-state-types/builtin/v10/system" + verifreg10 "github.com/filecoin-project/go-state-types/builtin/v10/verifreg" + account11 "github.com/filecoin-project/go-state-types/builtin/v11/account" + cron11 "github.com/filecoin-project/go-state-types/builtin/v11/cron" + datacap11 "github.com/filecoin-project/go-state-types/builtin/v11/datacap" + eam11 "github.com/filecoin-project/go-state-types/builtin/v11/eam" + ethaccount11 "github.com/filecoin-project/go-state-types/builtin/v11/ethaccount" + evm11 "github.com/filecoin-project/go-state-types/builtin/v11/evm" + _init11 "github.com/filecoin-project/go-state-types/builtin/v11/init" + market11 "github.com/filecoin-project/go-state-types/builtin/v11/market" + miner11 "github.com/filecoin-project/go-state-types/builtin/v11/miner" + multisig11 "github.com/filecoin-project/go-state-types/builtin/v11/multisig" + paych11 "github.com/filecoin-project/go-state-types/builtin/v11/paych" + placeholder11 "github.com/filecoin-project/go-state-types/builtin/v11/placeholder" + power11 "github.com/filecoin-project/go-state-types/builtin/v11/power" + reward11 "github.com/filecoin-project/go-state-types/builtin/v11/reward" + system11 "github.com/filecoin-project/go-state-types/builtin/v11/system" + verifreg11 "github.com/filecoin-project/go-state-types/builtin/v11/verifreg" + account8 "github.com/filecoin-project/go-state-types/builtin/v8/account" + cron8 "github.com/filecoin-project/go-state-types/builtin/v8/cron" + _init8 "github.com/filecoin-project/go-state-types/builtin/v8/init" + market8 "github.com/filecoin-project/go-state-types/builtin/v8/market" + miner8 "github.com/filecoin-project/go-state-types/builtin/v8/miner" + multisig8 "github.com/filecoin-project/go-state-types/builtin/v8/multisig" + paych8 "github.com/filecoin-project/go-state-types/builtin/v8/paych" + power8 "github.com/filecoin-project/go-state-types/builtin/v8/power" + reward8 "github.com/filecoin-project/go-state-types/builtin/v8/reward" + system8 "github.com/filecoin-project/go-state-types/builtin/v8/system" + verifreg8 "github.com/filecoin-project/go-state-types/builtin/v8/verifreg" + account9 "github.com/filecoin-project/go-state-types/builtin/v9/account" + cron9 "github.com/filecoin-project/go-state-types/builtin/v9/cron" + datacap9 "github.com/filecoin-project/go-state-types/builtin/v9/datacap" + _init9 "github.com/filecoin-project/go-state-types/builtin/v9/init" + market9 "github.com/filecoin-project/go-state-types/builtin/v9/market" + miner9 "github.com/filecoin-project/go-state-types/builtin/v9/miner" + multisig9 "github.com/filecoin-project/go-state-types/builtin/v9/multisig" + paych9 "github.com/filecoin-project/go-state-types/builtin/v9/paych" + power9 "github.com/filecoin-project/go-state-types/builtin/v9/power" + reward9 "github.com/filecoin-project/go-state-types/builtin/v9/reward" + system9 "github.com/filecoin-project/go-state-types/builtin/v9/system" + verifreg9 "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" + "github.com/filecoin-project/go-state-types/cbor" + "github.com/filecoin-project/go-state-types/manifest" + rtt "github.com/filecoin-project/go-state-types/rt" + + "github.com/filecoin-project/lotus/chain/actors" +) + +type RegistryEntry struct { + state cbor.Er + code cid.Cid + methods map[abi.MethodNum]builtin.MethodMeta +} + +func (r RegistryEntry) State() cbor.Er { + return r.state +} + +func (r RegistryEntry) Exports() map[abi.MethodNum]builtin.MethodMeta { + return r.methods +} + +func (r RegistryEntry) Code() cid.Cid { + return r.code +} + +func MakeRegistryLegacy(actors []rtt.VMActor) []RegistryEntry { + registry := make([]RegistryEntry, 0) + + for _, actor := range actors { + methodMap := make(map[abi.MethodNum]builtin.MethodMeta) + for methodNum, method := range actor.Exports() { + if method != nil { + methodMap[abi.MethodNum(methodNum)] = makeMethodMeta(method) + } + } + registry = append(registry, RegistryEntry{ + code: actor.Code(), + methods: methodMap, + state: actor.State(), + }) + } + + return registry +} + +func makeMethodMeta(method interface{}) builtin.MethodMeta { + ev := reflect.ValueOf(method) + // Extract the method names using reflection. These + // method names always match the field names in the + // `builtin.Method*` structs (tested in the specs-actors + // tests). + fnName := runtime.FuncForPC(ev.Pointer()).Name() + fnName = strings.TrimSuffix(fnName[strings.LastIndexByte(fnName, '.')+1:], "-fm") + return builtin.MethodMeta{ + Name: fnName, + Method: method, + } +} + +func MakeRegistry(av actorstypes.Version) []RegistryEntry { + if av < actorstypes.Version8 { + panic("expected version v8 and up only, use specs-actors for v0-7") + } + registry := make([]RegistryEntry, 0) + + codeIDs, err := actors.GetActorCodeIDs(av) + if err != nil { + panic(err) + } + + switch av { + + case actorstypes.Version8: + for key, codeID := range codeIDs { + switch key { + case manifest.AccountKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: account8.Methods, + state: new(account8.State), + }) + case manifest.CronKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: cron8.Methods, + state: new(cron8.State), + }) + case manifest.InitKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: _init8.Methods, + state: new(_init8.State), + }) + case manifest.MarketKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: market8.Methods, + state: new(market8.State), + }) + case manifest.MinerKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: miner8.Methods, + state: new(miner8.State), + }) + case manifest.MultisigKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: multisig8.Methods, + state: new(multisig8.State), + }) + case manifest.PaychKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: paych8.Methods, + state: new(paych8.State), + }) + case manifest.PowerKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: power8.Methods, + state: new(power8.State), + }) + case manifest.RewardKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: reward8.Methods, + state: new(reward8.State), + }) + case manifest.SystemKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: system8.Methods, + state: new(system8.State), + }) + case manifest.VerifregKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: verifreg8.Methods, + state: new(verifreg8.State), + }) + + } + } + + case actorstypes.Version9: + for key, codeID := range codeIDs { + switch key { + case manifest.AccountKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: account9.Methods, + state: new(account9.State), + }) + case manifest.CronKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: cron9.Methods, + state: new(cron9.State), + }) + case manifest.InitKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: _init9.Methods, + state: new(_init9.State), + }) + case manifest.MarketKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: market9.Methods, + state: new(market9.State), + }) + case manifest.MinerKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: miner9.Methods, + state: new(miner9.State), + }) + case manifest.MultisigKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: multisig9.Methods, + state: new(multisig9.State), + }) + case manifest.PaychKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: paych9.Methods, + state: new(paych9.State), + }) + case manifest.PowerKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: power9.Methods, + state: new(power9.State), + }) + case manifest.RewardKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: reward9.Methods, + state: new(reward9.State), + }) + case manifest.SystemKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: system9.Methods, + state: new(system9.State), + }) + case manifest.VerifregKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: verifreg9.Methods, + state: new(verifreg9.State), + }) + case manifest.DatacapKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: datacap9.Methods, + state: new(datacap9.State), + }) + + } + } + + case actorstypes.Version10: + for key, codeID := range codeIDs { + switch key { + case manifest.AccountKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: account10.Methods, + state: new(account10.State), + }) + case manifest.CronKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: cron10.Methods, + state: new(cron10.State), + }) + case manifest.InitKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: _init10.Methods, + state: new(_init10.State), + }) + case manifest.MarketKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: market10.Methods, + state: new(market10.State), + }) + case manifest.MinerKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: miner10.Methods, + state: new(miner10.State), + }) + case manifest.MultisigKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: multisig10.Methods, + state: new(multisig10.State), + }) + case manifest.PaychKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: paych10.Methods, + state: new(paych10.State), + }) + case manifest.PowerKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: power10.Methods, + state: new(power10.State), + }) + case manifest.RewardKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: reward10.Methods, + state: new(reward10.State), + }) + case manifest.SystemKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: system10.Methods, + state: new(system10.State), + }) + case manifest.VerifregKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: verifreg10.Methods, + state: new(verifreg10.State), + }) + case manifest.DatacapKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: datacap10.Methods, + state: new(datacap10.State), + }) + + case manifest.EvmKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: evm10.Methods, + state: new(evm10.State), + }) + case manifest.EamKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: eam10.Methods, + state: nil, + }) + case manifest.PlaceholderKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: placeholder10.Methods, + state: nil, + }) + case manifest.EthAccountKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: ethaccount10.Methods, + state: nil, + }) + + } + } + + case actorstypes.Version11: + for key, codeID := range codeIDs { + switch key { + case manifest.AccountKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: account11.Methods, + state: new(account11.State), + }) + case manifest.CronKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: cron11.Methods, + state: new(cron11.State), + }) + case manifest.InitKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: _init11.Methods, + state: new(_init11.State), + }) + case manifest.MarketKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: market11.Methods, + state: new(market11.State), + }) + case manifest.MinerKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: miner11.Methods, + state: new(miner11.State), + }) + case manifest.MultisigKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: multisig11.Methods, + state: new(multisig11.State), + }) + case manifest.PaychKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: paych11.Methods, + state: new(paych11.State), + }) + case manifest.PowerKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: power11.Methods, + state: new(power11.State), + }) + case manifest.RewardKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: reward11.Methods, + state: new(reward11.State), + }) + case manifest.SystemKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: system11.Methods, + state: new(system11.State), + }) + case manifest.VerifregKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: verifreg11.Methods, + state: new(verifreg11.State), + }) + case manifest.DatacapKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: datacap11.Methods, + state: new(datacap11.State), + }) + + case manifest.EvmKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: evm11.Methods, + state: new(evm11.State), + }) + case manifest.EamKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: eam11.Methods, + state: nil, + }) + case manifest.PlaceholderKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: placeholder11.Methods, + state: nil, + }) + case manifest.EthAccountKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: ethaccount11.Methods, + state: nil, + }) + + } + } + + default: + panic("expected version v8 and up only, use specs-actors for v0-7") + } + + return registry +} diff --git a/chain/actors/builtin/registry.go.template b/chain/actors/builtin/registry.go.template new file mode 100644 index 000000000..a63b00917 --- /dev/null +++ b/chain/actors/builtin/registry.go.template @@ -0,0 +1,219 @@ +package builtin + +import ( + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/ipfs/go-cid" + "reflect" + "runtime" + "strings" + + "github.com/filecoin-project/go-state-types/builtin" + {{range .versions}} + {{if (ge . 8)}} + account{{.}} "github.com/filecoin-project/go-state-types/builtin/v{{.}}/account" + cron{{.}} "github.com/filecoin-project/go-state-types/builtin/v{{.}}/cron" + _init{{.}} "github.com/filecoin-project/go-state-types/builtin/v{{.}}/init" + multisig{{.}} "github.com/filecoin-project/go-state-types/builtin/v{{.}}/multisig" + miner{{.}} "github.com/filecoin-project/go-state-types/builtin/v{{.}}/miner" + market{{.}} "github.com/filecoin-project/go-state-types/builtin/v{{.}}/market" + reward{{.}} "github.com/filecoin-project/go-state-types/builtin/v{{.}}/reward" + paych{{.}} "github.com/filecoin-project/go-state-types/builtin/v{{.}}/paych" + power{{.}} "github.com/filecoin-project/go-state-types/builtin/v{{.}}/power" + system{{.}} "github.com/filecoin-project/go-state-types/builtin/v{{.}}/system" + verifreg{{.}} "github.com/filecoin-project/go-state-types/builtin/v{{.}}/verifreg" + {{end}} + {{if (ge . 9)}} + datacap{{.}} "github.com/filecoin-project/go-state-types/builtin/v{{.}}/datacap" + {{end}} + {{if (ge . 10)}} + evm{{.}} "github.com/filecoin-project/go-state-types/builtin/v{{.}}/evm" + eam{{.}} "github.com/filecoin-project/go-state-types/builtin/v{{.}}/eam" + placeholder{{.}} "github.com/filecoin-project/go-state-types/builtin/v{{.}}/placeholder" + ethaccount{{.}} "github.com/filecoin-project/go-state-types/builtin/v{{.}}/ethaccount" + {{end}} + {{end}} + "github.com/filecoin-project/go-state-types/cbor" + rtt "github.com/filecoin-project/go-state-types/rt" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/manifest" +) + +type RegistryEntry struct { + state cbor.Er + code cid.Cid + methods map[abi.MethodNum]builtin.MethodMeta +} + +func (r RegistryEntry) State() cbor.Er { + return r.state +} + +func (r RegistryEntry) Exports() map[abi.MethodNum]builtin.MethodMeta { + return r.methods +} + +func (r RegistryEntry) Code() cid.Cid { + return r.code +} + +func MakeRegistryLegacy(actors []rtt.VMActor) []RegistryEntry { + registry := make([]RegistryEntry, 0) + + for _, actor := range actors { + methodMap := make(map[abi.MethodNum]builtin.MethodMeta) + for methodNum, method := range actor.Exports() { + if method != nil { + methodMap[abi.MethodNum(methodNum)] = makeMethodMeta(method) + } + } + registry = append(registry, RegistryEntry{ + code: actor.Code(), + methods: methodMap, + state: actor.State(), + }) + } + + return registry +} + +func makeMethodMeta(method interface{}) builtin.MethodMeta { + ev := reflect.ValueOf(method) + // Extract the method names using reflection. These + // method names always match the field names in the + // `builtin.Method*` structs (tested in the specs-actors + // tests). + fnName := runtime.FuncForPC(ev.Pointer()).Name() + fnName = strings.TrimSuffix(fnName[strings.LastIndexByte(fnName, '.')+1:], "-fm") + return builtin.MethodMeta{ + Name: fnName, + Method: method, + } +} + +func MakeRegistry(av actorstypes.Version) []RegistryEntry { + if av < actorstypes.Version8 { + panic("expected version v8 and up only, use specs-actors for v0-7") + } + registry := make([]RegistryEntry, 0) + + codeIDs, err := actors.GetActorCodeIDs(av) + if err != nil { + panic(err) + } + + switch av { + {{range .versions}} + {{if (ge . 8)}} + case actorstypes.Version{{.}}: + for key, codeID := range codeIDs { + switch key { + case manifest.AccountKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: account{{.}}.Methods, + state: new(account{{.}}.State), + }) + case manifest.CronKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: cron{{.}}.Methods, + state: new(cron{{.}}.State), + }) + case manifest.InitKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: _init{{.}}.Methods, + state: new(_init{{.}}.State), + }) + case manifest.MarketKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: market{{.}}.Methods, + state: new(market{{.}}.State), + }) + case manifest.MinerKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: miner{{.}}.Methods, + state: new(miner{{.}}.State), + }) + case manifest.MultisigKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: multisig{{.}}.Methods, + state: new(multisig{{.}}.State), + }) + case manifest.PaychKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: paych{{.}}.Methods, + state: new(paych{{.}}.State), + }) + case manifest.PowerKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: power{{.}}.Methods, + state: new(power{{.}}.State), + }) + case manifest.RewardKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: reward{{.}}.Methods, + state: new(reward{{.}}.State), + }) + case manifest.SystemKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: system{{.}}.Methods, + state: new(system{{.}}.State), + }) + case manifest.VerifregKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: verifreg{{.}}.Methods, + state: new(verifreg{{.}}.State), + }) + {{if (ge . 9)}}case manifest.DatacapKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: datacap{{.}}.Methods, + state: new(datacap{{.}}.State), + }){{end}} + {{if (ge . 10)}} + case manifest.EvmKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: evm{{.}}.Methods, + state: new(evm{{.}}.State), + }) + case manifest.EamKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: eam{{.}}.Methods, + state: nil, + }) + case manifest.PlaceholderKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: placeholder{{.}}.Methods, + state: nil, + }) + case manifest.EthAccountKey: + registry = append(registry, RegistryEntry{ + code: codeID, + methods: ethaccount{{.}}.Methods, + state: nil, + }) + {{end}} + } + } + {{end}} + {{end}} + + default: + panic("expected version v8 and up only, use specs-actors for v0-7") + } + + return registry +} diff --git a/chain/actors/builtin/reward/actor.go.template b/chain/actors/builtin/reward/actor.go.template new file mode 100644 index 000000000..81bf91ac3 --- /dev/null +++ b/chain/actors/builtin/reward/actor.go.template @@ -0,0 +1,99 @@ +package reward + +import ( + "github.com/ipfs/go-cid" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/abi" + reward0 "github.com/filecoin-project/specs-actors/actors/builtin/reward" + "github.com/filecoin-project/lotus/chain/actors" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-state-types/cbor" +{{range .versions}} + {{if (le . 7)}} + builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin" + {{end}} +{{end}} + builtin{{.latestVersion}} "github.com/filecoin-project/go-state-types/builtin" + + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/go-state-types/manifest" +) + +var ( + Address = builtin{{.latestVersion}}.RewardActorAddr + Methods = builtin{{.latestVersion}}.MethodsReward +) + +func Load(store adt.Store, act *types.Actor) (State, error) { + if name, av, ok := actors.GetActorMetaByCode(act.Code); ok { + if name != manifest.RewardKey { + return nil, xerrors.Errorf("actor code is not reward: %s", name) + } + + switch av { + {{range .versions}} + {{if (ge . 8)}} + case actorstypes.Version{{.}}: + return load{{.}}(store, act.Head) + {{end}} + {{end}} + } + } + + switch act.Code { +{{range .versions}} + {{if (le . 7)}} + case builtin{{.}}.RewardActorCodeID: + return load{{.}}(store, act.Head) + {{end}} +{{end}} + } + + return nil, xerrors.Errorf("unknown actor code %s", act.Code) +} + +func MakeState(store adt.Store, av actorstypes.Version, currRealizedPower abi.StoragePower) (State, error) { + switch av { +{{range .versions}} + case actorstypes.Version{{.}}: + return make{{.}}(store, currRealizedPower) +{{end}} +} + return nil, xerrors.Errorf("unknown actor version %d", av) +} + +type State interface { + cbor.Marshaler + + Code() cid.Cid + ActorKey() string + ActorVersion() actorstypes.Version + + ThisEpochBaselinePower() (abi.StoragePower, error) + ThisEpochReward() (abi.StoragePower, error) + ThisEpochRewardSmoothed() (builtin.FilterEstimate, error) + + EffectiveBaselinePower() (abi.StoragePower, error) + EffectiveNetworkTime() (abi.ChainEpoch, error) + + TotalStoragePowerReward() (abi.TokenAmount, error) + + CumsumBaseline() (abi.StoragePower, error) + CumsumRealized() (abi.StoragePower, error) + + InitialPledgeForPower(abi.StoragePower, abi.TokenAmount, *builtin.FilterEstimate, abi.TokenAmount) (abi.TokenAmount, error) + PreCommitDepositForPower(builtin.FilterEstimate, abi.StoragePower) (abi.TokenAmount, error) + GetState() interface{} +} + +type AwardBlockRewardParams = reward0.AwardBlockRewardParams + +func AllCodes() []cid.Cid { + return []cid.Cid{ {{range .versions}} + (&state{{.}}{}).Code(), + {{- end}} + } +} diff --git a/chain/actors/builtin/reward/reward.go b/chain/actors/builtin/reward/reward.go new file mode 100644 index 000000000..b0060a217 --- /dev/null +++ b/chain/actors/builtin/reward/reward.go @@ -0,0 +1,163 @@ +package reward + +import ( + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtin11 "github.com/filecoin-project/go-state-types/builtin" + "github.com/filecoin-project/go-state-types/cbor" + "github.com/filecoin-project/go-state-types/manifest" + builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" + reward0 "github.com/filecoin-project/specs-actors/actors/builtin/reward" + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + builtin3 "github.com/filecoin-project/specs-actors/v3/actors/builtin" + builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" + builtin6 "github.com/filecoin-project/specs-actors/v6/actors/builtin" + builtin7 "github.com/filecoin-project/specs-actors/v7/actors/builtin" + + "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/types" +) + +var ( + Address = builtin11.RewardActorAddr + Methods = builtin11.MethodsReward +) + +func Load(store adt.Store, act *types.Actor) (State, error) { + if name, av, ok := actors.GetActorMetaByCode(act.Code); ok { + if name != manifest.RewardKey { + return nil, xerrors.Errorf("actor code is not reward: %s", name) + } + + switch av { + + case actorstypes.Version8: + return load8(store, act.Head) + + case actorstypes.Version9: + return load9(store, act.Head) + + case actorstypes.Version10: + return load10(store, act.Head) + + case actorstypes.Version11: + return load11(store, act.Head) + + } + } + + switch act.Code { + + case builtin0.RewardActorCodeID: + return load0(store, act.Head) + + case builtin2.RewardActorCodeID: + return load2(store, act.Head) + + case builtin3.RewardActorCodeID: + return load3(store, act.Head) + + case builtin4.RewardActorCodeID: + return load4(store, act.Head) + + case builtin5.RewardActorCodeID: + return load5(store, act.Head) + + case builtin6.RewardActorCodeID: + return load6(store, act.Head) + + case builtin7.RewardActorCodeID: + return load7(store, act.Head) + + } + + return nil, xerrors.Errorf("unknown actor code %s", act.Code) +} + +func MakeState(store adt.Store, av actorstypes.Version, currRealizedPower abi.StoragePower) (State, error) { + switch av { + + case actorstypes.Version0: + return make0(store, currRealizedPower) + + case actorstypes.Version2: + return make2(store, currRealizedPower) + + case actorstypes.Version3: + return make3(store, currRealizedPower) + + case actorstypes.Version4: + return make4(store, currRealizedPower) + + case actorstypes.Version5: + return make5(store, currRealizedPower) + + case actorstypes.Version6: + return make6(store, currRealizedPower) + + case actorstypes.Version7: + return make7(store, currRealizedPower) + + case actorstypes.Version8: + return make8(store, currRealizedPower) + + case actorstypes.Version9: + return make9(store, currRealizedPower) + + case actorstypes.Version10: + return make10(store, currRealizedPower) + + case actorstypes.Version11: + return make11(store, currRealizedPower) + + } + return nil, xerrors.Errorf("unknown actor version %d", av) +} + +type State interface { + cbor.Marshaler + + Code() cid.Cid + ActorKey() string + ActorVersion() actorstypes.Version + + ThisEpochBaselinePower() (abi.StoragePower, error) + ThisEpochReward() (abi.StoragePower, error) + ThisEpochRewardSmoothed() (builtin.FilterEstimate, error) + + EffectiveBaselinePower() (abi.StoragePower, error) + EffectiveNetworkTime() (abi.ChainEpoch, error) + + TotalStoragePowerReward() (abi.TokenAmount, error) + + CumsumBaseline() (abi.StoragePower, error) + CumsumRealized() (abi.StoragePower, error) + + InitialPledgeForPower(abi.StoragePower, abi.TokenAmount, *builtin.FilterEstimate, abi.TokenAmount) (abi.TokenAmount, error) + PreCommitDepositForPower(builtin.FilterEstimate, abi.StoragePower) (abi.TokenAmount, error) + GetState() interface{} +} + +type AwardBlockRewardParams = reward0.AwardBlockRewardParams + +func AllCodes() []cid.Cid { + return []cid.Cid{ + (&state0{}).Code(), + (&state2{}).Code(), + (&state3{}).Code(), + (&state4{}).Code(), + (&state5{}).Code(), + (&state6{}).Code(), + (&state7{}).Code(), + (&state8{}).Code(), + (&state9{}).Code(), + (&state10{}).Code(), + (&state11{}).Code(), + } +} diff --git a/chain/actors/builtin/reward/state.go.template b/chain/actors/builtin/reward/state.go.template new file mode 100644 index 000000000..28ddb80f0 --- /dev/null +++ b/chain/actors/builtin/reward/state.go.template @@ -0,0 +1,140 @@ +package reward + +import ( + "fmt" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/abi" + "github.com/ipfs/go-cid" + + "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/go-state-types/manifest" + +{{if (le .v 7)}} + miner{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/builtin/miner" + reward{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/builtin/reward" + smoothing{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/util/smoothing" +{{else}} + smoothing{{.v}} "github.com/filecoin-project/go-state-types/builtin{{.import}}util/smoothing" + miner{{.v}} "github.com/filecoin-project/go-state-types/builtin{{.import}}miner" + reward{{.v}} "github.com/filecoin-project/go-state-types/builtin{{.import}}reward" +{{end}} +) + +var _ State = (*state{{.v}})(nil) + +func load{{.v}}(store adt.Store, root cid.Cid) (State, error) { + out := state{{.v}}{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make{{.v}}(store adt.Store, currRealizedPower abi.StoragePower) (State, error) { + out := state{{.v}}{store: store} + out.State = *reward{{.v}}.ConstructState(currRealizedPower) + return &out, nil +} + +type state{{.v}} struct { + reward{{.v}}.State + store adt.Store +} + +func (s *state{{.v}}) ThisEpochReward() (abi.TokenAmount, error) { + return s.State.ThisEpochReward, nil +} + +func (s *state{{.v}}) ThisEpochRewardSmoothed() (builtin.FilterEstimate, error) { +{{if (ge .v 2)}} + return builtin.FilterEstimate{ + PositionEstimate: s.State.ThisEpochRewardSmoothed.PositionEstimate, + VelocityEstimate: s.State.ThisEpochRewardSmoothed.VelocityEstimate, + }, nil +{{else}} + return builtin.FilterEstimate(*s.State.ThisEpochRewardSmoothed), nil +{{end}} +} + +func (s *state{{.v}}) ThisEpochBaselinePower() (abi.StoragePower, error) { + return s.State.ThisEpochBaselinePower, nil +} + +func (s *state{{.v}}) TotalStoragePowerReward() (abi.TokenAmount, error) { + return s.State.{{if (ge .v 2)}}TotalStoragePowerReward{{else}}TotalMined{{end}}, nil +} + +func (s *state{{.v}}) EffectiveBaselinePower() (abi.StoragePower, error) { + return s.State.EffectiveBaselinePower, nil +} + +func (s *state{{.v}}) EffectiveNetworkTime() (abi.ChainEpoch, error) { + return s.State.EffectiveNetworkTime, nil +} + +func (s *state{{.v}}) CumsumBaseline() (reward{{.v}}.Spacetime, error) { + return s.State.CumsumBaseline, nil +} + +func (s *state{{.v}}) CumsumRealized() (reward{{.v}}.Spacetime, error) { + return s.State.CumsumRealized, nil +} +{{if (ge .v 2)}} +func (s *state{{.v}}) InitialPledgeForPower(qaPower abi.StoragePower, networkTotalPledge abi.TokenAmount, networkQAPower *builtin.FilterEstimate, circSupply abi.TokenAmount) (abi.TokenAmount, error) { + return miner{{.v}}.InitialPledgeForPower( + qaPower, + s.State.ThisEpochBaselinePower, + s.State.ThisEpochRewardSmoothed, + smoothing{{.v}}.FilterEstimate{ + PositionEstimate: networkQAPower.PositionEstimate, + VelocityEstimate: networkQAPower.VelocityEstimate, + }, + circSupply, + ), nil +} +{{else}} +func (s *state0) InitialPledgeForPower(sectorWeight abi.StoragePower, networkTotalPledge abi.TokenAmount, networkQAPower *builtin.FilterEstimate, circSupply abi.TokenAmount) (abi.TokenAmount, error) { + return miner0.InitialPledgeForPower( + sectorWeight, + s.State.ThisEpochBaselinePower, + networkTotalPledge, + s.State.ThisEpochRewardSmoothed, + &smoothing0.FilterEstimate{ + PositionEstimate: networkQAPower.PositionEstimate, + VelocityEstimate: networkQAPower.VelocityEstimate, + }, + circSupply), nil +} +{{end}} +func (s *state{{.v}}) PreCommitDepositForPower(networkQAPower builtin.FilterEstimate, sectorWeight abi.StoragePower) (abi.TokenAmount, error) { + return miner{{.v}}.PreCommitDepositForPower(s.State.ThisEpochRewardSmoothed, + {{if (le .v 0)}}&{{end}}smoothing{{.v}}.FilterEstimate{ + PositionEstimate: networkQAPower.PositionEstimate, + VelocityEstimate: networkQAPower.VelocityEstimate, + }, + sectorWeight), nil +} + +func (s *state{{.v}}) GetState() interface{} { + return &s.State +} + +func (s *state{{.v}}) ActorKey() string { + return manifest.RewardKey +} + +func (s *state{{.v}}) ActorVersion() actorstypes.Version { + return actorstypes.Version{{.v}} +} + +func (s *state{{.v}}) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/reward/v0.go b/chain/actors/builtin/reward/v0.go new file mode 100644 index 000000000..66736f9e0 --- /dev/null +++ b/chain/actors/builtin/reward/v0.go @@ -0,0 +1,117 @@ +package reward + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + miner0 "github.com/filecoin-project/specs-actors/actors/builtin/miner" + reward0 "github.com/filecoin-project/specs-actors/actors/builtin/reward" + smoothing0 "github.com/filecoin-project/specs-actors/actors/util/smoothing" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" +) + +var _ State = (*state0)(nil) + +func load0(store adt.Store, root cid.Cid) (State, error) { + out := state0{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make0(store adt.Store, currRealizedPower abi.StoragePower) (State, error) { + out := state0{store: store} + out.State = *reward0.ConstructState(currRealizedPower) + return &out, nil +} + +type state0 struct { + reward0.State + store adt.Store +} + +func (s *state0) ThisEpochReward() (abi.TokenAmount, error) { + return s.State.ThisEpochReward, nil +} + +func (s *state0) ThisEpochRewardSmoothed() (builtin.FilterEstimate, error) { + + return builtin.FilterEstimate(*s.State.ThisEpochRewardSmoothed), nil + +} + +func (s *state0) ThisEpochBaselinePower() (abi.StoragePower, error) { + return s.State.ThisEpochBaselinePower, nil +} + +func (s *state0) TotalStoragePowerReward() (abi.TokenAmount, error) { + return s.State.TotalMined, nil +} + +func (s *state0) EffectiveBaselinePower() (abi.StoragePower, error) { + return s.State.EffectiveBaselinePower, nil +} + +func (s *state0) EffectiveNetworkTime() (abi.ChainEpoch, error) { + return s.State.EffectiveNetworkTime, nil +} + +func (s *state0) CumsumBaseline() (reward0.Spacetime, error) { + return s.State.CumsumBaseline, nil +} + +func (s *state0) CumsumRealized() (reward0.Spacetime, error) { + return s.State.CumsumRealized, nil +} + +func (s *state0) InitialPledgeForPower(sectorWeight abi.StoragePower, networkTotalPledge abi.TokenAmount, networkQAPower *builtin.FilterEstimate, circSupply abi.TokenAmount) (abi.TokenAmount, error) { + return miner0.InitialPledgeForPower( + sectorWeight, + s.State.ThisEpochBaselinePower, + networkTotalPledge, + s.State.ThisEpochRewardSmoothed, + &smoothing0.FilterEstimate{ + PositionEstimate: networkQAPower.PositionEstimate, + VelocityEstimate: networkQAPower.VelocityEstimate, + }, + circSupply), nil +} + +func (s *state0) PreCommitDepositForPower(networkQAPower builtin.FilterEstimate, sectorWeight abi.StoragePower) (abi.TokenAmount, error) { + return miner0.PreCommitDepositForPower(s.State.ThisEpochRewardSmoothed, + &smoothing0.FilterEstimate{ + PositionEstimate: networkQAPower.PositionEstimate, + VelocityEstimate: networkQAPower.VelocityEstimate, + }, + sectorWeight), nil +} + +func (s *state0) GetState() interface{} { + return &s.State +} + +func (s *state0) ActorKey() string { + return manifest.RewardKey +} + +func (s *state0) ActorVersion() actorstypes.Version { + return actorstypes.Version0 +} + +func (s *state0) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/reward/v10.go b/chain/actors/builtin/reward/v10.go new file mode 100644 index 000000000..3ffe9a267 --- /dev/null +++ b/chain/actors/builtin/reward/v10.go @@ -0,0 +1,120 @@ +package reward + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + miner10 "github.com/filecoin-project/go-state-types/builtin/v10/miner" + reward10 "github.com/filecoin-project/go-state-types/builtin/v10/reward" + smoothing10 "github.com/filecoin-project/go-state-types/builtin/v10/util/smoothing" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" +) + +var _ State = (*state10)(nil) + +func load10(store adt.Store, root cid.Cid) (State, error) { + out := state10{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make10(store adt.Store, currRealizedPower abi.StoragePower) (State, error) { + out := state10{store: store} + out.State = *reward10.ConstructState(currRealizedPower) + return &out, nil +} + +type state10 struct { + reward10.State + store adt.Store +} + +func (s *state10) ThisEpochReward() (abi.TokenAmount, error) { + return s.State.ThisEpochReward, nil +} + +func (s *state10) ThisEpochRewardSmoothed() (builtin.FilterEstimate, error) { + + return builtin.FilterEstimate{ + PositionEstimate: s.State.ThisEpochRewardSmoothed.PositionEstimate, + VelocityEstimate: s.State.ThisEpochRewardSmoothed.VelocityEstimate, + }, nil + +} + +func (s *state10) ThisEpochBaselinePower() (abi.StoragePower, error) { + return s.State.ThisEpochBaselinePower, nil +} + +func (s *state10) TotalStoragePowerReward() (abi.TokenAmount, error) { + return s.State.TotalStoragePowerReward, nil +} + +func (s *state10) EffectiveBaselinePower() (abi.StoragePower, error) { + return s.State.EffectiveBaselinePower, nil +} + +func (s *state10) EffectiveNetworkTime() (abi.ChainEpoch, error) { + return s.State.EffectiveNetworkTime, nil +} + +func (s *state10) CumsumBaseline() (reward10.Spacetime, error) { + return s.State.CumsumBaseline, nil +} + +func (s *state10) CumsumRealized() (reward10.Spacetime, error) { + return s.State.CumsumRealized, nil +} + +func (s *state10) InitialPledgeForPower(qaPower abi.StoragePower, networkTotalPledge abi.TokenAmount, networkQAPower *builtin.FilterEstimate, circSupply abi.TokenAmount) (abi.TokenAmount, error) { + return miner10.InitialPledgeForPower( + qaPower, + s.State.ThisEpochBaselinePower, + s.State.ThisEpochRewardSmoothed, + smoothing10.FilterEstimate{ + PositionEstimate: networkQAPower.PositionEstimate, + VelocityEstimate: networkQAPower.VelocityEstimate, + }, + circSupply, + ), nil +} + +func (s *state10) PreCommitDepositForPower(networkQAPower builtin.FilterEstimate, sectorWeight abi.StoragePower) (abi.TokenAmount, error) { + return miner10.PreCommitDepositForPower(s.State.ThisEpochRewardSmoothed, + smoothing10.FilterEstimate{ + PositionEstimate: networkQAPower.PositionEstimate, + VelocityEstimate: networkQAPower.VelocityEstimate, + }, + sectorWeight), nil +} + +func (s *state10) GetState() interface{} { + return &s.State +} + +func (s *state10) ActorKey() string { + return manifest.RewardKey +} + +func (s *state10) ActorVersion() actorstypes.Version { + return actorstypes.Version10 +} + +func (s *state10) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/reward/v11.go b/chain/actors/builtin/reward/v11.go new file mode 100644 index 000000000..1d6a5fdf4 --- /dev/null +++ b/chain/actors/builtin/reward/v11.go @@ -0,0 +1,120 @@ +package reward + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + miner11 "github.com/filecoin-project/go-state-types/builtin/v11/miner" + reward11 "github.com/filecoin-project/go-state-types/builtin/v11/reward" + smoothing11 "github.com/filecoin-project/go-state-types/builtin/v11/util/smoothing" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" +) + +var _ State = (*state11)(nil) + +func load11(store adt.Store, root cid.Cid) (State, error) { + out := state11{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make11(store adt.Store, currRealizedPower abi.StoragePower) (State, error) { + out := state11{store: store} + out.State = *reward11.ConstructState(currRealizedPower) + return &out, nil +} + +type state11 struct { + reward11.State + store adt.Store +} + +func (s *state11) ThisEpochReward() (abi.TokenAmount, error) { + return s.State.ThisEpochReward, nil +} + +func (s *state11) ThisEpochRewardSmoothed() (builtin.FilterEstimate, error) { + + return builtin.FilterEstimate{ + PositionEstimate: s.State.ThisEpochRewardSmoothed.PositionEstimate, + VelocityEstimate: s.State.ThisEpochRewardSmoothed.VelocityEstimate, + }, nil + +} + +func (s *state11) ThisEpochBaselinePower() (abi.StoragePower, error) { + return s.State.ThisEpochBaselinePower, nil +} + +func (s *state11) TotalStoragePowerReward() (abi.TokenAmount, error) { + return s.State.TotalStoragePowerReward, nil +} + +func (s *state11) EffectiveBaselinePower() (abi.StoragePower, error) { + return s.State.EffectiveBaselinePower, nil +} + +func (s *state11) EffectiveNetworkTime() (abi.ChainEpoch, error) { + return s.State.EffectiveNetworkTime, nil +} + +func (s *state11) CumsumBaseline() (reward11.Spacetime, error) { + return s.State.CumsumBaseline, nil +} + +func (s *state11) CumsumRealized() (reward11.Spacetime, error) { + return s.State.CumsumRealized, nil +} + +func (s *state11) InitialPledgeForPower(qaPower abi.StoragePower, networkTotalPledge abi.TokenAmount, networkQAPower *builtin.FilterEstimate, circSupply abi.TokenAmount) (abi.TokenAmount, error) { + return miner11.InitialPledgeForPower( + qaPower, + s.State.ThisEpochBaselinePower, + s.State.ThisEpochRewardSmoothed, + smoothing11.FilterEstimate{ + PositionEstimate: networkQAPower.PositionEstimate, + VelocityEstimate: networkQAPower.VelocityEstimate, + }, + circSupply, + ), nil +} + +func (s *state11) PreCommitDepositForPower(networkQAPower builtin.FilterEstimate, sectorWeight abi.StoragePower) (abi.TokenAmount, error) { + return miner11.PreCommitDepositForPower(s.State.ThisEpochRewardSmoothed, + smoothing11.FilterEstimate{ + PositionEstimate: networkQAPower.PositionEstimate, + VelocityEstimate: networkQAPower.VelocityEstimate, + }, + sectorWeight), nil +} + +func (s *state11) GetState() interface{} { + return &s.State +} + +func (s *state11) ActorKey() string { + return manifest.RewardKey +} + +func (s *state11) ActorVersion() actorstypes.Version { + return actorstypes.Version11 +} + +func (s *state11) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/reward/v2.go b/chain/actors/builtin/reward/v2.go new file mode 100644 index 000000000..6d640f2e9 --- /dev/null +++ b/chain/actors/builtin/reward/v2.go @@ -0,0 +1,120 @@ +package reward + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/miner" + reward2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/reward" + smoothing2 "github.com/filecoin-project/specs-actors/v2/actors/util/smoothing" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" +) + +var _ State = (*state2)(nil) + +func load2(store adt.Store, root cid.Cid) (State, error) { + out := state2{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make2(store adt.Store, currRealizedPower abi.StoragePower) (State, error) { + out := state2{store: store} + out.State = *reward2.ConstructState(currRealizedPower) + return &out, nil +} + +type state2 struct { + reward2.State + store adt.Store +} + +func (s *state2) ThisEpochReward() (abi.TokenAmount, error) { + return s.State.ThisEpochReward, nil +} + +func (s *state2) ThisEpochRewardSmoothed() (builtin.FilterEstimate, error) { + + return builtin.FilterEstimate{ + PositionEstimate: s.State.ThisEpochRewardSmoothed.PositionEstimate, + VelocityEstimate: s.State.ThisEpochRewardSmoothed.VelocityEstimate, + }, nil + +} + +func (s *state2) ThisEpochBaselinePower() (abi.StoragePower, error) { + return s.State.ThisEpochBaselinePower, nil +} + +func (s *state2) TotalStoragePowerReward() (abi.TokenAmount, error) { + return s.State.TotalStoragePowerReward, nil +} + +func (s *state2) EffectiveBaselinePower() (abi.StoragePower, error) { + return s.State.EffectiveBaselinePower, nil +} + +func (s *state2) EffectiveNetworkTime() (abi.ChainEpoch, error) { + return s.State.EffectiveNetworkTime, nil +} + +func (s *state2) CumsumBaseline() (reward2.Spacetime, error) { + return s.State.CumsumBaseline, nil +} + +func (s *state2) CumsumRealized() (reward2.Spacetime, error) { + return s.State.CumsumRealized, nil +} + +func (s *state2) InitialPledgeForPower(qaPower abi.StoragePower, networkTotalPledge abi.TokenAmount, networkQAPower *builtin.FilterEstimate, circSupply abi.TokenAmount) (abi.TokenAmount, error) { + return miner2.InitialPledgeForPower( + qaPower, + s.State.ThisEpochBaselinePower, + s.State.ThisEpochRewardSmoothed, + smoothing2.FilterEstimate{ + PositionEstimate: networkQAPower.PositionEstimate, + VelocityEstimate: networkQAPower.VelocityEstimate, + }, + circSupply, + ), nil +} + +func (s *state2) PreCommitDepositForPower(networkQAPower builtin.FilterEstimate, sectorWeight abi.StoragePower) (abi.TokenAmount, error) { + return miner2.PreCommitDepositForPower(s.State.ThisEpochRewardSmoothed, + smoothing2.FilterEstimate{ + PositionEstimate: networkQAPower.PositionEstimate, + VelocityEstimate: networkQAPower.VelocityEstimate, + }, + sectorWeight), nil +} + +func (s *state2) GetState() interface{} { + return &s.State +} + +func (s *state2) ActorKey() string { + return manifest.RewardKey +} + +func (s *state2) ActorVersion() actorstypes.Version { + return actorstypes.Version2 +} + +func (s *state2) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/reward/v3.go b/chain/actors/builtin/reward/v3.go new file mode 100644 index 000000000..fe8e555c9 --- /dev/null +++ b/chain/actors/builtin/reward/v3.go @@ -0,0 +1,120 @@ +package reward + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + miner3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/miner" + reward3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/reward" + smoothing3 "github.com/filecoin-project/specs-actors/v3/actors/util/smoothing" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" +) + +var _ State = (*state3)(nil) + +func load3(store adt.Store, root cid.Cid) (State, error) { + out := state3{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make3(store adt.Store, currRealizedPower abi.StoragePower) (State, error) { + out := state3{store: store} + out.State = *reward3.ConstructState(currRealizedPower) + return &out, nil +} + +type state3 struct { + reward3.State + store adt.Store +} + +func (s *state3) ThisEpochReward() (abi.TokenAmount, error) { + return s.State.ThisEpochReward, nil +} + +func (s *state3) ThisEpochRewardSmoothed() (builtin.FilterEstimate, error) { + + return builtin.FilterEstimate{ + PositionEstimate: s.State.ThisEpochRewardSmoothed.PositionEstimate, + VelocityEstimate: s.State.ThisEpochRewardSmoothed.VelocityEstimate, + }, nil + +} + +func (s *state3) ThisEpochBaselinePower() (abi.StoragePower, error) { + return s.State.ThisEpochBaselinePower, nil +} + +func (s *state3) TotalStoragePowerReward() (abi.TokenAmount, error) { + return s.State.TotalStoragePowerReward, nil +} + +func (s *state3) EffectiveBaselinePower() (abi.StoragePower, error) { + return s.State.EffectiveBaselinePower, nil +} + +func (s *state3) EffectiveNetworkTime() (abi.ChainEpoch, error) { + return s.State.EffectiveNetworkTime, nil +} + +func (s *state3) CumsumBaseline() (reward3.Spacetime, error) { + return s.State.CumsumBaseline, nil +} + +func (s *state3) CumsumRealized() (reward3.Spacetime, error) { + return s.State.CumsumRealized, nil +} + +func (s *state3) InitialPledgeForPower(qaPower abi.StoragePower, networkTotalPledge abi.TokenAmount, networkQAPower *builtin.FilterEstimate, circSupply abi.TokenAmount) (abi.TokenAmount, error) { + return miner3.InitialPledgeForPower( + qaPower, + s.State.ThisEpochBaselinePower, + s.State.ThisEpochRewardSmoothed, + smoothing3.FilterEstimate{ + PositionEstimate: networkQAPower.PositionEstimate, + VelocityEstimate: networkQAPower.VelocityEstimate, + }, + circSupply, + ), nil +} + +func (s *state3) PreCommitDepositForPower(networkQAPower builtin.FilterEstimate, sectorWeight abi.StoragePower) (abi.TokenAmount, error) { + return miner3.PreCommitDepositForPower(s.State.ThisEpochRewardSmoothed, + smoothing3.FilterEstimate{ + PositionEstimate: networkQAPower.PositionEstimate, + VelocityEstimate: networkQAPower.VelocityEstimate, + }, + sectorWeight), nil +} + +func (s *state3) GetState() interface{} { + return &s.State +} + +func (s *state3) ActorKey() string { + return manifest.RewardKey +} + +func (s *state3) ActorVersion() actorstypes.Version { + return actorstypes.Version3 +} + +func (s *state3) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/reward/v4.go b/chain/actors/builtin/reward/v4.go new file mode 100644 index 000000000..f0decd392 --- /dev/null +++ b/chain/actors/builtin/reward/v4.go @@ -0,0 +1,120 @@ +package reward + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + miner4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/miner" + reward4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/reward" + smoothing4 "github.com/filecoin-project/specs-actors/v4/actors/util/smoothing" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" +) + +var _ State = (*state4)(nil) + +func load4(store adt.Store, root cid.Cid) (State, error) { + out := state4{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make4(store adt.Store, currRealizedPower abi.StoragePower) (State, error) { + out := state4{store: store} + out.State = *reward4.ConstructState(currRealizedPower) + return &out, nil +} + +type state4 struct { + reward4.State + store adt.Store +} + +func (s *state4) ThisEpochReward() (abi.TokenAmount, error) { + return s.State.ThisEpochReward, nil +} + +func (s *state4) ThisEpochRewardSmoothed() (builtin.FilterEstimate, error) { + + return builtin.FilterEstimate{ + PositionEstimate: s.State.ThisEpochRewardSmoothed.PositionEstimate, + VelocityEstimate: s.State.ThisEpochRewardSmoothed.VelocityEstimate, + }, nil + +} + +func (s *state4) ThisEpochBaselinePower() (abi.StoragePower, error) { + return s.State.ThisEpochBaselinePower, nil +} + +func (s *state4) TotalStoragePowerReward() (abi.TokenAmount, error) { + return s.State.TotalStoragePowerReward, nil +} + +func (s *state4) EffectiveBaselinePower() (abi.StoragePower, error) { + return s.State.EffectiveBaselinePower, nil +} + +func (s *state4) EffectiveNetworkTime() (abi.ChainEpoch, error) { + return s.State.EffectiveNetworkTime, nil +} + +func (s *state4) CumsumBaseline() (reward4.Spacetime, error) { + return s.State.CumsumBaseline, nil +} + +func (s *state4) CumsumRealized() (reward4.Spacetime, error) { + return s.State.CumsumRealized, nil +} + +func (s *state4) InitialPledgeForPower(qaPower abi.StoragePower, networkTotalPledge abi.TokenAmount, networkQAPower *builtin.FilterEstimate, circSupply abi.TokenAmount) (abi.TokenAmount, error) { + return miner4.InitialPledgeForPower( + qaPower, + s.State.ThisEpochBaselinePower, + s.State.ThisEpochRewardSmoothed, + smoothing4.FilterEstimate{ + PositionEstimate: networkQAPower.PositionEstimate, + VelocityEstimate: networkQAPower.VelocityEstimate, + }, + circSupply, + ), nil +} + +func (s *state4) PreCommitDepositForPower(networkQAPower builtin.FilterEstimate, sectorWeight abi.StoragePower) (abi.TokenAmount, error) { + return miner4.PreCommitDepositForPower(s.State.ThisEpochRewardSmoothed, + smoothing4.FilterEstimate{ + PositionEstimate: networkQAPower.PositionEstimate, + VelocityEstimate: networkQAPower.VelocityEstimate, + }, + sectorWeight), nil +} + +func (s *state4) GetState() interface{} { + return &s.State +} + +func (s *state4) ActorKey() string { + return manifest.RewardKey +} + +func (s *state4) ActorVersion() actorstypes.Version { + return actorstypes.Version4 +} + +func (s *state4) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/reward/v5.go b/chain/actors/builtin/reward/v5.go new file mode 100644 index 000000000..82be12c0a --- /dev/null +++ b/chain/actors/builtin/reward/v5.go @@ -0,0 +1,120 @@ +package reward + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" + reward5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/reward" + smoothing5 "github.com/filecoin-project/specs-actors/v5/actors/util/smoothing" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" +) + +var _ State = (*state5)(nil) + +func load5(store adt.Store, root cid.Cid) (State, error) { + out := state5{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make5(store adt.Store, currRealizedPower abi.StoragePower) (State, error) { + out := state5{store: store} + out.State = *reward5.ConstructState(currRealizedPower) + return &out, nil +} + +type state5 struct { + reward5.State + store adt.Store +} + +func (s *state5) ThisEpochReward() (abi.TokenAmount, error) { + return s.State.ThisEpochReward, nil +} + +func (s *state5) ThisEpochRewardSmoothed() (builtin.FilterEstimate, error) { + + return builtin.FilterEstimate{ + PositionEstimate: s.State.ThisEpochRewardSmoothed.PositionEstimate, + VelocityEstimate: s.State.ThisEpochRewardSmoothed.VelocityEstimate, + }, nil + +} + +func (s *state5) ThisEpochBaselinePower() (abi.StoragePower, error) { + return s.State.ThisEpochBaselinePower, nil +} + +func (s *state5) TotalStoragePowerReward() (abi.TokenAmount, error) { + return s.State.TotalStoragePowerReward, nil +} + +func (s *state5) EffectiveBaselinePower() (abi.StoragePower, error) { + return s.State.EffectiveBaselinePower, nil +} + +func (s *state5) EffectiveNetworkTime() (abi.ChainEpoch, error) { + return s.State.EffectiveNetworkTime, nil +} + +func (s *state5) CumsumBaseline() (reward5.Spacetime, error) { + return s.State.CumsumBaseline, nil +} + +func (s *state5) CumsumRealized() (reward5.Spacetime, error) { + return s.State.CumsumRealized, nil +} + +func (s *state5) InitialPledgeForPower(qaPower abi.StoragePower, networkTotalPledge abi.TokenAmount, networkQAPower *builtin.FilterEstimate, circSupply abi.TokenAmount) (abi.TokenAmount, error) { + return miner5.InitialPledgeForPower( + qaPower, + s.State.ThisEpochBaselinePower, + s.State.ThisEpochRewardSmoothed, + smoothing5.FilterEstimate{ + PositionEstimate: networkQAPower.PositionEstimate, + VelocityEstimate: networkQAPower.VelocityEstimate, + }, + circSupply, + ), nil +} + +func (s *state5) PreCommitDepositForPower(networkQAPower builtin.FilterEstimate, sectorWeight abi.StoragePower) (abi.TokenAmount, error) { + return miner5.PreCommitDepositForPower(s.State.ThisEpochRewardSmoothed, + smoothing5.FilterEstimate{ + PositionEstimate: networkQAPower.PositionEstimate, + VelocityEstimate: networkQAPower.VelocityEstimate, + }, + sectorWeight), nil +} + +func (s *state5) GetState() interface{} { + return &s.State +} + +func (s *state5) ActorKey() string { + return manifest.RewardKey +} + +func (s *state5) ActorVersion() actorstypes.Version { + return actorstypes.Version5 +} + +func (s *state5) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/reward/v6.go b/chain/actors/builtin/reward/v6.go new file mode 100644 index 000000000..71884dada --- /dev/null +++ b/chain/actors/builtin/reward/v6.go @@ -0,0 +1,120 @@ +package reward + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + miner6 "github.com/filecoin-project/specs-actors/v6/actors/builtin/miner" + reward6 "github.com/filecoin-project/specs-actors/v6/actors/builtin/reward" + smoothing6 "github.com/filecoin-project/specs-actors/v6/actors/util/smoothing" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" +) + +var _ State = (*state6)(nil) + +func load6(store adt.Store, root cid.Cid) (State, error) { + out := state6{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make6(store adt.Store, currRealizedPower abi.StoragePower) (State, error) { + out := state6{store: store} + out.State = *reward6.ConstructState(currRealizedPower) + return &out, nil +} + +type state6 struct { + reward6.State + store adt.Store +} + +func (s *state6) ThisEpochReward() (abi.TokenAmount, error) { + return s.State.ThisEpochReward, nil +} + +func (s *state6) ThisEpochRewardSmoothed() (builtin.FilterEstimate, error) { + + return builtin.FilterEstimate{ + PositionEstimate: s.State.ThisEpochRewardSmoothed.PositionEstimate, + VelocityEstimate: s.State.ThisEpochRewardSmoothed.VelocityEstimate, + }, nil + +} + +func (s *state6) ThisEpochBaselinePower() (abi.StoragePower, error) { + return s.State.ThisEpochBaselinePower, nil +} + +func (s *state6) TotalStoragePowerReward() (abi.TokenAmount, error) { + return s.State.TotalStoragePowerReward, nil +} + +func (s *state6) EffectiveBaselinePower() (abi.StoragePower, error) { + return s.State.EffectiveBaselinePower, nil +} + +func (s *state6) EffectiveNetworkTime() (abi.ChainEpoch, error) { + return s.State.EffectiveNetworkTime, nil +} + +func (s *state6) CumsumBaseline() (reward6.Spacetime, error) { + return s.State.CumsumBaseline, nil +} + +func (s *state6) CumsumRealized() (reward6.Spacetime, error) { + return s.State.CumsumRealized, nil +} + +func (s *state6) InitialPledgeForPower(qaPower abi.StoragePower, networkTotalPledge abi.TokenAmount, networkQAPower *builtin.FilterEstimate, circSupply abi.TokenAmount) (abi.TokenAmount, error) { + return miner6.InitialPledgeForPower( + qaPower, + s.State.ThisEpochBaselinePower, + s.State.ThisEpochRewardSmoothed, + smoothing6.FilterEstimate{ + PositionEstimate: networkQAPower.PositionEstimate, + VelocityEstimate: networkQAPower.VelocityEstimate, + }, + circSupply, + ), nil +} + +func (s *state6) PreCommitDepositForPower(networkQAPower builtin.FilterEstimate, sectorWeight abi.StoragePower) (abi.TokenAmount, error) { + return miner6.PreCommitDepositForPower(s.State.ThisEpochRewardSmoothed, + smoothing6.FilterEstimate{ + PositionEstimate: networkQAPower.PositionEstimate, + VelocityEstimate: networkQAPower.VelocityEstimate, + }, + sectorWeight), nil +} + +func (s *state6) GetState() interface{} { + return &s.State +} + +func (s *state6) ActorKey() string { + return manifest.RewardKey +} + +func (s *state6) ActorVersion() actorstypes.Version { + return actorstypes.Version6 +} + +func (s *state6) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/reward/v7.go b/chain/actors/builtin/reward/v7.go new file mode 100644 index 000000000..bc39f3666 --- /dev/null +++ b/chain/actors/builtin/reward/v7.go @@ -0,0 +1,120 @@ +package reward + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + miner7 "github.com/filecoin-project/specs-actors/v7/actors/builtin/miner" + reward7 "github.com/filecoin-project/specs-actors/v7/actors/builtin/reward" + smoothing7 "github.com/filecoin-project/specs-actors/v7/actors/util/smoothing" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" +) + +var _ State = (*state7)(nil) + +func load7(store adt.Store, root cid.Cid) (State, error) { + out := state7{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make7(store adt.Store, currRealizedPower abi.StoragePower) (State, error) { + out := state7{store: store} + out.State = *reward7.ConstructState(currRealizedPower) + return &out, nil +} + +type state7 struct { + reward7.State + store adt.Store +} + +func (s *state7) ThisEpochReward() (abi.TokenAmount, error) { + return s.State.ThisEpochReward, nil +} + +func (s *state7) ThisEpochRewardSmoothed() (builtin.FilterEstimate, error) { + + return builtin.FilterEstimate{ + PositionEstimate: s.State.ThisEpochRewardSmoothed.PositionEstimate, + VelocityEstimate: s.State.ThisEpochRewardSmoothed.VelocityEstimate, + }, nil + +} + +func (s *state7) ThisEpochBaselinePower() (abi.StoragePower, error) { + return s.State.ThisEpochBaselinePower, nil +} + +func (s *state7) TotalStoragePowerReward() (abi.TokenAmount, error) { + return s.State.TotalStoragePowerReward, nil +} + +func (s *state7) EffectiveBaselinePower() (abi.StoragePower, error) { + return s.State.EffectiveBaselinePower, nil +} + +func (s *state7) EffectiveNetworkTime() (abi.ChainEpoch, error) { + return s.State.EffectiveNetworkTime, nil +} + +func (s *state7) CumsumBaseline() (reward7.Spacetime, error) { + return s.State.CumsumBaseline, nil +} + +func (s *state7) CumsumRealized() (reward7.Spacetime, error) { + return s.State.CumsumRealized, nil +} + +func (s *state7) InitialPledgeForPower(qaPower abi.StoragePower, networkTotalPledge abi.TokenAmount, networkQAPower *builtin.FilterEstimate, circSupply abi.TokenAmount) (abi.TokenAmount, error) { + return miner7.InitialPledgeForPower( + qaPower, + s.State.ThisEpochBaselinePower, + s.State.ThisEpochRewardSmoothed, + smoothing7.FilterEstimate{ + PositionEstimate: networkQAPower.PositionEstimate, + VelocityEstimate: networkQAPower.VelocityEstimate, + }, + circSupply, + ), nil +} + +func (s *state7) PreCommitDepositForPower(networkQAPower builtin.FilterEstimate, sectorWeight abi.StoragePower) (abi.TokenAmount, error) { + return miner7.PreCommitDepositForPower(s.State.ThisEpochRewardSmoothed, + smoothing7.FilterEstimate{ + PositionEstimate: networkQAPower.PositionEstimate, + VelocityEstimate: networkQAPower.VelocityEstimate, + }, + sectorWeight), nil +} + +func (s *state7) GetState() interface{} { + return &s.State +} + +func (s *state7) ActorKey() string { + return manifest.RewardKey +} + +func (s *state7) ActorVersion() actorstypes.Version { + return actorstypes.Version7 +} + +func (s *state7) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/reward/v8.go b/chain/actors/builtin/reward/v8.go new file mode 100644 index 000000000..5f6b96d29 --- /dev/null +++ b/chain/actors/builtin/reward/v8.go @@ -0,0 +1,120 @@ +package reward + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + miner8 "github.com/filecoin-project/go-state-types/builtin/v8/miner" + reward8 "github.com/filecoin-project/go-state-types/builtin/v8/reward" + smoothing8 "github.com/filecoin-project/go-state-types/builtin/v8/util/smoothing" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" +) + +var _ State = (*state8)(nil) + +func load8(store adt.Store, root cid.Cid) (State, error) { + out := state8{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make8(store adt.Store, currRealizedPower abi.StoragePower) (State, error) { + out := state8{store: store} + out.State = *reward8.ConstructState(currRealizedPower) + return &out, nil +} + +type state8 struct { + reward8.State + store adt.Store +} + +func (s *state8) ThisEpochReward() (abi.TokenAmount, error) { + return s.State.ThisEpochReward, nil +} + +func (s *state8) ThisEpochRewardSmoothed() (builtin.FilterEstimate, error) { + + return builtin.FilterEstimate{ + PositionEstimate: s.State.ThisEpochRewardSmoothed.PositionEstimate, + VelocityEstimate: s.State.ThisEpochRewardSmoothed.VelocityEstimate, + }, nil + +} + +func (s *state8) ThisEpochBaselinePower() (abi.StoragePower, error) { + return s.State.ThisEpochBaselinePower, nil +} + +func (s *state8) TotalStoragePowerReward() (abi.TokenAmount, error) { + return s.State.TotalStoragePowerReward, nil +} + +func (s *state8) EffectiveBaselinePower() (abi.StoragePower, error) { + return s.State.EffectiveBaselinePower, nil +} + +func (s *state8) EffectiveNetworkTime() (abi.ChainEpoch, error) { + return s.State.EffectiveNetworkTime, nil +} + +func (s *state8) CumsumBaseline() (reward8.Spacetime, error) { + return s.State.CumsumBaseline, nil +} + +func (s *state8) CumsumRealized() (reward8.Spacetime, error) { + return s.State.CumsumRealized, nil +} + +func (s *state8) InitialPledgeForPower(qaPower abi.StoragePower, networkTotalPledge abi.TokenAmount, networkQAPower *builtin.FilterEstimate, circSupply abi.TokenAmount) (abi.TokenAmount, error) { + return miner8.InitialPledgeForPower( + qaPower, + s.State.ThisEpochBaselinePower, + s.State.ThisEpochRewardSmoothed, + smoothing8.FilterEstimate{ + PositionEstimate: networkQAPower.PositionEstimate, + VelocityEstimate: networkQAPower.VelocityEstimate, + }, + circSupply, + ), nil +} + +func (s *state8) PreCommitDepositForPower(networkQAPower builtin.FilterEstimate, sectorWeight abi.StoragePower) (abi.TokenAmount, error) { + return miner8.PreCommitDepositForPower(s.State.ThisEpochRewardSmoothed, + smoothing8.FilterEstimate{ + PositionEstimate: networkQAPower.PositionEstimate, + VelocityEstimate: networkQAPower.VelocityEstimate, + }, + sectorWeight), nil +} + +func (s *state8) GetState() interface{} { + return &s.State +} + +func (s *state8) ActorKey() string { + return manifest.RewardKey +} + +func (s *state8) ActorVersion() actorstypes.Version { + return actorstypes.Version8 +} + +func (s *state8) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/reward/v9.go b/chain/actors/builtin/reward/v9.go new file mode 100644 index 000000000..6118e2b30 --- /dev/null +++ b/chain/actors/builtin/reward/v9.go @@ -0,0 +1,120 @@ +package reward + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + miner9 "github.com/filecoin-project/go-state-types/builtin/v9/miner" + reward9 "github.com/filecoin-project/go-state-types/builtin/v9/reward" + smoothing9 "github.com/filecoin-project/go-state-types/builtin/v9/util/smoothing" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" +) + +var _ State = (*state9)(nil) + +func load9(store adt.Store, root cid.Cid) (State, error) { + out := state9{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make9(store adt.Store, currRealizedPower abi.StoragePower) (State, error) { + out := state9{store: store} + out.State = *reward9.ConstructState(currRealizedPower) + return &out, nil +} + +type state9 struct { + reward9.State + store adt.Store +} + +func (s *state9) ThisEpochReward() (abi.TokenAmount, error) { + return s.State.ThisEpochReward, nil +} + +func (s *state9) ThisEpochRewardSmoothed() (builtin.FilterEstimate, error) { + + return builtin.FilterEstimate{ + PositionEstimate: s.State.ThisEpochRewardSmoothed.PositionEstimate, + VelocityEstimate: s.State.ThisEpochRewardSmoothed.VelocityEstimate, + }, nil + +} + +func (s *state9) ThisEpochBaselinePower() (abi.StoragePower, error) { + return s.State.ThisEpochBaselinePower, nil +} + +func (s *state9) TotalStoragePowerReward() (abi.TokenAmount, error) { + return s.State.TotalStoragePowerReward, nil +} + +func (s *state9) EffectiveBaselinePower() (abi.StoragePower, error) { + return s.State.EffectiveBaselinePower, nil +} + +func (s *state9) EffectiveNetworkTime() (abi.ChainEpoch, error) { + return s.State.EffectiveNetworkTime, nil +} + +func (s *state9) CumsumBaseline() (reward9.Spacetime, error) { + return s.State.CumsumBaseline, nil +} + +func (s *state9) CumsumRealized() (reward9.Spacetime, error) { + return s.State.CumsumRealized, nil +} + +func (s *state9) InitialPledgeForPower(qaPower abi.StoragePower, networkTotalPledge abi.TokenAmount, networkQAPower *builtin.FilterEstimate, circSupply abi.TokenAmount) (abi.TokenAmount, error) { + return miner9.InitialPledgeForPower( + qaPower, + s.State.ThisEpochBaselinePower, + s.State.ThisEpochRewardSmoothed, + smoothing9.FilterEstimate{ + PositionEstimate: networkQAPower.PositionEstimate, + VelocityEstimate: networkQAPower.VelocityEstimate, + }, + circSupply, + ), nil +} + +func (s *state9) PreCommitDepositForPower(networkQAPower builtin.FilterEstimate, sectorWeight abi.StoragePower) (abi.TokenAmount, error) { + return miner9.PreCommitDepositForPower(s.State.ThisEpochRewardSmoothed, + smoothing9.FilterEstimate{ + PositionEstimate: networkQAPower.PositionEstimate, + VelocityEstimate: networkQAPower.VelocityEstimate, + }, + sectorWeight), nil +} + +func (s *state9) GetState() interface{} { + return &s.State +} + +func (s *state9) ActorKey() string { + return manifest.RewardKey +} + +func (s *state9) ActorVersion() actorstypes.Version { + return actorstypes.Version9 +} + +func (s *state9) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/system/actor.go.template b/chain/actors/builtin/system/actor.go.template new file mode 100644 index 000000000..b725f0820 --- /dev/null +++ b/chain/actors/builtin/system/actor.go.template @@ -0,0 +1,79 @@ +package system + +import ( + "github.com/ipfs/go-cid" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/types" + "github.com/ipfs/go-cid" + + "golang.org/x/xerrors" + +{{range .versions}} + {{if (le . 7)}} + builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin" + {{end}} +{{end}} + builtin{{.latestVersion}} "github.com/filecoin-project/go-state-types/builtin" +) + +var ( + Address = builtin{{.latestVersion}}.SystemActorAddr +) + +func Load(store adt.Store, act *types.Actor) (State, error) { + if name, av, ok := actors.GetActorMetaByCode(act.Code); ok { + if name != manifest.SystemKey { + return nil, xerrors.Errorf("actor code is not system: %s", name) + } + + switch av { + {{range .versions}} + {{if (ge . 8)}} + case actorstypes.Version{{.}}: + return load{{.}}(store, act.Head) + {{end}} + {{end}} + } + } + + switch act.Code { +{{range .versions}} + {{if (le . 7)}} + case builtin{{.}}.SystemActorCodeID: + return load{{.}}(store, act.Head) + {{end}} +{{end}} + } + + return nil, xerrors.Errorf("unknown actor code %s", act.Code) +} + +func MakeState(store adt.Store, av actorstypes.Version, builtinActors cid.Cid) (State, error) { + switch av { +{{range .versions}} + case actorstypes.Version{{.}}: + return make{{.}}(store{{if (ge . 8)}}, builtinActors{{end}}) +{{end}} +} + return nil, xerrors.Errorf("unknown actor version %d", av) +} + +type State interface { + Code() cid.Cid + ActorKey() string + ActorVersion() actorstypes.Version + + GetState() interface{} + GetBuiltinActors() cid.Cid + SetBuiltinActors(cid.Cid) error +} + +func AllCodes() []cid.Cid { + return []cid.Cid{ {{range .versions}} + (&state{{.}}{}).Code(), + {{- end}} + } +} diff --git a/chain/actors/builtin/system/state.go.template b/chain/actors/builtin/system/state.go.template new file mode 100644 index 000000000..c6cab0583 --- /dev/null +++ b/chain/actors/builtin/system/state.go.template @@ -0,0 +1,81 @@ +package system + +import ( + "fmt" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors" + +{{if (le .v 7)}} + system{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/builtin/system" + "golang.org/x/xerrors" +{{else}} + system{{.v}} "github.com/filecoin-project/go-state-types/builtin{{.import}}system" +{{end}} +) + +var _ State = (*state{{.v}})(nil) + +func load{{.v}}(store adt.Store, root cid.Cid) (State, error) { + out := state{{.v}}{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make{{.v}}(store adt.Store{{if (ge .v 8)}}, builtinActors cid.Cid{{end}}) (State, error) { + out := state{{.v}}{store: store} + out.State = system{{.v}}.State{ + {{if (ge .v 8)}}BuiltinActors: builtinActors,{{end}} + } + return &out, nil +} + +type state{{.v}} struct { + system{{.v}}.State + store adt.Store +} + +func (s *state{{.v}}) GetState() interface{} { + return &s.State +} + +func (s *state{{.v}}) GetBuiltinActors() cid.Cid { +{{if (le .v 7)}} + return cid.Undef +{{else}} + return s.State.BuiltinActors +{{end}} +} + +func (s *state{{.v}}) SetBuiltinActors(c cid.Cid) error { +{{if (le .v 7)}} + return xerrors.New("cannot set manifest cid before v8") +{{else}} + s.State.BuiltinActors = c + return nil +{{end}} +} + +func (s *state{{.v}}) ActorKey() string { + return manifest.SystemKey +} + +func (s *state{{.v}}) ActorVersion() actorstypes.Version { + return actorstypes.Version{{.v}} +} + +func (s *state{{.v}}) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/system/system.go b/chain/actors/builtin/system/system.go new file mode 100644 index 000000000..4db8db610 --- /dev/null +++ b/chain/actors/builtin/system/system.go @@ -0,0 +1,142 @@ +package system + +import ( + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtin11 "github.com/filecoin-project/go-state-types/builtin" + "github.com/filecoin-project/go-state-types/manifest" + builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + builtin3 "github.com/filecoin-project/specs-actors/v3/actors/builtin" + builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" + builtin6 "github.com/filecoin-project/specs-actors/v6/actors/builtin" + builtin7 "github.com/filecoin-project/specs-actors/v7/actors/builtin" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/types" +) + +var ( + Address = builtin11.SystemActorAddr +) + +func Load(store adt.Store, act *types.Actor) (State, error) { + if name, av, ok := actors.GetActorMetaByCode(act.Code); ok { + if name != manifest.SystemKey { + return nil, xerrors.Errorf("actor code is not system: %s", name) + } + + switch av { + + case actorstypes.Version8: + return load8(store, act.Head) + + case actorstypes.Version9: + return load9(store, act.Head) + + case actorstypes.Version10: + return load10(store, act.Head) + + case actorstypes.Version11: + return load11(store, act.Head) + + } + } + + switch act.Code { + + case builtin0.SystemActorCodeID: + return load0(store, act.Head) + + case builtin2.SystemActorCodeID: + return load2(store, act.Head) + + case builtin3.SystemActorCodeID: + return load3(store, act.Head) + + case builtin4.SystemActorCodeID: + return load4(store, act.Head) + + case builtin5.SystemActorCodeID: + return load5(store, act.Head) + + case builtin6.SystemActorCodeID: + return load6(store, act.Head) + + case builtin7.SystemActorCodeID: + return load7(store, act.Head) + + } + + return nil, xerrors.Errorf("unknown actor code %s", act.Code) +} + +func MakeState(store adt.Store, av actorstypes.Version, builtinActors cid.Cid) (State, error) { + switch av { + + case actorstypes.Version0: + return make0(store) + + case actorstypes.Version2: + return make2(store) + + case actorstypes.Version3: + return make3(store) + + case actorstypes.Version4: + return make4(store) + + case actorstypes.Version5: + return make5(store) + + case actorstypes.Version6: + return make6(store) + + case actorstypes.Version7: + return make7(store) + + case actorstypes.Version8: + return make8(store, builtinActors) + + case actorstypes.Version9: + return make9(store, builtinActors) + + case actorstypes.Version10: + return make10(store, builtinActors) + + case actorstypes.Version11: + return make11(store, builtinActors) + + } + return nil, xerrors.Errorf("unknown actor version %d", av) +} + +type State interface { + Code() cid.Cid + ActorKey() string + ActorVersion() actorstypes.Version + + GetState() interface{} + GetBuiltinActors() cid.Cid + SetBuiltinActors(cid.Cid) error +} + +func AllCodes() []cid.Cid { + return []cid.Cid{ + (&state0{}).Code(), + (&state2{}).Code(), + (&state3{}).Code(), + (&state4{}).Code(), + (&state5{}).Code(), + (&state6{}).Code(), + (&state7{}).Code(), + (&state8{}).Code(), + (&state9{}).Code(), + (&state10{}).Code(), + (&state11{}).Code(), + } +} diff --git a/chain/actors/builtin/system/v0.go b/chain/actors/builtin/system/v0.go new file mode 100644 index 000000000..d5f0f079e --- /dev/null +++ b/chain/actors/builtin/system/v0.go @@ -0,0 +1,70 @@ +package system + +import ( + "fmt" + + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + system0 "github.com/filecoin-project/specs-actors/actors/builtin/system" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state0)(nil) + +func load0(store adt.Store, root cid.Cid) (State, error) { + out := state0{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make0(store adt.Store) (State, error) { + out := state0{store: store} + out.State = system0.State{} + return &out, nil +} + +type state0 struct { + system0.State + store adt.Store +} + +func (s *state0) GetState() interface{} { + return &s.State +} + +func (s *state0) GetBuiltinActors() cid.Cid { + + return cid.Undef + +} + +func (s *state0) SetBuiltinActors(c cid.Cid) error { + + return xerrors.New("cannot set manifest cid before v8") + +} + +func (s *state0) ActorKey() string { + return manifest.SystemKey +} + +func (s *state0) ActorVersion() actorstypes.Version { + return actorstypes.Version0 +} + +func (s *state0) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/system/v10.go b/chain/actors/builtin/system/v10.go new file mode 100644 index 000000000..2cdb39682 --- /dev/null +++ b/chain/actors/builtin/system/v10.go @@ -0,0 +1,72 @@ +package system + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + actorstypes "github.com/filecoin-project/go-state-types/actors" + system10 "github.com/filecoin-project/go-state-types/builtin/v10/system" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state10)(nil) + +func load10(store adt.Store, root cid.Cid) (State, error) { + out := state10{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make10(store adt.Store, builtinActors cid.Cid) (State, error) { + out := state10{store: store} + out.State = system10.State{ + BuiltinActors: builtinActors, + } + return &out, nil +} + +type state10 struct { + system10.State + store adt.Store +} + +func (s *state10) GetState() interface{} { + return &s.State +} + +func (s *state10) GetBuiltinActors() cid.Cid { + + return s.State.BuiltinActors + +} + +func (s *state10) SetBuiltinActors(c cid.Cid) error { + + s.State.BuiltinActors = c + return nil + +} + +func (s *state10) ActorKey() string { + return manifest.SystemKey +} + +func (s *state10) ActorVersion() actorstypes.Version { + return actorstypes.Version10 +} + +func (s *state10) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/system/v11.go b/chain/actors/builtin/system/v11.go new file mode 100644 index 000000000..9b92afaf0 --- /dev/null +++ b/chain/actors/builtin/system/v11.go @@ -0,0 +1,72 @@ +package system + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + actorstypes "github.com/filecoin-project/go-state-types/actors" + system11 "github.com/filecoin-project/go-state-types/builtin/v11/system" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state11)(nil) + +func load11(store adt.Store, root cid.Cid) (State, error) { + out := state11{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make11(store adt.Store, builtinActors cid.Cid) (State, error) { + out := state11{store: store} + out.State = system11.State{ + BuiltinActors: builtinActors, + } + return &out, nil +} + +type state11 struct { + system11.State + store adt.Store +} + +func (s *state11) GetState() interface{} { + return &s.State +} + +func (s *state11) GetBuiltinActors() cid.Cid { + + return s.State.BuiltinActors + +} + +func (s *state11) SetBuiltinActors(c cid.Cid) error { + + s.State.BuiltinActors = c + return nil + +} + +func (s *state11) ActorKey() string { + return manifest.SystemKey +} + +func (s *state11) ActorVersion() actorstypes.Version { + return actorstypes.Version11 +} + +func (s *state11) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/system/v2.go b/chain/actors/builtin/system/v2.go new file mode 100644 index 000000000..b0c642054 --- /dev/null +++ b/chain/actors/builtin/system/v2.go @@ -0,0 +1,70 @@ +package system + +import ( + "fmt" + + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + system2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/system" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state2)(nil) + +func load2(store adt.Store, root cid.Cid) (State, error) { + out := state2{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make2(store adt.Store) (State, error) { + out := state2{store: store} + out.State = system2.State{} + return &out, nil +} + +type state2 struct { + system2.State + store adt.Store +} + +func (s *state2) GetState() interface{} { + return &s.State +} + +func (s *state2) GetBuiltinActors() cid.Cid { + + return cid.Undef + +} + +func (s *state2) SetBuiltinActors(c cid.Cid) error { + + return xerrors.New("cannot set manifest cid before v8") + +} + +func (s *state2) ActorKey() string { + return manifest.SystemKey +} + +func (s *state2) ActorVersion() actorstypes.Version { + return actorstypes.Version2 +} + +func (s *state2) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/system/v3.go b/chain/actors/builtin/system/v3.go new file mode 100644 index 000000000..8334f780e --- /dev/null +++ b/chain/actors/builtin/system/v3.go @@ -0,0 +1,70 @@ +package system + +import ( + "fmt" + + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + system3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/system" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state3)(nil) + +func load3(store adt.Store, root cid.Cid) (State, error) { + out := state3{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make3(store adt.Store) (State, error) { + out := state3{store: store} + out.State = system3.State{} + return &out, nil +} + +type state3 struct { + system3.State + store adt.Store +} + +func (s *state3) GetState() interface{} { + return &s.State +} + +func (s *state3) GetBuiltinActors() cid.Cid { + + return cid.Undef + +} + +func (s *state3) SetBuiltinActors(c cid.Cid) error { + + return xerrors.New("cannot set manifest cid before v8") + +} + +func (s *state3) ActorKey() string { + return manifest.SystemKey +} + +func (s *state3) ActorVersion() actorstypes.Version { + return actorstypes.Version3 +} + +func (s *state3) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/system/v4.go b/chain/actors/builtin/system/v4.go new file mode 100644 index 000000000..227104f37 --- /dev/null +++ b/chain/actors/builtin/system/v4.go @@ -0,0 +1,70 @@ +package system + +import ( + "fmt" + + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + system4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/system" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state4)(nil) + +func load4(store adt.Store, root cid.Cid) (State, error) { + out := state4{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make4(store adt.Store) (State, error) { + out := state4{store: store} + out.State = system4.State{} + return &out, nil +} + +type state4 struct { + system4.State + store adt.Store +} + +func (s *state4) GetState() interface{} { + return &s.State +} + +func (s *state4) GetBuiltinActors() cid.Cid { + + return cid.Undef + +} + +func (s *state4) SetBuiltinActors(c cid.Cid) error { + + return xerrors.New("cannot set manifest cid before v8") + +} + +func (s *state4) ActorKey() string { + return manifest.SystemKey +} + +func (s *state4) ActorVersion() actorstypes.Version { + return actorstypes.Version4 +} + +func (s *state4) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/system/v5.go b/chain/actors/builtin/system/v5.go new file mode 100644 index 000000000..bbfb70b51 --- /dev/null +++ b/chain/actors/builtin/system/v5.go @@ -0,0 +1,70 @@ +package system + +import ( + "fmt" + + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + system5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/system" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state5)(nil) + +func load5(store adt.Store, root cid.Cid) (State, error) { + out := state5{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make5(store adt.Store) (State, error) { + out := state5{store: store} + out.State = system5.State{} + return &out, nil +} + +type state5 struct { + system5.State + store adt.Store +} + +func (s *state5) GetState() interface{} { + return &s.State +} + +func (s *state5) GetBuiltinActors() cid.Cid { + + return cid.Undef + +} + +func (s *state5) SetBuiltinActors(c cid.Cid) error { + + return xerrors.New("cannot set manifest cid before v8") + +} + +func (s *state5) ActorKey() string { + return manifest.SystemKey +} + +func (s *state5) ActorVersion() actorstypes.Version { + return actorstypes.Version5 +} + +func (s *state5) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/system/v6.go b/chain/actors/builtin/system/v6.go new file mode 100644 index 000000000..2319b5929 --- /dev/null +++ b/chain/actors/builtin/system/v6.go @@ -0,0 +1,70 @@ +package system + +import ( + "fmt" + + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + system6 "github.com/filecoin-project/specs-actors/v6/actors/builtin/system" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state6)(nil) + +func load6(store adt.Store, root cid.Cid) (State, error) { + out := state6{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make6(store adt.Store) (State, error) { + out := state6{store: store} + out.State = system6.State{} + return &out, nil +} + +type state6 struct { + system6.State + store adt.Store +} + +func (s *state6) GetState() interface{} { + return &s.State +} + +func (s *state6) GetBuiltinActors() cid.Cid { + + return cid.Undef + +} + +func (s *state6) SetBuiltinActors(c cid.Cid) error { + + return xerrors.New("cannot set manifest cid before v8") + +} + +func (s *state6) ActorKey() string { + return manifest.SystemKey +} + +func (s *state6) ActorVersion() actorstypes.Version { + return actorstypes.Version6 +} + +func (s *state6) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/system/v7.go b/chain/actors/builtin/system/v7.go new file mode 100644 index 000000000..0b10129e8 --- /dev/null +++ b/chain/actors/builtin/system/v7.go @@ -0,0 +1,70 @@ +package system + +import ( + "fmt" + + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + system7 "github.com/filecoin-project/specs-actors/v7/actors/builtin/system" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state7)(nil) + +func load7(store adt.Store, root cid.Cid) (State, error) { + out := state7{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make7(store adt.Store) (State, error) { + out := state7{store: store} + out.State = system7.State{} + return &out, nil +} + +type state7 struct { + system7.State + store adt.Store +} + +func (s *state7) GetState() interface{} { + return &s.State +} + +func (s *state7) GetBuiltinActors() cid.Cid { + + return cid.Undef + +} + +func (s *state7) SetBuiltinActors(c cid.Cid) error { + + return xerrors.New("cannot set manifest cid before v8") + +} + +func (s *state7) ActorKey() string { + return manifest.SystemKey +} + +func (s *state7) ActorVersion() actorstypes.Version { + return actorstypes.Version7 +} + +func (s *state7) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/system/v8.go b/chain/actors/builtin/system/v8.go new file mode 100644 index 000000000..eca3b0c04 --- /dev/null +++ b/chain/actors/builtin/system/v8.go @@ -0,0 +1,72 @@ +package system + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + actorstypes "github.com/filecoin-project/go-state-types/actors" + system8 "github.com/filecoin-project/go-state-types/builtin/v8/system" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state8)(nil) + +func load8(store adt.Store, root cid.Cid) (State, error) { + out := state8{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make8(store adt.Store, builtinActors cid.Cid) (State, error) { + out := state8{store: store} + out.State = system8.State{ + BuiltinActors: builtinActors, + } + return &out, nil +} + +type state8 struct { + system8.State + store adt.Store +} + +func (s *state8) GetState() interface{} { + return &s.State +} + +func (s *state8) GetBuiltinActors() cid.Cid { + + return s.State.BuiltinActors + +} + +func (s *state8) SetBuiltinActors(c cid.Cid) error { + + s.State.BuiltinActors = c + return nil + +} + +func (s *state8) ActorKey() string { + return manifest.SystemKey +} + +func (s *state8) ActorVersion() actorstypes.Version { + return actorstypes.Version8 +} + +func (s *state8) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/system/v9.go b/chain/actors/builtin/system/v9.go new file mode 100644 index 000000000..55e073efe --- /dev/null +++ b/chain/actors/builtin/system/v9.go @@ -0,0 +1,72 @@ +package system + +import ( + "fmt" + + "github.com/ipfs/go-cid" + + actorstypes "github.com/filecoin-project/go-state-types/actors" + system9 "github.com/filecoin-project/go-state-types/builtin/v9/system" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state9)(nil) + +func load9(store adt.Store, root cid.Cid) (State, error) { + out := state9{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make9(store adt.Store, builtinActors cid.Cid) (State, error) { + out := state9{store: store} + out.State = system9.State{ + BuiltinActors: builtinActors, + } + return &out, nil +} + +type state9 struct { + system9.State + store adt.Store +} + +func (s *state9) GetState() interface{} { + return &s.State +} + +func (s *state9) GetBuiltinActors() cid.Cid { + + return s.State.BuiltinActors + +} + +func (s *state9) SetBuiltinActors(c cid.Cid) error { + + s.State.BuiltinActors = c + return nil + +} + +func (s *state9) ActorKey() string { + return manifest.SystemKey +} + +func (s *state9) ActorVersion() actorstypes.Version { + return actorstypes.Version9 +} + +func (s *state9) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/verifreg/actor.go.template b/chain/actors/builtin/verifreg/actor.go.template new file mode 100644 index 000000000..9b779a68d --- /dev/null +++ b/chain/actors/builtin/verifreg/actor.go.template @@ -0,0 +1,100 @@ +package verifreg + +import ( + "github.com/ipfs/go-cid" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + "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/cbor" +{{range .versions}} + {{if (le . 7)}} + builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin" + {{end}} +{{end}} + builtin{{.latestVersion}} "github.com/filecoin-project/go-state-types/builtin" + + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/types" + verifregtypes "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" +) + +var ( + Address = builtin{{.latestVersion}}.VerifiedRegistryActorAddr + Methods = builtin{{.latestVersion}}.MethodsVerifiedRegistry +) + +func Load(store adt.Store, act *types.Actor) (State, error) { + if name, av, ok := actors.GetActorMetaByCode(act.Code); ok { + if name != manifest.VerifregKey { + return nil, xerrors.Errorf("actor code is not verifreg: %s", name) + } + + switch av { + {{range .versions}} + {{if (ge . 8)}} + case actorstypes.Version{{.}}: + return load{{.}}(store, act.Head) + {{end}} + {{end}} + } + } + + switch act.Code { +{{range .versions}} + {{if (le . 7)}} + case builtin{{.}}.VerifiedRegistryActorCodeID: + return load{{.}}(store, act.Head) + {{end}} +{{end}} + } + + return nil, xerrors.Errorf("unknown actor code %s", act.Code) +} + +func MakeState(store adt.Store, av actorstypes.Version, rootKeyAddress address.Address) (State, error) { + switch av { +{{range .versions}} + case actorstypes.Version{{.}}: + return make{{.}}(store, rootKeyAddress) +{{end}} +} + return nil, xerrors.Errorf("unknown actor version %d", av) +} + +type State interface { + cbor.Marshaler + + Code() cid.Cid + ActorKey() string + ActorVersion() actorstypes.Version + + RootKey() (address.Address, error) + VerifiedClientDataCap(address.Address) (bool, abi.StoragePower, error) + VerifierDataCap(address.Address) (bool, abi.StoragePower, error) + RemoveDataCapProposalID(verifier address.Address, client address.Address) (bool, uint64, error) + ForEachVerifier(func(addr address.Address, dcap abi.StoragePower) error) error + ForEachClient(func(addr address.Address, dcap abi.StoragePower) error) error + GetAllocation(clientIdAddr address.Address, allocationId AllocationId) (*Allocation, bool, error) + GetAllocations(clientIdAddr address.Address) (map[AllocationId]Allocation, error) + GetClaim(providerIdAddr address.Address, claimId ClaimId) (*Claim, bool, error) + GetClaims(providerIdAddr address.Address) (map[ClaimId]Claim, error) + GetClaimIdsBySector(providerIdAddr address.Address) (map[abi.SectorNumber][]ClaimId, error) + GetState() interface{} +} + +func AllCodes() []cid.Cid { + return []cid.Cid{ {{range .versions}} + (&state{{.}}{}).Code(), + {{- end}} + } +} + +type Allocation = verifregtypes.Allocation +type AllocationId = verifregtypes.AllocationId +type Claim = verifregtypes.Claim +type ClaimId = verifregtypes.ClaimId \ No newline at end of file diff --git a/chain/actors/builtin/verifreg/state.go.template b/chain/actors/builtin/verifreg/state.go.template new file mode 100644 index 000000000..adcbc22c2 --- /dev/null +++ b/chain/actors/builtin/verifreg/state.go.template @@ -0,0 +1,209 @@ +package verifreg + +import ( + "fmt" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "golang.org/x/xerrors" + +{{if (le .v 7)}} + {{if (ge .v 3)}} + builtin{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/builtin" + {{end}} + verifreg{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/builtin/verifreg" + adt{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/util/adt" +{{else}} + verifreg{{.v}} "github.com/filecoin-project/go-state-types/builtin{{.import}}verifreg" + adt{{.v}} "github.com/filecoin-project/go-state-types/builtin{{.import}}util/adt" + builtin{{.v}} "github.com/filecoin-project/go-state-types/builtin" +{{end}} +{{if (ge .v 9)}} + "github.com/filecoin-project/go-state-types/big" +{{if (gt .v 9)}} + verifreg9 "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" +{{end}} +{{else}} + verifreg9 "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" +{{end}} +) + +var _ State = (*state{{.v}})(nil) + +func load{{.v}}(store adt.Store, root cid.Cid) (State, error) { + out := state{{.v}}{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make{{.v}}(store adt.Store, rootKeyAddress address.Address) (State, error) { + out := state{{.v}}{store: store} + {{if (le .v 2)}} + em, err := adt{{.v}}.MakeEmptyMap(store).Root() + if err != nil { + return nil, err + } + + out.State = *verifreg{{.v}}.ConstructState(em, rootKeyAddress) + {{else}} + s, err := verifreg{{.v}}.ConstructState(store, rootKeyAddress) + if err != nil { + return nil, err + } + + out.State = *s + {{end}} + return &out, nil +} + +type state{{.v}} struct { + verifreg{{.v}}.State + store adt.Store +} + +func (s *state{{.v}}) RootKey() (address.Address, error) { + return s.State.RootKey, nil +} + +func (s *state{{.v}}) VerifiedClientDataCap(addr address.Address) (bool, abi.StoragePower, error) { +{{if (le .v 8)}} + return getDataCap(s.store, actors.Version{{.v}}, s.verifiedClients, addr) +{{else}} + return false, big.Zero(), xerrors.Errorf("unsupported in actors v{{.v}}") +{{end}} +} + +func (s *state{{.v}}) VerifierDataCap(addr address.Address) (bool, abi.StoragePower, error) { + return getDataCap(s.store, actors.Version{{.v}}, s.verifiers, addr) +} + +func (s *state{{.v}}) RemoveDataCapProposalID(verifier address.Address, client address.Address) (bool, uint64, error) { + return getRemoveDataCapProposalID(s.store, actors.Version{{.v}}, s.removeDataCapProposalIDs, verifier, client) +} + +func (s *state{{.v}}) ForEachVerifier(cb func(addr address.Address, dcap abi.StoragePower) error) error { + return forEachCap(s.store, actors.Version{{.v}}, s.verifiers, cb) +} + +func (s *state{{.v}}) ForEachClient(cb func(addr address.Address, dcap abi.StoragePower) error) error { +{{if (le .v 8)}} + return forEachCap(s.store, actors.Version{{.v}}, s.verifiedClients, cb) +{{else}} + return xerrors.Errorf("unsupported in actors v{{.v}}") +{{end}} +} + +func (s *state{{.v}}) verifiedClients() (adt.Map, error) { +{{if (le .v 8)}} + return adt{{.v}}.AsMap(s.store, s.VerifiedClients{{if (ge .v 3)}}, builtin{{.v}}.DefaultHamtBitwidth{{end}}) +{{else}} + return nil, xerrors.Errorf("unsupported in actors v{{.v}}") +{{end}} +} + +func (s *state{{.v}}) verifiers() (adt.Map, error) { + return adt{{.v}}.AsMap(s.store, s.Verifiers{{if (ge .v 3)}}, builtin{{.v}}.DefaultHamtBitwidth{{end}}) +} + +func (s *state{{.v}}) removeDataCapProposalIDs() (adt.Map, error) { + {{if le .v 6}}return nil, nil + {{else}}return adt{{.v}}.AsMap(s.store, s.RemoveDataCapProposalIDs, builtin{{.v}}.DefaultHamtBitwidth){{end}} +} + +func (s *state{{.v}}) GetState() interface{} { + return &s.State +} + +func (s *state{{.v}}) GetAllocation(clientIdAddr address.Address, allocationId verifreg9.AllocationId) (*Allocation, bool, error) { +{{if (le .v 8)}} + return nil, false, xerrors.Errorf("unsupported in actors v{{.v}}") +{{else}} + alloc, ok, err := s.FindAllocation(s.store, clientIdAddr, verifreg{{.v}}.AllocationId(allocationId)) + return (*Allocation)(alloc), ok, err{{end}} +} + +func (s *state{{.v}}) GetAllocations(clientIdAddr address.Address) (map[AllocationId]Allocation, error) { +{{if (le .v 8)}} + return nil, xerrors.Errorf("unsupported in actors v{{.v}}") +{{else}} + v{{.v}}Map, err := s.LoadAllocationsToMap(s.store, clientIdAddr) + + retMap := make(map[AllocationId]Allocation, len(v{{.v}}Map)) + for k, v := range v{{.v}}Map { + retMap[AllocationId(k)] = Allocation(v) + } + + return retMap, err +{{end}} +} + +func (s *state{{.v}}) GetClaim(providerIdAddr address.Address, claimId verifreg9.ClaimId) (*Claim, bool, error) { +{{if (le .v 8)}} + return nil, false, xerrors.Errorf("unsupported in actors v{{.v}}") +{{else}} + claim, ok, err := s.FindClaim(s.store, providerIdAddr, verifreg{{.v}}.ClaimId(claimId)) + return (*Claim)(claim), ok, err +{{end}} +} + +func (s *state{{.v}}) GetClaims(providerIdAddr address.Address) (map[ClaimId]Claim, error) { +{{if (le .v 8)}} + return nil, xerrors.Errorf("unsupported in actors v{{.v}}") +{{else}} + v{{.v}}Map, err := s.LoadClaimsToMap(s.store, providerIdAddr) + + retMap := make(map[ClaimId]Claim, len(v{{.v}}Map)) + for k, v := range v{{.v}}Map { + retMap[ClaimId(k)] = Claim(v) + } + + return retMap, err + +{{end}} +} + +func (s *state{{.v}}) GetClaimIdsBySector(providerIdAddr address.Address) (map[abi.SectorNumber][]ClaimId, error) { +{{if (le .v 8)}} + return nil, xerrors.Errorf("unsupported in actors v{{.v}}") +{{else}} + v{{.v}}Map, err := s.LoadClaimsToMap(s.store, providerIdAddr) + + retMap := make(map[abi.SectorNumber][]ClaimId) + for k, v := range v{{.v}}Map { + claims, ok := retMap[v.Sector] + if !ok { + retMap[v.Sector] = []ClaimId{ClaimId(k)} + } else { + retMap[v.Sector] = append(claims, ClaimId(k)) + } + } + + return retMap, err + +{{end}} +} + +func (s *state{{.v}}) ActorKey() string { + return manifest.VerifregKey +} + +func (s *state{{.v}}) ActorVersion() actorstypes.Version { + return actorstypes.Version{{.v}} +} + +func (s *state{{.v}}) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/verifreg/util.go b/chain/actors/builtin/verifreg/util.go new file mode 100644 index 000000000..09a7a132c --- /dev/null +++ b/chain/actors/builtin/verifreg/util.go @@ -0,0 +1,80 @@ +package verifreg + +import ( + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/specs-actors/v7/actors/builtin/verifreg" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +// taking this as a function instead of asking the caller to call it helps reduce some of the error +// checking boilerplate. +// +// "go made me do it" +type rootFunc func() (adt.Map, error) + +// Assumes that the bitwidth for v3 HAMTs is the DefaultHamtBitwidth +func getDataCap(store adt.Store, ver actors.Version, root rootFunc, addr address.Address) (bool, abi.StoragePower, error) { + if addr.Protocol() != address.ID { + return false, big.Zero(), xerrors.Errorf("can only look up ID addresses") + } + vh, err := root() + if err != nil { + return false, big.Zero(), xerrors.Errorf("loading verifreg: %w", err) + } + + var dcap abi.StoragePower + if found, err := vh.Get(abi.AddrKey(addr), &dcap); err != nil { + return false, big.Zero(), xerrors.Errorf("looking up addr: %w", err) + } else if !found { + return false, big.Zero(), nil + } + + return true, dcap, nil +} + +// Assumes that the bitwidth for v3 HAMTs is the DefaultHamtBitwidth +func forEachCap(store adt.Store, ver actors.Version, root rootFunc, cb func(addr address.Address, dcap abi.StoragePower) error) error { + vh, err := root() + if err != nil { + return xerrors.Errorf("loading verified clients: %w", err) + } + var dcap abi.StoragePower + return vh.ForEach(&dcap, func(key string) error { + a, err := address.NewFromBytes([]byte(key)) + if err != nil { + return err + } + return cb(a, dcap) + }) +} + +func getRemoveDataCapProposalID(store adt.Store, ver actors.Version, root rootFunc, verifier address.Address, client address.Address) (bool, uint64, error) { + if verifier.Protocol() != address.ID { + return false, 0, xerrors.Errorf("can only look up ID addresses") + } + if client.Protocol() != address.ID { + return false, 0, xerrors.Errorf("can only look up ID addresses") + } + vh, err := root() + if err != nil { + return false, 0, xerrors.Errorf("loading verifreg: %w", err) + } + if vh == nil { + return false, 0, xerrors.Errorf("remove data cap proposal hamt not found. you are probably using an incompatible version of actors") + } + + var id verifreg.RmDcProposalID + if found, err := vh.Get(abi.NewAddrPairKey(verifier, client), &id); err != nil { + return false, 0, xerrors.Errorf("looking up addr pair: %w", err) + } else if !found { + return false, 0, nil + } + + return true, id.ProposalID, nil +} diff --git a/chain/actors/builtin/verifreg/v0.go b/chain/actors/builtin/verifreg/v0.go new file mode 100644 index 000000000..9913c42c0 --- /dev/null +++ b/chain/actors/builtin/verifreg/v0.go @@ -0,0 +1,142 @@ +package verifreg + +import ( + "fmt" + + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + verifreg9 "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" + "github.com/filecoin-project/go-state-types/manifest" + verifreg0 "github.com/filecoin-project/specs-actors/actors/builtin/verifreg" + adt0 "github.com/filecoin-project/specs-actors/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state0)(nil) + +func load0(store adt.Store, root cid.Cid) (State, error) { + out := state0{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make0(store adt.Store, rootKeyAddress address.Address) (State, error) { + out := state0{store: store} + + em, err := adt0.MakeEmptyMap(store).Root() + if err != nil { + return nil, err + } + + out.State = *verifreg0.ConstructState(em, rootKeyAddress) + + return &out, nil +} + +type state0 struct { + verifreg0.State + store adt.Store +} + +func (s *state0) RootKey() (address.Address, error) { + return s.State.RootKey, nil +} + +func (s *state0) VerifiedClientDataCap(addr address.Address) (bool, abi.StoragePower, error) { + + return getDataCap(s.store, actors.Version0, s.verifiedClients, addr) + +} + +func (s *state0) VerifierDataCap(addr address.Address) (bool, abi.StoragePower, error) { + return getDataCap(s.store, actors.Version0, s.verifiers, addr) +} + +func (s *state0) RemoveDataCapProposalID(verifier address.Address, client address.Address) (bool, uint64, error) { + return getRemoveDataCapProposalID(s.store, actors.Version0, s.removeDataCapProposalIDs, verifier, client) +} + +func (s *state0) ForEachVerifier(cb func(addr address.Address, dcap abi.StoragePower) error) error { + return forEachCap(s.store, actors.Version0, s.verifiers, cb) +} + +func (s *state0) ForEachClient(cb func(addr address.Address, dcap abi.StoragePower) error) error { + + return forEachCap(s.store, actors.Version0, s.verifiedClients, cb) + +} + +func (s *state0) verifiedClients() (adt.Map, error) { + + return adt0.AsMap(s.store, s.VerifiedClients) + +} + +func (s *state0) verifiers() (adt.Map, error) { + return adt0.AsMap(s.store, s.Verifiers) +} + +func (s *state0) removeDataCapProposalIDs() (adt.Map, error) { + return nil, nil + +} + +func (s *state0) GetState() interface{} { + return &s.State +} + +func (s *state0) GetAllocation(clientIdAddr address.Address, allocationId verifreg9.AllocationId) (*Allocation, bool, error) { + + return nil, false, xerrors.Errorf("unsupported in actors v0") + +} + +func (s *state0) GetAllocations(clientIdAddr address.Address) (map[AllocationId]Allocation, error) { + + return nil, xerrors.Errorf("unsupported in actors v0") + +} + +func (s *state0) GetClaim(providerIdAddr address.Address, claimId verifreg9.ClaimId) (*Claim, bool, error) { + + return nil, false, xerrors.Errorf("unsupported in actors v0") + +} + +func (s *state0) GetClaims(providerIdAddr address.Address) (map[ClaimId]Claim, error) { + + return nil, xerrors.Errorf("unsupported in actors v0") + +} + +func (s *state0) GetClaimIdsBySector(providerIdAddr address.Address) (map[abi.SectorNumber][]ClaimId, error) { + + return nil, xerrors.Errorf("unsupported in actors v0") + +} + +func (s *state0) ActorKey() string { + return manifest.VerifregKey +} + +func (s *state0) ActorVersion() actorstypes.Version { + return actorstypes.Version0 +} + +func (s *state0) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/verifreg/v10.go b/chain/actors/builtin/verifreg/v10.go new file mode 100644 index 000000000..256f4d2f8 --- /dev/null +++ b/chain/actors/builtin/verifreg/v10.go @@ -0,0 +1,170 @@ +package verifreg + +import ( + "fmt" + + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + builtin10 "github.com/filecoin-project/go-state-types/builtin" + adt10 "github.com/filecoin-project/go-state-types/builtin/v10/util/adt" + verifreg10 "github.com/filecoin-project/go-state-types/builtin/v10/verifreg" + verifreg9 "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state10)(nil) + +func load10(store adt.Store, root cid.Cid) (State, error) { + out := state10{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make10(store adt.Store, rootKeyAddress address.Address) (State, error) { + out := state10{store: store} + + s, err := verifreg10.ConstructState(store, rootKeyAddress) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state10 struct { + verifreg10.State + store adt.Store +} + +func (s *state10) RootKey() (address.Address, error) { + return s.State.RootKey, nil +} + +func (s *state10) VerifiedClientDataCap(addr address.Address) (bool, abi.StoragePower, error) { + + return false, big.Zero(), xerrors.Errorf("unsupported in actors v10") + +} + +func (s *state10) VerifierDataCap(addr address.Address) (bool, abi.StoragePower, error) { + return getDataCap(s.store, actors.Version10, s.verifiers, addr) +} + +func (s *state10) RemoveDataCapProposalID(verifier address.Address, client address.Address) (bool, uint64, error) { + return getRemoveDataCapProposalID(s.store, actors.Version10, s.removeDataCapProposalIDs, verifier, client) +} + +func (s *state10) ForEachVerifier(cb func(addr address.Address, dcap abi.StoragePower) error) error { + return forEachCap(s.store, actors.Version10, s.verifiers, cb) +} + +func (s *state10) ForEachClient(cb func(addr address.Address, dcap abi.StoragePower) error) error { + + return xerrors.Errorf("unsupported in actors v10") + +} + +func (s *state10) verifiedClients() (adt.Map, error) { + + return nil, xerrors.Errorf("unsupported in actors v10") + +} + +func (s *state10) verifiers() (adt.Map, error) { + return adt10.AsMap(s.store, s.Verifiers, builtin10.DefaultHamtBitwidth) +} + +func (s *state10) removeDataCapProposalIDs() (adt.Map, error) { + return adt10.AsMap(s.store, s.RemoveDataCapProposalIDs, builtin10.DefaultHamtBitwidth) +} + +func (s *state10) GetState() interface{} { + return &s.State +} + +func (s *state10) GetAllocation(clientIdAddr address.Address, allocationId verifreg9.AllocationId) (*Allocation, bool, error) { + + alloc, ok, err := s.FindAllocation(s.store, clientIdAddr, verifreg10.AllocationId(allocationId)) + return (*Allocation)(alloc), ok, err +} + +func (s *state10) GetAllocations(clientIdAddr address.Address) (map[AllocationId]Allocation, error) { + + v10Map, err := s.LoadAllocationsToMap(s.store, clientIdAddr) + + retMap := make(map[AllocationId]Allocation, len(v10Map)) + for k, v := range v10Map { + retMap[AllocationId(k)] = Allocation(v) + } + + return retMap, err + +} + +func (s *state10) GetClaim(providerIdAddr address.Address, claimId verifreg9.ClaimId) (*Claim, bool, error) { + + claim, ok, err := s.FindClaim(s.store, providerIdAddr, verifreg10.ClaimId(claimId)) + return (*Claim)(claim), ok, err + +} + +func (s *state10) GetClaims(providerIdAddr address.Address) (map[ClaimId]Claim, error) { + + v10Map, err := s.LoadClaimsToMap(s.store, providerIdAddr) + + retMap := make(map[ClaimId]Claim, len(v10Map)) + for k, v := range v10Map { + retMap[ClaimId(k)] = Claim(v) + } + + return retMap, err + +} + +func (s *state10) GetClaimIdsBySector(providerIdAddr address.Address) (map[abi.SectorNumber][]ClaimId, error) { + + v10Map, err := s.LoadClaimsToMap(s.store, providerIdAddr) + + retMap := make(map[abi.SectorNumber][]ClaimId) + for k, v := range v10Map { + claims, ok := retMap[v.Sector] + if !ok { + retMap[v.Sector] = []ClaimId{ClaimId(k)} + } else { + retMap[v.Sector] = append(claims, ClaimId(k)) + } + } + + return retMap, err + +} + +func (s *state10) ActorKey() string { + return manifest.VerifregKey +} + +func (s *state10) ActorVersion() actorstypes.Version { + return actorstypes.Version10 +} + +func (s *state10) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/verifreg/v11.go b/chain/actors/builtin/verifreg/v11.go new file mode 100644 index 000000000..7b7b9e4c0 --- /dev/null +++ b/chain/actors/builtin/verifreg/v11.go @@ -0,0 +1,170 @@ +package verifreg + +import ( + "fmt" + + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + builtin11 "github.com/filecoin-project/go-state-types/builtin" + adt11 "github.com/filecoin-project/go-state-types/builtin/v11/util/adt" + verifreg11 "github.com/filecoin-project/go-state-types/builtin/v11/verifreg" + verifreg9 "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state11)(nil) + +func load11(store adt.Store, root cid.Cid) (State, error) { + out := state11{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make11(store adt.Store, rootKeyAddress address.Address) (State, error) { + out := state11{store: store} + + s, err := verifreg11.ConstructState(store, rootKeyAddress) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state11 struct { + verifreg11.State + store adt.Store +} + +func (s *state11) RootKey() (address.Address, error) { + return s.State.RootKey, nil +} + +func (s *state11) VerifiedClientDataCap(addr address.Address) (bool, abi.StoragePower, error) { + + return false, big.Zero(), xerrors.Errorf("unsupported in actors v11") + +} + +func (s *state11) VerifierDataCap(addr address.Address) (bool, abi.StoragePower, error) { + return getDataCap(s.store, actors.Version11, s.verifiers, addr) +} + +func (s *state11) RemoveDataCapProposalID(verifier address.Address, client address.Address) (bool, uint64, error) { + return getRemoveDataCapProposalID(s.store, actors.Version11, s.removeDataCapProposalIDs, verifier, client) +} + +func (s *state11) ForEachVerifier(cb func(addr address.Address, dcap abi.StoragePower) error) error { + return forEachCap(s.store, actors.Version11, s.verifiers, cb) +} + +func (s *state11) ForEachClient(cb func(addr address.Address, dcap abi.StoragePower) error) error { + + return xerrors.Errorf("unsupported in actors v11") + +} + +func (s *state11) verifiedClients() (adt.Map, error) { + + return nil, xerrors.Errorf("unsupported in actors v11") + +} + +func (s *state11) verifiers() (adt.Map, error) { + return adt11.AsMap(s.store, s.Verifiers, builtin11.DefaultHamtBitwidth) +} + +func (s *state11) removeDataCapProposalIDs() (adt.Map, error) { + return adt11.AsMap(s.store, s.RemoveDataCapProposalIDs, builtin11.DefaultHamtBitwidth) +} + +func (s *state11) GetState() interface{} { + return &s.State +} + +func (s *state11) GetAllocation(clientIdAddr address.Address, allocationId verifreg9.AllocationId) (*Allocation, bool, error) { + + alloc, ok, err := s.FindAllocation(s.store, clientIdAddr, verifreg11.AllocationId(allocationId)) + return (*Allocation)(alloc), ok, err +} + +func (s *state11) GetAllocations(clientIdAddr address.Address) (map[AllocationId]Allocation, error) { + + v11Map, err := s.LoadAllocationsToMap(s.store, clientIdAddr) + + retMap := make(map[AllocationId]Allocation, len(v11Map)) + for k, v := range v11Map { + retMap[AllocationId(k)] = Allocation(v) + } + + return retMap, err + +} + +func (s *state11) GetClaim(providerIdAddr address.Address, claimId verifreg9.ClaimId) (*Claim, bool, error) { + + claim, ok, err := s.FindClaim(s.store, providerIdAddr, verifreg11.ClaimId(claimId)) + return (*Claim)(claim), ok, err + +} + +func (s *state11) GetClaims(providerIdAddr address.Address) (map[ClaimId]Claim, error) { + + v11Map, err := s.LoadClaimsToMap(s.store, providerIdAddr) + + retMap := make(map[ClaimId]Claim, len(v11Map)) + for k, v := range v11Map { + retMap[ClaimId(k)] = Claim(v) + } + + return retMap, err + +} + +func (s *state11) GetClaimIdsBySector(providerIdAddr address.Address) (map[abi.SectorNumber][]ClaimId, error) { + + v11Map, err := s.LoadClaimsToMap(s.store, providerIdAddr) + + retMap := make(map[abi.SectorNumber][]ClaimId) + for k, v := range v11Map { + claims, ok := retMap[v.Sector] + if !ok { + retMap[v.Sector] = []ClaimId{ClaimId(k)} + } else { + retMap[v.Sector] = append(claims, ClaimId(k)) + } + } + + return retMap, err + +} + +func (s *state11) ActorKey() string { + return manifest.VerifregKey +} + +func (s *state11) ActorVersion() actorstypes.Version { + return actorstypes.Version11 +} + +func (s *state11) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/verifreg/v2.go b/chain/actors/builtin/verifreg/v2.go new file mode 100644 index 000000000..31f7f775d --- /dev/null +++ b/chain/actors/builtin/verifreg/v2.go @@ -0,0 +1,142 @@ +package verifreg + +import ( + "fmt" + + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + verifreg9 "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" + "github.com/filecoin-project/go-state-types/manifest" + verifreg2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/verifreg" + adt2 "github.com/filecoin-project/specs-actors/v2/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state2)(nil) + +func load2(store adt.Store, root cid.Cid) (State, error) { + out := state2{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make2(store adt.Store, rootKeyAddress address.Address) (State, error) { + out := state2{store: store} + + em, err := adt2.MakeEmptyMap(store).Root() + if err != nil { + return nil, err + } + + out.State = *verifreg2.ConstructState(em, rootKeyAddress) + + return &out, nil +} + +type state2 struct { + verifreg2.State + store adt.Store +} + +func (s *state2) RootKey() (address.Address, error) { + return s.State.RootKey, nil +} + +func (s *state2) VerifiedClientDataCap(addr address.Address) (bool, abi.StoragePower, error) { + + return getDataCap(s.store, actors.Version2, s.verifiedClients, addr) + +} + +func (s *state2) VerifierDataCap(addr address.Address) (bool, abi.StoragePower, error) { + return getDataCap(s.store, actors.Version2, s.verifiers, addr) +} + +func (s *state2) RemoveDataCapProposalID(verifier address.Address, client address.Address) (bool, uint64, error) { + return getRemoveDataCapProposalID(s.store, actors.Version2, s.removeDataCapProposalIDs, verifier, client) +} + +func (s *state2) ForEachVerifier(cb func(addr address.Address, dcap abi.StoragePower) error) error { + return forEachCap(s.store, actors.Version2, s.verifiers, cb) +} + +func (s *state2) ForEachClient(cb func(addr address.Address, dcap abi.StoragePower) error) error { + + return forEachCap(s.store, actors.Version2, s.verifiedClients, cb) + +} + +func (s *state2) verifiedClients() (adt.Map, error) { + + return adt2.AsMap(s.store, s.VerifiedClients) + +} + +func (s *state2) verifiers() (adt.Map, error) { + return adt2.AsMap(s.store, s.Verifiers) +} + +func (s *state2) removeDataCapProposalIDs() (adt.Map, error) { + return nil, nil + +} + +func (s *state2) GetState() interface{} { + return &s.State +} + +func (s *state2) GetAllocation(clientIdAddr address.Address, allocationId verifreg9.AllocationId) (*Allocation, bool, error) { + + return nil, false, xerrors.Errorf("unsupported in actors v2") + +} + +func (s *state2) GetAllocations(clientIdAddr address.Address) (map[AllocationId]Allocation, error) { + + return nil, xerrors.Errorf("unsupported in actors v2") + +} + +func (s *state2) GetClaim(providerIdAddr address.Address, claimId verifreg9.ClaimId) (*Claim, bool, error) { + + return nil, false, xerrors.Errorf("unsupported in actors v2") + +} + +func (s *state2) GetClaims(providerIdAddr address.Address) (map[ClaimId]Claim, error) { + + return nil, xerrors.Errorf("unsupported in actors v2") + +} + +func (s *state2) GetClaimIdsBySector(providerIdAddr address.Address) (map[abi.SectorNumber][]ClaimId, error) { + + return nil, xerrors.Errorf("unsupported in actors v2") + +} + +func (s *state2) ActorKey() string { + return manifest.VerifregKey +} + +func (s *state2) ActorVersion() actorstypes.Version { + return actorstypes.Version2 +} + +func (s *state2) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/verifreg/v3.go b/chain/actors/builtin/verifreg/v3.go new file mode 100644 index 000000000..3ea016fd5 --- /dev/null +++ b/chain/actors/builtin/verifreg/v3.go @@ -0,0 +1,143 @@ +package verifreg + +import ( + "fmt" + + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + verifreg9 "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" + "github.com/filecoin-project/go-state-types/manifest" + builtin3 "github.com/filecoin-project/specs-actors/v3/actors/builtin" + verifreg3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/verifreg" + adt3 "github.com/filecoin-project/specs-actors/v3/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state3)(nil) + +func load3(store adt.Store, root cid.Cid) (State, error) { + out := state3{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make3(store adt.Store, rootKeyAddress address.Address) (State, error) { + out := state3{store: store} + + s, err := verifreg3.ConstructState(store, rootKeyAddress) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state3 struct { + verifreg3.State + store adt.Store +} + +func (s *state3) RootKey() (address.Address, error) { + return s.State.RootKey, nil +} + +func (s *state3) VerifiedClientDataCap(addr address.Address) (bool, abi.StoragePower, error) { + + return getDataCap(s.store, actors.Version3, s.verifiedClients, addr) + +} + +func (s *state3) VerifierDataCap(addr address.Address) (bool, abi.StoragePower, error) { + return getDataCap(s.store, actors.Version3, s.verifiers, addr) +} + +func (s *state3) RemoveDataCapProposalID(verifier address.Address, client address.Address) (bool, uint64, error) { + return getRemoveDataCapProposalID(s.store, actors.Version3, s.removeDataCapProposalIDs, verifier, client) +} + +func (s *state3) ForEachVerifier(cb func(addr address.Address, dcap abi.StoragePower) error) error { + return forEachCap(s.store, actors.Version3, s.verifiers, cb) +} + +func (s *state3) ForEachClient(cb func(addr address.Address, dcap abi.StoragePower) error) error { + + return forEachCap(s.store, actors.Version3, s.verifiedClients, cb) + +} + +func (s *state3) verifiedClients() (adt.Map, error) { + + return adt3.AsMap(s.store, s.VerifiedClients, builtin3.DefaultHamtBitwidth) + +} + +func (s *state3) verifiers() (adt.Map, error) { + return adt3.AsMap(s.store, s.Verifiers, builtin3.DefaultHamtBitwidth) +} + +func (s *state3) removeDataCapProposalIDs() (adt.Map, error) { + return nil, nil + +} + +func (s *state3) GetState() interface{} { + return &s.State +} + +func (s *state3) GetAllocation(clientIdAddr address.Address, allocationId verifreg9.AllocationId) (*Allocation, bool, error) { + + return nil, false, xerrors.Errorf("unsupported in actors v3") + +} + +func (s *state3) GetAllocations(clientIdAddr address.Address) (map[AllocationId]Allocation, error) { + + return nil, xerrors.Errorf("unsupported in actors v3") + +} + +func (s *state3) GetClaim(providerIdAddr address.Address, claimId verifreg9.ClaimId) (*Claim, bool, error) { + + return nil, false, xerrors.Errorf("unsupported in actors v3") + +} + +func (s *state3) GetClaims(providerIdAddr address.Address) (map[ClaimId]Claim, error) { + + return nil, xerrors.Errorf("unsupported in actors v3") + +} + +func (s *state3) GetClaimIdsBySector(providerIdAddr address.Address) (map[abi.SectorNumber][]ClaimId, error) { + + return nil, xerrors.Errorf("unsupported in actors v3") + +} + +func (s *state3) ActorKey() string { + return manifest.VerifregKey +} + +func (s *state3) ActorVersion() actorstypes.Version { + return actorstypes.Version3 +} + +func (s *state3) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/verifreg/v4.go b/chain/actors/builtin/verifreg/v4.go new file mode 100644 index 000000000..464cc9fdc --- /dev/null +++ b/chain/actors/builtin/verifreg/v4.go @@ -0,0 +1,143 @@ +package verifreg + +import ( + "fmt" + + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + verifreg9 "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" + "github.com/filecoin-project/go-state-types/manifest" + builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" + verifreg4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/verifreg" + adt4 "github.com/filecoin-project/specs-actors/v4/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state4)(nil) + +func load4(store adt.Store, root cid.Cid) (State, error) { + out := state4{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make4(store adt.Store, rootKeyAddress address.Address) (State, error) { + out := state4{store: store} + + s, err := verifreg4.ConstructState(store, rootKeyAddress) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state4 struct { + verifreg4.State + store adt.Store +} + +func (s *state4) RootKey() (address.Address, error) { + return s.State.RootKey, nil +} + +func (s *state4) VerifiedClientDataCap(addr address.Address) (bool, abi.StoragePower, error) { + + return getDataCap(s.store, actors.Version4, s.verifiedClients, addr) + +} + +func (s *state4) VerifierDataCap(addr address.Address) (bool, abi.StoragePower, error) { + return getDataCap(s.store, actors.Version4, s.verifiers, addr) +} + +func (s *state4) RemoveDataCapProposalID(verifier address.Address, client address.Address) (bool, uint64, error) { + return getRemoveDataCapProposalID(s.store, actors.Version4, s.removeDataCapProposalIDs, verifier, client) +} + +func (s *state4) ForEachVerifier(cb func(addr address.Address, dcap abi.StoragePower) error) error { + return forEachCap(s.store, actors.Version4, s.verifiers, cb) +} + +func (s *state4) ForEachClient(cb func(addr address.Address, dcap abi.StoragePower) error) error { + + return forEachCap(s.store, actors.Version4, s.verifiedClients, cb) + +} + +func (s *state4) verifiedClients() (adt.Map, error) { + + return adt4.AsMap(s.store, s.VerifiedClients, builtin4.DefaultHamtBitwidth) + +} + +func (s *state4) verifiers() (adt.Map, error) { + return adt4.AsMap(s.store, s.Verifiers, builtin4.DefaultHamtBitwidth) +} + +func (s *state4) removeDataCapProposalIDs() (adt.Map, error) { + return nil, nil + +} + +func (s *state4) GetState() interface{} { + return &s.State +} + +func (s *state4) GetAllocation(clientIdAddr address.Address, allocationId verifreg9.AllocationId) (*Allocation, bool, error) { + + return nil, false, xerrors.Errorf("unsupported in actors v4") + +} + +func (s *state4) GetAllocations(clientIdAddr address.Address) (map[AllocationId]Allocation, error) { + + return nil, xerrors.Errorf("unsupported in actors v4") + +} + +func (s *state4) GetClaim(providerIdAddr address.Address, claimId verifreg9.ClaimId) (*Claim, bool, error) { + + return nil, false, xerrors.Errorf("unsupported in actors v4") + +} + +func (s *state4) GetClaims(providerIdAddr address.Address) (map[ClaimId]Claim, error) { + + return nil, xerrors.Errorf("unsupported in actors v4") + +} + +func (s *state4) GetClaimIdsBySector(providerIdAddr address.Address) (map[abi.SectorNumber][]ClaimId, error) { + + return nil, xerrors.Errorf("unsupported in actors v4") + +} + +func (s *state4) ActorKey() string { + return manifest.VerifregKey +} + +func (s *state4) ActorVersion() actorstypes.Version { + return actorstypes.Version4 +} + +func (s *state4) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/verifreg/v5.go b/chain/actors/builtin/verifreg/v5.go new file mode 100644 index 000000000..17901dd23 --- /dev/null +++ b/chain/actors/builtin/verifreg/v5.go @@ -0,0 +1,143 @@ +package verifreg + +import ( + "fmt" + + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + verifreg9 "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" + "github.com/filecoin-project/go-state-types/manifest" + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" + verifreg5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/verifreg" + adt5 "github.com/filecoin-project/specs-actors/v5/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state5)(nil) + +func load5(store adt.Store, root cid.Cid) (State, error) { + out := state5{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make5(store adt.Store, rootKeyAddress address.Address) (State, error) { + out := state5{store: store} + + s, err := verifreg5.ConstructState(store, rootKeyAddress) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state5 struct { + verifreg5.State + store adt.Store +} + +func (s *state5) RootKey() (address.Address, error) { + return s.State.RootKey, nil +} + +func (s *state5) VerifiedClientDataCap(addr address.Address) (bool, abi.StoragePower, error) { + + return getDataCap(s.store, actors.Version5, s.verifiedClients, addr) + +} + +func (s *state5) VerifierDataCap(addr address.Address) (bool, abi.StoragePower, error) { + return getDataCap(s.store, actors.Version5, s.verifiers, addr) +} + +func (s *state5) RemoveDataCapProposalID(verifier address.Address, client address.Address) (bool, uint64, error) { + return getRemoveDataCapProposalID(s.store, actors.Version5, s.removeDataCapProposalIDs, verifier, client) +} + +func (s *state5) ForEachVerifier(cb func(addr address.Address, dcap abi.StoragePower) error) error { + return forEachCap(s.store, actors.Version5, s.verifiers, cb) +} + +func (s *state5) ForEachClient(cb func(addr address.Address, dcap abi.StoragePower) error) error { + + return forEachCap(s.store, actors.Version5, s.verifiedClients, cb) + +} + +func (s *state5) verifiedClients() (adt.Map, error) { + + return adt5.AsMap(s.store, s.VerifiedClients, builtin5.DefaultHamtBitwidth) + +} + +func (s *state5) verifiers() (adt.Map, error) { + return adt5.AsMap(s.store, s.Verifiers, builtin5.DefaultHamtBitwidth) +} + +func (s *state5) removeDataCapProposalIDs() (adt.Map, error) { + return nil, nil + +} + +func (s *state5) GetState() interface{} { + return &s.State +} + +func (s *state5) GetAllocation(clientIdAddr address.Address, allocationId verifreg9.AllocationId) (*Allocation, bool, error) { + + return nil, false, xerrors.Errorf("unsupported in actors v5") + +} + +func (s *state5) GetAllocations(clientIdAddr address.Address) (map[AllocationId]Allocation, error) { + + return nil, xerrors.Errorf("unsupported in actors v5") + +} + +func (s *state5) GetClaim(providerIdAddr address.Address, claimId verifreg9.ClaimId) (*Claim, bool, error) { + + return nil, false, xerrors.Errorf("unsupported in actors v5") + +} + +func (s *state5) GetClaims(providerIdAddr address.Address) (map[ClaimId]Claim, error) { + + return nil, xerrors.Errorf("unsupported in actors v5") + +} + +func (s *state5) GetClaimIdsBySector(providerIdAddr address.Address) (map[abi.SectorNumber][]ClaimId, error) { + + return nil, xerrors.Errorf("unsupported in actors v5") + +} + +func (s *state5) ActorKey() string { + return manifest.VerifregKey +} + +func (s *state5) ActorVersion() actorstypes.Version { + return actorstypes.Version5 +} + +func (s *state5) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/verifreg/v6.go b/chain/actors/builtin/verifreg/v6.go new file mode 100644 index 000000000..68fac64cb --- /dev/null +++ b/chain/actors/builtin/verifreg/v6.go @@ -0,0 +1,143 @@ +package verifreg + +import ( + "fmt" + + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + verifreg9 "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" + "github.com/filecoin-project/go-state-types/manifest" + builtin6 "github.com/filecoin-project/specs-actors/v6/actors/builtin" + verifreg6 "github.com/filecoin-project/specs-actors/v6/actors/builtin/verifreg" + adt6 "github.com/filecoin-project/specs-actors/v6/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state6)(nil) + +func load6(store adt.Store, root cid.Cid) (State, error) { + out := state6{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make6(store adt.Store, rootKeyAddress address.Address) (State, error) { + out := state6{store: store} + + s, err := verifreg6.ConstructState(store, rootKeyAddress) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state6 struct { + verifreg6.State + store adt.Store +} + +func (s *state6) RootKey() (address.Address, error) { + return s.State.RootKey, nil +} + +func (s *state6) VerifiedClientDataCap(addr address.Address) (bool, abi.StoragePower, error) { + + return getDataCap(s.store, actors.Version6, s.verifiedClients, addr) + +} + +func (s *state6) VerifierDataCap(addr address.Address) (bool, abi.StoragePower, error) { + return getDataCap(s.store, actors.Version6, s.verifiers, addr) +} + +func (s *state6) RemoveDataCapProposalID(verifier address.Address, client address.Address) (bool, uint64, error) { + return getRemoveDataCapProposalID(s.store, actors.Version6, s.removeDataCapProposalIDs, verifier, client) +} + +func (s *state6) ForEachVerifier(cb func(addr address.Address, dcap abi.StoragePower) error) error { + return forEachCap(s.store, actors.Version6, s.verifiers, cb) +} + +func (s *state6) ForEachClient(cb func(addr address.Address, dcap abi.StoragePower) error) error { + + return forEachCap(s.store, actors.Version6, s.verifiedClients, cb) + +} + +func (s *state6) verifiedClients() (adt.Map, error) { + + return adt6.AsMap(s.store, s.VerifiedClients, builtin6.DefaultHamtBitwidth) + +} + +func (s *state6) verifiers() (adt.Map, error) { + return adt6.AsMap(s.store, s.Verifiers, builtin6.DefaultHamtBitwidth) +} + +func (s *state6) removeDataCapProposalIDs() (adt.Map, error) { + return nil, nil + +} + +func (s *state6) GetState() interface{} { + return &s.State +} + +func (s *state6) GetAllocation(clientIdAddr address.Address, allocationId verifreg9.AllocationId) (*Allocation, bool, error) { + + return nil, false, xerrors.Errorf("unsupported in actors v6") + +} + +func (s *state6) GetAllocations(clientIdAddr address.Address) (map[AllocationId]Allocation, error) { + + return nil, xerrors.Errorf("unsupported in actors v6") + +} + +func (s *state6) GetClaim(providerIdAddr address.Address, claimId verifreg9.ClaimId) (*Claim, bool, error) { + + return nil, false, xerrors.Errorf("unsupported in actors v6") + +} + +func (s *state6) GetClaims(providerIdAddr address.Address) (map[ClaimId]Claim, error) { + + return nil, xerrors.Errorf("unsupported in actors v6") + +} + +func (s *state6) GetClaimIdsBySector(providerIdAddr address.Address) (map[abi.SectorNumber][]ClaimId, error) { + + return nil, xerrors.Errorf("unsupported in actors v6") + +} + +func (s *state6) ActorKey() string { + return manifest.VerifregKey +} + +func (s *state6) ActorVersion() actorstypes.Version { + return actorstypes.Version6 +} + +func (s *state6) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/verifreg/v7.go b/chain/actors/builtin/verifreg/v7.go new file mode 100644 index 000000000..e8f3ac739 --- /dev/null +++ b/chain/actors/builtin/verifreg/v7.go @@ -0,0 +1,142 @@ +package verifreg + +import ( + "fmt" + + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + verifreg9 "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" + "github.com/filecoin-project/go-state-types/manifest" + builtin7 "github.com/filecoin-project/specs-actors/v7/actors/builtin" + verifreg7 "github.com/filecoin-project/specs-actors/v7/actors/builtin/verifreg" + adt7 "github.com/filecoin-project/specs-actors/v7/actors/util/adt" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state7)(nil) + +func load7(store adt.Store, root cid.Cid) (State, error) { + out := state7{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make7(store adt.Store, rootKeyAddress address.Address) (State, error) { + out := state7{store: store} + + s, err := verifreg7.ConstructState(store, rootKeyAddress) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state7 struct { + verifreg7.State + store adt.Store +} + +func (s *state7) RootKey() (address.Address, error) { + return s.State.RootKey, nil +} + +func (s *state7) VerifiedClientDataCap(addr address.Address) (bool, abi.StoragePower, error) { + + return getDataCap(s.store, actors.Version7, s.verifiedClients, addr) + +} + +func (s *state7) VerifierDataCap(addr address.Address) (bool, abi.StoragePower, error) { + return getDataCap(s.store, actors.Version7, s.verifiers, addr) +} + +func (s *state7) RemoveDataCapProposalID(verifier address.Address, client address.Address) (bool, uint64, error) { + return getRemoveDataCapProposalID(s.store, actors.Version7, s.removeDataCapProposalIDs, verifier, client) +} + +func (s *state7) ForEachVerifier(cb func(addr address.Address, dcap abi.StoragePower) error) error { + return forEachCap(s.store, actors.Version7, s.verifiers, cb) +} + +func (s *state7) ForEachClient(cb func(addr address.Address, dcap abi.StoragePower) error) error { + + return forEachCap(s.store, actors.Version7, s.verifiedClients, cb) + +} + +func (s *state7) verifiedClients() (adt.Map, error) { + + return adt7.AsMap(s.store, s.VerifiedClients, builtin7.DefaultHamtBitwidth) + +} + +func (s *state7) verifiers() (adt.Map, error) { + return adt7.AsMap(s.store, s.Verifiers, builtin7.DefaultHamtBitwidth) +} + +func (s *state7) removeDataCapProposalIDs() (adt.Map, error) { + return adt7.AsMap(s.store, s.RemoveDataCapProposalIDs, builtin7.DefaultHamtBitwidth) +} + +func (s *state7) GetState() interface{} { + return &s.State +} + +func (s *state7) GetAllocation(clientIdAddr address.Address, allocationId verifreg9.AllocationId) (*Allocation, bool, error) { + + return nil, false, xerrors.Errorf("unsupported in actors v7") + +} + +func (s *state7) GetAllocations(clientIdAddr address.Address) (map[AllocationId]Allocation, error) { + + return nil, xerrors.Errorf("unsupported in actors v7") + +} + +func (s *state7) GetClaim(providerIdAddr address.Address, claimId verifreg9.ClaimId) (*Claim, bool, error) { + + return nil, false, xerrors.Errorf("unsupported in actors v7") + +} + +func (s *state7) GetClaims(providerIdAddr address.Address) (map[ClaimId]Claim, error) { + + return nil, xerrors.Errorf("unsupported in actors v7") + +} + +func (s *state7) GetClaimIdsBySector(providerIdAddr address.Address) (map[abi.SectorNumber][]ClaimId, error) { + + return nil, xerrors.Errorf("unsupported in actors v7") + +} + +func (s *state7) ActorKey() string { + return manifest.VerifregKey +} + +func (s *state7) ActorVersion() actorstypes.Version { + return actorstypes.Version7 +} + +func (s *state7) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/verifreg/v8.go b/chain/actors/builtin/verifreg/v8.go new file mode 100644 index 000000000..89393c4d9 --- /dev/null +++ b/chain/actors/builtin/verifreg/v8.go @@ -0,0 +1,142 @@ +package verifreg + +import ( + "fmt" + + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtin8 "github.com/filecoin-project/go-state-types/builtin" + adt8 "github.com/filecoin-project/go-state-types/builtin/v8/util/adt" + verifreg8 "github.com/filecoin-project/go-state-types/builtin/v8/verifreg" + verifreg9 "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state8)(nil) + +func load8(store adt.Store, root cid.Cid) (State, error) { + out := state8{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make8(store adt.Store, rootKeyAddress address.Address) (State, error) { + out := state8{store: store} + + s, err := verifreg8.ConstructState(store, rootKeyAddress) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state8 struct { + verifreg8.State + store adt.Store +} + +func (s *state8) RootKey() (address.Address, error) { + return s.State.RootKey, nil +} + +func (s *state8) VerifiedClientDataCap(addr address.Address) (bool, abi.StoragePower, error) { + + return getDataCap(s.store, actors.Version8, s.verifiedClients, addr) + +} + +func (s *state8) VerifierDataCap(addr address.Address) (bool, abi.StoragePower, error) { + return getDataCap(s.store, actors.Version8, s.verifiers, addr) +} + +func (s *state8) RemoveDataCapProposalID(verifier address.Address, client address.Address) (bool, uint64, error) { + return getRemoveDataCapProposalID(s.store, actors.Version8, s.removeDataCapProposalIDs, verifier, client) +} + +func (s *state8) ForEachVerifier(cb func(addr address.Address, dcap abi.StoragePower) error) error { + return forEachCap(s.store, actors.Version8, s.verifiers, cb) +} + +func (s *state8) ForEachClient(cb func(addr address.Address, dcap abi.StoragePower) error) error { + + return forEachCap(s.store, actors.Version8, s.verifiedClients, cb) + +} + +func (s *state8) verifiedClients() (adt.Map, error) { + + return adt8.AsMap(s.store, s.VerifiedClients, builtin8.DefaultHamtBitwidth) + +} + +func (s *state8) verifiers() (adt.Map, error) { + return adt8.AsMap(s.store, s.Verifiers, builtin8.DefaultHamtBitwidth) +} + +func (s *state8) removeDataCapProposalIDs() (adt.Map, error) { + return adt8.AsMap(s.store, s.RemoveDataCapProposalIDs, builtin8.DefaultHamtBitwidth) +} + +func (s *state8) GetState() interface{} { + return &s.State +} + +func (s *state8) GetAllocation(clientIdAddr address.Address, allocationId verifreg9.AllocationId) (*Allocation, bool, error) { + + return nil, false, xerrors.Errorf("unsupported in actors v8") + +} + +func (s *state8) GetAllocations(clientIdAddr address.Address) (map[AllocationId]Allocation, error) { + + return nil, xerrors.Errorf("unsupported in actors v8") + +} + +func (s *state8) GetClaim(providerIdAddr address.Address, claimId verifreg9.ClaimId) (*Claim, bool, error) { + + return nil, false, xerrors.Errorf("unsupported in actors v8") + +} + +func (s *state8) GetClaims(providerIdAddr address.Address) (map[ClaimId]Claim, error) { + + return nil, xerrors.Errorf("unsupported in actors v8") + +} + +func (s *state8) GetClaimIdsBySector(providerIdAddr address.Address) (map[abi.SectorNumber][]ClaimId, error) { + + return nil, xerrors.Errorf("unsupported in actors v8") + +} + +func (s *state8) ActorKey() string { + return manifest.VerifregKey +} + +func (s *state8) ActorVersion() actorstypes.Version { + return actorstypes.Version8 +} + +func (s *state8) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/verifreg/v9.go b/chain/actors/builtin/verifreg/v9.go new file mode 100644 index 000000000..ce63c7f94 --- /dev/null +++ b/chain/actors/builtin/verifreg/v9.go @@ -0,0 +1,169 @@ +package verifreg + +import ( + "fmt" + + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + builtin9 "github.com/filecoin-project/go-state-types/builtin" + adt9 "github.com/filecoin-project/go-state-types/builtin/v9/util/adt" + verifreg9 "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var _ State = (*state9)(nil) + +func load9(store adt.Store, root cid.Cid) (State, error) { + out := state9{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +func make9(store adt.Store, rootKeyAddress address.Address) (State, error) { + out := state9{store: store} + + s, err := verifreg9.ConstructState(store, rootKeyAddress) + if err != nil { + return nil, err + } + + out.State = *s + + return &out, nil +} + +type state9 struct { + verifreg9.State + store adt.Store +} + +func (s *state9) RootKey() (address.Address, error) { + return s.State.RootKey, nil +} + +func (s *state9) VerifiedClientDataCap(addr address.Address) (bool, abi.StoragePower, error) { + + return false, big.Zero(), xerrors.Errorf("unsupported in actors v9") + +} + +func (s *state9) VerifierDataCap(addr address.Address) (bool, abi.StoragePower, error) { + return getDataCap(s.store, actors.Version9, s.verifiers, addr) +} + +func (s *state9) RemoveDataCapProposalID(verifier address.Address, client address.Address) (bool, uint64, error) { + return getRemoveDataCapProposalID(s.store, actors.Version9, s.removeDataCapProposalIDs, verifier, client) +} + +func (s *state9) ForEachVerifier(cb func(addr address.Address, dcap abi.StoragePower) error) error { + return forEachCap(s.store, actors.Version9, s.verifiers, cb) +} + +func (s *state9) ForEachClient(cb func(addr address.Address, dcap abi.StoragePower) error) error { + + return xerrors.Errorf("unsupported in actors v9") + +} + +func (s *state9) verifiedClients() (adt.Map, error) { + + return nil, xerrors.Errorf("unsupported in actors v9") + +} + +func (s *state9) verifiers() (adt.Map, error) { + return adt9.AsMap(s.store, s.Verifiers, builtin9.DefaultHamtBitwidth) +} + +func (s *state9) removeDataCapProposalIDs() (adt.Map, error) { + return adt9.AsMap(s.store, s.RemoveDataCapProposalIDs, builtin9.DefaultHamtBitwidth) +} + +func (s *state9) GetState() interface{} { + return &s.State +} + +func (s *state9) GetAllocation(clientIdAddr address.Address, allocationId verifreg9.AllocationId) (*Allocation, bool, error) { + + alloc, ok, err := s.FindAllocation(s.store, clientIdAddr, verifreg9.AllocationId(allocationId)) + return (*Allocation)(alloc), ok, err +} + +func (s *state9) GetAllocations(clientIdAddr address.Address) (map[AllocationId]Allocation, error) { + + v9Map, err := s.LoadAllocationsToMap(s.store, clientIdAddr) + + retMap := make(map[AllocationId]Allocation, len(v9Map)) + for k, v := range v9Map { + retMap[AllocationId(k)] = Allocation(v) + } + + return retMap, err + +} + +func (s *state9) GetClaim(providerIdAddr address.Address, claimId verifreg9.ClaimId) (*Claim, bool, error) { + + claim, ok, err := s.FindClaim(s.store, providerIdAddr, verifreg9.ClaimId(claimId)) + return (*Claim)(claim), ok, err + +} + +func (s *state9) GetClaims(providerIdAddr address.Address) (map[ClaimId]Claim, error) { + + v9Map, err := s.LoadClaimsToMap(s.store, providerIdAddr) + + retMap := make(map[ClaimId]Claim, len(v9Map)) + for k, v := range v9Map { + retMap[ClaimId(k)] = Claim(v) + } + + return retMap, err + +} + +func (s *state9) GetClaimIdsBySector(providerIdAddr address.Address) (map[abi.SectorNumber][]ClaimId, error) { + + v9Map, err := s.LoadClaimsToMap(s.store, providerIdAddr) + + retMap := make(map[abi.SectorNumber][]ClaimId) + for k, v := range v9Map { + claims, ok := retMap[v.Sector] + if !ok { + retMap[v.Sector] = []ClaimId{ClaimId(k)} + } else { + retMap[v.Sector] = append(claims, ClaimId(k)) + } + } + + return retMap, err + +} + +func (s *state9) ActorKey() string { + return manifest.VerifregKey +} + +func (s *state9) ActorVersion() actorstypes.Version { + return actorstypes.Version9 +} + +func (s *state9) Code() cid.Cid { + code, ok := actors.GetActorCodeID(s.ActorVersion(), s.ActorKey()) + if !ok { + panic(fmt.Errorf("didn't find actor %v code id for actor version %d", s.ActorKey(), s.ActorVersion())) + } + + return code +} diff --git a/chain/actors/builtin/verifreg/verifreg.go b/chain/actors/builtin/verifreg/verifreg.go new file mode 100644 index 000000000..eb911ea46 --- /dev/null +++ b/chain/actors/builtin/verifreg/verifreg.go @@ -0,0 +1,163 @@ +package verifreg + +import ( + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtin11 "github.com/filecoin-project/go-state-types/builtin" + verifregtypes "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" + "github.com/filecoin-project/go-state-types/cbor" + "github.com/filecoin-project/go-state-types/manifest" + builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + builtin3 "github.com/filecoin-project/specs-actors/v3/actors/builtin" + builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" + builtin6 "github.com/filecoin-project/specs-actors/v6/actors/builtin" + builtin7 "github.com/filecoin-project/specs-actors/v7/actors/builtin" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/types" +) + +var ( + Address = builtin11.VerifiedRegistryActorAddr + Methods = builtin11.MethodsVerifiedRegistry +) + +func Load(store adt.Store, act *types.Actor) (State, error) { + if name, av, ok := actors.GetActorMetaByCode(act.Code); ok { + if name != manifest.VerifregKey { + return nil, xerrors.Errorf("actor code is not verifreg: %s", name) + } + + switch av { + + case actorstypes.Version8: + return load8(store, act.Head) + + case actorstypes.Version9: + return load9(store, act.Head) + + case actorstypes.Version10: + return load10(store, act.Head) + + case actorstypes.Version11: + return load11(store, act.Head) + + } + } + + switch act.Code { + + case builtin0.VerifiedRegistryActorCodeID: + return load0(store, act.Head) + + case builtin2.VerifiedRegistryActorCodeID: + return load2(store, act.Head) + + case builtin3.VerifiedRegistryActorCodeID: + return load3(store, act.Head) + + case builtin4.VerifiedRegistryActorCodeID: + return load4(store, act.Head) + + case builtin5.VerifiedRegistryActorCodeID: + return load5(store, act.Head) + + case builtin6.VerifiedRegistryActorCodeID: + return load6(store, act.Head) + + case builtin7.VerifiedRegistryActorCodeID: + return load7(store, act.Head) + + } + + return nil, xerrors.Errorf("unknown actor code %s", act.Code) +} + +func MakeState(store adt.Store, av actorstypes.Version, rootKeyAddress address.Address) (State, error) { + switch av { + + case actorstypes.Version0: + return make0(store, rootKeyAddress) + + case actorstypes.Version2: + return make2(store, rootKeyAddress) + + case actorstypes.Version3: + return make3(store, rootKeyAddress) + + case actorstypes.Version4: + return make4(store, rootKeyAddress) + + case actorstypes.Version5: + return make5(store, rootKeyAddress) + + case actorstypes.Version6: + return make6(store, rootKeyAddress) + + case actorstypes.Version7: + return make7(store, rootKeyAddress) + + case actorstypes.Version8: + return make8(store, rootKeyAddress) + + case actorstypes.Version9: + return make9(store, rootKeyAddress) + + case actorstypes.Version10: + return make10(store, rootKeyAddress) + + case actorstypes.Version11: + return make11(store, rootKeyAddress) + + } + return nil, xerrors.Errorf("unknown actor version %d", av) +} + +type State interface { + cbor.Marshaler + + Code() cid.Cid + ActorKey() string + ActorVersion() actorstypes.Version + + RootKey() (address.Address, error) + VerifiedClientDataCap(address.Address) (bool, abi.StoragePower, error) + VerifierDataCap(address.Address) (bool, abi.StoragePower, error) + RemoveDataCapProposalID(verifier address.Address, client address.Address) (bool, uint64, error) + ForEachVerifier(func(addr address.Address, dcap abi.StoragePower) error) error + ForEachClient(func(addr address.Address, dcap abi.StoragePower) error) error + GetAllocation(clientIdAddr address.Address, allocationId AllocationId) (*Allocation, bool, error) + GetAllocations(clientIdAddr address.Address) (map[AllocationId]Allocation, error) + GetClaim(providerIdAddr address.Address, claimId ClaimId) (*Claim, bool, error) + GetClaims(providerIdAddr address.Address) (map[ClaimId]Claim, error) + GetClaimIdsBySector(providerIdAddr address.Address) (map[abi.SectorNumber][]ClaimId, error) + GetState() interface{} +} + +func AllCodes() []cid.Cid { + return []cid.Cid{ + (&state0{}).Code(), + (&state2{}).Code(), + (&state3{}).Code(), + (&state4{}).Code(), + (&state5{}).Code(), + (&state6{}).Code(), + (&state7{}).Code(), + (&state8{}).Code(), + (&state9{}).Code(), + (&state10{}).Code(), + (&state11{}).Code(), + } +} + +type Allocation = verifregtypes.Allocation +type AllocationId = verifregtypes.AllocationId +type Claim = verifregtypes.Claim +type ClaimId = verifregtypes.ClaimId diff --git a/chain/actors/manifest.go b/chain/actors/manifest.go new file mode 100644 index 000000000..f58768ca2 --- /dev/null +++ b/chain/actors/manifest.go @@ -0,0 +1,132 @@ +package actors + +import ( + "context" + "strings" + "sync" + + "github.com/ipfs/go-cid" + cbor "github.com/ipfs/go-ipld-cbor" + "golang.org/x/xerrors" + + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/manifest" + + "github.com/filecoin-project/lotus/chain/actors/adt" +) + +var manifestCids = make(map[actorstypes.Version]cid.Cid) +var manifests = make(map[actorstypes.Version]map[string]cid.Cid) +var actorMeta = make(map[cid.Cid]actorEntry) + +var ( + manifestMx sync.RWMutex +) + +type actorEntry struct { + name string + version actorstypes.Version +} + +// ClearManifests clears all known manifests. This is usually used in tests that need to switch networks. +func ClearManifests() { + manifestMx.Lock() + defer manifestMx.Unlock() + + manifestCids = make(map[actorstypes.Version]cid.Cid) + manifests = make(map[actorstypes.Version]map[string]cid.Cid) + actorMeta = make(map[cid.Cid]actorEntry) +} + +// RegisterManifest registers an actors manifest with lotus. +func RegisterManifest(av actorstypes.Version, manifestCid cid.Cid, entries map[string]cid.Cid) { + manifestMx.Lock() + defer manifestMx.Unlock() + + manifestCids[av] = manifestCid + manifests[av] = entries + + for name, c := range entries { + actorMeta[c] = actorEntry{name: name, version: av} + } +} + +// GetManifest gets a loaded manifest. +func GetManifest(av actorstypes.Version) (cid.Cid, bool) { + manifestMx.RLock() + defer manifestMx.RUnlock() + + c, ok := manifestCids[av] + return c, ok +} + +// ReadManifest reads a manifest from a blockstore. It does not "add" it. +func ReadManifest(ctx context.Context, store cbor.IpldStore, mfCid cid.Cid) (map[string]cid.Cid, error) { + adtStore := adt.WrapStore(ctx, store) + + var mf manifest.Manifest + if err := adtStore.Get(ctx, mfCid, &mf); err != nil { + return nil, xerrors.Errorf("error reading manifest (cid: %s): %w", mfCid, err) + } + + if err := mf.Load(ctx, adtStore); err != nil { + return nil, xerrors.Errorf("error loading manifest (cid: %s): %w", mfCid, err) + } + + var manifestData manifest.ManifestData + if err := store.Get(ctx, mf.Data, &manifestData); err != nil { + return nil, xerrors.Errorf("error loading manifest data: %w", err) + } + + metadata := make(map[string]cid.Cid) + for _, entry := range manifestData.Entries { + metadata[entry.Name] = entry.Code + } + + return metadata, nil +} + +// GetActorCodeIDsFromManifest looks up all builtin actor's code CIDs by actor version for versions that have a manifest. +func GetActorCodeIDsFromManifest(av actorstypes.Version) (map[string]cid.Cid, bool) { + manifestMx.RLock() + defer manifestMx.RUnlock() + + cids, ok := manifests[av] + return cids, ok +} + +// Given a Manifest CID, get the manifest from the store and Load data into its entries +func LoadManifest(ctx context.Context, mfCid cid.Cid, adtStore adt.Store) (*manifest.Manifest, error) { + var mf manifest.Manifest + + if err := adtStore.Get(ctx, mfCid, &mf); err != nil { + return nil, xerrors.Errorf("error reading manifest: %w", err) + } + + if err := mf.Load(ctx, adtStore); err != nil { + return nil, xerrors.Errorf("error loading manifest entries data: %w", err) + } + + return &mf, nil +} + +func GetActorMetaByCode(c cid.Cid) (string, actorstypes.Version, bool) { + manifestMx.RLock() + defer manifestMx.RUnlock() + + entry, ok := actorMeta[c] + if !ok { + return "", -1, false + } + + return entry.name, entry.version, true +} + +func CanonicalName(name string) string { + idx := strings.LastIndex(name, "/") + if idx >= 0 { + return name[idx+1:] + } + + return name +} diff --git a/chain/actors/params.go b/chain/actors/params.go index e14dcafc9..f09b0be55 100644 --- a/chain/actors/params.go +++ b/chain/actors/params.go @@ -3,15 +3,18 @@ package actors import ( "bytes" - "github.com/filecoin-project/lotus/chain/actors/aerrors" cbg "github.com/whyrusleeping/cbor-gen" + + "github.com/filecoin-project/go-state-types/exitcode" + + "github.com/filecoin-project/lotus/chain/actors/aerrors" ) func SerializeParams(i cbg.CBORMarshaler) ([]byte, aerrors.ActorError) { buf := new(bytes.Buffer) if err := i.MarshalCBOR(buf); err != nil { // TODO: shouldnt this be a fatal error? - return nil, aerrors.Absorb(err, 1, "failed to encode parameter") + return nil, aerrors.Absorb(err, exitcode.ErrSerialization, "failed to encode parameter") } return buf.Bytes(), nil } diff --git a/chain/actors/policy/policy.go b/chain/actors/policy/policy.go new file mode 100644 index 000000000..4b90c46a0 --- /dev/null +++ b/chain/actors/policy/policy.go @@ -0,0 +1,770 @@ +package policy + +import ( + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + builtin10 "github.com/filecoin-project/go-state-types/builtin" + builtin11 "github.com/filecoin-project/go-state-types/builtin" + builtin8 "github.com/filecoin-project/go-state-types/builtin" + builtin9 "github.com/filecoin-project/go-state-types/builtin" + market10 "github.com/filecoin-project/go-state-types/builtin/v10/market" + miner10 "github.com/filecoin-project/go-state-types/builtin/v10/miner" + verifreg10 "github.com/filecoin-project/go-state-types/builtin/v10/verifreg" + market11 "github.com/filecoin-project/go-state-types/builtin/v11/market" + miner11 "github.com/filecoin-project/go-state-types/builtin/v11/miner" + paych11 "github.com/filecoin-project/go-state-types/builtin/v11/paych" + verifreg11 "github.com/filecoin-project/go-state-types/builtin/v11/verifreg" + market8 "github.com/filecoin-project/go-state-types/builtin/v8/market" + miner8 "github.com/filecoin-project/go-state-types/builtin/v8/miner" + verifreg8 "github.com/filecoin-project/go-state-types/builtin/v8/verifreg" + market9 "github.com/filecoin-project/go-state-types/builtin/v9/market" + miner9 "github.com/filecoin-project/go-state-types/builtin/v9/miner" + verifreg9 "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" + "github.com/filecoin-project/go-state-types/network" + market0 "github.com/filecoin-project/specs-actors/actors/builtin/market" + miner0 "github.com/filecoin-project/specs-actors/actors/builtin/miner" + power0 "github.com/filecoin-project/specs-actors/actors/builtin/power" + verifreg0 "github.com/filecoin-project/specs-actors/actors/builtin/verifreg" + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market" + miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/miner" + verifreg2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/verifreg" + builtin3 "github.com/filecoin-project/specs-actors/v3/actors/builtin" + market3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/market" + miner3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/miner" + verifreg3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/verifreg" + builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" + market4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/market" + miner4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/miner" + verifreg4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/verifreg" + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" + market5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/market" + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" + verifreg5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/verifreg" + builtin6 "github.com/filecoin-project/specs-actors/v6/actors/builtin" + market6 "github.com/filecoin-project/specs-actors/v6/actors/builtin/market" + miner6 "github.com/filecoin-project/specs-actors/v6/actors/builtin/miner" + verifreg6 "github.com/filecoin-project/specs-actors/v6/actors/builtin/verifreg" + builtin7 "github.com/filecoin-project/specs-actors/v7/actors/builtin" + market7 "github.com/filecoin-project/specs-actors/v7/actors/builtin/market" + miner7 "github.com/filecoin-project/specs-actors/v7/actors/builtin/miner" + verifreg7 "github.com/filecoin-project/specs-actors/v7/actors/builtin/verifreg" +) + +const ( + ChainFinality = miner11.ChainFinality + SealRandomnessLookback = ChainFinality + PaychSettleDelay = paych11.SettleDelay + MaxPreCommitRandomnessLookback = builtin11.EpochsInDay + SealRandomnessLookback +) + +// SetSupportedProofTypes sets supported proof types, across all actor versions. +// This should only be used for testing. +func SetSupportedProofTypes(types ...abi.RegisteredSealProof) { + + miner0.SupportedProofTypes = make(map[abi.RegisteredSealProof]struct{}, len(types)) + + miner2.PreCommitSealProofTypesV0 = make(map[abi.RegisteredSealProof]struct{}, len(types)) + miner2.PreCommitSealProofTypesV7 = make(map[abi.RegisteredSealProof]struct{}, len(types)*2) + miner2.PreCommitSealProofTypesV8 = make(map[abi.RegisteredSealProof]struct{}, len(types)) + + miner3.PreCommitSealProofTypesV0 = make(map[abi.RegisteredSealProof]struct{}, len(types)) + miner3.PreCommitSealProofTypesV7 = make(map[abi.RegisteredSealProof]struct{}, len(types)*2) + miner3.PreCommitSealProofTypesV8 = make(map[abi.RegisteredSealProof]struct{}, len(types)) + + miner4.PreCommitSealProofTypesV0 = make(map[abi.RegisteredSealProof]struct{}, len(types)) + miner4.PreCommitSealProofTypesV7 = make(map[abi.RegisteredSealProof]struct{}, len(types)*2) + miner4.PreCommitSealProofTypesV8 = make(map[abi.RegisteredSealProof]struct{}, len(types)) + + miner5.PreCommitSealProofTypesV8 = make(map[abi.RegisteredSealProof]struct{}, len(types)) + + miner6.PreCommitSealProofTypesV8 = make(map[abi.RegisteredSealProof]struct{}, len(types)) + + miner7.PreCommitSealProofTypesV8 = make(map[abi.RegisteredSealProof]struct{}, len(types)) + + AddSupportedProofTypes(types...) +} + +// AddSupportedProofTypes sets supported proof types, across all actor versions. +// This should only be used for testing. +func AddSupportedProofTypes(types ...abi.RegisteredSealProof) { + for _, t := range types { + if t >= abi.RegisteredSealProof_StackedDrg2KiBV1_1 { + panic("must specify v1 proof types only") + } + // Set for all miner versions. + + miner0.SupportedProofTypes[t] = struct{}{} + + miner2.PreCommitSealProofTypesV0[t] = struct{}{} + miner2.PreCommitSealProofTypesV7[t] = struct{}{} + miner2.PreCommitSealProofTypesV7[t+abi.RegisteredSealProof_StackedDrg2KiBV1_1] = struct{}{} + miner2.PreCommitSealProofTypesV8[t+abi.RegisteredSealProof_StackedDrg2KiBV1_1] = struct{}{} + + miner3.PreCommitSealProofTypesV0[t] = struct{}{} + miner3.PreCommitSealProofTypesV7[t] = struct{}{} + miner3.PreCommitSealProofTypesV7[t+abi.RegisteredSealProof_StackedDrg2KiBV1_1] = struct{}{} + miner3.PreCommitSealProofTypesV8[t+abi.RegisteredSealProof_StackedDrg2KiBV1_1] = struct{}{} + + miner4.PreCommitSealProofTypesV0[t] = struct{}{} + miner4.PreCommitSealProofTypesV7[t] = struct{}{} + miner4.PreCommitSealProofTypesV7[t+abi.RegisteredSealProof_StackedDrg2KiBV1_1] = struct{}{} + miner4.PreCommitSealProofTypesV8[t+abi.RegisteredSealProof_StackedDrg2KiBV1_1] = struct{}{} + + miner5.PreCommitSealProofTypesV8[t+abi.RegisteredSealProof_StackedDrg2KiBV1_1] = struct{}{} + wpp, err := t.RegisteredWindowPoStProof() + if err != nil { + // Fine to panic, this is a test-only method + panic(err) + } + + miner5.WindowPoStProofTypes[wpp] = struct{}{} + + miner6.PreCommitSealProofTypesV8[t+abi.RegisteredSealProof_StackedDrg2KiBV1_1] = struct{}{} + wpp, err = t.RegisteredWindowPoStProof() + if err != nil { + // Fine to panic, this is a test-only method + panic(err) + } + + miner6.WindowPoStProofTypes[wpp] = struct{}{} + + miner7.PreCommitSealProofTypesV8[t+abi.RegisteredSealProof_StackedDrg2KiBV1_1] = struct{}{} + wpp, err = t.RegisteredWindowPoStProof() + if err != nil { + // Fine to panic, this is a test-only method + panic(err) + } + + miner7.WindowPoStProofTypes[wpp] = struct{}{} + + } +} + +// SetPreCommitChallengeDelay sets the pre-commit challenge delay across all +// actors versions. Use for testing. +func SetPreCommitChallengeDelay(delay abi.ChainEpoch) { + // Set for all miner versions. + + miner0.PreCommitChallengeDelay = delay + + miner2.PreCommitChallengeDelay = delay + + miner3.PreCommitChallengeDelay = delay + + miner4.PreCommitChallengeDelay = delay + + miner5.PreCommitChallengeDelay = delay + + miner6.PreCommitChallengeDelay = delay + + miner7.PreCommitChallengeDelay = delay + + miner8.PreCommitChallengeDelay = delay + + miner9.PreCommitChallengeDelay = delay + + miner10.PreCommitChallengeDelay = delay + + miner11.PreCommitChallengeDelay = delay + +} + +// TODO: this function shouldn't really exist. Instead, the API should expose the precommit delay. +func GetPreCommitChallengeDelay() abi.ChainEpoch { + return miner11.PreCommitChallengeDelay +} + +// SetConsensusMinerMinPower sets the minimum power of an individual miner must +// meet for leader election, across all actor versions. This should only be used +// for testing. +func SetConsensusMinerMinPower(p abi.StoragePower) { + + power0.ConsensusMinerMinPower = p + + for _, policy := range builtin2.SealProofPolicies { + policy.ConsensusMinerMinPower = p + } + + for _, policy := range builtin3.PoStProofPolicies { + policy.ConsensusMinerMinPower = p + } + + for _, policy := range builtin4.PoStProofPolicies { + policy.ConsensusMinerMinPower = p + } + + for _, policy := range builtin5.PoStProofPolicies { + policy.ConsensusMinerMinPower = p + } + + for _, policy := range builtin6.PoStProofPolicies { + policy.ConsensusMinerMinPower = p + } + + for _, policy := range builtin7.PoStProofPolicies { + policy.ConsensusMinerMinPower = p + } + + for _, policy := range builtin8.PoStProofPolicies { + policy.ConsensusMinerMinPower = p + } + + for _, policy := range builtin9.PoStProofPolicies { + policy.ConsensusMinerMinPower = p + } + + for _, policy := range builtin10.PoStProofPolicies { + policy.ConsensusMinerMinPower = p + } + + for _, policy := range builtin11.PoStProofPolicies { + policy.ConsensusMinerMinPower = p + } + +} + +// SetMinVerifiedDealSize sets the minimum size of a verified deal. This should +// only be used for testing. +func SetMinVerifiedDealSize(size abi.StoragePower) { + + verifreg0.MinVerifiedDealSize = size + + verifreg2.MinVerifiedDealSize = size + + verifreg3.MinVerifiedDealSize = size + + verifreg4.MinVerifiedDealSize = size + + verifreg5.MinVerifiedDealSize = size + + verifreg6.MinVerifiedDealSize = size + + verifreg7.MinVerifiedDealSize = size + + verifreg8.MinVerifiedDealSize = size + + verifreg9.MinVerifiedDealSize = size + + verifreg10.MinVerifiedDealSize = size + + verifreg11.MinVerifiedDealSize = size + +} + +func GetMaxProveCommitDuration(ver actorstypes.Version, t abi.RegisteredSealProof) (abi.ChainEpoch, error) { + switch ver { + + case actorstypes.Version0: + + return miner0.MaxSealDuration[t], nil + + case actorstypes.Version2: + + return miner2.MaxProveCommitDuration[t], nil + + case actorstypes.Version3: + + return miner3.MaxProveCommitDuration[t], nil + + case actorstypes.Version4: + + return miner4.MaxProveCommitDuration[t], nil + + case actorstypes.Version5: + + return miner5.MaxProveCommitDuration[t], nil + + case actorstypes.Version6: + + return miner6.MaxProveCommitDuration[t], nil + + case actorstypes.Version7: + + return miner7.MaxProveCommitDuration[t], nil + + case actorstypes.Version8: + + return miner8.MaxProveCommitDuration[t], nil + + case actorstypes.Version9: + + return miner9.MaxProveCommitDuration[t], nil + + case actorstypes.Version10: + + return miner10.MaxProveCommitDuration[t], nil + + case actorstypes.Version11: + + return miner11.MaxProveCommitDuration[t], nil + + default: + return 0, xerrors.Errorf("unsupported actors version") + } +} + +// SetProviderCollateralSupplyTarget sets the percentage of normalized circulating +// supply that must be covered by provider collateral in a deal. This should +// only be used for testing. +func SetProviderCollateralSupplyTarget(num, denom big.Int) { + + market2.ProviderCollateralSupplyTarget = builtin2.BigFrac{ + Numerator: num, + Denominator: denom, + } + + market3.ProviderCollateralSupplyTarget = builtin3.BigFrac{ + Numerator: num, + Denominator: denom, + } + + market4.ProviderCollateralSupplyTarget = builtin4.BigFrac{ + Numerator: num, + Denominator: denom, + } + + market5.ProviderCollateralSupplyTarget = builtin5.BigFrac{ + Numerator: num, + Denominator: denom, + } + + market6.ProviderCollateralSupplyTarget = builtin6.BigFrac{ + Numerator: num, + Denominator: denom, + } + + market7.ProviderCollateralSupplyTarget = builtin7.BigFrac{ + Numerator: num, + Denominator: denom, + } + + market8.ProviderCollateralSupplyTarget = builtin8.BigFrac{ + Numerator: num, + Denominator: denom, + } + + market9.ProviderCollateralSupplyTarget = builtin9.BigFrac{ + Numerator: num, + Denominator: denom, + } + + market10.ProviderCollateralSupplyTarget = builtin10.BigFrac{ + Numerator: num, + Denominator: denom, + } + + market11.ProviderCollateralSupplyTarget = builtin11.BigFrac{ + Numerator: num, + Denominator: denom, + } + +} + +func DealProviderCollateralBounds( + size abi.PaddedPieceSize, verified bool, + rawBytePower, qaPower, baselinePower abi.StoragePower, + circulatingFil abi.TokenAmount, nwVer network.Version, +) (min, max abi.TokenAmount, err error) { + v, err := actorstypes.VersionForNetwork(nwVer) + if err != nil { + return big.Zero(), big.Zero(), err + } + switch v { + + case actorstypes.Version0: + + min, max := market0.DealProviderCollateralBounds(size, verified, rawBytePower, qaPower, baselinePower, circulatingFil, nwVer) + return min, max, nil + + case actorstypes.Version2: + + min, max := market2.DealProviderCollateralBounds(size, verified, rawBytePower, qaPower, baselinePower, circulatingFil) + return min, max, nil + + case actorstypes.Version3: + + min, max := market3.DealProviderCollateralBounds(size, verified, rawBytePower, qaPower, baselinePower, circulatingFil) + return min, max, nil + + case actorstypes.Version4: + + min, max := market4.DealProviderCollateralBounds(size, verified, rawBytePower, qaPower, baselinePower, circulatingFil) + return min, max, nil + + case actorstypes.Version5: + + min, max := market5.DealProviderCollateralBounds(size, verified, rawBytePower, qaPower, baselinePower, circulatingFil) + return min, max, nil + + case actorstypes.Version6: + + min, max := market6.DealProviderCollateralBounds(size, verified, rawBytePower, qaPower, baselinePower, circulatingFil) + return min, max, nil + + case actorstypes.Version7: + + min, max := market7.DealProviderCollateralBounds(size, verified, rawBytePower, qaPower, baselinePower, circulatingFil) + return min, max, nil + + case actorstypes.Version8: + + min, max := market8.DealProviderCollateralBounds(size, verified, rawBytePower, qaPower, baselinePower, circulatingFil) + return min, max, nil + + case actorstypes.Version9: + + min, max := market9.DealProviderCollateralBounds(size, verified, rawBytePower, qaPower, baselinePower, circulatingFil) + return min, max, nil + + case actorstypes.Version10: + + min, max := market10.DealProviderCollateralBounds(size, verified, rawBytePower, qaPower, baselinePower, circulatingFil) + return min, max, nil + + case actorstypes.Version11: + + min, max := market11.DealProviderCollateralBounds(size, verified, rawBytePower, qaPower, baselinePower, circulatingFil) + return min, max, nil + + default: + return big.Zero(), big.Zero(), xerrors.Errorf("unsupported actors version") + } +} + +func DealDurationBounds(pieceSize abi.PaddedPieceSize) (min, max abi.ChainEpoch) { + return market11.DealDurationBounds(pieceSize) +} + +// Sets the challenge window and scales the proving period to match (such that +// there are always 48 challenge windows in a proving period). +func SetWPoStChallengeWindow(period abi.ChainEpoch) { + + miner0.WPoStChallengeWindow = period + miner0.WPoStProvingPeriod = period * abi.ChainEpoch(miner0.WPoStPeriodDeadlines) + + miner2.WPoStChallengeWindow = period + miner2.WPoStProvingPeriod = period * abi.ChainEpoch(miner2.WPoStPeriodDeadlines) + + miner3.WPoStChallengeWindow = period + miner3.WPoStProvingPeriod = period * abi.ChainEpoch(miner3.WPoStPeriodDeadlines) + + // by default, this is 2x finality which is 30 periods. + // scale it if we're scaling the challenge period. + miner3.WPoStDisputeWindow = period * 30 + + miner4.WPoStChallengeWindow = period + miner4.WPoStProvingPeriod = period * abi.ChainEpoch(miner4.WPoStPeriodDeadlines) + + // by default, this is 2x finality which is 30 periods. + // scale it if we're scaling the challenge period. + miner4.WPoStDisputeWindow = period * 30 + + miner5.WPoStChallengeWindow = period + miner5.WPoStProvingPeriod = period * abi.ChainEpoch(miner5.WPoStPeriodDeadlines) + + // by default, this is 2x finality which is 30 periods. + // scale it if we're scaling the challenge period. + miner5.WPoStDisputeWindow = period * 30 + + miner6.WPoStChallengeWindow = period + miner6.WPoStProvingPeriod = period * abi.ChainEpoch(miner6.WPoStPeriodDeadlines) + + // by default, this is 2x finality which is 30 periods. + // scale it if we're scaling the challenge period. + miner6.WPoStDisputeWindow = period * 30 + + miner7.WPoStChallengeWindow = period + miner7.WPoStProvingPeriod = period * abi.ChainEpoch(miner7.WPoStPeriodDeadlines) + + // by default, this is 2x finality which is 30 periods. + // scale it if we're scaling the challenge period. + miner7.WPoStDisputeWindow = period * 30 + + miner8.WPoStChallengeWindow = period + miner8.WPoStProvingPeriod = period * abi.ChainEpoch(miner8.WPoStPeriodDeadlines) + + // by default, this is 2x finality which is 30 periods. + // scale it if we're scaling the challenge period. + miner8.WPoStDisputeWindow = period * 30 + + miner9.WPoStChallengeWindow = period + miner9.WPoStProvingPeriod = period * abi.ChainEpoch(miner9.WPoStPeriodDeadlines) + + // by default, this is 2x finality which is 30 periods. + // scale it if we're scaling the challenge period. + miner9.WPoStDisputeWindow = period * 30 + + miner10.WPoStChallengeWindow = period + miner10.WPoStProvingPeriod = period * abi.ChainEpoch(miner10.WPoStPeriodDeadlines) + + // by default, this is 2x finality which is 30 periods. + // scale it if we're scaling the challenge period. + miner10.WPoStDisputeWindow = period * 30 + + miner11.WPoStChallengeWindow = period + miner11.WPoStProvingPeriod = period * abi.ChainEpoch(miner11.WPoStPeriodDeadlines) + + // by default, this is 2x finality which is 30 periods. + // scale it if we're scaling the challenge period. + miner11.WPoStDisputeWindow = period * 30 + +} + +func GetWinningPoStSectorSetLookback(nwVer network.Version) abi.ChainEpoch { + if nwVer <= network.Version3 { + return 10 + } + + // NOTE: if this ever changes, adjust it in a (*Miner).mineOne() logline as well + return ChainFinality +} + +func GetMaxSectorExpirationExtension() abi.ChainEpoch { + return miner11.MaxSectorExpirationExtension +} + +func GetMinSectorExpiration() abi.ChainEpoch { + return miner11.MinSectorExpiration +} + +func GetMaxPoStPartitions(nv network.Version, p abi.RegisteredPoStProof) (int, error) { + sectorsPerPart, err := builtin11.PoStProofWindowPoStPartitionSectors(p) + if err != nil { + return 0, err + } + maxSectors, err := GetAddressedSectorsMax(nv) + if err != nil { + return 0, err + } + return int(uint64(maxSectors) / sectorsPerPart), nil +} + +func GetDefaultAggregationProof() abi.RegisteredAggregationProof { + return abi.RegisteredAggregationProof_SnarkPackV1 +} + +func GetSectorMaxLifetime(proof abi.RegisteredSealProof, nwVer network.Version) abi.ChainEpoch { + if nwVer <= network.Version10 { + return builtin4.SealProofPoliciesV0[proof].SectorMaxLifetime + } + + return builtin11.SealProofPoliciesV11[proof].SectorMaxLifetime +} + +func GetAddressedSectorsMax(nwVer network.Version) (int, error) { + v, err := actorstypes.VersionForNetwork(nwVer) + if err != nil { + return 0, err + } + switch v { + + case actorstypes.Version0: + return miner0.AddressedSectorsMax, nil + + case actorstypes.Version2: + return miner2.AddressedSectorsMax, nil + + case actorstypes.Version3: + return miner3.AddressedSectorsMax, nil + + case actorstypes.Version4: + return miner4.AddressedSectorsMax, nil + + case actorstypes.Version5: + return miner5.AddressedSectorsMax, nil + + case actorstypes.Version6: + return miner6.AddressedSectorsMax, nil + + case actorstypes.Version7: + return miner7.AddressedSectorsMax, nil + + case actorstypes.Version8: + return miner8.AddressedSectorsMax, nil + + case actorstypes.Version9: + return miner9.AddressedSectorsMax, nil + + case actorstypes.Version10: + return miner10.AddressedSectorsMax, nil + + case actorstypes.Version11: + return miner11.AddressedSectorsMax, nil + + default: + return 0, xerrors.Errorf("unsupported network version") + } +} + +func GetDeclarationsMax(nwVer network.Version) (int, error) { + v, err := actorstypes.VersionForNetwork(nwVer) + if err != nil { + return 0, err + } + switch v { + + case actorstypes.Version0: + + // TODO: Should we instead error here since the concept doesn't exist yet? + return miner0.AddressedPartitionsMax, nil + + case actorstypes.Version2: + + return miner2.DeclarationsMax, nil + + case actorstypes.Version3: + + return miner3.DeclarationsMax, nil + + case actorstypes.Version4: + + return miner4.DeclarationsMax, nil + + case actorstypes.Version5: + + return miner5.DeclarationsMax, nil + + case actorstypes.Version6: + + return miner6.DeclarationsMax, nil + + case actorstypes.Version7: + + return miner7.DeclarationsMax, nil + + case actorstypes.Version8: + + return miner8.DeclarationsMax, nil + + case actorstypes.Version9: + + return miner9.DeclarationsMax, nil + + case actorstypes.Version10: + + return miner10.DeclarationsMax, nil + + case actorstypes.Version11: + + return miner11.DeclarationsMax, nil + + default: + return 0, xerrors.Errorf("unsupported network version") + } +} + +func AggregateProveCommitNetworkFee(nwVer network.Version, aggregateSize int, baseFee abi.TokenAmount) (abi.TokenAmount, error) { + v, err := actorstypes.VersionForNetwork(nwVer) + if err != nil { + return big.Zero(), err + } + switch v { + + case actorstypes.Version0: + + return big.Zero(), nil + + case actorstypes.Version2: + + return big.Zero(), nil + + case actorstypes.Version3: + + return big.Zero(), nil + + case actorstypes.Version4: + + return big.Zero(), nil + + case actorstypes.Version5: + + return miner5.AggregateNetworkFee(aggregateSize, baseFee), nil + + case actorstypes.Version6: + + return miner6.AggregateProveCommitNetworkFee(aggregateSize, baseFee), nil + + case actorstypes.Version7: + + return miner7.AggregateProveCommitNetworkFee(aggregateSize, baseFee), nil + + case actorstypes.Version8: + + return miner8.AggregateProveCommitNetworkFee(aggregateSize, baseFee), nil + + case actorstypes.Version9: + + return miner9.AggregateProveCommitNetworkFee(aggregateSize, baseFee), nil + + case actorstypes.Version10: + + return miner10.AggregateProveCommitNetworkFee(aggregateSize, baseFee), nil + + case actorstypes.Version11: + + return miner11.AggregateProveCommitNetworkFee(aggregateSize, baseFee), nil + + default: + return big.Zero(), xerrors.Errorf("unsupported network version") + } +} + +func AggregatePreCommitNetworkFee(nwVer network.Version, aggregateSize int, baseFee abi.TokenAmount) (abi.TokenAmount, error) { + v, err := actorstypes.VersionForNetwork(nwVer) + if err != nil { + return big.Zero(), err + } + switch v { + + case actorstypes.Version0: + + return big.Zero(), nil + + case actorstypes.Version2: + + return big.Zero(), nil + + case actorstypes.Version3: + + return big.Zero(), nil + + case actorstypes.Version4: + + return big.Zero(), nil + + case actorstypes.Version5: + + return big.Zero(), nil + + case actorstypes.Version6: + + return miner6.AggregatePreCommitNetworkFee(aggregateSize, baseFee), nil + + case actorstypes.Version7: + + return miner7.AggregatePreCommitNetworkFee(aggregateSize, baseFee), nil + + case actorstypes.Version8: + + return miner8.AggregatePreCommitNetworkFee(aggregateSize, baseFee), nil + + case actorstypes.Version9: + + return miner9.AggregatePreCommitNetworkFee(aggregateSize, baseFee), nil + + case actorstypes.Version10: + + return miner10.AggregatePreCommitNetworkFee(aggregateSize, baseFee), nil + + case actorstypes.Version11: + + return miner11.AggregatePreCommitNetworkFee(aggregateSize, baseFee), nil + + default: + return big.Zero(), xerrors.Errorf("unsupported network version") + } +} diff --git a/chain/actors/policy/policy.go.template b/chain/actors/policy/policy.go.template new file mode 100644 index 000000000..f5178500a --- /dev/null +++ b/chain/actors/policy/policy.go.template @@ -0,0 +1,327 @@ +package policy + +import ( + actorstypes "github.com/filecoin-project/go-state-types/actors" + + "github.com/filecoin-project/go-state-types/big" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/network" + + {{range .versions}} + {{if (ge . 8)}} + builtin{{.}} "github.com/filecoin-project/go-state-types/builtin" + miner{{.}} "github.com/filecoin-project/go-state-types/builtin{{import .}}miner" + market{{.}} "github.com/filecoin-project/go-state-types/builtin{{import .}}market" + verifreg{{.}} "github.com/filecoin-project/go-state-types/builtin{{import .}}verifreg" + {{else}} + {{if (ge . 2)}} + builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin" + {{end}} + market{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin/market" + miner{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin/miner" + verifreg{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin/verifreg" + {{if (eq . 0)}} + power{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin/power" + {{end}} + {{end}} + {{end}} + + paych{{.latestVersion}} "github.com/filecoin-project/go-state-types/builtin{{import .latestVersion}}paych" + +) + +const ( + ChainFinality = miner{{.latestVersion}}.ChainFinality + SealRandomnessLookback = ChainFinality + PaychSettleDelay = paych{{.latestVersion}}.SettleDelay + MaxPreCommitRandomnessLookback = builtin{{.latestVersion}}.EpochsInDay + SealRandomnessLookback +) + +// SetSupportedProofTypes sets supported proof types, across all actor versions. +// This should only be used for testing. +func SetSupportedProofTypes(types ...abi.RegisteredSealProof) { + {{range .versions}} + {{if (eq . 0)}} + miner{{.}}.SupportedProofTypes = make(map[abi.RegisteredSealProof]struct{}, len(types)) + {{else if (le . 4)}} + miner{{.}}.PreCommitSealProofTypesV0 = make(map[abi.RegisteredSealProof]struct{}, len(types)) + miner{{.}}.PreCommitSealProofTypesV7 = make(map[abi.RegisteredSealProof]struct{}, len(types)*2) + miner{{.}}.PreCommitSealProofTypesV8 = make(map[abi.RegisteredSealProof]struct{}, len(types)) + {{else if (le . 7)}} + miner{{.}}.PreCommitSealProofTypesV8 = make(map[abi.RegisteredSealProof]struct{}, len(types)) + {{end}} + {{end}} + + AddSupportedProofTypes(types...) +} + +// AddSupportedProofTypes sets supported proof types, across all actor versions. +// This should only be used for testing. +func AddSupportedProofTypes(types ...abi.RegisteredSealProof) { + for _, t := range types { + if t >= abi.RegisteredSealProof_StackedDrg2KiBV1_1 { + panic("must specify v1 proof types only") + } + // Set for all miner versions. + + {{range .versions}} + {{if (eq . 0)}} + miner{{.}}.SupportedProofTypes[t] = struct{}{} + {{else if (le . 4)}} + miner{{.}}.PreCommitSealProofTypesV0[t] = struct{}{} + miner{{.}}.PreCommitSealProofTypesV7[t] = struct{}{} + miner{{.}}.PreCommitSealProofTypesV7[t+abi.RegisteredSealProof_StackedDrg2KiBV1_1] = struct{}{} + miner{{.}}.PreCommitSealProofTypesV8[t+abi.RegisteredSealProof_StackedDrg2KiBV1_1] = struct{}{} + {{else if (eq . 5)}} + miner{{.}}.PreCommitSealProofTypesV8[t+abi.RegisteredSealProof_StackedDrg2KiBV1_1] = struct{}{} + wpp, err := t.RegisteredWindowPoStProof() + if err != nil { + // Fine to panic, this is a test-only method + panic(err) + } + + miner{{.}}.WindowPoStProofTypes[wpp] = struct{}{} + {{else if (le . 7)}} + miner{{.}}.PreCommitSealProofTypesV8[t+abi.RegisteredSealProof_StackedDrg2KiBV1_1] = struct{}{} + wpp, err = t.RegisteredWindowPoStProof() + if err != nil { + // Fine to panic, this is a test-only method + panic(err) + } + + miner{{.}}.WindowPoStProofTypes[wpp] = struct{}{} + {{end}} + {{end}} + } +} + +// SetPreCommitChallengeDelay sets the pre-commit challenge delay across all +// actors versions. Use for testing. +func SetPreCommitChallengeDelay(delay abi.ChainEpoch) { + // Set for all miner versions. + {{range .versions}} + miner{{.}}.PreCommitChallengeDelay = delay + {{end}} +} + +// TODO: this function shouldn't really exist. Instead, the API should expose the precommit delay. +func GetPreCommitChallengeDelay() abi.ChainEpoch { + return miner{{.latestVersion}}.PreCommitChallengeDelay +} + +// SetConsensusMinerMinPower sets the minimum power of an individual miner must +// meet for leader election, across all actor versions. This should only be used +// for testing. +func SetConsensusMinerMinPower(p abi.StoragePower) { + {{range .versions}} + {{if (eq . 0)}} + power{{.}}.ConsensusMinerMinPower = p + {{else if (eq . 2)}} + for _, policy := range builtin{{.}}.SealProofPolicies { + policy.ConsensusMinerMinPower = p + } + {{else}} + for _, policy := range builtin{{.}}.PoStProofPolicies { + policy.ConsensusMinerMinPower = p + } + {{end}} + {{end}} +} + +// SetMinVerifiedDealSize sets the minimum size of a verified deal. This should +// only be used for testing. +func SetMinVerifiedDealSize(size abi.StoragePower) { + {{range .versions}} + verifreg{{.}}.MinVerifiedDealSize = size + {{end}} +} + +func GetMaxProveCommitDuration(ver actorstypes.Version, t abi.RegisteredSealProof) (abi.ChainEpoch, error) { + switch ver { + {{range .versions}} + case actorstypes.Version{{.}}: + {{if (eq . 0)}} + return miner{{.}}.MaxSealDuration[t], nil + {{else}} + return miner{{.}}.MaxProveCommitDuration[t], nil + {{end}} + {{end}} + default: + return 0, xerrors.Errorf("unsupported actors version") + } +} + +// SetProviderCollateralSupplyTarget sets the percentage of normalized circulating +// supply that must be covered by provider collateral in a deal. This should +// only be used for testing. +func SetProviderCollateralSupplyTarget(num, denom big.Int) { +{{range .versions}} + {{if (ge . 2)}} + market{{.}}.ProviderCollateralSupplyTarget = builtin{{.}}.BigFrac{ + Numerator: num, + Denominator: denom, + } + {{end}} +{{end}} +} + +func DealProviderCollateralBounds( + size abi.PaddedPieceSize, verified bool, + rawBytePower, qaPower, baselinePower abi.StoragePower, + circulatingFil abi.TokenAmount, nwVer network.Version, +) (min, max abi.TokenAmount, err error) { + v, err := actorstypes.VersionForNetwork(nwVer) + if err != nil { + return big.Zero(), big.Zero(), err + } + switch v { + {{range .versions}} + case actorstypes.Version{{.}}: + {{if (eq . 0)}} + min, max := market{{.}}.DealProviderCollateralBounds(size, verified, rawBytePower, qaPower, baselinePower, circulatingFil, nwVer) + return min, max, nil + {{else}} + min, max := market{{.}}.DealProviderCollateralBounds(size, verified, rawBytePower, qaPower, baselinePower, circulatingFil) + return min, max, nil + {{end}} + {{end}} + default: + return big.Zero(), big.Zero(), xerrors.Errorf("unsupported actors version") + } +} + +func DealDurationBounds(pieceSize abi.PaddedPieceSize) (min, max abi.ChainEpoch) { + return market{{.latestVersion}}.DealDurationBounds(pieceSize) +} + +// Sets the challenge window and scales the proving period to match (such that +// there are always 48 challenge windows in a proving period). +func SetWPoStChallengeWindow(period abi.ChainEpoch) { + {{range .versions}} + miner{{.}}.WPoStChallengeWindow = period + miner{{.}}.WPoStProvingPeriod = period * abi.ChainEpoch(miner{{.}}.WPoStPeriodDeadlines) + {{if (ge . 3)}} + // by default, this is 2x finality which is 30 periods. + // scale it if we're scaling the challenge period. + miner{{.}}.WPoStDisputeWindow = period * 30 + {{end}} + {{end}} +} + +func GetWinningPoStSectorSetLookback(nwVer network.Version) abi.ChainEpoch { + if nwVer <= network.Version3 { + return 10 + } + + // NOTE: if this ever changes, adjust it in a (*Miner).mineOne() logline as well + return ChainFinality +} + +func GetMaxSectorExpirationExtension() abi.ChainEpoch { + return miner{{.latestVersion}}.MaxSectorExpirationExtension +} + +func GetMinSectorExpiration() abi.ChainEpoch { + return miner{{.latestVersion}}.MinSectorExpiration +} + +func GetMaxPoStPartitions(nv network.Version, p abi.RegisteredPoStProof) (int, error) { + sectorsPerPart, err := builtin{{.latestVersion}}.PoStProofWindowPoStPartitionSectors(p) + if err != nil { + return 0, err + } + maxSectors, err := GetAddressedSectorsMax(nv) + if err != nil { + return 0, err + } + return int(uint64(maxSectors) / sectorsPerPart), nil +} + +func GetDefaultAggregationProof() abi.RegisteredAggregationProof { + return abi.RegisteredAggregationProof_SnarkPackV1 +} + +func GetSectorMaxLifetime(proof abi.RegisteredSealProof, nwVer network.Version) abi.ChainEpoch { + if nwVer <= network.Version10 { + return builtin4.SealProofPoliciesV0[proof].SectorMaxLifetime + } + + return builtin{{.latestVersion}}.SealProofPoliciesV11[proof].SectorMaxLifetime +} + +func GetAddressedSectorsMax(nwVer network.Version) (int, error) { + v, err := actorstypes.VersionForNetwork(nwVer) + if err != nil { + return 0, err + } + switch v { + {{range .versions}} + case actorstypes.Version{{.}}: + return miner{{.}}.AddressedSectorsMax, nil + {{end}} + default: + return 0, xerrors.Errorf("unsupported network version") + } +} + +func GetDeclarationsMax(nwVer network.Version) (int, error) { + v, err := actorstypes.VersionForNetwork(nwVer) + if err != nil { + return 0, err + } + switch v { + {{range .versions}} + case actorstypes.Version{{.}}: + {{if (eq . 0)}} + // TODO: Should we instead error here since the concept doesn't exist yet? + return miner{{.}}.AddressedPartitionsMax, nil + {{else}} + return miner{{.}}.DeclarationsMax, nil + {{end}} + {{end}} + default: + return 0, xerrors.Errorf("unsupported network version") + } +} + +func AggregateProveCommitNetworkFee(nwVer network.Version, aggregateSize int, baseFee abi.TokenAmount) (abi.TokenAmount, error) { + v, err := actorstypes.VersionForNetwork(nwVer) + if err != nil { + return big.Zero(), err + } + switch v { + {{range .versions}} + case actorstypes.Version{{.}}: + {{if (ge . 6)}} + return miner{{.}}.AggregateProveCommitNetworkFee(aggregateSize, baseFee), nil + {{else if (eq . 5)}} + return miner{{.}}.AggregateNetworkFee(aggregateSize, baseFee), nil + {{else}} + return big.Zero(), nil + {{end}} + {{end}} + default: + return big.Zero(), xerrors.Errorf("unsupported network version") + } +} + +func AggregatePreCommitNetworkFee(nwVer network.Version, aggregateSize int, baseFee abi.TokenAmount) (abi.TokenAmount, error) { + v, err := actorstypes.VersionForNetwork(nwVer) + if err != nil { + return big.Zero(), err + } + switch v { + {{range .versions}} + case actorstypes.Version{{.}}: + {{if (ge . 6)}} + return miner{{.}}.AggregatePreCommitNetworkFee(aggregateSize, baseFee), nil + {{else}} + return big.Zero(), nil + {{end}} + {{end}} + default: + return big.Zero(), xerrors.Errorf("unsupported network version") + } +} diff --git a/chain/actors/policy/policy_test.go b/chain/actors/policy/policy_test.go new file mode 100644 index 000000000..726fca95a --- /dev/null +++ b/chain/actors/policy/policy_test.go @@ -0,0 +1,86 @@ +// stm: #unit +package policy + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/network" + builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" + miner0 "github.com/filecoin-project/specs-actors/actors/builtin/miner" + paych0 "github.com/filecoin-project/specs-actors/actors/builtin/paych" + verifreg0 "github.com/filecoin-project/specs-actors/actors/builtin/verifreg" + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/miner" + paych2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/paych" + verifreg2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/verifreg" +) + +func TestSupportedProofTypes(t *testing.T) { + var oldTypes []abi.RegisteredSealProof + for t := range miner0.SupportedProofTypes { + oldTypes = append(oldTypes, t) + } + //stm: @BLOCKCHAIN_POLICY_SET_MAX_SUPPORTED_PROOF_TYPES_001 + t.Cleanup(func() { + SetSupportedProofTypes(oldTypes...) + }) + + SetSupportedProofTypes(abi.RegisteredSealProof_StackedDrg2KiBV1) + require.EqualValues(t, + miner0.SupportedProofTypes, + map[abi.RegisteredSealProof]struct{}{ + abi.RegisteredSealProof_StackedDrg2KiBV1: {}, + }, + ) + //stm: @BLOCKCHAIN_POLICY_ADD_MAX_SUPPORTED_PROOF_TYPES_001 + AddSupportedProofTypes(abi.RegisteredSealProof_StackedDrg8MiBV1) + require.EqualValues(t, + miner0.SupportedProofTypes, + map[abi.RegisteredSealProof]struct{}{ + abi.RegisteredSealProof_StackedDrg2KiBV1: {}, + abi.RegisteredSealProof_StackedDrg8MiBV1: {}, + }, + ) +} + +// Tests assumptions about policies being the same between actor versions. +func TestAssumptions(t *testing.T) { + //stm: @BLOCKCHAIN_POLICY_ASSUMPTIONS_001 + require.EqualValues(t, miner0.SupportedProofTypes, miner2.PreCommitSealProofTypesV0) + require.Equal(t, miner0.PreCommitChallengeDelay, miner2.PreCommitChallengeDelay) + require.Equal(t, miner0.MaxSectorExpirationExtension, miner2.MaxSectorExpirationExtension) + require.Equal(t, miner0.ChainFinality, miner2.ChainFinality) + require.Equal(t, miner0.WPoStChallengeWindow, miner2.WPoStChallengeWindow) + require.Equal(t, miner0.WPoStProvingPeriod, miner2.WPoStProvingPeriod) + require.Equal(t, miner0.WPoStPeriodDeadlines, miner2.WPoStPeriodDeadlines) + require.Equal(t, miner0.AddressedSectorsMax, miner2.AddressedSectorsMax) + require.Equal(t, paych0.SettleDelay, paych2.SettleDelay) + require.True(t, verifreg0.MinVerifiedDealSize.Equals(verifreg2.MinVerifiedDealSize)) +} + +func TestPartitionSizes(t *testing.T) { + //stm: @CHAIN_ACTOR_PARTITION_SIZES_001 + for _, p := range abi.SealProofInfos { + sizeNew, err := builtin2.PoStProofWindowPoStPartitionSectors(p.WindowPoStProof) + require.NoError(t, err) + sizeOld, err := builtin0.PoStProofWindowPoStPartitionSectors(p.WindowPoStProof) + if err != nil { + // new proof type. + continue + } + require.Equal(t, sizeOld, sizeNew) + } +} + +func TestPoStSize(t *testing.T) { + //stm: @BLOCKCHAIN_POLICY_GET_MAX_POST_PARTITIONS_001 + v12PoStSize, err := GetMaxPoStPartitions(network.Version12, abi.RegisteredPoStProof_StackedDrgWindow64GiBV1) + require.Equal(t, 4, v12PoStSize) + require.NoError(t, err) + v13PoStSize, err := GetMaxPoStPartitions(network.Version13, abi.RegisteredPoStProof_StackedDrgWindow64GiBV1) + require.NoError(t, err) + require.Equal(t, 10, v13PoStSize) +} diff --git a/chain/actors/version.go b/chain/actors/version.go new file mode 100644 index 000000000..3a5b935bf --- /dev/null +++ b/chain/actors/version.go @@ -0,0 +1,35 @@ +package actors + +type Version int + +/* inline-gen template + +var LatestVersion = {{.latestActorsVersion}} + +var Versions = []int{ {{range .actorVersions}} {{.}}, {{end}} } + +const ({{range .actorVersions}} + Version{{.}} Version = {{.}}{{end}} +) + +/* inline-gen start */ + +var LatestVersion = 11 + +var Versions = []int{0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11} + +const ( + Version0 Version = 0 + Version2 Version = 2 + Version3 Version = 3 + Version4 Version = 4 + Version5 Version = 5 + Version6 Version = 6 + Version7 Version = 7 + Version8 Version = 8 + Version9 Version = 9 + Version10 Version = 10 + Version11 Version = 11 +) + +/* inline-gen end */ diff --git a/chain/badtscache.go b/chain/badtscache.go index cdb75f842..0f215dcdc 100644 --- a/chain/badtscache.go +++ b/chain/badtscache.go @@ -1,17 +1,49 @@ package chain import ( - "github.com/filecoin-project/lotus/build" - lru "github.com/hashicorp/golang-lru" + "fmt" + + lru "github.com/hashicorp/golang-lru/v2" "github.com/ipfs/go-cid" + + "github.com/filecoin-project/lotus/build" ) type BadBlockCache struct { - badBlocks *lru.ARCCache + badBlocks *lru.ARCCache[cid.Cid, BadBlockReason] +} + +type BadBlockReason struct { + Reason string + TipSet []cid.Cid + OriginalReason *BadBlockReason +} + +func NewBadBlockReason(cid []cid.Cid, format string, i ...interface{}) BadBlockReason { + return BadBlockReason{ + TipSet: cid, + Reason: fmt.Sprintf(format, i...), + } +} + +func (bbr BadBlockReason) Linked(reason string, i ...interface{}) BadBlockReason { + or := &bbr + if bbr.OriginalReason != nil { + or = bbr.OriginalReason + } + return BadBlockReason{Reason: fmt.Sprintf(reason, i...), OriginalReason: or} +} + +func (bbr BadBlockReason) String() string { + res := bbr.Reason + if bbr.OriginalReason != nil { + res += " caused by: " + fmt.Sprintf("%s %s", bbr.OriginalReason.TipSet, bbr.OriginalReason.String()) + } + return res } func NewBadBlockCache() *BadBlockCache { - cache, err := lru.NewARC(build.BadBlockCacheSize) + cache, err := lru.NewARC[cid.Cid, BadBlockReason](build.BadBlockCacheSize) if err != nil { panic(err) // ok } @@ -21,15 +53,18 @@ func NewBadBlockCache() *BadBlockCache { } } -func (bts *BadBlockCache) Add(c cid.Cid, reason string) { - bts.badBlocks.Add(c, reason) +func (bts *BadBlockCache) Add(c cid.Cid, bbr BadBlockReason) { + bts.badBlocks.Add(c, bbr) } -func (bts *BadBlockCache) Has(c cid.Cid) (string, bool) { - rval, ok := bts.badBlocks.Get(c) - if !ok { - return "", false - } - - return rval.(string), true +func (bts *BadBlockCache) Remove(c cid.Cid) { + bts.badBlocks.Remove(c) +} + +func (bts *BadBlockCache) Purge() { + bts.badBlocks.Purge() +} + +func (bts *BadBlockCache) Has(c cid.Cid) (BadBlockReason, bool) { + return bts.badBlocks.Get(c) } diff --git a/chain/beacon/beacon.go b/chain/beacon/beacon.go index 34405f3c8..aa76bcffe 100644 --- a/chain/beacon/beacon.go +++ b/chain/beacon/beacon.go @@ -2,12 +2,15 @@ package beacon import ( "context" - "time" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/specs-actors/actors/abi" - logging "github.com/ipfs/go-log" + logging "github.com/ipfs/go-log/v2" "golang.org/x/xerrors" + + "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/types" ) var log = logging.Logger("beacon") @@ -17,14 +20,54 @@ type Response struct { Err error } +type Schedule []BeaconPoint + +func (bs Schedule) BeaconForEpoch(e abi.ChainEpoch) RandomBeacon { + for i := len(bs) - 1; i >= 0; i-- { + bp := bs[i] + if e >= bp.Start { + return bp.Beacon + } + } + return bs[0].Beacon +} + +type BeaconPoint struct { + Start abi.ChainEpoch + Beacon RandomBeacon +} + +// RandomBeacon represents a system that provides randomness to Lotus. +// Other components interrogate the RandomBeacon to acquire randomness that's +// valid for a specific chain epoch. Also to verify beacon entries that have +// been posted on chain. type RandomBeacon interface { Entry(context.Context, uint64) <-chan Response VerifyEntry(types.BeaconEntry, types.BeaconEntry) error - MaxBeaconRoundForEpoch(abi.ChainEpoch, types.BeaconEntry) uint64 + MaxBeaconRoundForEpoch(network.Version, abi.ChainEpoch) uint64 } -func ValidateBlockValues(b RandomBeacon, h *types.BlockHeader, prevEntry types.BeaconEntry) error { - maxRound := b.MaxBeaconRoundForEpoch(h.Height, prevEntry) +func ValidateBlockValues(bSchedule Schedule, nv network.Version, h *types.BlockHeader, parentEpoch abi.ChainEpoch, + prevEntry types.BeaconEntry) error { + { + parentBeacon := bSchedule.BeaconForEpoch(parentEpoch) + currBeacon := bSchedule.BeaconForEpoch(h.Height) + if parentBeacon != currBeacon { + if len(h.BeaconEntries) != 2 { + return xerrors.Errorf("expected two beacon entries at beacon fork, got %d", len(h.BeaconEntries)) + } + err := currBeacon.VerifyEntry(h.BeaconEntries[1], h.BeaconEntries[0]) + if err != nil { + return xerrors.Errorf("beacon at fork point invalid: (%v, %v): %w", + h.BeaconEntries[1], h.BeaconEntries[0], err) + } + return nil + } + } + + // TODO: fork logic + b := bSchedule.BeaconForEpoch(h.Height) + maxRound := b.MaxBeaconRoundForEpoch(nv, h.Height) if maxRound == prevEntry.Round { if len(h.BeaconEntries) != 0 { return xerrors.Errorf("expected not to have any beacon entries in this block, got %d", len(h.BeaconEntries)) @@ -32,6 +75,10 @@ func ValidateBlockValues(b RandomBeacon, h *types.BlockHeader, prevEntry types.B return nil } + if len(h.BeaconEntries) == 0 { + return xerrors.Errorf("expected to have beacon entries in this block, but didn't find any") + } + last := h.BeaconEntries[len(h.BeaconEntries)-1] if last.Round != maxRound { return xerrors.Errorf("expected final beacon entry in block to be at round %d, got %d", maxRound, last.Round) @@ -47,10 +94,35 @@ func ValidateBlockValues(b RandomBeacon, h *types.BlockHeader, prevEntry types.B return nil } -func BeaconEntriesForBlock(ctx context.Context, beacon RandomBeacon, round abi.ChainEpoch, prev types.BeaconEntry) ([]types.BeaconEntry, error) { - start := time.Now() +func BeaconEntriesForBlock(ctx context.Context, bSchedule Schedule, nv network.Version, epoch abi.ChainEpoch, parentEpoch abi.ChainEpoch, prev types.BeaconEntry) ([]types.BeaconEntry, error) { + { + parentBeacon := bSchedule.BeaconForEpoch(parentEpoch) + currBeacon := bSchedule.BeaconForEpoch(epoch) + if parentBeacon != currBeacon { + // Fork logic + round := currBeacon.MaxBeaconRoundForEpoch(nv, epoch) + out := make([]types.BeaconEntry, 2) + rch := currBeacon.Entry(ctx, round-1) + res := <-rch + if res.Err != nil { + return nil, xerrors.Errorf("getting entry %d returned error: %w", round-1, res.Err) + } + out[0] = res.Entry + rch = currBeacon.Entry(ctx, round) + res = <-rch + if res.Err != nil { + return nil, xerrors.Errorf("getting entry %d returned error: %w", round, res.Err) + } + out[1] = res.Entry + return out, nil + } + } - maxRound := beacon.MaxBeaconRoundForEpoch(round, prev) + beacon := bSchedule.BeaconForEpoch(epoch) + + start := build.Clock.Now() + + maxRound := beacon.MaxBeaconRoundForEpoch(nv, epoch) if maxRound == prev.Round { return nil, nil } @@ -73,11 +145,11 @@ func BeaconEntriesForBlock(ctx context.Context, beacon RandomBeacon, round abi.C out = append(out, resp.Entry) cur = resp.Entry.Round - 1 case <-ctx.Done(): - return nil, xerrors.Errorf("context timed out waiting on beacon entry to come back for round %d: %w", round, ctx.Err()) + return nil, xerrors.Errorf("context timed out waiting on beacon entry to come back for epoch %d: %w", epoch, ctx.Err()) } } - log.Debugw("fetching beacon entries", "took", time.Since(start), "numEntries", len(out)) + log.Debugw("fetching beacon entries", "took", build.Clock.Since(start), "numEntries", len(out)) reverse(out) return out, nil } diff --git a/chain/beacon/drand/drand.go b/chain/beacon/drand/drand.go index 94ee2a28d..9b62a7928 100644 --- a/chain/beacon/drand/drand.go +++ b/chain/beacon/drand/drand.go @@ -1,48 +1,34 @@ package drand import ( + "bytes" "context" - "math/rand" - "sync" "time" + dchain "github.com/drand/drand/chain" + dclient "github.com/drand/drand/client" + hclient "github.com/drand/drand/client/http" + "github.com/drand/drand/common/scheme" + dlog "github.com/drand/drand/log" + gclient "github.com/drand/drand/lp2p/client" + "github.com/drand/kyber" + lru "github.com/hashicorp/golang-lru/v2" + logging "github.com/ipfs/go-log/v2" + pubsub "github.com/libp2p/go-libp2p-pubsub" + "go.uber.org/zap" + "golang.org/x/xerrors" + + "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/beacon" "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/specs-actors/actors/abi" - "golang.org/x/xerrors" - - logging "github.com/ipfs/go-log" - - dbeacon "github.com/drand/drand/beacon" - "github.com/drand/drand/core" - dkey "github.com/drand/drand/key" - dnet "github.com/drand/drand/net" - dproto "github.com/drand/drand/protobuf/drand" + "github.com/filecoin-project/lotus/node/modules/dtypes" ) var log = logging.Logger("drand") -var drandServers = []string{ - "nicolas.drand.fil-test.net:443", - "philipp.drand.fil-test.net:443", - "mathilde.drand.fil-test.net:443", - "ludovic.drand.fil-test.net:443", - "gabbi.drand.fil-test.net:443", - "linus.drand.fil-test.net:443", - "jeff.drand.fil-test.net:443", -} - -var drandPubKey *dkey.DistPublic - -func init() { - drandPubKey = new(dkey.DistPublic) - err := drandPubKey.FromTOML(&dkey.DistPublicTOML{Coefficients: build.DrandCoeffs}) - if err != nil { - panic(err) - } -} - type drandPeer struct { addr string tls bool @@ -56,14 +42,17 @@ func (dp *drandPeer) IsTLS() bool { return dp.tls } +// DrandBeacon connects Lotus with a drand network in order to provide +// randomness to the system in a way that's aligned with Filecoin rounds/epochs. +// +// We connect to drand peers via their public HTTP endpoints. The peers are +// enumerated in the drandServers variable. +// +// The root trust for the Drand chain is configured from build.DrandChain. type DrandBeacon struct { - client dnet.Client + client dclient.Client - peers []dnet.Peer - peersIndex int - peersIndexMtx sync.Mutex - - pubkey *dkey.DistPublic + pubkey kyber.Point // seconds interval time.Duration @@ -72,138 +61,120 @@ type DrandBeacon struct { filGenTime uint64 filRoundTime uint64 - cacheLk sync.Mutex - localCache map[uint64]types.BeaconEntry + localCache *lru.Cache[uint64, *types.BeaconEntry] } -func NewDrandBeacon(genesisTs, interval uint64) (*DrandBeacon, error) { +// DrandHTTPClient interface overrides the user agent used by drand +type DrandHTTPClient interface { + SetUserAgent(string) +} + +type logger struct { + *zap.SugaredLogger +} + +func (l *logger) With(args ...interface{}) dlog.Logger { + return &logger{l.SugaredLogger.With(args...)} +} + +func (l *logger) Named(s string) dlog.Logger { + return &logger{l.SugaredLogger.Named(s)} +} + +func NewDrandBeacon(genesisTs, interval uint64, ps *pubsub.PubSub, config dtypes.DrandConfig) (*DrandBeacon, error) { if genesisTs == 0 { panic("what are you doing this cant be zero") } + + drandChain, err := dchain.InfoFromJSON(bytes.NewReader([]byte(config.ChainInfoJSON))) + if err != nil { + return nil, xerrors.Errorf("unable to unmarshal drand chain info: %w", err) + } + + var clients []dclient.Client + for _, url := range config.Servers { + hc, err := hclient.NewWithInfo(url, drandChain, nil) + if err != nil { + return nil, xerrors.Errorf("could not create http drand client: %w", err) + } + hc.(DrandHTTPClient).SetUserAgent("drand-client-lotus/" + build.BuildVersion) + clients = append(clients, hc) + + } + + opts := []dclient.Option{ + dclient.WithChainInfo(drandChain), + dclient.WithCacheSize(1024), + dclient.WithLogger(&logger{&log.SugaredLogger}), + } + + if ps != nil { + opts = append(opts, gclient.WithPubsub(ps)) + } else { + log.Info("drand beacon without pubsub") + } + + client, err := dclient.Wrap(clients, opts...) + if err != nil { + return nil, xerrors.Errorf("creating drand client: %w", err) + } + + lc, err := lru.New[uint64, *types.BeaconEntry](1024) + if err != nil { + return nil, err + } + db := &DrandBeacon{ - client: dnet.NewGrpcClient(), - localCache: make(map[uint64]types.BeaconEntry), - } - for _, ds := range drandServers { - db.peers = append(db.peers, &drandPeer{addr: ds, tls: true}) + client: client, + localCache: lc, } - db.peersIndex = rand.Intn(len(db.peers)) - - groupResp, err := db.client.Group(context.TODO(), db.peers[db.peersIndex], &dproto.GroupRequest{}) - if err != nil { - return nil, xerrors.Errorf("failed to get group response from beacon peer: %w", err) - } - - kgroup, err := core.ProtoToGroup(groupResp) - if err != nil { - return nil, xerrors.Errorf("failed to parse group response: %w", err) - } - - // TODO: verify these values are what we expect them to be - if !kgroup.PublicKey.Equal(drandPubKey) { - return nil, xerrors.Errorf("public key does not match") - } - // fmt.Printf("Drand Pubkey:\n%#v\n", kgroup.PublicKey.TOML()) // use to print public key - db.pubkey = drandPubKey - db.interval = kgroup.Period - db.drandGenTime = uint64(kgroup.GenesisTime) + db.pubkey = drandChain.PublicKey + db.interval = drandChain.Period + db.drandGenTime = uint64(drandChain.GenesisTime) db.filRoundTime = interval db.filGenTime = genesisTs - // TODO: the stream currently gives you back *all* values since drand genesis. - // Having the stream in the background is merely an optimization, so not a big deal to disable it for now - // go db.handleStreamingUpdates() - return db, nil } -func (db *DrandBeacon) rotatePeersIndex() { - db.peersIndexMtx.Lock() - nval := rand.Intn(len(db.peers)) - db.peersIndex = nval - db.peersIndexMtx.Unlock() - - log.Warnf("rotated to drand peer %d, %q", nval, db.peers[nval].Address()) -} - -func (db *DrandBeacon) getPeerIndex() int { - db.peersIndexMtx.Lock() - defer db.peersIndexMtx.Unlock() - return db.peersIndex -} - -func (db *DrandBeacon) handleStreamingUpdates() { - for { - p := db.peers[db.getPeerIndex()] - ch, err := db.client.PublicRandStream(context.Background(), p, &dproto.PublicRandRequest{}) - if err != nil { - log.Warnf("failed to get public rand stream to peer %q: %s", p.Address(), err) - log.Warnf("trying again in 10 seconds") - db.rotatePeersIndex() - time.Sleep(time.Second * 10) - continue - } - - for e := range ch { - db.cacheValue(types.BeaconEntry{ - Round: e.Round, - Data: e.Signature, - }) - } - - log.Warnf("drand beacon stream to peer %q broke, reconnecting in 10 seconds", p.Address()) - db.rotatePeersIndex() - time.Sleep(time.Second * 10) - } -} - func (db *DrandBeacon) Entry(ctx context.Context, round uint64) <-chan beacon.Response { - // check cache, it it if there, otherwise query the endpoint - cres := db.getCachedValue(round) - if cres != nil { - out := make(chan beacon.Response, 1) - out <- beacon.Response{Entry: *cres} - close(out) - return out - } - out := make(chan beacon.Response, 1) + if round != 0 { + be := db.getCachedValue(round) + if be != nil { + out <- beacon.Response{Entry: *be} + close(out) + return out + } + } go func() { - p := db.peers[db.getPeerIndex()] - resp, err := db.client.PublicRand(ctx, p, &dproto.PublicRandRequest{Round: round}) + start := build.Clock.Now() + log.Debugw("start fetching randomness", "round", round) + resp, err := db.client.Get(ctx, round) var br beacon.Response if err != nil { - db.rotatePeersIndex() - br.Err = xerrors.Errorf("drand peer %q failed publicRand request: %w", p.Address(), err) + br.Err = xerrors.Errorf("drand failed Get request: %w", err) } else { - br.Entry.Round = resp.GetRound() - br.Entry.Data = resp.GetSignature() + br.Entry.Round = resp.Round() + br.Entry.Data = resp.Signature() } - + log.Debugw("done fetching randomness", "round", round, "took", build.Clock.Since(start)) out <- br close(out) }() return out } - func (db *DrandBeacon) cacheValue(e types.BeaconEntry) { - db.cacheLk.Lock() - defer db.cacheLk.Unlock() - db.localCache[e.Round] = e + db.localCache.Add(e.Round, &e) } func (db *DrandBeacon) getCachedValue(round uint64) *types.BeaconEntry { - db.cacheLk.Lock() - defer db.cacheLk.Unlock() - v, ok := db.localCache[round] - if !ok { - return nil - } - return &v + v, _ := db.localCache.Get(round) + return v } func (db *DrandBeacon) VerifyEntry(curr types.BeaconEntry, prev types.BeaconEntry) error { @@ -211,24 +182,56 @@ func (db *DrandBeacon) VerifyEntry(curr types.BeaconEntry, prev types.BeaconEntr // TODO handle genesis better return nil } - b := &dbeacon.Beacon{ + + if curr.Round != prev.Round+1 { + return xerrors.Errorf("invalid beacon entry: cur (%d) != prev (%d) + 1", curr.Round, prev.Round) + } + + if be := db.getCachedValue(curr.Round); be != nil { + if !bytes.Equal(curr.Data, be.Data) { + return xerrors.New("invalid beacon value, does not match cached good value") + } + // return no error if the value is in the cache already + return nil + } + b := &dchain.Beacon{ PreviousSig: prev.Data, Round: curr.Round, Signature: curr.Data, } - //log.Warnw("VerifyEntry", "beacon", b) - err := dbeacon.VerifyBeacon(db.pubkey.Key(), b) + err := dchain.NewVerifier(scheme.GetSchemeFromEnv()).VerifyBeacon(*b, db.pubkey) if err == nil { db.cacheValue(curr) } return err } -func (db *DrandBeacon) MaxBeaconRoundForEpoch(filEpoch abi.ChainEpoch, prevEntry types.BeaconEntry) uint64 { +func (db *DrandBeacon) MaxBeaconRoundForEpoch(nv network.Version, filEpoch abi.ChainEpoch) uint64 { // TODO: sometimes the genesis time for filecoin is zero and this goes negative latestTs := ((uint64(filEpoch) * db.filRoundTime) + db.filGenTime) - db.filRoundTime + + if nv <= network.Version15 { + return db.maxBeaconRoundV1(latestTs) + } + + return db.maxBeaconRoundV2(latestTs) +} + +func (db *DrandBeacon) maxBeaconRoundV1(latestTs uint64) uint64 { dround := (latestTs - db.drandGenTime) / uint64(db.interval.Seconds()) return dround } +func (db *DrandBeacon) maxBeaconRoundV2(latestTs uint64) uint64 { + if latestTs < db.drandGenTime { + return 1 + } + + fromGenesis := latestTs - db.drandGenTime + // we take the time from genesis divided by the periods in seconds, that + // gives us the number of periods since genesis. We also add +1 because + // round 1 starts at genesis time. + return fromGenesis/uint64(db.interval.Seconds()) + 1 +} + var _ beacon.RandomBeacon = (*DrandBeacon)(nil) diff --git a/chain/beacon/drand/drand_test.go b/chain/beacon/drand/drand_test.go index 2055597bd..7269139ca 100644 --- a/chain/beacon/drand/drand_test.go +++ b/chain/beacon/drand/drand_test.go @@ -1,14 +1,39 @@ +// stm: ignore +// Only tests external library behavior, therefore it should not be annotated package drand import ( - "fmt" + "context" + "os" "testing" + + dchain "github.com/drand/drand/chain" + hclient "github.com/drand/drand/client/http" + "github.com/stretchr/testify/assert" + + "github.com/filecoin-project/go-state-types/network" + + "github.com/filecoin-project/lotus/build" ) -func TestPrintDrandPubkey(t *testing.T) { - bc, err := NewDrandBeacon(1, 1) - if err != nil { - t.Fatal(err) - } - fmt.Printf("Drand Pubkey:\n%#v\n", bc.pubkey.TOML()) +func TestPrintGroupInfo(t *testing.T) { + server := build.DrandConfigs[build.DrandDevnet].Servers[0] + c, err := hclient.New(server, nil, nil) + assert.NoError(t, err) + cg := c.(interface { + FetchChainInfo(ctx context.Context, groupHash []byte) (*dchain.Info, error) + }) + chain, err := cg.FetchChainInfo(context.Background(), nil) + assert.NoError(t, err) + err = chain.ToJSON(os.Stdout, nil) + assert.NoError(t, err) +} + +func TestMaxBeaconRoundForEpoch(t *testing.T) { + todayTs := uint64(1652222222) + db, err := NewDrandBeacon(todayTs, build.BlockDelaySecs, nil, build.DrandConfigs[build.DrandDevnet]) + assert.NoError(t, err) + mbr15 := db.MaxBeaconRoundForEpoch(network.Version15, 100) + mbr16 := db.MaxBeaconRoundForEpoch(network.Version16, 100) + assert.Equal(t, mbr15+1, mbr16) } diff --git a/chain/beacon/mock.go b/chain/beacon/mock.go index dc45ae895..3f26da109 100644 --- a/chain/beacon/mock.go +++ b/chain/beacon/mock.go @@ -6,10 +6,13 @@ import ( "encoding/binary" "time" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/specs-actors/actors/abi" "github.com/minio/blake2b-simd" "golang.org/x/xerrors" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/network" + + "github.com/filecoin-project/lotus/chain/types" ) // Mock beacon assumes that filecoin rounds are 1:1 mapped with the beacon rounds @@ -53,12 +56,9 @@ func (mb *mockBeacon) VerifyEntry(from types.BeaconEntry, to types.BeaconEntry) return nil } -func (mb *mockBeacon) IsEntryForEpoch(e types.BeaconEntry, epoch abi.ChainEpoch, nulls int) (bool, error) { - return int64(e.Round) <= int64(epoch) && int64(epoch)-int64(nulls) >= int64(e.Round), nil -} - -func (mb *mockBeacon) MaxBeaconRoundForEpoch(epoch abi.ChainEpoch, prevEntry types.BeaconEntry) uint64 { - return uint64(epoch) +func (mb *mockBeacon) MaxBeaconRoundForEpoch(nv network.Version, epoch abi.ChainEpoch) uint64 { + // offset for better testing + return uint64(epoch + 100) } var _ RandomBeacon = (*mockBeacon)(nil) diff --git a/chain/block_receipt_tracker.go b/chain/block_receipt_tracker.go index f182fd180..9c1e035a2 100644 --- a/chain/block_receipt_tracker.go +++ b/chain/block_receipt_tracker.go @@ -5,9 +5,11 @@ import ( "sync" "time" + lru "github.com/hashicorp/golang-lru/v2" + "github.com/libp2p/go-libp2p/core/peer" + + "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/types" - "github.com/hashicorp/golang-lru" - peer "github.com/libp2p/go-libp2p-core/peer" ) type blockReceiptTracker struct { @@ -15,7 +17,7 @@ type blockReceiptTracker struct { // using an LRU cache because i don't want to handle all the edge cases for // manual cleanup and maintenance of a fixed size set - cache *lru.Cache + cache *lru.Cache[types.TipSetKey, *peerSet] } type peerSet struct { @@ -23,7 +25,7 @@ type peerSet struct { } func newBlockReceiptTracker() *blockReceiptTracker { - c, _ := lru.New(512) + c, _ := lru.New[types.TipSetKey, *peerSet](512) return &blockReceiptTracker{ cache: c, } @@ -37,27 +39,25 @@ func (brt *blockReceiptTracker) Add(p peer.ID, ts *types.TipSet) { if !ok { pset := &peerSet{ peers: map[peer.ID]time.Time{ - p: time.Now(), + p: build.Clock.Now(), }, } brt.cache.Add(ts.Key(), pset) return } - val.(*peerSet).peers[p] = time.Now() + val.peers[p] = build.Clock.Now() } func (brt *blockReceiptTracker) GetPeers(ts *types.TipSet) []peer.ID { brt.lk.Lock() defer brt.lk.Unlock() - val, ok := brt.cache.Get(ts.Key()) + ps, ok := brt.cache.Get(ts.Key()) if !ok { return nil } - ps := val.(*peerSet) - out := make([]peer.ID, 0, len(ps.peers)) for p := range ps.peers { out = append(out, p) diff --git a/chain/blocksync/blocksync.go b/chain/blocksync/blocksync.go deleted file mode 100644 index ccb7a5498..000000000 --- a/chain/blocksync/blocksync.go +++ /dev/null @@ -1,257 +0,0 @@ -package blocksync - -import ( - "bufio" - "context" - "time" - - "github.com/libp2p/go-libp2p-core/protocol" - "go.opencensus.io/trace" - "golang.org/x/xerrors" - - cborutil "github.com/filecoin-project/go-cbor-util" - "github.com/filecoin-project/lotus/chain/store" - "github.com/filecoin-project/lotus/chain/types" - - "github.com/ipfs/go-cid" - logging "github.com/ipfs/go-log/v2" - inet "github.com/libp2p/go-libp2p-core/network" - "github.com/libp2p/go-libp2p-core/peer" -) - -var log = logging.Logger("blocksync") - -type NewStreamFunc func(context.Context, peer.ID, ...protocol.ID) (inet.Stream, error) - -const BlockSyncProtocolID = "/fil/sync/blk/0.0.1" - -const BlockSyncMaxRequestLength = 800 - -type BlockSyncService struct { - cs *store.ChainStore -} - -type BlockSyncRequest struct { - Start []cid.Cid - RequestLength uint64 - - Options uint64 -} - -type BSOptions struct { - IncludeBlocks bool - IncludeMessages bool -} - -func ParseBSOptions(optfield uint64) *BSOptions { - return &BSOptions{ - IncludeBlocks: optfield&(BSOptBlocks) != 0, - IncludeMessages: optfield&(BSOptMessages) != 0, - } -} - -const ( - BSOptBlocks = 1 << iota - BSOptMessages -) - -const ( - StatusOK = uint64(0) - StatusPartial = uint64(101) - StatusNotFound = uint64(201) - StatusGoAway = uint64(202) - StatusInternalError = uint64(203) - StatusBadRequest = uint64(204) -) - -type BlockSyncResponse struct { - Chain []*BSTipSet - - Status uint64 - Message string -} - -type BSTipSet struct { - Blocks []*types.BlockHeader - - BlsMessages []*types.Message - BlsMsgIncludes [][]uint64 - - SecpkMessages []*types.SignedMessage - SecpkMsgIncludes [][]uint64 -} - -func NewBlockSyncService(cs *store.ChainStore) *BlockSyncService { - return &BlockSyncService{ - cs: cs, - } -} - -func (bss *BlockSyncService) HandleStream(s inet.Stream) { - ctx, span := trace.StartSpan(context.Background(), "blocksync.HandleStream") - defer span.End() - - defer s.Close() - - var req BlockSyncRequest - if err := cborutil.ReadCborRPC(bufio.NewReader(s), &req); err != nil { - log.Warnf("failed to read block sync request: %s", err) - return - } - log.Infow("block sync request", "start", req.Start, "len", req.RequestLength) - - resp, err := bss.processRequest(ctx, s.Conn().RemotePeer(), &req) - if err != nil { - log.Warn("failed to process block sync request: ", err) - return - } - - writeDeadline := 60 * time.Second - s.SetDeadline(time.Now().Add(writeDeadline)) - if err := cborutil.WriteCborRPC(s, resp); err != nil { - log.Warnw("failed to write back response for handle stream", "err", err, "peer", s.Conn().RemotePeer()) - return - } -} - -func (bss *BlockSyncService) processRequest(ctx context.Context, p peer.ID, req *BlockSyncRequest) (*BlockSyncResponse, error) { - _, span := trace.StartSpan(ctx, "blocksync.ProcessRequest") - defer span.End() - - opts := ParseBSOptions(req.Options) - if len(req.Start) == 0 { - return &BlockSyncResponse{ - Status: StatusBadRequest, - Message: "no cids given in blocksync request", - }, nil - } - - span.AddAttributes( - trace.BoolAttribute("blocks", opts.IncludeBlocks), - trace.BoolAttribute("messages", opts.IncludeMessages), - trace.Int64Attribute("reqlen", int64(req.RequestLength)), - ) - - reqlen := req.RequestLength - if reqlen > BlockSyncMaxRequestLength { - log.Warnw("limiting blocksync request length", "orig", req.RequestLength, "peer", p) - reqlen = BlockSyncMaxRequestLength - } - - chain, err := collectChainSegment(bss.cs, types.NewTipSetKey(req.Start...), reqlen, opts) - if err != nil { - log.Warn("encountered error while responding to block sync request: ", err) - return &BlockSyncResponse{ - Status: StatusInternalError, - Message: err.Error(), - }, nil - } - - status := StatusOK - if reqlen < req.RequestLength { - status = StatusPartial - } - - return &BlockSyncResponse{ - Chain: chain, - Status: status, - }, nil -} - -func collectChainSegment(cs *store.ChainStore, start types.TipSetKey, length uint64, opts *BSOptions) ([]*BSTipSet, error) { - var bstips []*BSTipSet - cur := start - for { - var bst BSTipSet - ts, err := cs.LoadTipSet(cur) - if err != nil { - return nil, xerrors.Errorf("failed loading tipset %s: %w", cur, err) - } - - if opts.IncludeMessages { - bmsgs, bmincl, smsgs, smincl, err := gatherMessages(cs, ts) - if err != nil { - return nil, xerrors.Errorf("gather messages failed: %w", err) - } - - bst.BlsMessages = bmsgs - bst.BlsMsgIncludes = bmincl - bst.SecpkMessages = smsgs - bst.SecpkMsgIncludes = smincl - } - - if opts.IncludeBlocks { - bst.Blocks = ts.Blocks() - } - - bstips = append(bstips, &bst) - - if uint64(len(bstips)) >= length || ts.Height() == 0 { - return bstips, nil - } - - cur = ts.Parents() - } -} - -func gatherMessages(cs *store.ChainStore, ts *types.TipSet) ([]*types.Message, [][]uint64, []*types.SignedMessage, [][]uint64, error) { - blsmsgmap := make(map[cid.Cid]uint64) - secpkmsgmap := make(map[cid.Cid]uint64) - var secpkmsgs []*types.SignedMessage - var blsmsgs []*types.Message - var secpkincl, blsincl [][]uint64 - - for _, b := range ts.Blocks() { - bmsgs, smsgs, err := cs.MessagesForBlock(b) - if err != nil { - return nil, nil, nil, nil, err - } - - bmi := make([]uint64, 0, len(bmsgs)) - for _, m := range bmsgs { - i, ok := blsmsgmap[m.Cid()] - if !ok { - i = uint64(len(blsmsgs)) - blsmsgs = append(blsmsgs, m) - blsmsgmap[m.Cid()] = i - } - - bmi = append(bmi, i) - } - blsincl = append(blsincl, bmi) - - smi := make([]uint64, 0, len(smsgs)) - for _, m := range smsgs { - i, ok := secpkmsgmap[m.Cid()] - if !ok { - i = uint64(len(secpkmsgs)) - secpkmsgs = append(secpkmsgs, m) - secpkmsgmap[m.Cid()] = i - } - - smi = append(smi, i) - } - secpkincl = append(secpkincl, smi) - } - - return blsmsgs, blsincl, secpkmsgs, secpkincl, nil -} - -func bstsToFullTipSet(bts *BSTipSet) (*store.FullTipSet, error) { - fts := &store.FullTipSet{} - for i, b := range bts.Blocks { - fb := &types.FullBlock{ - Header: b, - } - for _, mi := range bts.BlsMsgIncludes[i] { - fb.BlsMessages = append(fb.BlsMessages, bts.BlsMessages[mi]) - } - for _, mi := range bts.SecpkMsgIncludes[i] { - fb.SecpkMessages = append(fb.SecpkMessages, bts.SecpkMessages[mi]) - } - - fts.Blocks = append(fts.Blocks, fb) - } - - return fts, nil -} diff --git a/chain/blocksync/blocksync_client.go b/chain/blocksync/blocksync_client.go deleted file mode 100644 index 0790cb128..000000000 --- a/chain/blocksync/blocksync_client.go +++ /dev/null @@ -1,590 +0,0 @@ -package blocksync - -import ( - "bufio" - "context" - "fmt" - "math/rand" - "sort" - "sync" - "time" - - blocks "github.com/ipfs/go-block-format" - bserv "github.com/ipfs/go-blockservice" - "github.com/ipfs/go-cid" - graphsync "github.com/ipfs/go-graphsync" - gsnet "github.com/ipfs/go-graphsync/network" - host "github.com/libp2p/go-libp2p-core/host" - inet "github.com/libp2p/go-libp2p-core/network" - "github.com/libp2p/go-libp2p-core/peer" - "go.opencensus.io/trace" - "golang.org/x/xerrors" - - cborutil "github.com/filecoin-project/go-cbor-util" - "github.com/filecoin-project/lotus/chain/store" - "github.com/filecoin-project/lotus/chain/types" - incrt "github.com/filecoin-project/lotus/lib/increadtimeout" - "github.com/filecoin-project/lotus/lib/peermgr" - "github.com/filecoin-project/lotus/node/modules/dtypes" -) - -type BlockSync struct { - bserv bserv.BlockService - gsync graphsync.GraphExchange - host host.Host - - syncPeers *bsPeerTracker - peerMgr *peermgr.PeerMgr -} - -func NewBlockSyncClient(bserv dtypes.ChainBlockService, h host.Host, pmgr peermgr.MaybePeerMgr, gs dtypes.Graphsync) *BlockSync { - return &BlockSync{ - bserv: bserv, - host: h, - syncPeers: newPeerTracker(pmgr.Mgr), - peerMgr: pmgr.Mgr, - gsync: gs, - } -} - -func (bs *BlockSync) processStatus(req *BlockSyncRequest, res *BlockSyncResponse) error { - switch res.Status { - case StatusPartial: // Partial Response - return xerrors.Errorf("not handling partial blocksync responses yet") - case StatusNotFound: // req.Start not found - return xerrors.Errorf("not found") - case StatusGoAway: // Go Away - return xerrors.Errorf("not handling 'go away' blocksync responses yet") - case StatusInternalError: // Internal Error - return xerrors.Errorf("block sync peer errored: %s", res.Message) - case StatusBadRequest: - return xerrors.Errorf("block sync request invalid: %s", res.Message) - default: - return xerrors.Errorf("unrecognized response code: %d", res.Status) - } -} - -func (bs *BlockSync) GetBlocks(ctx context.Context, tsk types.TipSetKey, count int) ([]*types.TipSet, error) { - ctx, span := trace.StartSpan(ctx, "bsync.GetBlocks") - defer span.End() - if span.IsRecordingEvents() { - span.AddAttributes( - trace.StringAttribute("tipset", fmt.Sprint(tsk.Cids())), - trace.Int64Attribute("count", int64(count)), - ) - } - - req := &BlockSyncRequest{ - Start: tsk.Cids(), - RequestLength: uint64(count), - Options: BSOptBlocks, - } - - peers := bs.getPeers() - // randomize the first few peers so we don't always pick the same peer - shufflePrefix(peers) - - start := time.Now() - var oerr error - - for _, p := range peers { - // TODO: doing this synchronously isnt great, but fetching in parallel - // may not be a good idea either. think about this more - select { - case <-ctx.Done(): - return nil, xerrors.Errorf("blocksync getblocks failed: %w", ctx.Err()) - default: - } - - res, err := bs.sendRequestToPeer(ctx, p, req) - if err != nil { - oerr = err - if !xerrors.Is(err, inet.ErrNoConn) { - log.Warnf("BlockSync request failed for peer %s: %s", p.String(), err) - } - continue - } - - if res.Status == StatusOK || res.Status == StatusPartial { - resp, err := bs.processBlocksResponse(req, res) - if err != nil { - return nil, xerrors.Errorf("success response from peer failed to process: %w", err) - } - bs.syncPeers.logGlobalSuccess(time.Since(start)) - bs.host.ConnManager().TagPeer(p, "bsync", 25) - return resp, nil - } - - oerr = bs.processStatus(req, res) - if oerr != nil { - log.Warnf("BlockSync peer %s response was an error: %s", p.String(), oerr) - } - } - return nil, xerrors.Errorf("GetBlocks failed with all peers: %w", oerr) -} - -func (bs *BlockSync) GetFullTipSet(ctx context.Context, p peer.ID, tsk types.TipSetKey) (*store.FullTipSet, error) { - // TODO: round robin through these peers on error - - req := &BlockSyncRequest{ - Start: tsk.Cids(), - RequestLength: 1, - Options: BSOptBlocks | BSOptMessages, - } - - res, err := bs.sendRequestToPeer(ctx, p, req) - if err != nil { - return nil, err - } - - switch res.Status { - case 0: // Success - if len(res.Chain) == 0 { - return nil, fmt.Errorf("got zero length chain response") - } - bts := res.Chain[0] - - return bstsToFullTipSet(bts) - case 101: // Partial Response - return nil, xerrors.Errorf("partial responses are not handled for single tipset fetching") - case 201: // req.Start not found - return nil, fmt.Errorf("not found") - case 202: // Go Away - return nil, xerrors.Errorf("received 'go away' response peer") - case 203: // Internal Error - return nil, fmt.Errorf("block sync peer errored: %q", res.Message) - case 204: // Invalid Request - return nil, fmt.Errorf("block sync request invalid: %q", res.Message) - default: - return nil, fmt.Errorf("unrecognized response code") - } -} - -func shufflePrefix(peers []peer.ID) { - pref := 5 - if len(peers) < pref { - pref = len(peers) - } - - buf := make([]peer.ID, pref) - perm := rand.Perm(pref) - for i, v := range perm { - buf[i] = peers[v] - } - - copy(peers, buf) -} - -func (bs *BlockSync) GetChainMessages(ctx context.Context, h *types.TipSet, count uint64) ([]*BSTipSet, error) { - ctx, span := trace.StartSpan(ctx, "GetChainMessages") - defer span.End() - - peers := bs.getPeers() - // randomize the first few peers so we don't always pick the same peer - shufflePrefix(peers) - - req := &BlockSyncRequest{ - Start: h.Cids(), - RequestLength: count, - Options: BSOptMessages, - } - - var err error - start := time.Now() - - for _, p := range peers { - res, rerr := bs.sendRequestToPeer(ctx, p, req) - if rerr != nil { - err = rerr - log.Warnf("BlockSync request failed for peer %s: %s", p.String(), err) - continue - } - - if res.Status == StatusOK { - bs.syncPeers.logGlobalSuccess(time.Since(start)) - return res.Chain, nil - } - - if res.Status == StatusPartial { - // TODO: track partial response sizes to ensure we don't overrequest too often - return res.Chain, nil - } - - err = bs.processStatus(req, res) - if err != nil { - log.Warnf("BlockSync peer %s response was an error: %s", p.String(), err) - } - } - - if err == nil { - return nil, xerrors.Errorf("GetChainMessages failed, no peers connected") - } - - // TODO: What if we have no peers (and err is nil)? - return nil, xerrors.Errorf("GetChainMessages failed with all peers(%d): %w", len(peers), err) -} - -func (bs *BlockSync) sendRequestToPeer(ctx context.Context, p peer.ID, req *BlockSyncRequest) (_ *BlockSyncResponse, err error) { - ctx, span := trace.StartSpan(ctx, "sendRequestToPeer") - defer span.End() - - defer func() { - if err != nil { - if span.IsRecordingEvents() { - span.SetStatus(trace.Status{ - Code: 5, - Message: err.Error(), - }) - } - } - }() - - if span.IsRecordingEvents() { - span.AddAttributes( - trace.StringAttribute("peer", p.Pretty()), - ) - } - - gsproto := string(gsnet.ProtocolGraphsync) - supp, err := bs.host.Peerstore().SupportsProtocols(p, BlockSyncProtocolID, gsproto) - if err != nil { - return nil, xerrors.Errorf("failed to get protocols for peer: %w", err) - } - - if len(supp) == 0 { - return nil, xerrors.Errorf("peer %s supports no known sync protocols", p) - } - - switch supp[0] { - case BlockSyncProtocolID: - res, err := bs.fetchBlocksBlockSync(ctx, p, req) - if err != nil { - return nil, xerrors.Errorf("blocksync req failed: %w", err) - } - return res, nil - case gsproto: - res, err := bs.fetchBlocksGraphSync(ctx, p, req) - if err != nil { - return nil, xerrors.Errorf("graphsync req failed: %w", err) - } - return res, nil - default: - return nil, xerrors.Errorf("peerstore somehow returned unexpected protocols: %v", supp) - } - -} -func (bs *BlockSync) fetchBlocksBlockSync(ctx context.Context, p peer.ID, req *BlockSyncRequest) (*BlockSyncResponse, error) { - ctx, span := trace.StartSpan(ctx, "blockSyncFetch") - defer span.End() - - start := time.Now() - s, err := bs.host.NewStream(inet.WithNoDial(ctx, "should already have connection"), p, BlockSyncProtocolID) - if err != nil { - bs.RemovePeer(p) - return nil, xerrors.Errorf("failed to open stream to peer: %w", err) - } - s.SetWriteDeadline(time.Now().Add(5 * time.Second)) - - if err := cborutil.WriteCborRPC(s, req); err != nil { - s.SetWriteDeadline(time.Time{}) - bs.syncPeers.logFailure(p, time.Since(start)) - return nil, err - } - s.SetWriteDeadline(time.Time{}) - - var res BlockSyncResponse - r := incrt.New(s, 50<<10, 5*time.Second) - if err := cborutil.ReadCborRPC(bufio.NewReader(r), &res); err != nil { - bs.syncPeers.logFailure(p, time.Since(start)) - return nil, err - } - - if span.IsRecordingEvents() { - span.AddAttributes( - trace.Int64Attribute("resp_status", int64(res.Status)), - trace.StringAttribute("msg", res.Message), - trace.Int64Attribute("chain_len", int64(len(res.Chain))), - ) - } - - bs.syncPeers.logSuccess(p, time.Since(start)) - return &res, nil -} - -func (bs *BlockSync) processBlocksResponse(req *BlockSyncRequest, res *BlockSyncResponse) ([]*types.TipSet, error) { - if len(res.Chain) == 0 { - return nil, xerrors.Errorf("got no blocks in successful blocksync response") - } - - cur, err := types.NewTipSet(res.Chain[0].Blocks) - if err != nil { - return nil, err - } - - out := []*types.TipSet{cur} - for bi := 1; bi < len(res.Chain); bi++ { - next := res.Chain[bi].Blocks - nts, err := types.NewTipSet(next) - if err != nil { - return nil, err - } - - if !types.CidArrsEqual(cur.Parents().Cids(), nts.Cids()) { - return nil, fmt.Errorf("parents of tipset[%d] were not tipset[%d]", bi-1, bi) - } - - out = append(out, nts) - cur = nts - } - return out, nil -} - -func (bs *BlockSync) GetBlock(ctx context.Context, c cid.Cid) (*types.BlockHeader, error) { - sb, err := bs.bserv.GetBlock(ctx, c) - if err != nil { - return nil, err - } - - return types.DecodeBlock(sb.RawData()) -} - -func (bs *BlockSync) AddPeer(p peer.ID) { - bs.syncPeers.addPeer(p) -} - -func (bs *BlockSync) RemovePeer(p peer.ID) { - bs.syncPeers.removePeer(p) -} - -func (bs *BlockSync) getPeers() []peer.ID { - return bs.syncPeers.prefSortedPeers() -} - -func (bs *BlockSync) FetchMessagesByCids(ctx context.Context, cids []cid.Cid) ([]*types.Message, error) { - out := make([]*types.Message, len(cids)) - - err := bs.fetchCids(ctx, cids, func(i int, b blocks.Block) error { - msg, err := types.DecodeMessage(b.RawData()) - if err != nil { - return err - } - - if out[i] != nil { - return fmt.Errorf("received duplicate message") - } - - out[i] = msg - return nil - }) - if err != nil { - return nil, err - } - return out, nil -} - -func (bs *BlockSync) FetchSignedMessagesByCids(ctx context.Context, cids []cid.Cid) ([]*types.SignedMessage, error) { - out := make([]*types.SignedMessage, len(cids)) - - err := bs.fetchCids(ctx, cids, func(i int, b blocks.Block) error { - smsg, err := types.DecodeSignedMessage(b.RawData()) - if err != nil { - return err - } - - if out[i] != nil { - return fmt.Errorf("received duplicate message") - } - - out[i] = smsg - return nil - }) - if err != nil { - return nil, err - } - return out, nil -} - -func (bs *BlockSync) fetchCids(ctx context.Context, cids []cid.Cid, cb func(int, blocks.Block) error) error { - resp := bs.bserv.GetBlocks(context.TODO(), cids) - - m := make(map[cid.Cid]int) - for i, c := range cids { - m[c] = i - } - - for i := 0; i < len(cids); i++ { - select { - case v, ok := <-resp: - if !ok { - if i == len(cids)-1 { - break - } - - return fmt.Errorf("failed to fetch all messages") - } - - ix, ok := m[v.Cid()] - if !ok { - return fmt.Errorf("received message we didnt ask for") - } - - if err := cb(ix, v); err != nil { - return err - } - } - } - - return nil -} - -type peerStats struct { - successes int - failures int - firstSeen time.Time - averageTime time.Duration -} - -type bsPeerTracker struct { - lk sync.Mutex - - peers map[peer.ID]*peerStats - avgGlobalTime time.Duration - - pmgr *peermgr.PeerMgr -} - -func newPeerTracker(pmgr *peermgr.PeerMgr) *bsPeerTracker { - return &bsPeerTracker{ - peers: make(map[peer.ID]*peerStats), - pmgr: pmgr, - } -} - -func (bpt *bsPeerTracker) addPeer(p peer.ID) { - bpt.lk.Lock() - defer bpt.lk.Unlock() - if _, ok := bpt.peers[p]; ok { - return - } - bpt.peers[p] = &peerStats{ - firstSeen: time.Now(), - } - -} - -const ( - // newPeerMul is how much better than average is the new peer assumed to be - // less than one to encourouge trying new peers - newPeerMul = 0.9 -) - -func (bpt *bsPeerTracker) prefSortedPeers() []peer.ID { - // TODO: this could probably be cached, but as long as its not too many peers, fine for now - bpt.lk.Lock() - defer bpt.lk.Unlock() - out := make([]peer.ID, 0, len(bpt.peers)) - for p := range bpt.peers { - out = append(out, p) - } - - // sort by 'expected cost' of requesting data from that peer - // additionally handle edge cases where not enough data is available - sort.Slice(out, func(i, j int) bool { - pi := bpt.peers[out[i]] - pj := bpt.peers[out[j]] - - var costI, costJ float64 - - getPeerInitLat := func(p peer.ID) float64 { - var res float64 - if bpt.pmgr != nil { - if lat, ok := bpt.pmgr.GetPeerLatency(p); ok { - res = float64(lat) - } - } - if res == 0 { - res = float64(bpt.avgGlobalTime) - } - return res * newPeerMul - } - - if pi.successes+pi.failures > 0 { - failRateI := float64(pi.failures) / float64(pi.failures+pi.successes) - costI = float64(pi.averageTime) + failRateI*float64(bpt.avgGlobalTime) - } else { - costI = getPeerInitLat(out[i]) - } - - if pj.successes+pj.failures > 0 { - failRateJ := float64(pj.failures) / float64(pj.failures+pj.successes) - costJ = float64(pj.averageTime) + failRateJ*float64(bpt.avgGlobalTime) - } else { - costJ = getPeerInitLat(out[j]) - } - - return costI < costJ - }) - - return out -} - -const ( - // xInvAlpha = (N+1)/2 - - localInvAlpha = 5 // 86% of the value is the last 9 - globalInvAlpha = 20 // 86% of the value is the last 39 -) - -func (bpt *bsPeerTracker) logGlobalSuccess(dur time.Duration) { - bpt.lk.Lock() - defer bpt.lk.Unlock() - - if bpt.avgGlobalTime == 0 { - bpt.avgGlobalTime = dur - return - } - delta := (dur - bpt.avgGlobalTime) / globalInvAlpha - bpt.avgGlobalTime += delta -} - -func logTime(pi *peerStats, dur time.Duration) { - if pi.averageTime == 0 { - pi.averageTime = dur - return - } - delta := (dur - pi.averageTime) / localInvAlpha - pi.averageTime += delta - -} - -func (bpt *bsPeerTracker) logSuccess(p peer.ID, dur time.Duration) { - bpt.lk.Lock() - defer bpt.lk.Unlock() - - if pi, ok := bpt.peers[p]; !ok { - log.Warnw("log success called on peer not in tracker", "peerid", p.String()) - return - } else { - pi.successes++ - - logTime(pi, dur) - } -} - -func (bpt *bsPeerTracker) logFailure(p peer.ID, dur time.Duration) { - bpt.lk.Lock() - defer bpt.lk.Unlock() - if pi, ok := bpt.peers[p]; !ok { - log.Warn("log failure called on peer not in tracker", "peerid", p.String()) - return - } else { - pi.failures++ - logTime(pi, dur) - } -} - -func (bpt *bsPeerTracker) removePeer(p peer.ID) { - bpt.lk.Lock() - defer bpt.lk.Unlock() - delete(bpt.peers, p) -} diff --git a/chain/blocksync/cbor_gen.go b/chain/blocksync/cbor_gen.go deleted file mode 100644 index 583a5b58d..000000000 --- a/chain/blocksync/cbor_gen.go +++ /dev/null @@ -1,578 +0,0 @@ -// Code generated by github.com/whyrusleeping/cbor-gen. DO NOT EDIT. - -package blocksync - -import ( - "fmt" - "io" - - "github.com/filecoin-project/lotus/chain/types" - "github.com/ipfs/go-cid" - cbg "github.com/whyrusleeping/cbor-gen" - xerrors "golang.org/x/xerrors" -) - -var _ = xerrors.Errorf - -var lengthBufBlockSyncRequest = []byte{131} - -func (t *BlockSyncRequest) MarshalCBOR(w io.Writer) error { - if t == nil { - _, err := w.Write(cbg.CborNull) - return err - } - if _, err := w.Write(lengthBufBlockSyncRequest); err != nil { - return err - } - - scratch := make([]byte, 9) - - // t.Start ([]cid.Cid) (slice) - if len(t.Start) > cbg.MaxLength { - return xerrors.Errorf("Slice value in field t.Start was too long") - } - - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.Start))); err != nil { - return err - } - for _, v := range t.Start { - if err := cbg.WriteCidBuf(scratch, w, v); err != nil { - return xerrors.Errorf("failed writing cid field t.Start: %w", err) - } - } - - // t.RequestLength (uint64) (uint64) - - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.RequestLength)); err != nil { - return err - } - - // t.Options (uint64) (uint64) - - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Options)); err != nil { - return err - } - - return nil -} - -func (t *BlockSyncRequest) UnmarshalCBOR(r io.Reader) error { - br := cbg.GetPeeker(r) - scratch := make([]byte, 8) - - maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) - if err != nil { - return err - } - if maj != cbg.MajArray { - return fmt.Errorf("cbor input should be of type array") - } - - if extra != 3 { - return fmt.Errorf("cbor input had wrong number of fields") - } - - // t.Start ([]cid.Cid) (slice) - - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) - if err != nil { - return err - } - - if extra > cbg.MaxLength { - return fmt.Errorf("t.Start: array too large (%d)", extra) - } - - if maj != cbg.MajArray { - return fmt.Errorf("expected cbor array") - } - - if extra > 0 { - t.Start = make([]cid.Cid, extra) - } - - for i := 0; i < int(extra); i++ { - - c, err := cbg.ReadCid(br) - if err != nil { - return xerrors.Errorf("reading cid field t.Start failed: %w", err) - } - t.Start[i] = c - } - - // t.RequestLength (uint64) (uint64) - - { - - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) - if err != nil { - return err - } - if maj != cbg.MajUnsignedInt { - return fmt.Errorf("wrong type for uint64 field") - } - t.RequestLength = uint64(extra) - - } - // t.Options (uint64) (uint64) - - { - - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) - if err != nil { - return err - } - if maj != cbg.MajUnsignedInt { - return fmt.Errorf("wrong type for uint64 field") - } - t.Options = uint64(extra) - - } - return nil -} - -var lengthBufBlockSyncResponse = []byte{131} - -func (t *BlockSyncResponse) MarshalCBOR(w io.Writer) error { - if t == nil { - _, err := w.Write(cbg.CborNull) - return err - } - if _, err := w.Write(lengthBufBlockSyncResponse); err != nil { - return err - } - - scratch := make([]byte, 9) - - // t.Chain ([]*blocksync.BSTipSet) (slice) - if len(t.Chain) > cbg.MaxLength { - return xerrors.Errorf("Slice value in field t.Chain was too long") - } - - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.Chain))); err != nil { - return err - } - for _, v := range t.Chain { - if err := v.MarshalCBOR(w); err != nil { - return err - } - } - - // t.Status (uint64) (uint64) - - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Status)); err != nil { - return err - } - - // t.Message (string) (string) - if len(t.Message) > cbg.MaxLength { - return xerrors.Errorf("Value in field t.Message was too long") - } - - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajTextString, uint64(len(t.Message))); err != nil { - return err - } - if _, err := io.WriteString(w, t.Message); err != nil { - return err - } - return nil -} - -func (t *BlockSyncResponse) UnmarshalCBOR(r io.Reader) error { - br := cbg.GetPeeker(r) - scratch := make([]byte, 8) - - maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) - if err != nil { - return err - } - if maj != cbg.MajArray { - return fmt.Errorf("cbor input should be of type array") - } - - if extra != 3 { - return fmt.Errorf("cbor input had wrong number of fields") - } - - // t.Chain ([]*blocksync.BSTipSet) (slice) - - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) - if err != nil { - return err - } - - if extra > cbg.MaxLength { - return fmt.Errorf("t.Chain: array too large (%d)", extra) - } - - if maj != cbg.MajArray { - return fmt.Errorf("expected cbor array") - } - - if extra > 0 { - t.Chain = make([]*BSTipSet, extra) - } - - for i := 0; i < int(extra); i++ { - - var v BSTipSet - if err := v.UnmarshalCBOR(br); err != nil { - return err - } - - t.Chain[i] = &v - } - - // t.Status (uint64) (uint64) - - { - - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) - if err != nil { - return err - } - if maj != cbg.MajUnsignedInt { - return fmt.Errorf("wrong type for uint64 field") - } - t.Status = uint64(extra) - - } - // t.Message (string) (string) - - { - sval, err := cbg.ReadStringBuf(br, scratch) - if err != nil { - return err - } - - t.Message = string(sval) - } - return nil -} - -var lengthBufBSTipSet = []byte{133} - -func (t *BSTipSet) MarshalCBOR(w io.Writer) error { - if t == nil { - _, err := w.Write(cbg.CborNull) - return err - } - if _, err := w.Write(lengthBufBSTipSet); err != nil { - return err - } - - scratch := make([]byte, 9) - - // t.Blocks ([]*types.BlockHeader) (slice) - if len(t.Blocks) > cbg.MaxLength { - return xerrors.Errorf("Slice value in field t.Blocks was too long") - } - - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.Blocks))); err != nil { - return err - } - for _, v := range t.Blocks { - if err := v.MarshalCBOR(w); err != nil { - return err - } - } - - // t.BlsMessages ([]*types.Message) (slice) - if len(t.BlsMessages) > cbg.MaxLength { - return xerrors.Errorf("Slice value in field t.BlsMessages was too long") - } - - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.BlsMessages))); err != nil { - return err - } - for _, v := range t.BlsMessages { - if err := v.MarshalCBOR(w); err != nil { - return err - } - } - - // t.BlsMsgIncludes ([][]uint64) (slice) - if len(t.BlsMsgIncludes) > cbg.MaxLength { - return xerrors.Errorf("Slice value in field t.BlsMsgIncludes was too long") - } - - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.BlsMsgIncludes))); err != nil { - return err - } - for _, v := range t.BlsMsgIncludes { - if len(v) > cbg.MaxLength { - return xerrors.Errorf("Slice value in field v was too long") - } - - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(v))); err != nil { - return err - } - for _, v := range v { - if err := cbg.CborWriteHeader(w, cbg.MajUnsignedInt, uint64(v)); err != nil { - return err - } - } - } - - // t.SecpkMessages ([]*types.SignedMessage) (slice) - if len(t.SecpkMessages) > cbg.MaxLength { - return xerrors.Errorf("Slice value in field t.SecpkMessages was too long") - } - - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.SecpkMessages))); err != nil { - return err - } - for _, v := range t.SecpkMessages { - if err := v.MarshalCBOR(w); err != nil { - return err - } - } - - // t.SecpkMsgIncludes ([][]uint64) (slice) - if len(t.SecpkMsgIncludes) > cbg.MaxLength { - return xerrors.Errorf("Slice value in field t.SecpkMsgIncludes was too long") - } - - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.SecpkMsgIncludes))); err != nil { - return err - } - for _, v := range t.SecpkMsgIncludes { - if len(v) > cbg.MaxLength { - return xerrors.Errorf("Slice value in field v was too long") - } - - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(v))); err != nil { - return err - } - for _, v := range v { - if err := cbg.CborWriteHeader(w, cbg.MajUnsignedInt, uint64(v)); err != nil { - return err - } - } - } - return nil -} - -func (t *BSTipSet) UnmarshalCBOR(r io.Reader) error { - br := cbg.GetPeeker(r) - scratch := make([]byte, 8) - - maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) - if err != nil { - return err - } - if maj != cbg.MajArray { - return fmt.Errorf("cbor input should be of type array") - } - - if extra != 5 { - return fmt.Errorf("cbor input had wrong number of fields") - } - - // t.Blocks ([]*types.BlockHeader) (slice) - - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) - if err != nil { - return err - } - - if extra > cbg.MaxLength { - return fmt.Errorf("t.Blocks: array too large (%d)", extra) - } - - if maj != cbg.MajArray { - return fmt.Errorf("expected cbor array") - } - - if extra > 0 { - t.Blocks = make([]*types.BlockHeader, extra) - } - - for i := 0; i < int(extra); i++ { - - var v types.BlockHeader - if err := v.UnmarshalCBOR(br); err != nil { - return err - } - - t.Blocks[i] = &v - } - - // t.BlsMessages ([]*types.Message) (slice) - - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) - if err != nil { - return err - } - - if extra > cbg.MaxLength { - return fmt.Errorf("t.BlsMessages: array too large (%d)", extra) - } - - if maj != cbg.MajArray { - return fmt.Errorf("expected cbor array") - } - - if extra > 0 { - t.BlsMessages = make([]*types.Message, extra) - } - - for i := 0; i < int(extra); i++ { - - var v types.Message - if err := v.UnmarshalCBOR(br); err != nil { - return err - } - - t.BlsMessages[i] = &v - } - - // t.BlsMsgIncludes ([][]uint64) (slice) - - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) - if err != nil { - return err - } - - if extra > cbg.MaxLength { - return fmt.Errorf("t.BlsMsgIncludes: array too large (%d)", extra) - } - - if maj != cbg.MajArray { - return fmt.Errorf("expected cbor array") - } - - if extra > 0 { - t.BlsMsgIncludes = make([][]uint64, extra) - } - - for i := 0; i < int(extra); i++ { - { - var maj byte - var extra uint64 - var err error - - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) - if err != nil { - return err - } - - if extra > cbg.MaxLength { - return fmt.Errorf("t.BlsMsgIncludes[i]: array too large (%d)", extra) - } - - if maj != cbg.MajArray { - return fmt.Errorf("expected cbor array") - } - - if extra > 0 { - t.BlsMsgIncludes[i] = make([]uint64, extra) - } - - for j := 0; j < int(extra); j++ { - - maj, val, err := cbg.CborReadHeaderBuf(br, scratch) - if err != nil { - return xerrors.Errorf("failed to read uint64 for t.BlsMsgIncludes[i] slice: %w", err) - } - - if maj != cbg.MajUnsignedInt { - return xerrors.Errorf("value read for array t.BlsMsgIncludes[i] was not a uint, instead got %d", maj) - } - - t.BlsMsgIncludes[i][j] = uint64(val) - } - - } - } - - // t.SecpkMessages ([]*types.SignedMessage) (slice) - - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) - if err != nil { - return err - } - - if extra > cbg.MaxLength { - return fmt.Errorf("t.SecpkMessages: array too large (%d)", extra) - } - - if maj != cbg.MajArray { - return fmt.Errorf("expected cbor array") - } - - if extra > 0 { - t.SecpkMessages = make([]*types.SignedMessage, extra) - } - - for i := 0; i < int(extra); i++ { - - var v types.SignedMessage - if err := v.UnmarshalCBOR(br); err != nil { - return err - } - - t.SecpkMessages[i] = &v - } - - // t.SecpkMsgIncludes ([][]uint64) (slice) - - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) - if err != nil { - return err - } - - if extra > cbg.MaxLength { - return fmt.Errorf("t.SecpkMsgIncludes: array too large (%d)", extra) - } - - if maj != cbg.MajArray { - return fmt.Errorf("expected cbor array") - } - - if extra > 0 { - t.SecpkMsgIncludes = make([][]uint64, extra) - } - - for i := 0; i < int(extra); i++ { - { - var maj byte - var extra uint64 - var err error - - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) - if err != nil { - return err - } - - if extra > cbg.MaxLength { - return fmt.Errorf("t.SecpkMsgIncludes[i]: array too large (%d)", extra) - } - - if maj != cbg.MajArray { - return fmt.Errorf("expected cbor array") - } - - if extra > 0 { - t.SecpkMsgIncludes[i] = make([]uint64, extra) - } - - for j := 0; j < int(extra); j++ { - - maj, val, err := cbg.CborReadHeaderBuf(br, scratch) - if err != nil { - return xerrors.Errorf("failed to read uint64 for t.SecpkMsgIncludes[i] slice: %w", err) - } - - if maj != cbg.MajUnsignedInt { - return xerrors.Errorf("value read for array t.SecpkMsgIncludes[i] was not a uint, instead got %d", maj) - } - - t.SecpkMsgIncludes[i][j] = uint64(val) - } - - } - } - - return nil -} diff --git a/chain/blocksync/graphsync_client.go b/chain/blocksync/graphsync_client.go deleted file mode 100644 index 03e4a30e5..000000000 --- a/chain/blocksync/graphsync_client.go +++ /dev/null @@ -1,151 +0,0 @@ -package blocksync - -import ( - "context" - - "github.com/ipfs/go-cid" - "github.com/ipfs/go-datastore" - "github.com/ipfs/go-graphsync" - "github.com/ipld/go-ipld-prime" - "github.com/libp2p/go-libp2p-core/peer" - "golang.org/x/xerrors" - - store "github.com/filecoin-project/lotus/chain/store" - "github.com/filecoin-project/lotus/chain/types" - - cidlink "github.com/ipld/go-ipld-prime/linking/cid" - basicnode "github.com/ipld/go-ipld-prime/node/basic" - ipldselector "github.com/ipld/go-ipld-prime/traversal/selector" - selectorbuilder "github.com/ipld/go-ipld-prime/traversal/selector/builder" -) - -const ( - - // AMT selector recursion. An AMT has arity of 8 so this gives allows - // us to retrieve trees with 8^10 (1,073,741,824) elements. - amtRecursionDepth = uint32(10) - - // some constants for looking up tuple encoded struct fields - // field index of Parents field in a block header - blockIndexParentsField = 5 - - // field index of Messages field in a block header - blockIndexMessagesField = 10 - - // field index of AMT node in AMT head - amtHeadNodeFieldIndex = 2 - - // field index of links array AMT node - amtNodeLinksFieldIndex = 1 - - // field index of values array AMT node - amtNodeValuesFieldIndex = 2 - - // maximum depth per traversal - maxRequestLength = 50 -) - -var amtSelector selectorbuilder.SelectorSpec - -func init() { - // builer for selectors - ssb := selectorbuilder.NewSelectorSpecBuilder(basicnode.Style.Any) - // amt selector -- needed to selector through a messages AMT - amtSelector = ssb.ExploreIndex(amtHeadNodeFieldIndex, - ssb.ExploreRecursive(ipldselector.RecursionLimitDepth(int(amtRecursionDepth)), - ssb.ExploreUnion( - ssb.ExploreIndex(amtNodeLinksFieldIndex, - ssb.ExploreAll(ssb.ExploreRecursiveEdge())), - ssb.ExploreIndex(amtNodeValuesFieldIndex, - ssb.ExploreAll(ssb.Matcher()))))) -} - -func selectorForRequest(req *BlockSyncRequest) ipld.Node { - // builer for selectors - ssb := selectorbuilder.NewSelectorSpecBuilder(basicnode.Style.Any) - - bso := ParseBSOptions(req.Options) - if bso.IncludeMessages { - return ssb.ExploreRecursive(ipldselector.RecursionLimitDepth(int(req.RequestLength)), - ssb.ExploreIndex(blockIndexParentsField, - ssb.ExploreUnion( - ssb.ExploreAll( - ssb.ExploreIndex(blockIndexMessagesField, - ssb.ExploreRange(0, 2, amtSelector), - )), - ssb.ExploreIndex(0, ssb.ExploreRecursiveEdge()), - ))).Node() - } - return ssb.ExploreRecursive(ipldselector.RecursionLimitDepth(int(req.RequestLength)), ssb.ExploreIndex(blockIndexParentsField, - ssb.ExploreUnion( - ssb.ExploreAll( - ssb.Matcher(), - ), - ssb.ExploreIndex(0, ssb.ExploreRecursiveEdge()), - ))).Node() -} - -func firstTipsetSelector(req *BlockSyncRequest) ipld.Node { - // builer for selectors - ssb := selectorbuilder.NewSelectorSpecBuilder(basicnode.Style.Any) - - bso := ParseBSOptions(req.Options) - if bso.IncludeMessages { - return ssb.ExploreIndex(blockIndexMessagesField, - ssb.ExploreRange(0, 2, amtSelector), - ).Node() - } - return ssb.Matcher().Node() - -} - -func (bs *BlockSync) executeGsyncSelector(ctx context.Context, p peer.ID, root cid.Cid, sel ipld.Node) error { - extension := graphsync.ExtensionData{ - Name: "chainsync", - Data: nil, - } - _, errs := bs.gsync.Request(ctx, p, cidlink.Link{Cid: root}, sel, extension) - - for err := range errs { - return xerrors.Errorf("failed to complete graphsync request: %w", err) - } - return nil -} - -// Fallback for interacting with other non-lotus nodes -func (bs *BlockSync) fetchBlocksGraphSync(ctx context.Context, p peer.ID, req *BlockSyncRequest) (*BlockSyncResponse, error) { - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - immediateTsSelector := firstTipsetSelector(req) - - // Do this because we can only request one root at a time - for _, r := range req.Start { - if err := bs.executeGsyncSelector(ctx, p, r, immediateTsSelector); err != nil { - return nil, err - } - } - - if req.RequestLength > maxRequestLength { - req.RequestLength = maxRequestLength - } - - sel := selectorForRequest(req) - - // execute the selector forreal - if err := bs.executeGsyncSelector(ctx, p, req.Start[0], sel); err != nil { - return nil, err - } - - // Now pull the data we fetched out of the chainstore (where it should now be persisted) - tempcs := store.NewChainStore(bs.bserv.Blockstore(), datastore.NewMapDatastore(), nil) - - opts := ParseBSOptions(req.Options) - tsk := types.NewTipSetKey(req.Start...) - chain, err := collectChainSegment(tempcs, tsk, req.RequestLength, opts) - if err != nil { - return nil, xerrors.Errorf("failed to load chain data from chainstore after successful graphsync response (start = %v): %w", req.Start, err) - } - - return &BlockSyncResponse{Chain: chain}, nil -} diff --git a/chain/checkpoint.go b/chain/checkpoint.go new file mode 100644 index 000000000..f9b0bb4eb --- /dev/null +++ b/chain/checkpoint.go @@ -0,0 +1,57 @@ +package chain + +import ( + "context" + + "golang.org/x/xerrors" + + "github.com/filecoin-project/lotus/chain/types" +) + +func (syncer *Syncer) SyncCheckpoint(ctx context.Context, tsk types.TipSetKey) error { + if tsk == types.EmptyTSK { + return xerrors.Errorf("called with empty tsk") + } + + ts, err := syncer.ChainStore().LoadTipSet(ctx, tsk) + if err != nil { + tss, err := syncer.Exchange.GetBlocks(ctx, tsk, 1) + if err != nil { + return xerrors.Errorf("failed to fetch tipset: %w", err) + } else if len(tss) != 1 { + return xerrors.Errorf("expected 1 tipset, got %d", len(tss)) + } + ts = tss[0] + } + + if err := syncer.switchChain(ctx, ts); err != nil { + return xerrors.Errorf("failed to switch chain when syncing checkpoint: %w", err) + } + + if err := syncer.ChainStore().SetCheckpoint(ctx, ts); err != nil { + return xerrors.Errorf("failed to set the chain checkpoint: %w", err) + } + + return nil +} + +func (syncer *Syncer) switchChain(ctx context.Context, ts *types.TipSet) error { + hts := syncer.ChainStore().GetHeaviestTipSet() + if hts.Equals(ts) { + return nil + } + + if anc, err := syncer.store.IsAncestorOf(ctx, ts, hts); err == nil && anc { + return nil + } + + // Otherwise, sync the chain and set the head. + if err := syncer.collectChain(ctx, ts, hts, true); err != nil { + return xerrors.Errorf("failed to collect chain for checkpoint: %w", err) + } + + if err := syncer.ChainStore().SetHead(ctx, ts); err != nil { + return xerrors.Errorf("failed to set the chain head: %w", err) + } + return nil +} diff --git a/chain/consensus/common.go b/chain/consensus/common.go new file mode 100644 index 000000000..1d9fb3646 --- /dev/null +++ b/chain/consensus/common.go @@ -0,0 +1,514 @@ +package consensus + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/go-multierror" + "github.com/ipfs/go-cid" + cbor "github.com/ipfs/go-ipld-cbor" + logging "github.com/ipfs/go-log/v2" + pubsub "github.com/libp2p/go-libp2p-pubsub" + "github.com/multiformats/go-varint" + cbg "github.com/whyrusleeping/cbor-gen" + "go.opencensus.io/stats" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + builtintypes "github.com/filecoin-project/go-state-types/builtin" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/go-state-types/network" + blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" + + "github.com/filecoin-project/lotus/api" + bstore "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/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" + "github.com/filecoin-project/lotus/lib/async" + "github.com/filecoin-project/lotus/metrics" +) + +// Common operations shared by all consensus algorithm implementations. +var log = logging.Logger("consensus-common") + +// RunAsyncChecks accepts a list of checks to perform in parallel. +// +// Each consensus algorithm may choose to perform a set of different +// checks when a new blocks is received. +func RunAsyncChecks(ctx context.Context, await []async.ErrorFuture) error { + var merr error + for _, fut := range await { + if err := fut.AwaitContext(ctx); err != nil { + merr = multierror.Append(merr, err) + } + } + if merr != nil { + mulErr := merr.(*multierror.Error) + mulErr.ErrorFormat = func(es []error) string { + if len(es) == 1 { + return fmt.Sprintf("1 error occurred:\n\t* %+v\n\n", es[0]) + } + + points := make([]string, len(es)) + for i, err := range es { + points[i] = fmt.Sprintf("* %+v", err) + } + + return fmt.Sprintf( + "%d errors occurred:\n\t%s\n\n", + len(es), strings.Join(points, "\n\t")) + } + return mulErr + } + + return nil +} + +// CommonBlkChecks performed by all consensus implementations. +func CommonBlkChecks(ctx context.Context, sm *stmgr.StateManager, cs *store.ChainStore, + b *types.FullBlock, baseTs *types.TipSet) []async.ErrorFuture { + h := b.Header + msgsCheck := async.Err(func() error { + if b.Cid() == build.WhitelistedBlock { + return nil + } + + if err := checkBlockMessages(ctx, sm, cs, b, baseTs); err != nil { + return xerrors.Errorf("block had invalid messages: %w", err) + } + return nil + }) + + baseFeeCheck := async.Err(func() error { + baseFee, err := cs.ComputeBaseFee(ctx, baseTs) + if err != nil { + return xerrors.Errorf("computing base fee: %w", err) + } + if types.BigCmp(baseFee, b.Header.ParentBaseFee) != 0 { + return xerrors.Errorf("base fee doesn't match: %s (header) != %s (computed)", + b.Header.ParentBaseFee, baseFee) + } + return nil + }) + + stateRootCheck := async.Err(func() error { + stateroot, precp, err := sm.TipSetState(ctx, baseTs) + if err != nil { + return xerrors.Errorf("get tipsetstate(%d, %s) failed: %w", h.Height, h.Parents, err) + } + + if stateroot != h.ParentStateRoot { + msgs, err := cs.MessagesForTipset(ctx, baseTs) + if err != nil { + log.Error("failed to load messages for tipset during tipset state mismatch error: ", err) + } else { + log.Warn("Messages for tipset with mismatching state:") + for i, m := range msgs { + mm := m.VMMessage() + log.Warnf("Message[%d]: from=%s to=%s method=%d params=%x", i, mm.From, mm.To, mm.Method, mm.Params) + } + } + + return xerrors.Errorf("parent state root did not match computed state (%s != %s)", h.ParentStateRoot, stateroot) + } + + if precp != h.ParentMessageReceipts { + return xerrors.Errorf("parent receipts root did not match computed value (%s != %s)", precp, h.ParentMessageReceipts) + } + return nil + }) + + return []async.ErrorFuture{ + msgsCheck, + baseFeeCheck, + stateRootCheck, + } +} + +func IsValidForSending(nv network.Version, act *types.Actor) bool { + // Before nv18 (Hygge), we only supported built-in account actors as senders. + // + // Note: this gate is probably superfluous, since: + // 1. Placeholder actors cannot be created before nv18. + // 2. EthAccount actors cannot be created before nv18. + // 3. Delegated addresses cannot be created before nv18. + // + // But it's a safeguard. + // + // Note 2: ad-hoc checks for network versions like this across the codebase + // will be problematic with networks with diverging version lineages + // (e.g. Hyperspace). We need to revisit this strategy entirely. + if nv < network.Version18 { + return builtin.IsAccountActor(act.Code) + } + + // After nv18, we also support other kinds of senders. + if builtin.IsAccountActor(act.Code) || builtin.IsEthAccountActor(act.Code) { + return true + } + + // Allow placeholder actors with a delegated address and nonce 0 to send a message. + // These will be converted to an EthAccount actor on first send. + if !builtin.IsPlaceholderActor(act.Code) || act.Nonce != 0 || act.Address == nil || act.Address.Protocol() != address.Delegated { + return false + } + + // Only allow such actors to send if their delegated address is in the EAM's namespace. + id, _, err := varint.FromUvarint(act.Address.Payload()) + return err == nil && id == builtintypes.EthereumAddressManagerActorID +} + +func checkBlockMessages(ctx context.Context, sm *stmgr.StateManager, cs *store.ChainStore, b *types.FullBlock, baseTs *types.TipSet) error { + { + var sigCids []cid.Cid // this is what we get for people not wanting the marshalcbor method on the cid type + var pubks [][]byte + + for _, m := range b.BlsMessages { + sigCids = append(sigCids, m.Cid()) + + pubk, err := sm.GetBlsPublicKey(ctx, m.From, baseTs) + if err != nil { + return xerrors.Errorf("failed to load bls public to validate block: %w", err) + } + + pubks = append(pubks, pubk) + } + + if err := VerifyBlsAggregate(ctx, b.Header.BLSAggregate, sigCids, pubks); err != nil { + return xerrors.Errorf("bls aggregate signature was invalid: %w", err) + } + } + + nonces := make(map[address.Address]uint64) + + stateroot, _, err := sm.TipSetState(ctx, baseTs) + if err != nil { + return xerrors.Errorf("failed to compute tipsettate for %s: %w", baseTs.Key(), err) + } + + st, err := state.LoadStateTree(cs.ActorStore(ctx), stateroot) + if err != nil { + return xerrors.Errorf("failed to load base state tree: %w", err) + } + + nv := sm.GetNetworkVersion(ctx, b.Header.Height) + pl := vm.PricelistByEpoch(b.Header.Height) + var sumGasLimit int64 + checkMsg := func(msg types.ChainMsg) error { + m := msg.VMMessage() + + // Phase 1: syntactic validation, as defined in the spec + minGas := pl.OnChainMessage(msg.ChainLength()) + if err := m.ValidForBlockInclusion(minGas.Total(), nv); err != nil { + return xerrors.Errorf("msg %s invalid for block inclusion: %w", m.Cid(), err) + } + + // ValidForBlockInclusion checks if any single message does not exceed BlockGasLimit + // So below is overflow safe + sumGasLimit += m.GasLimit + if sumGasLimit > build.BlockGasLimit { + return xerrors.Errorf("block gas limit exceeded") + } + + // Phase 2: (Partial) semantic validation: + // the sender exists and is an account actor, and the nonces make sense + var sender address.Address + if nv >= network.Version13 { + sender, err = st.LookupID(m.From) + if err != nil { + return xerrors.Errorf("failed to lookup sender %s: %w", m.From, err) + } + } else { + sender = m.From + } + + if _, ok := nonces[sender]; !ok { + // `GetActor` does not validate that this is an account actor. + act, err := st.GetActor(sender) + if err != nil { + return xerrors.Errorf("failed to get actor: %w", err) + } + + if !IsValidForSending(nv, act) { + return xerrors.New("Sender must be an account actor") + } + nonces[sender] = act.Nonce + } + + if nonces[sender] != m.Nonce { + return xerrors.Errorf("wrong nonce (exp: %d, got: %d)", nonces[sender], m.Nonce) + } + nonces[sender]++ + + return nil + } + + // Validate message arrays in a temporary blockstore. + tmpbs := bstore.NewMemory() + tmpstore := blockadt.WrapStore(ctx, cbor.NewCborStore(tmpbs)) + + bmArr := blockadt.MakeEmptyArray(tmpstore) + for i, m := range b.BlsMessages { + if err := checkMsg(m); err != nil { + return xerrors.Errorf("block had invalid bls message at index %d: %w", i, err) + } + + c, err := store.PutMessage(ctx, tmpbs, m) + if err != nil { + return xerrors.Errorf("failed to store message %s: %w", m.Cid(), err) + } + + k := cbg.CborCid(c) + if err := bmArr.Set(uint64(i), &k); err != nil { + return xerrors.Errorf("failed to put bls message at index %d: %w", i, err) + } + } + + smArr := blockadt.MakeEmptyArray(tmpstore) + for i, m := range b.SecpkMessages { + if nv >= network.Version14 && !IsValidSecpkSigType(nv, m.Signature.Type) { + return xerrors.Errorf("block had invalid signed message at index %d: %w", i, err) + } + + if err := checkMsg(m); err != nil { + return xerrors.Errorf("block had invalid secpk message at index %d: %w", i, err) + } + + // `From` being an account actor is only validated inside the `vm.ResolveToDeterministicAddr` call + // in `StateManager.ResolveToDeterministicAddress` here (and not in `checkMsg`). + kaddr, err := sm.ResolveToDeterministicAddress(ctx, m.Message.From, baseTs) + if err != nil { + return xerrors.Errorf("failed to resolve key addr: %w", err) + } + + if err := AuthenticateMessage(m, kaddr); err != nil { + return xerrors.Errorf("failed to validate signature: %w", err) + } + + c, err := store.PutMessage(ctx, tmpbs, m) + if err != nil { + return xerrors.Errorf("failed to store message %s: %w", m.Cid(), err) + } + k := cbg.CborCid(c) + if err := smArr.Set(uint64(i), &k); err != nil { + return xerrors.Errorf("failed to put secpk message at index %d: %w", i, err) + } + } + + bmroot, err := bmArr.Root() + if err != nil { + return xerrors.Errorf("failed to root bls msgs: %w", err) + + } + + smroot, err := smArr.Root() + if err != nil { + return xerrors.Errorf("failed to root secp msgs: %w", err) + } + + mrcid, err := tmpstore.Put(ctx, &types.MsgMeta{ + BlsMessages: bmroot, + SecpkMessages: smroot, + }) + if err != nil { + return xerrors.Errorf("failed to put msg meta: %w", err) + } + + if b.Header.Messages != mrcid { + return fmt.Errorf("messages didnt match message root in header") + } + + // Finally, flush. + err = vm.Copy(ctx, tmpbs, cs.ChainBlockstore(), mrcid) + if err != nil { + return xerrors.Errorf("failed to flush:%w", err) + } + + return nil +} + +// CreateBlockHeader generates the block header from the block template of +// the block being proposed. +func CreateBlockHeader(ctx context.Context, sm *stmgr.StateManager, pts *types.TipSet, + bt *api.BlockTemplate) (*types.BlockHeader, []*types.Message, []*types.SignedMessage, error) { + + st, recpts, err := sm.TipSetState(ctx, pts) + if err != nil { + return nil, nil, nil, xerrors.Errorf("failed to load tipset state: %w", err) + } + next := &types.BlockHeader{ + Miner: bt.Miner, + Parents: bt.Parents.Cids(), + Ticket: bt.Ticket, + ElectionProof: bt.Eproof, + + BeaconEntries: bt.BeaconValues, + Height: bt.Epoch, + Timestamp: bt.Timestamp, + WinPoStProof: bt.WinningPoStProof, + ParentStateRoot: st, + ParentMessageReceipts: recpts, + } + + var blsMessages []*types.Message + var secpkMessages []*types.SignedMessage + + var blsMsgCids, secpkMsgCids []cid.Cid + var blsSigs []crypto.Signature + nv := sm.GetNetworkVersion(ctx, bt.Epoch) + for _, msg := range bt.Messages { + if msg.Signature.Type == crypto.SigTypeBLS { + blsSigs = append(blsSigs, msg.Signature) + blsMessages = append(blsMessages, &msg.Message) + + c, err := sm.ChainStore().PutMessage(ctx, &msg.Message) + if err != nil { + return nil, nil, nil, err + } + + blsMsgCids = append(blsMsgCids, c) + } else if IsValidSecpkSigType(nv, msg.Signature.Type) { + c, err := sm.ChainStore().PutMessage(ctx, msg) + if err != nil { + return nil, nil, nil, err + } + + secpkMsgCids = append(secpkMsgCids, c) + secpkMessages = append(secpkMessages, msg) + + } else { + return nil, nil, nil, xerrors.Errorf("unknown sig type: %d", msg.Signature.Type) + } + } + + store := sm.ChainStore().ActorStore(ctx) + blsmsgroot, err := ToMessagesArray(store, blsMsgCids) + if err != nil { + return nil, nil, nil, xerrors.Errorf("building bls amt: %w", err) + } + secpkmsgroot, err := ToMessagesArray(store, secpkMsgCids) + if err != nil { + return nil, nil, nil, xerrors.Errorf("building secpk amt: %w", err) + } + + mmcid, err := store.Put(store.Context(), &types.MsgMeta{ + BlsMessages: blsmsgroot, + SecpkMessages: secpkmsgroot, + }) + if err != nil { + return nil, nil, nil, err + } + next.Messages = mmcid + + aggSig, err := AggregateSignatures(blsSigs) + if err != nil { + return nil, nil, nil, err + } + + next.BLSAggregate = aggSig + pweight, err := sm.ChainStore().Weight(ctx, pts) + if err != nil { + return nil, nil, nil, err + } + next.ParentWeight = pweight + + baseFee, err := sm.ChainStore().ComputeBaseFee(ctx, pts) + if err != nil { + return nil, nil, nil, xerrors.Errorf("computing base fee: %w", err) + } + next.ParentBaseFee = baseFee + + return next, blsMessages, secpkMessages, err + +} + +// Basic sanity-checks performed when a block is proposed locally. +func validateLocalBlock(ctx context.Context, msg *pubsub.Message) (pubsub.ValidationResult, string) { + stats.Record(ctx, metrics.BlockPublished.M(1)) + + if size := msg.Size(); size > 1<<20-1<<15 { + log.Errorf("ignoring oversize block (%dB)", size) + return pubsub.ValidationIgnore, "oversize_block" + } + + blk, what, err := decodeAndCheckBlock(msg) + if err != nil { + log.Errorf("got invalid local block: %s", err) + return pubsub.ValidationIgnore, what + } + + msg.ValidatorData = blk + stats.Record(ctx, metrics.BlockValidationSuccess.M(1)) + return pubsub.ValidationAccept, "" +} + +func decodeAndCheckBlock(msg *pubsub.Message) (*types.BlockMsg, string, error) { + blk, err := types.DecodeBlockMsg(msg.GetData()) + if err != nil { + return nil, "invalid", xerrors.Errorf("error decoding block: %w", err) + } + + if count := len(blk.BlsMessages) + len(blk.SecpkMessages); count > build.BlockMessageLimit { + return nil, "too_many_messages", fmt.Errorf("block contains too many messages (%d)", count) + } + + // make sure we have a signature + if blk.Header.BlockSig == nil { + return nil, "missing_signature", fmt.Errorf("block without a signature") + } + + return blk, "", nil +} + +func validateMsgMeta(ctx context.Context, msg *types.BlockMsg) error { + // TODO there has to be a simpler way to do this without the blockstore dance + // block headers use adt0 + store := blockadt.WrapStore(ctx, cbor.NewCborStore(bstore.NewMemory())) + bmArr := blockadt.MakeEmptyArray(store) + smArr := blockadt.MakeEmptyArray(store) + + for i, m := range msg.BlsMessages { + c := cbg.CborCid(m) + if err := bmArr.Set(uint64(i), &c); err != nil { + return err + } + } + + for i, m := range msg.SecpkMessages { + c := cbg.CborCid(m) + if err := smArr.Set(uint64(i), &c); err != nil { + return err + } + } + + bmroot, err := bmArr.Root() + if err != nil { + return err + } + + smroot, err := smArr.Root() + if err != nil { + return err + } + + mrcid, err := store.Put(store.Context(), &types.MsgMeta{ + BlsMessages: bmroot, + SecpkMessages: smroot, + }) + + if err != nil { + return err + } + + if msg.Header.Messages != mrcid { + return fmt.Errorf("messages didn't match root cid in header") + } + + return nil +} diff --git a/chain/consensus/compute_state.go b/chain/consensus/compute_state.go new file mode 100644 index 000000000..6b08519af --- /dev/null +++ b/chain/consensus/compute_state.go @@ -0,0 +1,373 @@ +package consensus + +import ( + "context" + "sync/atomic" + "time" + + "github.com/ipfs/go-cid" + cbor "github.com/ipfs/go-ipld-cbor" + cbg "github.com/whyrusleeping/cbor-gen" + "go.opencensus.io/stats" + "go.opencensus.io/trace" + "golang.org/x/xerrors" + + amt4 "github.com/filecoin-project/go-amt-ipld/v4" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + exported0 "github.com/filecoin-project/specs-actors/actors/builtin/exported" + blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" + exported2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/exported" + exported3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/exported" + exported4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/exported" + exported5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/exported" + exported6 "github.com/filecoin-project/specs-actors/v6/actors/builtin/exported" + exported7 "github.com/filecoin-project/specs-actors/v7/actors/builtin/exported" + + "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/cron" + "github.com/filecoin-project/lotus/chain/actors/builtin/reward" + "github.com/filecoin-project/lotus/chain/rand" + "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/metrics" +) + +func NewActorRegistry() *vm.ActorRegistry { + inv := vm.NewActorRegistry() + + inv.Register(actorstypes.Version0, vm.ActorsVersionPredicate(actorstypes.Version0), builtin.MakeRegistryLegacy(exported0.BuiltinActors())) + inv.Register(actorstypes.Version2, vm.ActorsVersionPredicate(actorstypes.Version2), builtin.MakeRegistryLegacy(exported2.BuiltinActors())) + inv.Register(actorstypes.Version3, vm.ActorsVersionPredicate(actorstypes.Version3), builtin.MakeRegistryLegacy(exported3.BuiltinActors())) + inv.Register(actorstypes.Version4, vm.ActorsVersionPredicate(actorstypes.Version4), builtin.MakeRegistryLegacy(exported4.BuiltinActors())) + inv.Register(actorstypes.Version5, vm.ActorsVersionPredicate(actorstypes.Version5), builtin.MakeRegistryLegacy(exported5.BuiltinActors())) + inv.Register(actorstypes.Version6, vm.ActorsVersionPredicate(actorstypes.Version6), builtin.MakeRegistryLegacy(exported6.BuiltinActors())) + inv.Register(actorstypes.Version7, vm.ActorsVersionPredicate(actorstypes.Version7), builtin.MakeRegistryLegacy(exported7.BuiltinActors())) + inv.Register(actorstypes.Version8, vm.ActorsVersionPredicate(actorstypes.Version8), builtin.MakeRegistry(actorstypes.Version8)) + inv.Register(actorstypes.Version9, vm.ActorsVersionPredicate(actorstypes.Version9), builtin.MakeRegistry(actorstypes.Version9)) + inv.Register(actorstypes.Version10, vm.ActorsVersionPredicate(actorstypes.Version10), builtin.MakeRegistry(actorstypes.Version10)) + inv.Register(actorstypes.Version11, vm.ActorsVersionPredicate(actorstypes.Version11), builtin.MakeRegistry(actorstypes.Version11)) + + return inv +} + +type TipSetExecutor struct { + reward RewardFunc +} + +func NewTipSetExecutor(r RewardFunc) *TipSetExecutor { + return &TipSetExecutor{reward: r} +} + +func (t *TipSetExecutor) NewActorRegistry() *vm.ActorRegistry { + return NewActorRegistry() +} + +type FilecoinBlockMessages struct { + store.BlockMessages + + WinCount int64 +} + +func (t *TipSetExecutor) ApplyBlocks(ctx context.Context, + sm *stmgr.StateManager, + parentEpoch abi.ChainEpoch, + pstate cid.Cid, + bms []FilecoinBlockMessages, + epoch abi.ChainEpoch, + r vm.Rand, + em stmgr.ExecMonitor, + vmTracing bool, + baseFee abi.TokenAmount, + ts *types.TipSet) (cid.Cid, cid.Cid, error) { + done := metrics.Timer(ctx, metrics.VMApplyBlocksTotal) + defer done() + + partDone := metrics.Timer(ctx, metrics.VMApplyEarly) + defer func() { + partDone() + }() + + ctx = blockstore.WithHotView(ctx) + makeVm := func(base cid.Cid, e abi.ChainEpoch, timestamp uint64) (vm.Interface, error) { + vmopt := &vm.VMOpts{ + StateBase: base, + Epoch: e, + Timestamp: timestamp, + Rand: r, + Bstore: sm.ChainStore().StateBlockstore(), + Actors: NewActorRegistry(), + Syscalls: sm.Syscalls, + CircSupplyCalc: sm.GetVMCirculatingSupply, + NetworkVersion: sm.GetNetworkVersion(ctx, e), + BaseFee: baseFee, + LookbackState: stmgr.LookbackStateGetterForTipset(sm, ts), + TipSetGetter: stmgr.TipSetGetterForTipset(sm.ChainStore(), ts), + Tracing: vmTracing, + ReturnEvents: sm.ChainStore().IsStoringEvents(), + ExecutionLane: vm.ExecutionLanePriority, + } + + return sm.VMConstructor()(ctx, vmopt) + } + + var cronGas int64 + + runCron := func(vmCron vm.Interface, epoch abi.ChainEpoch) error { + cronMsg := &types.Message{ + To: cron.Address, + From: builtin.SystemActorAddr, + Nonce: uint64(epoch), + Value: types.NewInt(0), + GasFeeCap: types.NewInt(0), + GasPremium: types.NewInt(0), + GasLimit: build.BlockGasLimit * 10000, // Make super sure this is never too little + Method: cron.Methods.EpochTick, + Params: nil, + } + ret, err := vmCron.ApplyImplicitMessage(ctx, cronMsg) + if err != nil { + return xerrors.Errorf("running cron: %w", err) + } + + cronGas += ret.GasUsed + + if em != nil { + if err := em.MessageApplied(ctx, ts, cronMsg.Cid(), cronMsg, ret, true); err != nil { + return xerrors.Errorf("callback failed on cron message: %w", err) + } + } + if ret.ExitCode != 0 { + return xerrors.Errorf("cron exit was non-zero: %d", ret.ExitCode) + } + + return nil + } + + // May get filled with the genesis block header if there are null rounds + // for which to backfill cron execution. + var genesis *types.BlockHeader + + // There were null rounds in between the current epoch and the parent epoch. + for i := parentEpoch; i < epoch; i++ { + var err error + if i > parentEpoch { + if genesis == nil { + if genesis, err = sm.ChainStore().GetGenesis(ctx); err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("failed to get genesis when backfilling null rounds: %w", err) + } + } + + ts := genesis.Timestamp + build.BlockDelaySecs*(uint64(i)) + vmCron, err := makeVm(pstate, i, ts) + if err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("making cron vm: %w", err) + } + + // run cron for null rounds if any + if err = runCron(vmCron, i); err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("running cron: %w", err) + } + + pstate, err = vmCron.Flush(ctx) + if err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("flushing cron vm: %w", err) + } + } + + // handle state forks + // XXX: The state tree + pstate, err = sm.HandleStateForks(ctx, pstate, i, em, ts) + if err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("error handling state forks: %w", err) + } + } + + vmEarly := partDone() + earlyCronGas := cronGas + cronGas = 0 + partDone = metrics.Timer(ctx, metrics.VMApplyMessages) + + vmi, err := makeVm(pstate, epoch, ts.MinTimestamp()) + if err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("making vm: %w", err) + } + + var ( + receipts []*types.MessageReceipt + storingEvents = sm.ChainStore().IsStoringEvents() + events [][]types.Event + processedMsgs = make(map[cid.Cid]struct{}) + ) + + var msgGas int64 + + for _, b := range bms { + penalty := types.NewInt(0) + gasReward := big.Zero() + + for _, cm := range append(b.BlsMessages, b.SecpkMessages...) { + m := cm.VMMessage() + if _, found := processedMsgs[m.Cid()]; found { + continue + } + r, err := vmi.ApplyMessage(ctx, cm) + if err != nil { + return cid.Undef, cid.Undef, err + } + + msgGas += r.GasUsed + + receipts = append(receipts, &r.MessageReceipt) + gasReward = big.Add(gasReward, r.GasCosts.MinerTip) + penalty = big.Add(penalty, r.GasCosts.MinerPenalty) + + if storingEvents { + // Appends nil when no events are returned to preserve positional alignment. + events = append(events, r.Events) + } + + if em != nil { + if err := em.MessageApplied(ctx, ts, cm.Cid(), m, r, false); err != nil { + return cid.Undef, cid.Undef, err + } + } + processedMsgs[m.Cid()] = struct{}{} + } + + params := &reward.AwardBlockRewardParams{ + Miner: b.Miner, + Penalty: penalty, + GasReward: gasReward, + WinCount: b.WinCount, + } + rErr := t.reward(ctx, vmi, em, epoch, ts, params) + if rErr != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("error applying reward: %w", rErr) + } + } + + vmMsg := partDone() + partDone = metrics.Timer(ctx, metrics.VMApplyCron) + + if err := runCron(vmi, epoch); err != nil { + return cid.Cid{}, cid.Cid{}, err + } + + vmCron := partDone() + partDone = metrics.Timer(ctx, metrics.VMApplyFlush) + + rectarr := blockadt.MakeEmptyArray(sm.ChainStore().ActorStore(ctx)) + for i, receipt := range receipts { + if err := rectarr.Set(uint64(i), receipt); err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("failed to build receipts amt: %w", err) + } + } + rectroot, err := rectarr.Root() + if err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("failed to build receipts amt: %w", err) + } + + // Slice will be empty if not storing events. + for i, evs := range events { + if len(evs) == 0 { + continue + } + switch root, err := t.StoreEventsAMT(ctx, sm.ChainStore(), evs); { + case err != nil: + return cid.Undef, cid.Undef, xerrors.Errorf("failed to store events amt: %w", err) + case i >= len(receipts): + return cid.Undef, cid.Undef, xerrors.Errorf("assertion failed: receipt and events array lengths inconsistent") + case receipts[i].EventsRoot == nil: + return cid.Undef, cid.Undef, xerrors.Errorf("assertion failed: VM returned events with no events root") + case root != *receipts[i].EventsRoot: + return cid.Undef, cid.Undef, xerrors.Errorf("assertion failed: returned events AMT root does not match derived") + } + } + + st, err := vmi.Flush(ctx) + if err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("vm flush failed: %w", err) + } + + vmFlush := partDone() + partDone = func() time.Duration { return time.Duration(0) } + + log.Infow("ApplyBlocks stats", "early", vmEarly, "earlyCronGas", earlyCronGas, "vmMsg", vmMsg, "msgGas", msgGas, "vmCron", vmCron, "cronGas", cronGas, "vmFlush", vmFlush, "epoch", epoch, "tsk", ts.Key()) + + stats.Record(ctx, metrics.VMSends.M(int64(atomic.LoadUint64(&vm.StatSends))), + metrics.VMApplied.M(int64(atomic.LoadUint64(&vm.StatApplied)))) + + return st, rectroot, nil +} + +func (t *TipSetExecutor) ExecuteTipSet(ctx context.Context, + sm *stmgr.StateManager, + ts *types.TipSet, + em stmgr.ExecMonitor, + vmTracing bool) (stateroot cid.Cid, rectsroot cid.Cid, err error) { + ctx, span := trace.StartSpan(ctx, "computeTipSetState") + defer span.End() + + blks := ts.Blocks() + + for i := 0; i < len(blks); i++ { + for j := i + 1; j < len(blks); j++ { + if blks[i].Miner == blks[j].Miner { + return cid.Undef, cid.Undef, + xerrors.Errorf("duplicate miner in a tipset (%s %s)", + blks[i].Miner, blks[j].Miner) + } + } + } + + if ts.Height() == 0 { + // NB: This is here because the process that executes blocks requires that the + // block miner reference a valid miner in the state tree. Unless we create some + // magical genesis miner, this won't work properly, so we short circuit here + // This avoids the question of 'who gets paid the genesis block reward' + return blks[0].ParentStateRoot, blks[0].ParentMessageReceipts, nil + } + + var parentEpoch abi.ChainEpoch + pstate := blks[0].ParentStateRoot + if blks[0].Height > 0 { + parent, err := sm.ChainStore().GetBlock(ctx, blks[0].Parents[0]) + if err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("getting parent block: %w", err) + } + + parentEpoch = parent.Height + } + + r := rand.NewStateRand(sm.ChainStore(), ts.Cids(), sm.Beacon(), sm.GetNetworkVersion) + + blkmsgs, err := sm.ChainStore().BlockMsgsForTipset(ctx, ts) + if err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("getting block messages for tipset: %w", err) + } + fbmsgs := make([]FilecoinBlockMessages, len(blkmsgs)) + for i := range fbmsgs { + fbmsgs[i].BlockMessages = blkmsgs[i] + fbmsgs[i].WinCount = ts.Blocks()[i].ElectionProof.WinCount + } + baseFee := blks[0].ParentBaseFee + + return t.ApplyBlocks(ctx, sm, parentEpoch, pstate, fbmsgs, blks[0].Height, r, em, vmTracing, baseFee, ts) +} + +func (t *TipSetExecutor) StoreEventsAMT(ctx context.Context, cs *store.ChainStore, events []types.Event) (cid.Cid, error) { + cst := cbor.NewCborStore(cs.ChainBlockstore()) + objs := make([]cbg.CBORMarshaler, len(events)) + for i := 0; i < len(events); i++ { + objs[i] = &events[i] + } + return amt4.FromArray(ctx, cst, objs, amt4.UseTreeBitWidth(types.EventAMTBitwidth)) +} + +var _ stmgr.Executor = &TipSetExecutor{} diff --git a/chain/consensus/filcns/FVMLiftoff.txt b/chain/consensus/filcns/FVMLiftoff.txt new file mode 100644 index 000000000..4bff5c25b --- /dev/null +++ b/chain/consensus/filcns/FVMLiftoff.txt @@ -0,0 +1,57 @@ + . + + . ' ` . + . ' . + + . ' . ' | + . ' . ' | ++ . ' . + +| ` . . ' . ' . +| + . ' . + ++ | . ' . ' | + ` . | . ' . ' | ++ + . ' . + +| ` . . ' . ' +| + . ' ++ | . ' . + ` . | '. ` . + + ` . ` . + ` . ` . ` . --- --- + ` . ` . . + /\__\ ___ /\ \ + ` . + ' | /:/ _/_ /\ \ |::\ \ + ` . | | /:/ /\__\ \:\ \ |:|:\ \ + ` . | . + /:/ /:/ / \:\ \ __|:|\:\ \ + + ' /:/_/:/ / ___ \:\__\ /::::|_\:\__\ + \:\/:/ / /\ \ |:| | \:\~~\ \/__/ + \::/__/ \:\ \|:| | \:\ \ + \:\ \ \:\__|:|__| \:\ \ + \:\__\ \::::/__/ \:\__\ + \/__/ ~~~~ \/__/ + ___ ___ ___ ___ + /\__\ /\ \ /\__\ /\__\ + ___ /:/ _/_ ___ /::\ \ /:/ _/_ /:/ _/_ + /\__\ /:/ /\__\ /\__\ /:/\:\ \ /:/ /\__\ /:/ /\__\ + ___ ___ /:/__/ /:/ /:/ / /:/ / /:/ \:\ \ /:/ /:/ / /:/ /:/ / + /\ \ /\__\ /::\ \ /:/_/:/ / /:/__/ /:/__/ \:\__\ /:/_/:/ / /:/_/:/ / + \:\ \ /:/ / \/\:\ \__ \:\/:/ / /::\ \ \:\ \ /:/ / \:\/:/ / \:\/:/ / + \:\ /:/ / ~~\:\/\__\ \::/__/ /:/\:\ \ \:\ /:/ / \::/__/ \::/__/ . + + \:\/:/ / \::/ / \:\ \ \/__\:\ \ \:\/:/ / \:\ \ \:\ \ . ' ` . + \::/ / /:/ / \:\__\ \:\__\ \::/ / \:\__\ \:\__\ . ' . + + \/__/ \/__/ \/__/ \/__/ \/__/ \/__/ \/__/ . ' . ' | + . ' . ' | + + . ' . + + | ` . . ' . ' . + | + . ' . + + + | . ' . ' | + ` . | . ' . ' | + + + . ' . + + | ` . . ' . ' + | + . ' + + | . ' . + ` . | '. ` . + + ` . ` . + ` . ` . ` . + ` . ` . . + + ` . + ' | + ` . | | + ` . | . + + + diff --git a/chain/consensus/filcns/filecoin.go b/chain/consensus/filcns/filecoin.go new file mode 100644 index 000000000..509eb8a5e --- /dev/null +++ b/chain/consensus/filcns/filecoin.go @@ -0,0 +1,546 @@ +package filcns + +import ( + "bytes" + "context" + "errors" + "os" + "time" + + "github.com/ipfs/go-cid" + logging "github.com/ipfs/go-log/v2" + "go.opencensus.io/trace" + "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/crypto" + "github.com/filecoin-project/go-state-types/network" + "github.com/filecoin-project/specs-actors/v7/actors/runtime/proof" + + "github.com/filecoin-project/lotus/api" + "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" + "github.com/filecoin-project/lotus/chain/actors/builtin/power" + "github.com/filecoin-project/lotus/chain/actors/builtin/reward" + "github.com/filecoin-project/lotus/chain/beacon" + "github.com/filecoin-project/lotus/chain/consensus" + "github.com/filecoin-project/lotus/chain/rand" + "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/lib/async" + "github.com/filecoin-project/lotus/lib/sigs" + "github.com/filecoin-project/lotus/storage/sealer/ffiwrapper" + "github.com/filecoin-project/lotus/storage/sealer/storiface" +) + +var log = logging.Logger("fil-consensus") + +type FilecoinEC struct { + // The interface for accessing and putting tipsets into local storage + store *store.ChainStore + + // handle to the random beacon for verification + beacon beacon.Schedule + + // the state manager handles making state queries + sm *stmgr.StateManager + + verifier storiface.Verifier + + genesis *types.TipSet +} + +// Blocks that are more than MaxHeightDrift epochs above +// the theoretical max height based on systime are quickly rejected +const MaxHeightDrift = 5 + +var RewardFunc = func(ctx context.Context, vmi vm.Interface, em stmgr.ExecMonitor, + epoch abi.ChainEpoch, ts *types.TipSet, params *reward.AwardBlockRewardParams) error { + ser, err := actors.SerializeParams(params) + if err != nil { + return xerrors.Errorf("failed to serialize award params: %w", err) + } + rwMsg := &types.Message{ + From: builtin.SystemActorAddr, + To: reward.Address, + Nonce: uint64(epoch), + Value: types.NewInt(0), + GasFeeCap: types.NewInt(0), + GasPremium: types.NewInt(0), + GasLimit: 1 << 30, + Method: reward.Methods.AwardBlockReward, + Params: ser, + } + ret, actErr := vmi.ApplyImplicitMessage(ctx, rwMsg) + if actErr != nil { + return xerrors.Errorf("failed to apply reward message: %w", actErr) + } + if em != nil { + if err := em.MessageApplied(ctx, ts, rwMsg.Cid(), rwMsg, ret, true); err != nil { + return xerrors.Errorf("callback failed on reward message: %w", err) + } + } + + if ret.ExitCode != 0 { + return xerrors.Errorf("reward application message failed (exit %d): %s", ret.ExitCode, ret.ActorErr) + } + return nil +} + +func NewFilecoinExpectedConsensus(sm *stmgr.StateManager, beacon beacon.Schedule, verifier storiface.Verifier, genesis chain.Genesis) consensus.Consensus { + if build.InsecurePoStValidation { + log.Warn("*********************************************************************************************") + log.Warn(" [INSECURE-POST-VALIDATION] Insecure test validation is enabled. If you see this outside of a test, it is a severe bug! ") + log.Warn("*********************************************************************************************") + } + + return &FilecoinEC{ + store: sm.ChainStore(), + beacon: beacon, + sm: sm, + verifier: verifier, + genesis: genesis, + } +} + +func (filec *FilecoinEC) ValidateBlock(ctx context.Context, b *types.FullBlock) (err error) { + if err := blockSanityChecks(b.Header); err != nil { + return xerrors.Errorf("incoming header failed basic sanity checks: %w", err) + } + + h := b.Header + + baseTs, err := filec.store.LoadTipSet(ctx, types.NewTipSetKey(h.Parents...)) + if err != nil { + return xerrors.Errorf("load parent tipset failed (%s): %w", h.Parents, err) + } + + winPoStNv := filec.sm.GetNetworkVersion(ctx, baseTs.Height()) + + lbts, lbst, err := stmgr.GetLookbackTipSetForRound(ctx, filec.sm, baseTs, h.Height) + if err != nil { + return xerrors.Errorf("failed to get lookback tipset for block: %w", err) + } + + prevBeacon, err := filec.store.GetLatestBeaconEntry(ctx, baseTs) + if err != nil { + return xerrors.Errorf("failed to get latest beacon entry: %w", err) + } + + // fast checks first + if h.Height <= baseTs.Height() { + return xerrors.Errorf("block height not greater than parent height: %d != %d", h.Height, baseTs.Height()) + } + + nulls := h.Height - (baseTs.Height() + 1) + if tgtTs := baseTs.MinTimestamp() + build.BlockDelaySecs*uint64(nulls+1); h.Timestamp != tgtTs { + return xerrors.Errorf("block has wrong timestamp: %d != %d", h.Timestamp, tgtTs) + } + + now := uint64(build.Clock.Now().Unix()) + if h.Timestamp > now+build.AllowableClockDriftSecs { + return xerrors.Errorf("block was from the future (now=%d, blk=%d): %w", now, h.Timestamp, consensus.ErrTemporal) + } + if h.Timestamp > now { + log.Warn("Got block from the future, but within threshold", h.Timestamp, build.Clock.Now().Unix()) + } + + minerCheck := async.Err(func() error { + if err := filec.minerIsValid(ctx, h.Miner, baseTs); err != nil { + return xerrors.Errorf("minerIsValid failed: %w", err) + } + return nil + }) + + pweight, err := filec.store.Weight(ctx, baseTs) + if err != nil { + return xerrors.Errorf("getting parent weight: %w", err) + } + + if types.BigCmp(pweight, b.Header.ParentWeight) != 0 { + return xerrors.Errorf("parrent weight different: %s (header) != %s (computed)", + b.Header.ParentWeight, pweight) + } + + // Stuff that needs worker address + waddr, err := stmgr.GetMinerWorkerRaw(ctx, filec.sm, lbst, h.Miner) + if err != nil { + return xerrors.Errorf("GetMinerWorkerRaw failed: %w", err) + } + + winnerCheck := async.Err(func() error { + if h.ElectionProof.WinCount < 1 { + return xerrors.Errorf("block is not claiming to be a winner") + } + + eligible, err := stmgr.MinerEligibleToMine(ctx, filec.sm, h.Miner, baseTs, lbts) + if err != nil { + return xerrors.Errorf("determining if miner has min power failed: %w", err) + } + + if !eligible { + return xerrors.New("block's miner is ineligible to mine") + } + + rBeacon := *prevBeacon + if len(h.BeaconEntries) != 0 { + rBeacon = h.BeaconEntries[len(h.BeaconEntries)-1] + } + buf := new(bytes.Buffer) + if err := h.Miner.MarshalCBOR(buf); err != nil { + return xerrors.Errorf("failed to marshal miner address to cbor: %w", err) + } + + vrfBase, err := rand.DrawRandomness(rBeacon.Data, crypto.DomainSeparationTag_ElectionProofProduction, h.Height, buf.Bytes()) + if err != nil { + return xerrors.Errorf("could not draw randomness: %w", err) + } + + if err := VerifyElectionPoStVRF(ctx, waddr, vrfBase, h.ElectionProof.VRFProof); err != nil { + return xerrors.Errorf("validating block election proof failed: %w", err) + } + + slashed, err := stmgr.GetMinerSlashed(ctx, filec.sm, baseTs, h.Miner) + if err != nil { + return xerrors.Errorf("failed to check if block miner was slashed: %w", err) + } + + if slashed { + return xerrors.Errorf("received block was from slashed or invalid miner") + } + + mpow, tpow, _, err := stmgr.GetPowerRaw(ctx, filec.sm, lbst, h.Miner) + if err != nil { + return xerrors.Errorf("failed getting power: %w", err) + } + + j := h.ElectionProof.ComputeWinCount(mpow.QualityAdjPower, tpow.QualityAdjPower) + if h.ElectionProof.WinCount != j { + return xerrors.Errorf("miner claims wrong number of wins: miner: %d, computed: %d", h.ElectionProof.WinCount, j) + } + + return nil + }) + + blockSigCheck := async.Err(func() error { + if err := verifyBlockSignature(ctx, h, waddr); err != nil { + return xerrors.Errorf("check block signature failed: %w", err) + } + return nil + + }) + + beaconValuesCheck := async.Err(func() error { + if os.Getenv("LOTUS_IGNORE_DRAND") == "_yes_" { + return nil + } + + nv := filec.sm.GetNetworkVersion(ctx, h.Height) + if err := beacon.ValidateBlockValues(filec.beacon, nv, h, baseTs.Height(), *prevBeacon); err != nil { + return xerrors.Errorf("failed to validate blocks random beacon values: %w", err) + } + return nil + }) + + tktsCheck := async.Err(func() error { + buf := new(bytes.Buffer) + if err := h.Miner.MarshalCBOR(buf); err != nil { + return xerrors.Errorf("failed to marshal miner address to cbor: %w", err) + } + + if h.Height > build.UpgradeSmokeHeight { + buf.Write(baseTs.MinTicket().VRFProof) + } + + beaconBase := *prevBeacon + if len(h.BeaconEntries) != 0 { + beaconBase = h.BeaconEntries[len(h.BeaconEntries)-1] + } + + vrfBase, err := rand.DrawRandomness(beaconBase.Data, crypto.DomainSeparationTag_TicketProduction, h.Height-build.TicketRandomnessLookback, buf.Bytes()) + if err != nil { + return xerrors.Errorf("failed to compute vrf base for ticket: %w", err) + } + + err = VerifyElectionPoStVRF(ctx, waddr, vrfBase, h.Ticket.VRFProof) + if err != nil { + return xerrors.Errorf("validating block tickets failed: %w", err) + } + return nil + }) + + wproofCheck := async.Err(func() error { + if err := filec.VerifyWinningPoStProof(ctx, winPoStNv, h, *prevBeacon, lbst, waddr); err != nil { + return xerrors.Errorf("invalid election post: %w", err) + } + return nil + }) + + commonChecks := consensus.CommonBlkChecks(ctx, filec.sm, filec.store, b, baseTs) + await := append([]async.ErrorFuture{ + minerCheck, + tktsCheck, + blockSigCheck, + beaconValuesCheck, + wproofCheck, + winnerCheck, + }, commonChecks...) + + return consensus.RunAsyncChecks(ctx, await) +} + +func blockSanityChecks(h *types.BlockHeader) error { + if h.ElectionProof == nil { + return xerrors.Errorf("block cannot have nil election proof") + } + + if h.Ticket == nil { + return xerrors.Errorf("block cannot have nil ticket") + } + + if h.BlockSig == nil { + return xerrors.Errorf("block had nil signature") + } + + if h.BLSAggregate == nil { + return xerrors.Errorf("block had nil bls aggregate signature") + } + + if h.Miner.Protocol() != address.ID { + return xerrors.Errorf("block had non-ID miner address") + } + + return nil +} + +func (filec *FilecoinEC) VerifyWinningPoStProof(ctx context.Context, nv network.Version, h *types.BlockHeader, prevBeacon types.BeaconEntry, lbst cid.Cid, waddr address.Address) error { + if build.InsecurePoStValidation { + if len(h.WinPoStProof) == 0 { + return xerrors.Errorf("[INSECURE-POST-VALIDATION] No winning post proof given") + } + + if string(h.WinPoStProof[0].ProofBytes) == "valid proof" { + return nil + } + return xerrors.Errorf("[INSECURE-POST-VALIDATION] winning post was invalid") + } + + buf := new(bytes.Buffer) + if err := h.Miner.MarshalCBOR(buf); err != nil { + return xerrors.Errorf("failed to marshal miner address: %w", err) + } + + rbase := prevBeacon + if len(h.BeaconEntries) > 0 { + rbase = h.BeaconEntries[len(h.BeaconEntries)-1] + } + + rand, err := rand.DrawRandomness(rbase.Data, crypto.DomainSeparationTag_WinningPoStChallengeSeed, h.Height, buf.Bytes()) + if err != nil { + return xerrors.Errorf("failed to get randomness for verifying winning post proof: %w", err) + } + + mid, err := address.IDFromAddress(h.Miner) + if err != nil { + return xerrors.Errorf("failed to get ID from miner address %s: %w", h.Miner, err) + } + + xsectors, err := stmgr.GetSectorsForWinningPoSt(ctx, nv, filec.verifier, filec.sm, lbst, h.Miner, rand) + if err != nil { + return xerrors.Errorf("getting winning post sector set: %w", err) + } + + sectors := make([]proof.SectorInfo, len(xsectors)) + for i, xsi := range xsectors { + sectors[i] = proof.SectorInfo{ + SealProof: xsi.SealProof, + SectorNumber: xsi.SectorNumber, + SealedCID: xsi.SealedCID, + } + } + + ok, err := ffiwrapper.ProofVerifier.VerifyWinningPoSt(ctx, proof.WinningPoStVerifyInfo{ + Randomness: rand, + Proofs: h.WinPoStProof, + ChallengedSectors: sectors, + Prover: abi.ActorID(mid), + }) + if err != nil { + return xerrors.Errorf("failed to verify election post: %w", err) + } + + if !ok { + log.Errorf("invalid winning post (block: %s, %x; %v)", h.Cid(), rand, sectors) + return xerrors.Errorf("winning post was invalid") + } + + return nil +} + +func (filec *FilecoinEC) IsEpochInConsensusRange(epoch abi.ChainEpoch) bool { + if filec.genesis == nil { + return true + } + + // Don't try to sync anything before finality. Don't propagate such blocks either. + // + // We use _our_ current head, not the expected head, because the network's head can lag on + // catch-up (after a network outage). + if epoch < filec.store.GetHeaviestTipSet().Height()-build.Finality { + return false + } + + now := uint64(build.Clock.Now().Unix()) + return epoch <= (abi.ChainEpoch((now-filec.genesis.MinTimestamp())/build.BlockDelaySecs) + MaxHeightDrift) +} + +func (filec *FilecoinEC) minerIsValid(ctx context.Context, maddr address.Address, baseTs *types.TipSet) error { + act, err := filec.sm.LoadActor(ctx, power.Address, baseTs) + if err != nil { + return xerrors.Errorf("failed to load power actor: %w", err) + } + + powState, err := power.Load(filec.store.ActorStore(ctx), act) + if err != nil { + return xerrors.Errorf("failed to load power actor state: %w", err) + } + + _, exist, err := powState.MinerPower(maddr) + if err != nil { + return xerrors.Errorf("failed to look up miner's claim: %w", err) + } + + if !exist { + return xerrors.New("miner isn't valid") + } + + return nil +} + +func VerifyElectionPoStVRF(ctx context.Context, worker address.Address, rand []byte, evrf []byte) error { + return VerifyVRF(ctx, worker, rand, evrf) +} + +func VerifyVRF(ctx context.Context, worker address.Address, vrfBase, vrfproof []byte) error { + _, span := trace.StartSpan(ctx, "VerifyVRF") + defer span.End() + + sig := &crypto.Signature{ + Type: crypto.SigTypeBLS, + Data: vrfproof, + } + + if err := sigs.Verify(sig, worker, vrfBase); err != nil { + return xerrors.Errorf("vrf was invalid: %w", err) + } + + return nil +} + +var ErrSoftFailure = errors.New("soft validation failure") +var ErrInsufficientPower = errors.New("incoming block's miner does not have minimum power") + +func (filec *FilecoinEC) ValidateBlockHeader(ctx context.Context, b *types.BlockHeader) (rejectReason string, err error) { + + // we want to ensure that it is a block from a known miner; we reject blocks from unknown miners + // to prevent spam attacks. + // the logic works as follows: we lookup the miner in the chain for its key. + // if we can find it then it's a known miner and we can validate the signature. + // if we can't find it, we check whether we are (near) synced in the chain. + // if we are not synced we cannot validate the block and we must ignore it. + // if we are synced and the miner is unknown, then the block is rejcected. + key, err := filec.checkPowerAndGetWorkerKey(ctx, b) + if err != nil { + if err != ErrSoftFailure && filec.isChainNearSynced() { + log.Warnf("received block from unknown miner or miner that doesn't meet min power over pubsub; rejecting message") + return "unknown_miner", err + } + + log.Warnf("cannot validate block message; unknown miner or miner that doesn't meet min power in unsynced chain: %s", b.Cid()) + return "", err // ignore + } + + if b.ElectionProof.WinCount < 1 { + log.Errorf("block is not claiming to be winning") + return "not_winning", xerrors.Errorf("block not winning") + } + + err = sigs.CheckBlockSignature(ctx, b, key) + if err != nil { + log.Errorf("block signature verification failed: %s", err) + return "signature_verification_failed", err + } + + return "", nil +} + +func (filec *FilecoinEC) checkPowerAndGetWorkerKey(ctx context.Context, bh *types.BlockHeader) (address.Address, error) { + // we check that the miner met the minimum power at the lookback tipset + + baseTs := filec.store.GetHeaviestTipSet() + lbts, lbst, err := stmgr.GetLookbackTipSetForRound(ctx, filec.sm, baseTs, bh.Height) + if err != nil { + log.Warnf("failed to load lookback tipset for incoming block: %s", err) + return address.Undef, ErrSoftFailure + } + + key, err := stmgr.GetMinerWorkerRaw(ctx, filec.sm, lbst, bh.Miner) + if err != nil { + log.Warnf("failed to resolve worker key for miner %s and block height %d: %s", bh.Miner, bh.Height, err) + return address.Undef, ErrSoftFailure + } + + // NOTE: we check to see if the miner was eligible in the lookback + // tipset - 1 for historical reasons. DO NOT use the lookback state + // returned by GetLookbackTipSetForRound. + + eligible, err := stmgr.MinerEligibleToMine(ctx, filec.sm, bh.Miner, baseTs, lbts) + if err != nil { + log.Warnf("failed to determine if incoming block's miner has minimum power: %s", err) + return address.Undef, ErrSoftFailure + } + + if !eligible { + log.Warnf("incoming block's miner is ineligible") + return address.Undef, ErrInsufficientPower + } + + return key, nil +} + +func (filec *FilecoinEC) isChainNearSynced() bool { + ts := filec.store.GetHeaviestTipSet() + timestamp := ts.MinTimestamp() + timestampTime := time.Unix(int64(timestamp), 0) + return build.Clock.Since(timestampTime) < 6*time.Hour +} + +func verifyBlockSignature(ctx context.Context, h *types.BlockHeader, + addr address.Address) error { + return sigs.CheckBlockSignature(ctx, h, addr) +} + +func signBlock(ctx context.Context, w api.Wallet, + addr address.Address, next *types.BlockHeader) error { + + nosigbytes, err := next.SigningBytes() + if err != nil { + return xerrors.Errorf("failed to get signing bytes for block: %w", err) + } + + sig, err := w.WalletSign(ctx, addr, nosigbytes, api.MsgMeta{ + Type: api.MTBlock, + }) + if err != nil { + return xerrors.Errorf("failed to sign new block: %w", err) + } + next.BlockSig = sig + return nil +} + +var _ consensus.Consensus = &FilecoinEC{} diff --git a/chain/consensus/filcns/mine.go b/chain/consensus/filcns/mine.go new file mode 100644 index 000000000..956cba252 --- /dev/null +++ b/chain/consensus/filcns/mine.go @@ -0,0 +1,46 @@ +package filcns + +import ( + "context" + + "golang.org/x/xerrors" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/consensus" + "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/types" +) + +func (filec *FilecoinEC) CreateBlock(ctx context.Context, w api.Wallet, bt *api.BlockTemplate) (*types.FullBlock, error) { + pts, err := filec.sm.ChainStore().LoadTipSet(ctx, bt.Parents) + if err != nil { + return nil, xerrors.Errorf("failed to load parent tipset: %w", err) + } + + _, lbst, err := stmgr.GetLookbackTipSetForRound(ctx, filec.sm, pts, bt.Epoch) + if err != nil { + return nil, xerrors.Errorf("getting lookback miner actor state: %w", err) + } + + worker, err := stmgr.GetMinerWorkerRaw(ctx, filec.sm, lbst, bt.Miner) + if err != nil { + return nil, xerrors.Errorf("failed to get miner worker: %w", err) + } + + next, blsMessages, secpkMessages, err := consensus.CreateBlockHeader(ctx, filec.sm, pts, bt) + if err != nil { + return nil, xerrors.Errorf("failed to process messages from block template: %w", err) + } + + if err := signBlock(ctx, w, worker, next); err != nil { + return nil, xerrors.Errorf("failed to sign new block: %w", err) + } + + fullBlock := &types.FullBlock{ + Header: next, + BlsMessages: blsMessages, + SecpkMessages: secpkMessages, + } + + return fullBlock, nil +} diff --git a/chain/consensus/filcns/upgrades.go b/chain/consensus/filcns/upgrades.go new file mode 100644 index 000000000..075937a3c --- /dev/null +++ b/chain/consensus/filcns/upgrades.go @@ -0,0 +1,1977 @@ +package filcns + +import ( + "context" + _ "embed" + "fmt" + "os" + "runtime" + "strconv" + "time" + + "github.com/docker/go-units" + "github.com/ipfs/go-cid" + cbor "github.com/ipfs/go-ipld-cbor" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + nv18 "github.com/filecoin-project/go-state-types/builtin/v10/migration" + nv19 "github.com/filecoin-project/go-state-types/builtin/v11/migration" + nv17 "github.com/filecoin-project/go-state-types/builtin/v9/migration" + "github.com/filecoin-project/go-state-types/manifest" + "github.com/filecoin-project/go-state-types/migration" + "github.com/filecoin-project/go-state-types/network" + "github.com/filecoin-project/go-state-types/rt" + gstStore "github.com/filecoin-project/go-state-types/store" + builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" + miner0 "github.com/filecoin-project/specs-actors/actors/builtin/miner" + multisig0 "github.com/filecoin-project/specs-actors/actors/builtin/multisig" + power0 "github.com/filecoin-project/specs-actors/actors/builtin/power" + "github.com/filecoin-project/specs-actors/actors/migration/nv3" + adt0 "github.com/filecoin-project/specs-actors/actors/util/adt" + "github.com/filecoin-project/specs-actors/v2/actors/migration/nv4" + "github.com/filecoin-project/specs-actors/v2/actors/migration/nv7" + "github.com/filecoin-project/specs-actors/v3/actors/migration/nv10" + "github.com/filecoin-project/specs-actors/v4/actors/migration/nv12" + "github.com/filecoin-project/specs-actors/v5/actors/migration/nv13" + "github.com/filecoin-project/specs-actors/v6/actors/migration/nv14" + "github.com/filecoin-project/specs-actors/v7/actors/migration/nv15" + "github.com/filecoin-project/specs-actors/v8/actors/migration/nv16" + + "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/actors/builtin/multisig" + "github.com/filecoin-project/lotus/chain/actors/builtin/system" + "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" + "github.com/filecoin-project/lotus/node/bundle" +) + +//go:embed FVMLiftoff.txt +var fvmLiftoffBanner string + +var ( + MigrationMaxWorkerCount int + EnvMigrationMaxWorkerCount = "LOTUS_MIGRATION_MAX_WORKER_COUNT" +) + +func init() { + // the default calculation used for migration worker count + MigrationMaxWorkerCount = runtime.NumCPU() + // check if an alternative value was request by environment + if mwcs := os.Getenv(EnvMigrationMaxWorkerCount); mwcs != "" { + mwc, err := strconv.ParseInt(mwcs, 10, 32) + if err != nil { + log.Warnf("invalid value for %s (%s) defaulting to %d: %s", EnvMigrationMaxWorkerCount, mwcs, MigrationMaxWorkerCount, err) + return + } + // use value from environment + log.Infof("migration worker cound set from %s (%d)", EnvMigrationMaxWorkerCount, mwc) + MigrationMaxWorkerCount = int(mwc) + return + } + log.Infof("migration worker count: %d", MigrationMaxWorkerCount) +} + +func DefaultUpgradeSchedule() stmgr.UpgradeSchedule { + var us stmgr.UpgradeSchedule + + updates := []stmgr.Upgrade{{ + Height: build.UpgradeBreezeHeight, + Network: network.Version1, + Migration: UpgradeFaucetBurnRecovery, + }, { + Height: build.UpgradeSmokeHeight, + Network: network.Version2, + Migration: nil, + }, { + Height: build.UpgradeIgnitionHeight, + Network: network.Version3, + Migration: UpgradeIgnition, + }, { + Height: build.UpgradeRefuelHeight, + Network: network.Version3, + Migration: UpgradeRefuel, + }, { + Height: build.UpgradeAssemblyHeight, + Network: network.Version4, + Expensive: true, + Migration: UpgradeActorsV2, + }, { + Height: build.UpgradeTapeHeight, + Network: network.Version5, + Migration: nil, + }, { + Height: build.UpgradeLiftoffHeight, + Network: network.Version5, + Migration: UpgradeLiftoff, + }, { + Height: build.UpgradeKumquatHeight, + Network: network.Version6, + Migration: nil, + }, { + Height: build.UpgradeCalicoHeight, + Network: network.Version7, + Migration: UpgradeCalico, + }, { + Height: build.UpgradePersianHeight, + Network: network.Version8, + Migration: nil, + }, { + Height: build.UpgradeOrangeHeight, + Network: network.Version9, + Migration: nil, + }, { + Height: build.UpgradeTrustHeight, + Network: network.Version10, + Migration: UpgradeActorsV3, + PreMigrations: []stmgr.PreMigration{{ + PreMigration: PreUpgradeActorsV3, + StartWithin: 120, + DontStartWithin: 60, + StopWithin: 35, + }, { + PreMigration: PreUpgradeActorsV3, + StartWithin: 30, + DontStartWithin: 15, + StopWithin: 5, + }}, + Expensive: true, + }, { + Height: build.UpgradeNorwegianHeight, + Network: network.Version11, + Migration: nil, + }, { + Height: build.UpgradeTurboHeight, + Network: network.Version12, + Migration: UpgradeActorsV4, + PreMigrations: []stmgr.PreMigration{{ + PreMigration: PreUpgradeActorsV4, + StartWithin: 120, + DontStartWithin: 60, + StopWithin: 35, + }, { + PreMigration: PreUpgradeActorsV4, + StartWithin: 30, + DontStartWithin: 15, + StopWithin: 5, + }}, + Expensive: true, + }, { + Height: build.UpgradeHyperdriveHeight, + Network: network.Version13, + Migration: UpgradeActorsV5, + PreMigrations: []stmgr.PreMigration{{ + PreMigration: PreUpgradeActorsV5, + StartWithin: 120, + DontStartWithin: 60, + StopWithin: 35, + }, { + PreMigration: PreUpgradeActorsV5, + StartWithin: 30, + DontStartWithin: 15, + StopWithin: 5, + }}, + Expensive: true, + }, { + Height: build.UpgradeChocolateHeight, + Network: network.Version14, + Migration: UpgradeActorsV6, + PreMigrations: []stmgr.PreMigration{{ + PreMigration: PreUpgradeActorsV6, + StartWithin: 120, + DontStartWithin: 60, + StopWithin: 35, + }, { + PreMigration: PreUpgradeActorsV6, + StartWithin: 30, + DontStartWithin: 15, + StopWithin: 5, + }}, + Expensive: true, + }, { + Height: build.UpgradeOhSnapHeight, + Network: network.Version15, + Migration: UpgradeActorsV7, + PreMigrations: []stmgr.PreMigration{{ + PreMigration: PreUpgradeActorsV7, + StartWithin: 180, + DontStartWithin: 60, + StopWithin: 5, + }}, + Expensive: true, + }, { + Height: build.UpgradeSkyrHeight, + Network: network.Version16, + Migration: UpgradeActorsV8, + PreMigrations: []stmgr.PreMigration{{ + PreMigration: PreUpgradeActorsV8, + StartWithin: 180, + DontStartWithin: 60, + StopWithin: 5, + }}, + Expensive: true, + }, { + Height: build.UpgradeSharkHeight, + Network: network.Version17, + Migration: UpgradeActorsV9, + PreMigrations: []stmgr.PreMigration{{ + PreMigration: PreUpgradeActorsV9, + StartWithin: 240, + DontStartWithin: 60, + StopWithin: 20, + }, { + PreMigration: PreUpgradeActorsV9, + StartWithin: 15, + DontStartWithin: 10, + StopWithin: 5, + }}, + Expensive: true, + }, { + Height: build.UpgradeHyggeHeight, + Network: network.Version18, + Migration: UpgradeActorsV10, + PreMigrations: []stmgr.PreMigration{{ + PreMigration: PreUpgradeActorsV10, + StartWithin: 60, + DontStartWithin: 10, + StopWithin: 5, + }}, + Expensive: true, + }, { + Height: build.UpgradeLightningHeight, + Network: network.Version19, + Migration: UpgradeActorsV11, + PreMigrations: []stmgr.PreMigration{{ + PreMigration: PreUpgradeActorsV11, + StartWithin: 120, + DontStartWithin: 15, + StopWithin: 10, + }}, + Expensive: true, + }, { + Height: build.UpgradeThunderHeight, + Network: network.Version20, + Migration: nil, + }, + } + + for _, u := range updates { + if u.Height < 0 { + // upgrade disabled + continue + } + us = append(us, u) + } + return us +} + +func UpgradeFaucetBurnRecovery(ctx context.Context, sm *stmgr.StateManager, _ stmgr.MigrationCache, em stmgr.ExecMonitor, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { + // Some initial parameters + FundsForMiners := types.FromFil(1_000_000) + LookbackEpoch := abi.ChainEpoch(32000) + AccountCap := types.FromFil(0) + BaseMinerBalance := types.FromFil(20) + DesiredReimbursementBalance := types.FromFil(5_000_000) + + isSystemAccount := func(addr address.Address) (bool, error) { + id, err := address.IDFromAddress(addr) + if err != nil { + return false, xerrors.Errorf("id address: %w", err) + } + + if id < 1000 { + return true, nil + } + return false, nil + } + + minerFundsAlloc := func(pow, tpow abi.StoragePower) abi.TokenAmount { + return types.BigDiv(types.BigMul(pow, FundsForMiners), tpow) + } + + // Grab lookback state for account checks + lbts, err := sm.ChainStore().GetTipsetByHeight(ctx, LookbackEpoch, ts, false) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to get tipset at lookback height: %w", err) + } + + lbtree, err := sm.ParentState(lbts) + if err != nil { + return cid.Undef, xerrors.Errorf("loading state tree failed: %w", err) + } + + tree, err := sm.StateTree(root) + if err != nil { + return cid.Undef, xerrors.Errorf("getting state tree: %w", err) + } + + type transfer struct { + From address.Address + To address.Address + Amt abi.TokenAmount + } + + var transfers []transfer + subcalls := make([]types.ExecutionTrace, 0) + transferCb := func(trace types.ExecutionTrace) { + subcalls = append(subcalls, trace) + } + + // Take all excess funds away, put them into the reserve account + err = tree.ForEach(func(addr address.Address, act *types.Actor) error { + switch act.Code { + case builtin0.AccountActorCodeID, builtin0.MultisigActorCodeID, builtin0.PaymentChannelActorCodeID: + sysAcc, err := isSystemAccount(addr) + if err != nil { + return xerrors.Errorf("checking system account: %w", err) + } + + if !sysAcc { + transfers = append(transfers, transfer{ + From: addr, + To: builtin.ReserveAddress, + Amt: act.Balance, + }) + } + case builtin0.StorageMinerActorCodeID: + var st miner0.State + if err := sm.ChainStore().ActorStore(ctx).Get(ctx, act.Head, &st); err != nil { + return xerrors.Errorf("failed to load miner state: %w", err) + } + + var available abi.TokenAmount + { + defer func() { + if err := recover(); err != nil { + log.Warnf("Get available balance failed (%s, %s, %s): %s", addr, act.Head, act.Balance, err) + } + available = abi.NewTokenAmount(0) + }() + // this panics if the miner doesnt have enough funds to cover their locked pledge + available = st.GetAvailableBalance(act.Balance) + } + + if !available.IsZero() { + transfers = append(transfers, transfer{ + From: addr, + To: builtin.ReserveAddress, + Amt: available, + }) + } + } + return nil + }) + if err != nil { + return cid.Undef, xerrors.Errorf("foreach over state tree failed: %w", err) + } + + // Execute transfers from previous step + for _, t := range transfers { + if err := stmgr.DoTransfer(tree, t.From, t.To, t.Amt, transferCb); err != nil { + return cid.Undef, xerrors.Errorf("transfer %s %s->%s failed: %w", t.Amt, t.From, t.To, err) + } + } + + // pull up power table to give miners back some funds proportional to their power + var ps power0.State + powAct, err := tree.GetActor(builtin0.StoragePowerActorAddr) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to load power actor: %w", err) + } + + cst := cbor.NewCborStore(sm.ChainStore().StateBlockstore()) + if err := cst.Get(ctx, powAct.Head, &ps); err != nil { + return cid.Undef, xerrors.Errorf("failed to get power actor state: %w", err) + } + + totalPower := ps.TotalBytesCommitted + + var transfersBack []transfer + // Now, we return some funds to places where they are needed + err = tree.ForEach(func(addr address.Address, act *types.Actor) error { + lbact, err := lbtree.GetActor(addr) + if err != nil { + if !xerrors.Is(err, types.ErrActorNotFound) { + return xerrors.Errorf("failed to get actor in lookback state") + } + } + + prevBalance := abi.NewTokenAmount(0) + if lbact != nil { + prevBalance = lbact.Balance + } + + switch act.Code { + case builtin0.AccountActorCodeID, builtin0.MultisigActorCodeID, builtin0.PaymentChannelActorCodeID: + nbalance := big.Min(prevBalance, AccountCap) + if nbalance.Sign() != 0 { + transfersBack = append(transfersBack, transfer{ + From: builtin.ReserveAddress, + To: addr, + Amt: nbalance, + }) + } + case builtin0.StorageMinerActorCodeID: + var st miner0.State + if err := sm.ChainStore().ActorStore(ctx).Get(ctx, act.Head, &st); err != nil { + return xerrors.Errorf("failed to load miner state: %w", err) + } + + var minfo miner0.MinerInfo + if err := cst.Get(ctx, st.Info, &minfo); err != nil { + return xerrors.Errorf("failed to get miner info: %w", err) + } + + sectorsArr, err := adt0.AsArray(sm.ChainStore().ActorStore(ctx), st.Sectors) + if err != nil { + return xerrors.Errorf("failed to load sectors array: %w", err) + } + + slen := sectorsArr.Length() + + power := types.BigMul(types.NewInt(slen), types.NewInt(uint64(minfo.SectorSize))) + + mfunds := minerFundsAlloc(power, totalPower) + transfersBack = append(transfersBack, transfer{ + From: builtin.ReserveAddress, + To: minfo.Worker, + Amt: mfunds, + }) + + // Now make sure to give each miner who had power at the lookback some FIL + lbact, err := lbtree.GetActor(addr) + if err == nil { + var lbst miner0.State + if err := sm.ChainStore().ActorStore(ctx).Get(ctx, lbact.Head, &lbst); err != nil { + return xerrors.Errorf("failed to load miner state: %w", err) + } + + lbsectors, err := adt0.AsArray(sm.ChainStore().ActorStore(ctx), lbst.Sectors) + if err != nil { + return xerrors.Errorf("failed to load lb sectors array: %w", err) + } + + if lbsectors.Length() > 0 { + transfersBack = append(transfersBack, transfer{ + From: builtin.ReserveAddress, + To: minfo.Worker, + Amt: BaseMinerBalance, + }) + } + + } else { + log.Warnf("failed to get miner in lookback state: %s", err) + } + } + return nil + }) + if err != nil { + return cid.Undef, xerrors.Errorf("foreach over state tree failed: %w", err) + } + + for _, t := range transfersBack { + if err := stmgr.DoTransfer(tree, t.From, t.To, t.Amt, transferCb); err != nil { + return cid.Undef, xerrors.Errorf("transfer %s %s->%s failed: %w", t.Amt, t.From, t.To, err) + } + } + + // transfer all burnt funds back to the reserve account + burntAct, err := tree.GetActor(builtin0.BurntFundsActorAddr) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to load burnt funds actor: %w", err) + } + if err := stmgr.DoTransfer(tree, builtin0.BurntFundsActorAddr, builtin.ReserveAddress, burntAct.Balance, transferCb); err != nil { + return cid.Undef, xerrors.Errorf("failed to unburn funds: %w", err) + } + + // Top up the reimbursement service + reimbAddr, err := address.NewFromString("t0111") + if err != nil { + return cid.Undef, xerrors.Errorf("failed to parse reimbursement service address") + } + + reimb, err := tree.GetActor(reimbAddr) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to load reimbursement account actor: %w", err) + } + + difference := types.BigSub(DesiredReimbursementBalance, reimb.Balance) + if err := stmgr.DoTransfer(tree, builtin.ReserveAddress, reimbAddr, difference, transferCb); err != nil { + return cid.Undef, xerrors.Errorf("failed to top up reimbursement account: %w", err) + } + + // Now, a final sanity check to make sure the balances all check out + total := abi.NewTokenAmount(0) + err = tree.ForEach(func(addr address.Address, act *types.Actor) error { + total = types.BigAdd(total, act.Balance) + return nil + }) + if err != nil { + return cid.Undef, xerrors.Errorf("checking final state balance failed: %w", err) + } + + exp := types.FromFil(build.FilBase) + if !exp.Equals(total) { + return cid.Undef, xerrors.Errorf("resultant state tree account balance was not correct: %s", total) + } + + if em != nil { + // record the transfer in execution traces + + fakeMsg := stmgr.MakeFakeMsg(builtin.SystemActorAddr, builtin.SystemActorAddr, big.Zero(), uint64(epoch)) + + if err := em.MessageApplied(ctx, ts, fakeMsg.Cid(), fakeMsg, &vm.ApplyRet{ + MessageReceipt: *stmgr.MakeFakeRct(), + ActorErr: nil, + ExecutionTrace: types.ExecutionTrace{ + Msg: types.MessageTrace{ + To: fakeMsg.To, + From: fakeMsg.From, + }, + Subcalls: subcalls, + }, + Duration: 0, + GasCosts: nil, + }, false); err != nil { + return cid.Undef, xerrors.Errorf("recording transfers: %w", err) + } + } + + return tree.Flush(ctx) +} + +func UpgradeIgnition(ctx context.Context, sm *stmgr.StateManager, _ stmgr.MigrationCache, cb stmgr.ExecMonitor, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { + store := sm.ChainStore().ActorStore(ctx) + + if build.UpgradeLiftoffHeight <= epoch { + return cid.Undef, xerrors.Errorf("liftoff height must be beyond ignition height") + } + + nst, err := nv3.MigrateStateTree(ctx, store, root, epoch) + if err != nil { + return cid.Undef, xerrors.Errorf("migrating actors state: %w", err) + } + + tree, err := sm.StateTree(nst) + if err != nil { + return cid.Undef, xerrors.Errorf("getting state tree: %w", err) + } + + err = stmgr.SetNetworkName(ctx, store, tree, "ignition") + if err != nil { + return cid.Undef, xerrors.Errorf("setting network name: %w", err) + } + + split1, err := address.NewFromString("t0115") + if err != nil { + return cid.Undef, xerrors.Errorf("first split address: %w", err) + } + + split2, err := address.NewFromString("t0116") + if err != nil { + return cid.Undef, xerrors.Errorf("second split address: %w", err) + } + + err = resetGenesisMsigs0(ctx, sm, store, tree, build.UpgradeLiftoffHeight) + if err != nil { + return cid.Undef, xerrors.Errorf("resetting genesis msig start epochs: %w", err) + } + + err = splitGenesisMultisig0(ctx, cb, split1, store, tree, 50, epoch, ts) + if err != nil { + return cid.Undef, xerrors.Errorf("splitting first msig: %w", err) + } + + err = splitGenesisMultisig0(ctx, cb, split2, store, tree, 50, epoch, ts) + if err != nil { + return cid.Undef, xerrors.Errorf("splitting second msig: %w", err) + } + + err = nv3.CheckStateTree(ctx, store, nst, epoch, builtin0.TotalFilecoin) + if err != nil { + return cid.Undef, xerrors.Errorf("sanity check after ignition upgrade failed: %w", err) + } + + return tree.Flush(ctx) +} + +func splitGenesisMultisig0(ctx context.Context, em stmgr.ExecMonitor, addr address.Address, store adt0.Store, tree *state.StateTree, portions uint64, epoch abi.ChainEpoch, ts *types.TipSet) error { + if portions < 1 { + return xerrors.Errorf("cannot split into 0 portions") + } + + mact, err := tree.GetActor(addr) + if err != nil { + return xerrors.Errorf("getting msig actor: %w", err) + } + + mst, err := multisig.Load(store, mact) + if err != nil { + return xerrors.Errorf("getting msig state: %w", err) + } + + signers, err := mst.Signers() + if err != nil { + return xerrors.Errorf("getting msig signers: %w", err) + } + + thresh, err := mst.Threshold() + if err != nil { + return xerrors.Errorf("getting msig threshold: %w", err) + } + + ibal, err := mst.InitialBalance() + if err != nil { + return xerrors.Errorf("getting msig initial balance: %w", err) + } + + se, err := mst.StartEpoch() + if err != nil { + return xerrors.Errorf("getting msig start epoch: %w", err) + } + + ud, err := mst.UnlockDuration() + if err != nil { + return xerrors.Errorf("getting msig unlock duration: %w", err) + } + + pending, err := adt0.MakeEmptyMap(store).Root() + if err != nil { + return xerrors.Errorf("failed to create empty map: %w", err) + } + + newIbal := big.Div(ibal, types.NewInt(portions)) + newState := &multisig0.State{ + Signers: signers, + NumApprovalsThreshold: thresh, + NextTxnID: 0, + InitialBalance: newIbal, + StartEpoch: se, + UnlockDuration: ud, + PendingTxns: pending, + } + + scid, err := store.Put(ctx, newState) + if err != nil { + return xerrors.Errorf("storing new state: %w", err) + } + + newActor := types.Actor{ + Code: builtin0.MultisigActorCodeID, + Head: scid, + Nonce: 0, + Balance: big.Zero(), + } + + i := uint64(0) + subcalls := make([]types.ExecutionTrace, 0, portions) + transferCb := func(trace types.ExecutionTrace) { + subcalls = append(subcalls, trace) + } + + for i < portions { + keyAddr, err := stmgr.MakeKeyAddr(addr, i) + if err != nil { + return xerrors.Errorf("creating key address: %w", err) + } + + idAddr, err := tree.RegisterNewAddress(keyAddr) + if err != nil { + return xerrors.Errorf("registering new address: %w", err) + } + + err = tree.SetActor(idAddr, &newActor) + if err != nil { + return xerrors.Errorf("setting new msig actor state: %w", err) + } + + if err := stmgr.DoTransfer(tree, addr, idAddr, newIbal, transferCb); err != nil { + return xerrors.Errorf("transferring split msig balance: %w", err) + } + + i++ + } + + if em != nil { + // record the transfer in execution traces + + fakeMsg := stmgr.MakeFakeMsg(builtin.SystemActorAddr, addr, big.Zero(), uint64(epoch)) + + if err := em.MessageApplied(ctx, ts, fakeMsg.Cid(), fakeMsg, &vm.ApplyRet{ + MessageReceipt: *stmgr.MakeFakeRct(), + ActorErr: nil, + ExecutionTrace: types.ExecutionTrace{ + Msg: types.MessageTrace{ + From: fakeMsg.From, + To: fakeMsg.To, + }, + Subcalls: subcalls, + }, + Duration: 0, + GasCosts: nil, + }, false); err != nil { + return xerrors.Errorf("recording transfers: %w", err) + } + } + + return nil +} + +// TODO: After the Liftoff epoch, refactor this to use resetMultisigVesting +func resetGenesisMsigs0(ctx context.Context, sm *stmgr.StateManager, store adt0.Store, tree *state.StateTree, startEpoch abi.ChainEpoch) error { + gb, err := sm.ChainStore().GetGenesis(ctx) + if err != nil { + return xerrors.Errorf("getting genesis block: %w", err) + } + + gts, err := types.NewTipSet([]*types.BlockHeader{gb}) + if err != nil { + return xerrors.Errorf("getting genesis tipset: %w", err) + } + + cst := cbor.NewCborStore(sm.ChainStore().StateBlockstore()) + genesisTree, err := state.LoadStateTree(cst, gts.ParentState()) + if err != nil { + return xerrors.Errorf("loading state tree: %w", err) + } + + err = genesisTree.ForEach(func(addr address.Address, genesisActor *types.Actor) error { + if genesisActor.Code == builtin0.MultisigActorCodeID { + currActor, err := tree.GetActor(addr) + if err != nil { + return xerrors.Errorf("loading actor: %w", err) + } + + var currState multisig0.State + if err := store.Get(ctx, currActor.Head, &currState); err != nil { + return xerrors.Errorf("reading multisig state: %w", err) + } + + currState.StartEpoch = startEpoch + + currActor.Head, err = store.Put(ctx, &currState) + if err != nil { + return xerrors.Errorf("writing new multisig state: %w", err) + } + + if err := tree.SetActor(addr, currActor); err != nil { + return xerrors.Errorf("setting multisig actor: %w", err) + } + } + return nil + }) + + if err != nil { + return xerrors.Errorf("iterating over genesis actors: %w", err) + } + + return nil +} + +func resetMultisigVesting0(ctx context.Context, store adt0.Store, tree *state.StateTree, addr address.Address, startEpoch abi.ChainEpoch, duration abi.ChainEpoch, balance abi.TokenAmount) error { + act, err := tree.GetActor(addr) + if err != nil { + return xerrors.Errorf("getting actor: %w", err) + } + + if !builtin.IsMultisigActor(act.Code) { + return xerrors.Errorf("actor wasn't msig: %w", err) + } + + var msigState multisig0.State + if err := store.Get(ctx, act.Head, &msigState); err != nil { + return xerrors.Errorf("reading multisig state: %w", err) + } + + msigState.StartEpoch = startEpoch + msigState.UnlockDuration = duration + msigState.InitialBalance = balance + + act.Head, err = store.Put(ctx, &msigState) + if err != nil { + return xerrors.Errorf("writing new multisig state: %w", err) + } + + if err := tree.SetActor(addr, act); err != nil { + return xerrors.Errorf("setting multisig actor: %w", err) + } + + return nil +} + +func UpgradeRefuel(ctx context.Context, sm *stmgr.StateManager, _ stmgr.MigrationCache, cb stmgr.ExecMonitor, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { + + store := sm.ChainStore().ActorStore(ctx) + tree, err := sm.StateTree(root) + if err != nil { + return cid.Undef, xerrors.Errorf("getting state tree: %w", err) + } + + err = resetMultisigVesting0(ctx, store, tree, builtin.SaftAddress, 0, 0, big.Zero()) + if err != nil { + return cid.Undef, xerrors.Errorf("tweaking msig vesting: %w", err) + } + + err = resetMultisigVesting0(ctx, store, tree, builtin.ReserveAddress, 0, 0, big.Zero()) + if err != nil { + return cid.Undef, xerrors.Errorf("tweaking msig vesting: %w", err) + } + + err = resetMultisigVesting0(ctx, store, tree, builtin.RootVerifierAddress, 0, 0, big.Zero()) + if err != nil { + return cid.Undef, xerrors.Errorf("tweaking msig vesting: %w", err) + } + + return tree.Flush(ctx) +} + +func UpgradeActorsV2(ctx context.Context, sm *stmgr.StateManager, _ stmgr.MigrationCache, cb stmgr.ExecMonitor, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { + buf := blockstore.NewTieredBstore(sm.ChainStore().StateBlockstore(), blockstore.NewMemorySync()) + store := store.ActorStore(ctx, buf) + + info, err := store.Put(ctx, new(types.StateInfo0)) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to create new state info for actors v2: %w", err) + } + + newHamtRoot, err := nv4.MigrateStateTree(ctx, store, root, epoch, nv4.DefaultConfig()) + if err != nil { + return cid.Undef, xerrors.Errorf("upgrading to actors v2: %w", err) + } + + newRoot, err := store.Put(ctx, &types.StateRoot{ + Version: types.StateTreeVersion1, + Actors: newHamtRoot, + Info: info, + }) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to persist new state root: %w", err) + } + + // perform some basic sanity checks to make sure everything still works. + if newSm, err := state.LoadStateTree(store, newRoot); err != nil { + return cid.Undef, xerrors.Errorf("state tree sanity load failed: %w", err) + } else if newRoot2, err := newSm.Flush(ctx); err != nil { + return cid.Undef, xerrors.Errorf("state tree sanity flush failed: %w", err) + } else if newRoot2 != newRoot { + return cid.Undef, xerrors.Errorf("state-root mismatch: %s != %s", newRoot, newRoot2) + } else if _, err := newSm.GetActor(builtin0.InitActorAddr); err != nil { + return cid.Undef, xerrors.Errorf("failed to load init actor after upgrade: %w", err) + } + + { + from := buf + to := buf.Read() + + if err := vm.Copy(ctx, from, to, newRoot); err != nil { + return cid.Undef, xerrors.Errorf("copying migrated tree: %w", err) + } + } + + return newRoot, nil +} + +func UpgradeLiftoff(ctx context.Context, sm *stmgr.StateManager, _ stmgr.MigrationCache, cb stmgr.ExecMonitor, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { + tree, err := sm.StateTree(root) + if err != nil { + return cid.Undef, xerrors.Errorf("getting state tree: %w", err) + } + + err = stmgr.SetNetworkName(ctx, sm.ChainStore().ActorStore(ctx), tree, "mainnet") + if err != nil { + return cid.Undef, xerrors.Errorf("setting network name: %w", err) + } + + return tree.Flush(ctx) +} + +func UpgradeCalico(ctx context.Context, sm *stmgr.StateManager, _ stmgr.MigrationCache, cb stmgr.ExecMonitor, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { + if build.BuildType != build.BuildMainnet { + return root, nil + } + + store := sm.ChainStore().ActorStore(ctx) + var stateRoot types.StateRoot + if err := store.Get(ctx, root, &stateRoot); err != nil { + return cid.Undef, xerrors.Errorf("failed to decode state root: %w", err) + } + + if stateRoot.Version != types.StateTreeVersion1 { + return cid.Undef, xerrors.Errorf( + "expected state root version 1 for calico upgrade, got %d", + stateRoot.Version, + ) + } + + newHamtRoot, err := nv7.MigrateStateTree(ctx, store, stateRoot.Actors, epoch, nv7.DefaultConfig()) + if err != nil { + return cid.Undef, xerrors.Errorf("running nv7 migration: %w", err) + } + + newRoot, err := store.Put(ctx, &types.StateRoot{ + Version: stateRoot.Version, + Actors: newHamtRoot, + Info: stateRoot.Info, + }) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to persist new state root: %w", err) + } + + // perform some basic sanity checks to make sure everything still works. + if newSm, err := state.LoadStateTree(store, newRoot); err != nil { + return cid.Undef, xerrors.Errorf("state tree sanity load failed: %w", err) + } else if newRoot2, err := newSm.Flush(ctx); err != nil { + return cid.Undef, xerrors.Errorf("state tree sanity flush failed: %w", err) + } else if newRoot2 != newRoot { + return cid.Undef, xerrors.Errorf("state-root mismatch: %s != %s", newRoot, newRoot2) + } else if _, err := newSm.GetActor(builtin0.InitActorAddr); err != nil { + return cid.Undef, xerrors.Errorf("failed to load init actor after upgrade: %w", err) + } + + return newRoot, nil +} + +func UpgradeActorsV3(ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, cb stmgr.ExecMonitor, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { + // Use all the CPUs except 3. + workerCount := MigrationMaxWorkerCount - 3 + if workerCount <= 0 { + workerCount = 1 + } + + config := nv10.Config{ + MaxWorkers: uint(workerCount), + JobQueueSize: 1000, + ResultQueueSize: 100, + ProgressLogPeriod: 10 * time.Second, + } + newRoot, err := upgradeActorsV3Common(ctx, sm, cache, root, epoch, ts, config) + if err != nil { + return cid.Undef, xerrors.Errorf("migrating actors v3 state: %w", err) + } + + tree, err := sm.StateTree(newRoot) + if err != nil { + return cid.Undef, xerrors.Errorf("getting state tree: %w", err) + } + + if build.BuildType == build.BuildMainnet { + err := stmgr.TerminateActor(ctx, tree, build.ZeroAddress, cb, epoch, ts) + if err != nil && !xerrors.Is(err, types.ErrActorNotFound) { + return cid.Undef, xerrors.Errorf("deleting zero bls actor: %w", err) + } + + newRoot, err = tree.Flush(ctx) + if err != nil { + return cid.Undef, xerrors.Errorf("flushing state tree: %w", err) + } + } + + return newRoot, nil +} + +func PreUpgradeActorsV3(ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) error { + // Use half the CPUs for pre-migration, but leave at least 3. + workerCount := MigrationMaxWorkerCount + if workerCount <= 4 { + workerCount = 1 + } else { + workerCount /= 2 + } + config := nv10.Config{MaxWorkers: uint(workerCount)} + _, err := upgradeActorsV3Common(ctx, sm, cache, root, epoch, ts, config) + return err +} + +func upgradeActorsV3Common( + ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, + root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet, + config nv10.Config, +) (cid.Cid, error) { + buf := blockstore.NewTieredBstore(sm.ChainStore().StateBlockstore(), blockstore.NewMemorySync()) + store := store.ActorStore(ctx, buf) + + // Load the state root. + var stateRoot types.StateRoot + if err := store.Get(ctx, root, &stateRoot); err != nil { + return cid.Undef, xerrors.Errorf("failed to decode state root: %w", err) + } + + if stateRoot.Version != types.StateTreeVersion1 { + return cid.Undef, xerrors.Errorf( + "expected state root version 1 for actors v3 upgrade, got %d", + stateRoot.Version, + ) + } + + // Perform the migration + newHamtRoot, err := nv10.MigrateStateTree(ctx, store, stateRoot.Actors, epoch, config, migrationLogger{}, cache) + if err != nil { + return cid.Undef, xerrors.Errorf("upgrading to actors v3: %w", err) + } + + // Persist the result. + newRoot, err := store.Put(ctx, &types.StateRoot{ + Version: types.StateTreeVersion2, + Actors: newHamtRoot, + Info: stateRoot.Info, + }) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to persist new state root: %w", err) + } + + // Persist the new tree. + + { + from := buf + to := buf.Read() + + if err := vm.Copy(ctx, from, to, newRoot); err != nil { + return cid.Undef, xerrors.Errorf("copying migrated tree: %w", err) + } + } + + return newRoot, nil +} + +func UpgradeActorsV4(ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, cb stmgr.ExecMonitor, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { + // Use all the CPUs except 3. + workerCount := MigrationMaxWorkerCount - 3 + if workerCount <= 0 { + workerCount = 1 + } + + config := nv12.Config{ + MaxWorkers: uint(workerCount), + JobQueueSize: 1000, + ResultQueueSize: 100, + ProgressLogPeriod: 10 * time.Second, + } + + newRoot, err := upgradeActorsV4Common(ctx, sm, cache, root, epoch, ts, config) + if err != nil { + return cid.Undef, xerrors.Errorf("migrating actors v4 state: %w", err) + } + + return newRoot, nil +} + +func PreUpgradeActorsV4(ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) error { + // Use half the CPUs for pre-migration, but leave at least 3. + workerCount := MigrationMaxWorkerCount + if workerCount <= 4 { + workerCount = 1 + } else { + workerCount /= 2 + } + config := nv12.Config{MaxWorkers: uint(workerCount)} + _, err := upgradeActorsV4Common(ctx, sm, cache, root, epoch, ts, config) + return err +} + +func upgradeActorsV4Common( + ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, + root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet, + config nv12.Config, +) (cid.Cid, error) { + buf := blockstore.NewTieredBstore(sm.ChainStore().StateBlockstore(), blockstore.NewMemorySync()) + store := store.ActorStore(ctx, buf) + + // Load the state root. + var stateRoot types.StateRoot + if err := store.Get(ctx, root, &stateRoot); err != nil { + return cid.Undef, xerrors.Errorf("failed to decode state root: %w", err) + } + + if stateRoot.Version != types.StateTreeVersion2 { + return cid.Undef, xerrors.Errorf( + "expected state root version 2 for actors v4 upgrade, got %d", + stateRoot.Version, + ) + } + + // Perform the migration + newHamtRoot, err := nv12.MigrateStateTree(ctx, store, stateRoot.Actors, epoch, config, migrationLogger{}, cache) + if err != nil { + return cid.Undef, xerrors.Errorf("upgrading to actors v4: %w", err) + } + + // Persist the result. + newRoot, err := store.Put(ctx, &types.StateRoot{ + Version: types.StateTreeVersion3, + Actors: newHamtRoot, + Info: stateRoot.Info, + }) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to persist new state root: %w", err) + } + + // Persist the new tree. + + { + from := buf + to := buf.Read() + + if err := vm.Copy(ctx, from, to, newRoot); err != nil { + return cid.Undef, xerrors.Errorf("copying migrated tree: %w", err) + } + } + + return newRoot, nil +} + +func UpgradeActorsV5(ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, cb stmgr.ExecMonitor, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { + // Use all the CPUs except 3. + workerCount := MigrationMaxWorkerCount - 3 + if workerCount <= 0 { + workerCount = 1 + } + + config := nv13.Config{ + MaxWorkers: uint(workerCount), + JobQueueSize: 1000, + ResultQueueSize: 100, + ProgressLogPeriod: 10 * time.Second, + } + + newRoot, err := upgradeActorsV5Common(ctx, sm, cache, root, epoch, ts, config) + if err != nil { + return cid.Undef, xerrors.Errorf("migrating actors v5 state: %w", err) + } + + return newRoot, nil +} + +func PreUpgradeActorsV5(ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) error { + // Use half the CPUs for pre-migration, but leave at least 3. + workerCount := MigrationMaxWorkerCount + if workerCount <= 4 { + workerCount = 1 + } else { + workerCount /= 2 + } + config := nv13.Config{MaxWorkers: uint(workerCount)} + _, err := upgradeActorsV5Common(ctx, sm, cache, root, epoch, ts, config) + return err +} + +func upgradeActorsV5Common( + ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, + root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet, + config nv13.Config, +) (cid.Cid, error) { + buf := blockstore.NewTieredBstore(sm.ChainStore().StateBlockstore(), blockstore.NewMemorySync()) + store := store.ActorStore(ctx, buf) + + // Load the state root. + var stateRoot types.StateRoot + if err := store.Get(ctx, root, &stateRoot); err != nil { + return cid.Undef, xerrors.Errorf("failed to decode state root: %w", err) + } + + if stateRoot.Version != types.StateTreeVersion3 { + return cid.Undef, xerrors.Errorf( + "expected state root version 3 for actors v5 upgrade, got %d", + stateRoot.Version, + ) + } + + // Perform the migration + newHamtRoot, err := nv13.MigrateStateTree(ctx, store, stateRoot.Actors, epoch, config, migrationLogger{}, cache) + if err != nil { + return cid.Undef, xerrors.Errorf("upgrading to actors v5: %w", err) + } + + // Persist the result. + newRoot, err := store.Put(ctx, &types.StateRoot{ + Version: types.StateTreeVersion4, + Actors: newHamtRoot, + Info: stateRoot.Info, + }) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to persist new state root: %w", err) + } + + // Persist the new tree. + + { + from := buf + to := buf.Read() + + if err := vm.Copy(ctx, from, to, newRoot); err != nil { + return cid.Undef, xerrors.Errorf("copying migrated tree: %w", err) + } + } + + return newRoot, nil +} + +func UpgradeActorsV6(ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, cb stmgr.ExecMonitor, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { + // Use all the CPUs except 3. + workerCount := MigrationMaxWorkerCount - 3 + if workerCount <= 0 { + workerCount = 1 + } + + config := nv14.Config{ + MaxWorkers: uint(workerCount), + JobQueueSize: 1000, + ResultQueueSize: 100, + ProgressLogPeriod: 10 * time.Second, + } + + newRoot, err := upgradeActorsV6Common(ctx, sm, cache, root, epoch, ts, config) + if err != nil { + return cid.Undef, xerrors.Errorf("migrating actors v5 state: %w", err) + } + + return newRoot, nil +} + +func PreUpgradeActorsV6(ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) error { + // Use half the CPUs for pre-migration, but leave at least 3. + workerCount := MigrationMaxWorkerCount + if workerCount <= 4 { + workerCount = 1 + } else { + workerCount /= 2 + } + config := nv14.Config{MaxWorkers: uint(workerCount)} + _, err := upgradeActorsV6Common(ctx, sm, cache, root, epoch, ts, config) + return err +} + +func upgradeActorsV6Common( + ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, + root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet, + config nv14.Config, +) (cid.Cid, error) { + buf := blockstore.NewTieredBstore(sm.ChainStore().StateBlockstore(), blockstore.NewMemorySync()) + store := store.ActorStore(ctx, buf) + + // Load the state root. + var stateRoot types.StateRoot + if err := store.Get(ctx, root, &stateRoot); err != nil { + return cid.Undef, xerrors.Errorf("failed to decode state root: %w", err) + } + + if stateRoot.Version != types.StateTreeVersion4 { + return cid.Undef, xerrors.Errorf( + "expected state root version 4 for actors v6 upgrade, got %d", + stateRoot.Version, + ) + } + + // Perform the migration + newHamtRoot, err := nv14.MigrateStateTree(ctx, store, stateRoot.Actors, epoch, config, migrationLogger{}, cache) + if err != nil { + return cid.Undef, xerrors.Errorf("upgrading to actors v6: %w", err) + } + + // Persist the result. + newRoot, err := store.Put(ctx, &types.StateRoot{ + Version: types.StateTreeVersion4, + Actors: newHamtRoot, + Info: stateRoot.Info, + }) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to persist new state root: %w", err) + } + + // Persist the new tree. + + { + from := buf + to := buf.Read() + + if err := vm.Copy(ctx, from, to, newRoot); err != nil { + return cid.Undef, xerrors.Errorf("copying migrated tree: %w", err) + } + } + + return newRoot, nil +} + +func UpgradeActorsV7(ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, cb stmgr.ExecMonitor, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { + // Use all the CPUs except 3. + workerCount := MigrationMaxWorkerCount - 3 + if workerCount <= 0 { + workerCount = 1 + } + + config := nv15.Config{ + MaxWorkers: uint(workerCount), + JobQueueSize: 1000, + ResultQueueSize: 100, + ProgressLogPeriod: 10 * time.Second, + } + + newRoot, err := upgradeActorsV7Common(ctx, sm, cache, root, epoch, ts, config) + if err != nil { + return cid.Undef, xerrors.Errorf("migrating actors v6 state: %w", err) + } + + return newRoot, nil +} + +func PreUpgradeActorsV7(ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) error { + // Use half the CPUs for pre-migration, but leave at least 3. + workerCount := MigrationMaxWorkerCount + if workerCount <= 4 { + workerCount = 1 + } else { + workerCount /= 2 + } + + lbts, lbRoot, err := stmgr.GetLookbackTipSetForRound(ctx, sm, ts, epoch) + if err != nil { + return xerrors.Errorf("error getting lookback ts for premigration: %w", err) + } + + config := nv15.Config{MaxWorkers: uint(workerCount), + ProgressLogPeriod: time.Minute * 5} + + _, err = upgradeActorsV7Common(ctx, sm, cache, lbRoot, epoch, lbts, config) + return err +} + +func upgradeActorsV7Common( + ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, + root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet, + config nv15.Config, +) (cid.Cid, error) { + writeStore := blockstore.NewAutobatch(ctx, sm.ChainStore().StateBlockstore(), units.GiB/4) + // TODO: pretty sure we'd achieve nothing by doing this, confirm in review + //buf := blockstore.NewTieredBstore(sm.ChainStore().StateBlockstore(), writeStore) + store := store.ActorStore(ctx, writeStore) + // Load the state root. + var stateRoot types.StateRoot + if err := store.Get(ctx, root, &stateRoot); err != nil { + return cid.Undef, xerrors.Errorf("failed to decode state root: %w", err) + } + + if stateRoot.Version != types.StateTreeVersion4 { + return cid.Undef, xerrors.Errorf( + "expected state root version 4 for actors v7 upgrade, got %d", + stateRoot.Version, + ) + } + + // Perform the migration + newHamtRoot, err := nv15.MigrateStateTree(ctx, store, stateRoot.Actors, epoch, config, migrationLogger{}, cache) + if err != nil { + return cid.Undef, xerrors.Errorf("upgrading to actors v7: %w", err) + } + + // Persist the result. + newRoot, err := store.Put(ctx, &types.StateRoot{ + Version: types.StateTreeVersion4, + Actors: newHamtRoot, + Info: stateRoot.Info, + }) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to persist new state root: %w", err) + } + + // Persists the new tree and shuts down the flush worker + if err := writeStore.Flush(ctx); err != nil { + return cid.Undef, xerrors.Errorf("writeStore flush failed: %w", err) + } + + if err := writeStore.Shutdown(ctx); err != nil { + return cid.Undef, xerrors.Errorf("writeStore shutdown failed: %w", err) + } + + return newRoot, nil +} + +func UpgradeActorsV8(ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, cb stmgr.ExecMonitor, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { + // Use all the CPUs except 3. + workerCount := MigrationMaxWorkerCount - 3 + if workerCount <= 0 { + workerCount = 1 + } + + config := nv16.Config{ + MaxWorkers: uint(workerCount), + JobQueueSize: 1000, + ResultQueueSize: 100, + ProgressLogPeriod: 10 * time.Second, + } + + newRoot, err := upgradeActorsV8Common(ctx, sm, cache, root, epoch, ts, config) + if err != nil { + return cid.Undef, xerrors.Errorf("migrating actors v7 state: %w", err) + } + + fmt.Print(fvmLiftoffBanner) + + return newRoot, nil +} + +func PreUpgradeActorsV8(ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) error { + // Use half the CPUs for pre-migration, but leave at least 3. + workerCount := MigrationMaxWorkerCount + if workerCount <= 4 { + workerCount = 1 + } else { + workerCount /= 2 + } + + lbts, lbRoot, err := stmgr.GetLookbackTipSetForRound(ctx, sm, ts, epoch) + if err != nil { + return xerrors.Errorf("error getting lookback ts for premigration: %w", err) + } + + config := nv16.Config{MaxWorkers: uint(workerCount), + ProgressLogPeriod: time.Minute * 5} + + _, err = upgradeActorsV8Common(ctx, sm, cache, lbRoot, epoch, lbts, config) + return err +} + +func upgradeActorsV8Common( + ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, + root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet, + config nv16.Config, +) (cid.Cid, error) { + buf := blockstore.NewTieredBstore(sm.ChainStore().StateBlockstore(), blockstore.NewMemorySync()) + store := store.ActorStore(ctx, buf) + + // ensure that the manifest is loaded in the blockstore + if err := bundle.LoadBundles(ctx, buf, actorstypes.Version8); err != nil { + return cid.Undef, xerrors.Errorf("failed to load manifest bundle: %w", err) + } + + // Load the state root. + var stateRoot types.StateRoot + if err := store.Get(ctx, root, &stateRoot); err != nil { + return cid.Undef, xerrors.Errorf("failed to decode state root: %w", err) + } + + if stateRoot.Version != types.StateTreeVersion4 { + return cid.Undef, xerrors.Errorf( + "expected state root version 4 for actors v8 upgrade, got %d", + stateRoot.Version, + ) + } + + manifest, ok := actors.GetManifest(actorstypes.Version8) + if !ok { + return cid.Undef, xerrors.Errorf("no manifest CID for v8 upgrade") + } + + // Perform the migration + newHamtRoot, err := nv16.MigrateStateTree(ctx, store, manifest, stateRoot.Actors, epoch, config, migrationLogger{}, cache) + if err != nil { + return cid.Undef, xerrors.Errorf("upgrading to actors v8: %w", err) + } + + // Persist the result. + newRoot, err := store.Put(ctx, &types.StateRoot{ + Version: types.StateTreeVersion4, + Actors: newHamtRoot, + Info: stateRoot.Info, + }) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to persist new state root: %w", err) + } + + // Persist the new tree. + + { + from := buf + to := buf.Read() + + if err := vm.Copy(ctx, from, to, newRoot); err != nil { + return cid.Undef, xerrors.Errorf("copying migrated tree: %w", err) + } + } + + return newRoot, nil +} + +func UpgradeActorsV9(ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, cb stmgr.ExecMonitor, + root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { + // Use all the CPUs except 3. + workerCount := MigrationMaxWorkerCount - 3 + if workerCount <= 0 { + workerCount = 1 + } + + config := nv17.Config{ + MaxWorkers: uint(workerCount), + JobQueueSize: 1000, + ResultQueueSize: 100, + ProgressLogPeriod: 10 * time.Second, + } + + newRoot, err := upgradeActorsV9Common(ctx, sm, cache, root, epoch, ts, config) + if err != nil { + return cid.Undef, xerrors.Errorf("migrating actors v8 state: %w", err) + } + + return newRoot, nil +} + +func PreUpgradeActorsV9(ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, root cid.Cid, + epoch abi.ChainEpoch, ts *types.TipSet) error { + // Use half the CPUs for pre-migration, but leave at least 3. + workerCount := MigrationMaxWorkerCount + if workerCount <= 4 { + workerCount = 1 + } else { + workerCount /= 2 + } + + lbts, lbRoot, err := stmgr.GetLookbackTipSetForRound(ctx, sm, ts, epoch) + if err != nil { + return xerrors.Errorf("error getting lookback ts for premigration: %w", err) + } + + config := nv17.Config{MaxWorkers: uint(workerCount), + ProgressLogPeriod: time.Minute * 5} + + _, err = upgradeActorsV9Common(ctx, sm, cache, lbRoot, epoch, lbts, config) + return err +} + +func upgradeActorsV9Common( + ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, + root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet, + config nv17.Config, +) (cid.Cid, error) { + writeStore := blockstore.NewAutobatch(ctx, sm.ChainStore().StateBlockstore(), units.GiB/4) + store := store.ActorStore(ctx, writeStore) + + // ensure that the manifest is loaded in the blockstore + if err := bundle.LoadBundles(ctx, sm.ChainStore().StateBlockstore(), actorstypes.Version9); err != nil { + return cid.Undef, xerrors.Errorf("failed to load manifest bundle: %w", err) + } + + // Load the state root. + var stateRoot types.StateRoot + if err := store.Get(ctx, root, &stateRoot); err != nil { + return cid.Undef, xerrors.Errorf("failed to decode state root: %w", err) + } + + if stateRoot.Version != types.StateTreeVersion4 { + return cid.Undef, xerrors.Errorf( + "expected state root version 4 for actors v9 upgrade, got %d", + stateRoot.Version, + ) + } + + manifest, ok := actors.GetManifest(actorstypes.Version9) + if !ok { + return cid.Undef, xerrors.Errorf("no manifest CID for v9 upgrade") + } + + // Perform the migration + newHamtRoot, err := nv17.MigrateStateTree(ctx, store, manifest, stateRoot.Actors, epoch, config, + migrationLogger{}, cache) + if err != nil { + return cid.Undef, xerrors.Errorf("upgrading to actors v9: %w", err) + } + + // Persist the result. + newRoot, err := store.Put(ctx, &types.StateRoot{ + Version: types.StateTreeVersion4, + Actors: newHamtRoot, + Info: stateRoot.Info, + }) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to persist new state root: %w", err) + } + + // Persists the new tree and shuts down the flush worker + if err := writeStore.Flush(ctx); err != nil { + return cid.Undef, xerrors.Errorf("writeStore flush failed: %w", err) + } + + if err := writeStore.Shutdown(ctx); err != nil { + return cid.Undef, xerrors.Errorf("writeStore shutdown failed: %w", err) + } + + return newRoot, nil +} + +func PreUpgradeActorsV10(ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) error { + // Use half the CPUs for pre-migration, but leave at least 3. + workerCount := MigrationMaxWorkerCount + if workerCount <= 4 { + workerCount = 1 + } else { + workerCount /= 2 + } + + lbts, lbRoot, err := stmgr.GetLookbackTipSetForRound(ctx, sm, ts, epoch) + if err != nil { + return xerrors.Errorf("error getting lookback ts for premigration: %w", err) + } + + config := migration.Config{ + MaxWorkers: uint(workerCount), + ProgressLogPeriod: time.Minute * 5, + } + + _, err = upgradeActorsV10Common(ctx, sm, cache, lbRoot, epoch, lbts, config) + return err +} + +func UpgradeActorsV10(ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, cb stmgr.ExecMonitor, + root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { + // Use all the CPUs except 3. + workerCount := MigrationMaxWorkerCount - 3 + if workerCount <= 0 { + workerCount = 1 + } + + config := migration.Config{ + MaxWorkers: uint(workerCount), + JobQueueSize: 1000, + ResultQueueSize: 100, + ProgressLogPeriod: 10 * time.Second, + } + + newRoot, err := upgradeActorsV10Common(ctx, sm, cache, root, epoch, ts, config) + if err != nil { + return cid.Undef, xerrors.Errorf("migrating actors v10 state: %w", err) + } + + return newRoot, nil +} + +func upgradeActorsV10Common( + ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, + root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet, + config migration.Config, +) (cid.Cid, error) { + buf := blockstore.NewTieredBstore(sm.ChainStore().StateBlockstore(), blockstore.NewMemorySync()) + store := store.ActorStore(ctx, buf) + + // ensure that the manifest is loaded in the blockstore + if err := bundle.LoadBundles(ctx, sm.ChainStore().StateBlockstore(), actorstypes.Version10); err != nil { + return cid.Undef, xerrors.Errorf("failed to load manifest bundle: %w", err) + } + + // Load the state root. + var stateRoot types.StateRoot + if err := store.Get(ctx, root, &stateRoot); err != nil { + return cid.Undef, xerrors.Errorf("failed to decode state root: %w", err) + } + + if stateRoot.Version != types.StateTreeVersion4 { + return cid.Undef, xerrors.Errorf( + "expected state root version 4 for actors v9 upgrade, got %d", + stateRoot.Version, + ) + } + + manifest, ok := actors.GetManifest(actorstypes.Version10) + if !ok { + return cid.Undef, xerrors.Errorf("no manifest CID for v9 upgrade") + } + + // Perform the migration + newHamtRoot, err := nv18.MigrateStateTree(ctx, store, manifest, stateRoot.Actors, epoch, config, + migrationLogger{}, cache) + if err != nil { + return cid.Undef, xerrors.Errorf("upgrading to actors v10: %w", err) + } + + // Persist the result. + newRoot, err := store.Put(ctx, &types.StateRoot{ + Version: types.StateTreeVersion5, + Actors: newHamtRoot, + Info: stateRoot.Info, + }) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to persist new state root: %w", err) + } + + // Persist the new tree. + + { + from := buf + to := buf.Read() + + if err := vm.Copy(ctx, from, to, newRoot); err != nil { + return cid.Undef, xerrors.Errorf("copying migrated tree: %w", err) + } + } + + return newRoot, nil +} + +func PreUpgradeActorsV11(ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) error { + // Use half the CPUs for pre-migration, but leave at least 3. + workerCount := MigrationMaxWorkerCount + if workerCount <= 4 { + workerCount = 1 + } else { + workerCount /= 2 + } + + lbts, lbRoot, err := stmgr.GetLookbackTipSetForRound(ctx, sm, ts, epoch) + if err != nil { + return xerrors.Errorf("error getting lookback ts for premigration: %w", err) + } + + config := migration.Config{ + MaxWorkers: uint(workerCount), + ProgressLogPeriod: time.Minute * 5, + } + + _, err = upgradeActorsV11Common(ctx, sm, cache, lbRoot, epoch, lbts, config) + return err +} + +func UpgradeActorsV11(ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, cb stmgr.ExecMonitor, + root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { + // Use all the CPUs except 2. + workerCount := MigrationMaxWorkerCount - 3 + if workerCount <= 0 { + workerCount = 1 + } + config := migration.Config{ + MaxWorkers: uint(workerCount), + JobQueueSize: 1000, + ResultQueueSize: 100, + ProgressLogPeriod: 10 * time.Second, + } + newRoot, err := upgradeActorsV11Common(ctx, sm, cache, root, epoch, ts, config) + if err != nil { + return cid.Undef, xerrors.Errorf("migrating actors v11 state: %w", err) + } + return newRoot, nil +} + +func upgradeActorsV11Common( + ctx context.Context, sm *stmgr.StateManager, cache stmgr.MigrationCache, + root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet, + config migration.Config, +) (cid.Cid, error) { + writeStore := blockstore.NewAutobatch(ctx, sm.ChainStore().StateBlockstore(), units.GiB/4) + adtStore := store.ActorStore(ctx, writeStore) + // ensure that the manifest is loaded in the blockstore + if err := bundle.LoadBundles(ctx, writeStore, actorstypes.Version11); err != nil { + return cid.Undef, xerrors.Errorf("failed to load manifest bundle: %w", err) + } + + // Load the state root. + var stateRoot types.StateRoot + if err := adtStore.Get(ctx, root, &stateRoot); err != nil { + return cid.Undef, xerrors.Errorf("failed to decode state root: %w", err) + } + + if stateRoot.Version != types.StateTreeVersion5 { + return cid.Undef, xerrors.Errorf( + "expected state root version 5 for actors v11 upgrade, got %d", + stateRoot.Version, + ) + } + + manifest, ok := actors.GetManifest(actorstypes.Version11) + if !ok { + return cid.Undef, xerrors.Errorf("no manifest CID for v11 upgrade") + } + + // Perform the migration + newHamtRoot, err := nv19.MigrateStateTree(ctx, adtStore, manifest, stateRoot.Actors, epoch, config, + migrationLogger{}, cache) + if err != nil { + return cid.Undef, xerrors.Errorf("upgrading to actors v11: %w", err) + } + + // Persist the result. + newRoot, err := adtStore.Put(ctx, &types.StateRoot{ + Version: types.StateTreeVersion5, + Actors: newHamtRoot, + Info: stateRoot.Info, + }) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to persist new state root: %w", err) + } + + // Persists the new tree and shuts down the flush worker + if err := writeStore.Flush(ctx); err != nil { + return cid.Undef, xerrors.Errorf("writeStore flush failed: %w", err) + } + + if err := writeStore.Shutdown(ctx); err != nil { + return cid.Undef, xerrors.Errorf("writeStore shutdown failed: %w", err) + } + + return newRoot, nil +} + +// Example upgrade function if upgrade requires only code changes +//func UpgradeActorsV9(ctx context.Context, sm *stmgr.StateManager, _ stmgr.MigrationCache, _ stmgr.ExecMonitor, root cid.Cid, _ abi.ChainEpoch, _ *types.TipSet) (cid.Cid, error) { +// buf := blockstore.NewTieredBstore(sm.ChainStore().StateBlockstore(), blockstore.NewMemorySync()) +// +// av := actors.Version9 +// // This may change for upgrade +// newStateTreeVersion := types.StateTreeVersion4 +// +// // ensure that the manifest is loaded in the blockstore +// if err := bundle.FetchAndLoadBundles(ctx, buf, map[actors.Version]build.Bundle{ +// av: build.BuiltinActorReleases[av], +// }); err != nil { +// return cid.Undef, xerrors.Errorf("failed to load manifest bundle: %w", err) +// } +// +// newActorsManifestCid, ok := actors.GetManifest(av) +// if !ok { +// return cid.Undef, xerrors.Errorf("no manifest CID for v8 upgrade") +// } +// +// bstore := sm.ChainStore().StateBlockstore() +// return LiteMigration(ctx, bstore, newActorsManifestCid, root, av, types.StateTreeVersion4, newStateTreeVersion) +//} + +func LiteMigration(ctx context.Context, bstore blockstore.Blockstore, newActorsManifestCid cid.Cid, root cid.Cid, oldAv actorstypes.Version, newAv actorstypes.Version, oldStateTreeVersion types.StateTreeVersion, newStateTreeVersion types.StateTreeVersion) (cid.Cid, error) { + buf := blockstore.NewTieredBstore(bstore, blockstore.NewMemorySync()) + store := store.ActorStore(ctx, buf) + adtStore := gstStore.WrapStore(ctx, store) + + // Load the state root. + var stateRoot types.StateRoot + if err := store.Get(ctx, root, &stateRoot); err != nil { + return cid.Undef, xerrors.Errorf("failed to decode state root: %w", err) + } + + if stateRoot.Version != oldStateTreeVersion { + return cid.Undef, xerrors.Errorf( + "expected state tree version %d for actors code upgrade, got %d", + oldStateTreeVersion, + stateRoot.Version, + ) + } + + st, err := state.LoadStateTree(store, root) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to load state tree: %w", err) + } + + oldManifestData, err := stmgr.GetManifestData(ctx, st) + if err != nil { + return cid.Undef, xerrors.Errorf("error loading old actor manifest: %w", err) + } + + // load new manifest + newManifest, err := actors.LoadManifest(ctx, newActorsManifestCid, store) + if err != nil { + return cid.Undef, xerrors.Errorf("error loading new manifest: %w", err) + } + + var newManifestData manifest.ManifestData + if err := store.Get(ctx, newManifest.Data, &newManifestData); err != nil { + return cid.Undef, xerrors.Errorf("error loading new manifest data: %w", err) + } + + if len(oldManifestData.Entries) != len(manifest.GetBuiltinActorsKeys(oldAv)) { + return cid.Undef, xerrors.Errorf("incomplete old manifest with %d code CIDs", len(oldManifestData.Entries)) + } + if len(newManifestData.Entries) != len(manifest.GetBuiltinActorsKeys(newAv)) { + return cid.Undef, xerrors.Errorf("incomplete new manifest with %d code CIDs", len(newManifestData.Entries)) + } + + // Maps prior version code CIDs to migration functions. + migrations := make(map[cid.Cid]cid.Cid) + + for _, entry := range oldManifestData.Entries { + newCodeCid, ok := newManifest.Get(entry.Name) + if !ok { + return cid.Undef, xerrors.Errorf("code cid for %s actor not found in new manifest", entry.Name) + } + + migrations[entry.Code] = newCodeCid + } + + startTime := time.Now() + + // Load output state tree + actorsOut, err := state.NewStateTree(adtStore, newStateTreeVersion) + if err != nil { + return cid.Undef, err + } + + // Insert migrated records in output state tree. + err = st.ForEach(func(addr address.Address, actorIn *types.Actor) error { + newCid, ok := migrations[actorIn.Code] + if !ok { + return xerrors.Errorf("new code cid not found in migrations for actor %s", addr) + } + var head cid.Cid + if addr == system.Address { + newSystemState, err := system.MakeState(store, newAv, newManifest.Data) + if err != nil { + return xerrors.Errorf("could not make system actor state: %w", err) + } + head, err = store.Put(ctx, newSystemState) + if err != nil { + return xerrors.Errorf("could not set system actor state head: %w", err) + } + } else { + head = actorIn.Head + } + newActor := types.Actor{ + Code: newCid, + Head: head, + Nonce: actorIn.Nonce, + Balance: actorIn.Balance, + } + err = actorsOut.SetActor(addr, &newActor) + if err != nil { + return xerrors.Errorf("could not set actor at address %s: %w", addr, err) + } + + return nil + }) + if err != nil { + return cid.Undef, xerrors.Errorf("failed update actor states: %w", err) + } + + elapsed := time.Since(startTime) + log.Infof("All done after %v. Flushing state tree root.", elapsed) + newRoot, err := actorsOut.Flush(ctx) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to flush new actors: %w", err) + } + + // Persist the new tree. + { + from := buf + to := buf.Read() + + if err := vm.Copy(ctx, from, to, newRoot); err != nil { + return cid.Undef, xerrors.Errorf("copying migrated tree: %w", err) + } + } + + return newRoot, nil +} + +type migrationLogger struct{} + +func (ml migrationLogger) Log(level rt.LogLevel, msg string, args ...interface{}) { + switch level { + case rt.DEBUG: + log.Debugf(msg, args...) + case rt.INFO: + log.Infof(msg, args...) + case rt.WARN: + log.Warnf(msg, args...) + case rt.ERROR: + log.Errorf(msg, args...) + } +} diff --git a/chain/consensus/filcns/weight.go b/chain/consensus/filcns/weight.go new file mode 100644 index 000000000..ab90840c5 --- /dev/null +++ b/chain/consensus/filcns/weight.go @@ -0,0 +1,83 @@ +package filcns + +import ( + "context" + "math/big" + + cbor "github.com/ipfs/go-ipld-cbor" + "golang.org/x/xerrors" + + big2 "github.com/filecoin-project/go-state-types/big" + + bstore "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors/builtin/power" + "github.com/filecoin-project/lotus/chain/state" + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/chain/types" +) + +var zero = types.NewInt(0) + +func Weight(ctx context.Context, stateBs bstore.Blockstore, ts *types.TipSet) (types.BigInt, error) { + if ts == nil { + return types.NewInt(0), nil + } + // >>> w[r] <<< + wFunction(totalPowerAtTipset(ts)) * 2^8 + (wFunction(totalPowerAtTipset(ts)) * sum(ts.blocks[].ElectionProof.WinCount) * wRatio_num * 2^8) / (e * wRatio_den) + + var out = new(big.Int).Set(ts.ParentWeight().Int) + + // >>> wFunction(totalPowerAtTipset(ts)) * 2^8 <<< + (wFunction(totalPowerAtTipset(ts)) * sum(ts.blocks[].ElectionProof.WinCount) * wRatio_num * 2^8) / (e * wRatio_den) + + var tpow big2.Int + { + cst := cbor.NewCborStore(stateBs) + state, err := state.LoadStateTree(cst, ts.ParentState()) + if err != nil { + return types.NewInt(0), xerrors.Errorf("load state tree: %w", err) + } + + act, err := state.GetActor(power.Address) + if err != nil { + return types.NewInt(0), xerrors.Errorf("get power actor: %w", err) + } + + powState, err := power.Load(store.ActorStore(ctx, stateBs), act) + if err != nil { + return types.NewInt(0), xerrors.Errorf("failed to load power actor state: %w", err) + } + + claim, err := powState.TotalPower() + if err != nil { + return types.NewInt(0), xerrors.Errorf("failed to get total power: %w", err) + } + + tpow = claim.QualityAdjPower // TODO: REVIEW: Is this correct? + } + + log2P := int64(0) + if tpow.GreaterThan(zero) { + log2P = int64(tpow.BitLen() - 1) + } else { + // Not really expect to be here ... + return types.EmptyInt, xerrors.Errorf("All power in the net is gone. You network might be disconnected, or the net is dead!") + } + + out.Add(out, big.NewInt(log2P<<8)) + + // (wFunction(totalPowerAtTipset(ts)) * sum(ts.blocks[].ElectionProof.WinCount) * wRatio_num * 2^8) / (e * wRatio_den) + + totalJ := int64(0) + for _, b := range ts.Blocks() { + totalJ += b.ElectionProof.WinCount + } + + eWeight := big.NewInt((log2P * build.WRatioNum)) + eWeight = eWeight.Lsh(eWeight, 8) + eWeight = eWeight.Mul(eWeight, new(big.Int).SetInt64(totalJ)) + eWeight = eWeight.Div(eWeight, big.NewInt(int64(build.BlocksPerEpoch*build.WRatioDen))) + + out = out.Add(out, eWeight) + + return types.BigInt{Int: out}, nil +} diff --git a/chain/consensus/iface.go b/chain/consensus/iface.go new file mode 100644 index 000000000..9449cb5a4 --- /dev/null +++ b/chain/consensus/iface.go @@ -0,0 +1,103 @@ +package consensus + +import ( + "context" + + pubsub "github.com/libp2p/go-libp2p-pubsub" + "go.opencensus.io/stats" + + "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/reward" + "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/vm" + "github.com/filecoin-project/lotus/metrics" +) + +type Consensus interface { + // ValidateBlockHeader is called by peers when they receive a new block through the network. + // + // This is a fast sanity-check validation performed by the PubSub protocol before delivering + // it to the syncer. It checks that the block has the right format and it performs + // other consensus-specific light verifications like ensuring that the block is signed by + // a valid miner, or that it includes all the data required for a full verification. + ValidateBlockHeader(ctx context.Context, b *types.BlockHeader) (rejectReason string, err error) + + // ValidateBlock is called by the syncer to determine if to accept a block or not. + // + // It performs all the checks needed by the syncer to accept + // the block (signature verifications, VRF checks, message validity, etc.) + ValidateBlock(ctx context.Context, b *types.FullBlock) (err error) + + // IsEpochInConsensusRange returns true if the epoch is "in range" for consensus. That is: + // - It's not before finality. + // - It's not too far in the future. + IsEpochInConsensusRange(epoch abi.ChainEpoch) bool + + // CreateBlock implements all the logic required to propose and assemble a new Filecoin block. + // + // This function encapsulate all the consensus-specific actions to propose a new block + // such as the ordering of transactions, the inclusion of consensus proofs, the signature + // of the block, etc. + CreateBlock(ctx context.Context, w api.Wallet, bt *api.BlockTemplate) (*types.FullBlock, error) +} + +// RewardFunc parametrizes the logic for rewards when a message is executed. +// +// Each consensus implementation can set their own reward function. +type RewardFunc func(ctx context.Context, vmi vm.Interface, em stmgr.ExecMonitor, + epoch abi.ChainEpoch, ts *types.TipSet, params *reward.AwardBlockRewardParams) error + +// ValidateBlockPubsub implements the common checks performed by all consensus implementations +// when a block is received through the pubsub channel. +func ValidateBlockPubsub(ctx context.Context, cns Consensus, self bool, msg *pubsub.Message) (pubsub.ValidationResult, string) { + if self { + return validateLocalBlock(ctx, msg) + } + + // track validation time + begin := build.Clock.Now() + defer func() { + log.Debugf("block validation time: %s", build.Clock.Since(begin)) + }() + + stats.Record(ctx, metrics.BlockReceived.M(1)) + + blk, what, err := decodeAndCheckBlock(msg) + if err != nil { + log.Error("got invalid block over pubsub: ", err) + return pubsub.ValidationReject, what + } + + if !cns.IsEpochInConsensusRange(blk.Header.Height) { + // We ignore these blocks instead of rejecting to avoid breaking the network if + // we're recovering from an outage (e.g., where nobody agrees on where "head" is + // currently). + log.Warnf("received block outside of consensus range (%d)", blk.Header.Height) + return pubsub.ValidationIgnore, "invalid_block_height" + } + + // validate the block meta: the Message CID in the header must match the included messages + err = validateMsgMeta(ctx, blk) + if err != nil { + log.Warnf("error validating message metadata: %s", err) + return pubsub.ValidationReject, "invalid_block_meta" + } + + reject, err := cns.ValidateBlockHeader(ctx, blk.Header) + if err != nil { + if reject == "" { + log.Warn("ignoring block msg: ", err) + return pubsub.ValidationIgnore, reject + } + return pubsub.ValidationReject, reject + } + + // all good, accept the block + msg.ValidatorData = blk + stats.Record(ctx, metrics.BlockValidationSuccess.M(1)) + return pubsub.ValidationAccept, "" +} diff --git a/chain/consensus/signatures.go b/chain/consensus/signatures.go new file mode 100644 index 000000000..cb0e229a8 --- /dev/null +++ b/chain/consensus/signatures.go @@ -0,0 +1,62 @@ +package consensus + +import ( + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/go-state-types/network" + + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/ethtypes" + "github.com/filecoin-project/lotus/lib/sigs" +) + +// AuthenticateMessage authenticates the message by verifying that the supplied +// SignedMessage was signed by the indicated Address, computing the correct +// signature payload depending on the signature type. The supplied Address type +// must be recognized by the registered verifier for the signature type. +func AuthenticateMessage(msg *types.SignedMessage, signer address.Address) error { + var digest []byte + + typ := msg.Signature.Type + switch typ { + case crypto.SigTypeDelegated: + txArgs, err := ethtypes.EthTxArgsFromUnsignedEthMessage(&msg.Message) + if err != nil { + return xerrors.Errorf("failed to reconstruct eth transaction: %w", err) + } + roundTripMsg, err := txArgs.ToUnsignedMessage(msg.Message.From) + if err != nil { + return xerrors.Errorf("failed to reconstruct filecoin msg: %w", err) + } + + if !msg.Message.Equals(roundTripMsg) { + return xerrors.New("ethereum tx failed to roundtrip") + } + + rlpEncodedMsg, err := txArgs.ToRlpUnsignedMsg() + if err != nil { + return xerrors.Errorf("failed to repack eth rlp message: %w", err) + } + digest = rlpEncodedMsg + default: + digest = msg.Message.Cid().Bytes() + } + + if err := sigs.Verify(&msg.Signature, signer, digest); err != nil { + return xerrors.Errorf("message %s has invalid signature (type %d): %w", msg.Cid(), typ, err) + } + return nil +} + +// IsValidSecpkSigType checks that a signature type is valid for the network +// version, for a "secpk" message. +func IsValidSecpkSigType(nv network.Version, typ crypto.SigType) bool { + switch { + case nv < network.Version18: + return typ == crypto.SigTypeSecp256k1 + default: + return typ == crypto.SigTypeSecp256k1 || typ == crypto.SigTypeDelegated + } +} diff --git a/chain/consensus/utils.go b/chain/consensus/utils.go new file mode 100644 index 000000000..2fb43b608 --- /dev/null +++ b/chain/consensus/utils.go @@ -0,0 +1,83 @@ +package consensus + +import ( + "context" + "errors" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "go.opencensus.io/trace" + "golang.org/x/xerrors" + + ffi "github.com/filecoin-project/filecoin-ffi" + "github.com/filecoin-project/go-state-types/crypto" + blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" +) + +var ErrTemporal = errors.New("temporal error") + +func VerifyBlsAggregate(ctx context.Context, sig *crypto.Signature, msgs []cid.Cid, pubks [][]byte) error { + _, span := trace.StartSpan(ctx, "syncer.VerifyBlsAggregate") + defer span.End() + span.AddAttributes( + trace.Int64Attribute("msgCount", int64(len(msgs))), + ) + + msgsS := make([]ffi.Message, len(msgs)) + pubksS := make([]ffi.PublicKey, len(msgs)) + for i := 0; i < len(msgs); i++ { + msgsS[i] = msgs[i].Bytes() + copy(pubksS[i][:], pubks[i][:ffi.PublicKeyBytes]) + } + + sigS := new(ffi.Signature) + copy(sigS[:], sig.Data[:ffi.SignatureBytes]) + + if len(msgs) == 0 { + return nil + } + + valid := ffi.HashVerify(sigS, msgsS, pubksS) + if !valid { + return xerrors.New("bls aggregate signature failed to verify") + } + return nil +} + +func AggregateSignatures(sigs []crypto.Signature) (*crypto.Signature, error) { + sigsS := make([]ffi.Signature, len(sigs)) + for i := 0; i < len(sigs); i++ { + copy(sigsS[i][:], sigs[i].Data[:ffi.SignatureBytes]) + } + + aggSig := ffi.Aggregate(sigsS) + if aggSig == nil { + if len(sigs) > 0 { + return nil, xerrors.Errorf("bls.Aggregate returned nil with %d signatures", len(sigs)) + } + + zeroSig := ffi.CreateZeroSignature() + + // Note: for blst this condition should not happen - nil should not + // be returned + return &crypto.Signature{ + Type: crypto.SigTypeBLS, + Data: zeroSig[:], + }, nil + } + return &crypto.Signature{ + Type: crypto.SigTypeBLS, + Data: aggSig[:], + }, nil +} + +func ToMessagesArray(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() +} diff --git a/chain/ethhashlookup/eth_transaction_hash_lookup.go b/chain/ethhashlookup/eth_transaction_hash_lookup.go new file mode 100644 index 000000000..d93680912 --- /dev/null +++ b/chain/ethhashlookup/eth_transaction_hash_lookup.go @@ -0,0 +1,157 @@ +package ethhashlookup + +import ( + "database/sql" + "errors" + "strconv" + + "github.com/ipfs/go-cid" + _ "github.com/mattn/go-sqlite3" + "golang.org/x/xerrors" + + "github.com/filecoin-project/lotus/chain/types/ethtypes" +) + +var ErrNotFound = errors.New("not found") + +var pragmas = []string{ + "PRAGMA synchronous = normal", + "PRAGMA temp_store = memory", + "PRAGMA mmap_size = 30000000000", + "PRAGMA page_size = 32768", + "PRAGMA auto_vacuum = NONE", + "PRAGMA automatic_index = OFF", + "PRAGMA journal_mode = WAL", + "PRAGMA read_uncommitted = ON", +} + +var ddls = []string{ + `CREATE TABLE IF NOT EXISTS eth_tx_hashes ( + hash TEXT PRIMARY KEY NOT NULL, + cid TEXT NOT NULL UNIQUE, + insertion_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL + )`, + + `CREATE INDEX IF NOT EXISTS insertion_time_index ON eth_tx_hashes (insertion_time)`, + + // metadata containing version of schema + `CREATE TABLE IF NOT EXISTS _meta ( + version UINT64 NOT NULL UNIQUE + )`, + + // version 1. + `INSERT OR IGNORE INTO _meta (version) VALUES (1)`, +} + +const schemaVersion = 1 + +const ( + insertTxHash = `INSERT INTO eth_tx_hashes + (hash, cid) + VALUES(?, ?) + ON CONFLICT (hash) DO UPDATE SET insertion_time = CURRENT_TIMESTAMP` +) + +type EthTxHashLookup struct { + db *sql.DB +} + +func (ei *EthTxHashLookup) UpsertHash(txHash ethtypes.EthHash, c cid.Cid) error { + hashEntry, err := ei.db.Prepare(insertTxHash) + if err != nil { + return xerrors.Errorf("prepare insert event: %w", err) + } + + _, err = hashEntry.Exec(txHash.String(), c.String()) + return err +} + +func (ei *EthTxHashLookup) GetCidFromHash(txHash ethtypes.EthHash) (cid.Cid, error) { + row := ei.db.QueryRow("SELECT cid FROM eth_tx_hashes WHERE hash = :hash;", sql.Named("hash", txHash.String())) + + var c string + err := row.Scan(&c) + if err != nil { + if err == sql.ErrNoRows { + return cid.Undef, ErrNotFound + } + return cid.Undef, err + } + return cid.Decode(c) +} + +func (ei *EthTxHashLookup) GetHashFromCid(c cid.Cid) (ethtypes.EthHash, error) { + row := ei.db.QueryRow("SELECT hash FROM eth_tx_hashes WHERE cid = :cid;", sql.Named("cid", c.String())) + + var hashString string + err := row.Scan(&c) + if err != nil { + if err == sql.ErrNoRows { + return ethtypes.EmptyEthHash, ErrNotFound + } + return ethtypes.EmptyEthHash, err + } + return ethtypes.ParseEthHash(hashString) +} + +func (ei *EthTxHashLookup) DeleteEntriesOlderThan(days int) (int64, error) { + res, err := ei.db.Exec("DELETE FROM eth_tx_hashes WHERE insertion_time < datetime('now', ?);", "-"+strconv.Itoa(days)+" day") + if err != nil { + return 0, err + } + + return res.RowsAffected() +} + +func NewTransactionHashLookup(path string) (*EthTxHashLookup, error) { + db, err := sql.Open("sqlite3", path+"?mode=rwc") + if err != nil { + return nil, xerrors.Errorf("open sqlite3 database: %w", err) + } + + for _, pragma := range pragmas { + if _, err := db.Exec(pragma); err != nil { + _ = db.Close() + return nil, xerrors.Errorf("exec pragma %q: %w", pragma, err) + } + } + + q, err := db.Query("SELECT name FROM sqlite_master WHERE type='table' AND name='_meta';") + if err == sql.ErrNoRows || !q.Next() { + // empty database, create the schema + for _, ddl := range ddls { + if _, err := db.Exec(ddl); err != nil { + _ = db.Close() + return nil, xerrors.Errorf("exec ddl %q: %w", ddl, err) + } + } + } else if err != nil { + _ = db.Close() + return nil, xerrors.Errorf("looking for _meta table: %w", err) + } else { + // Ensure we don't open a database from a different schema version + + row := db.QueryRow("SELECT max(version) FROM _meta") + var version int + err := row.Scan(&version) + if err != nil { + _ = db.Close() + return nil, xerrors.Errorf("invalid database version: no version found") + } + if version != schemaVersion { + _ = db.Close() + return nil, xerrors.Errorf("invalid database version: got %d, expected %d", version, schemaVersion) + } + } + + return &EthTxHashLookup{ + db: db, + }, nil +} + +func (ei *EthTxHashLookup) Close() error { + if ei.db == nil { + return nil + } + return ei.db.Close() +} diff --git a/chain/events/cache.go b/chain/events/cache.go new file mode 100644 index 000000000..2eba1f085 --- /dev/null +++ b/chain/events/cache.go @@ -0,0 +1,35 @@ +package events + +import ( + "context" + + "github.com/ipfs/go-cid" + + "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/chain/types" +) + +type uncachedAPI interface { + ChainNotify(context.Context) (<-chan []*api.HeadChange, error) + ChainGetPath(ctx context.Context, from, to types.TipSetKey) ([]*api.HeadChange, error) + StateSearchMsg(ctx context.Context, from types.TipSetKey, msg cid.Cid, limit abi.ChainEpoch, allowReplaced bool) (*api.MsgLookup, error) + + StateGetActor(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*types.Actor, error) // optional / for CalledMsg +} + +type cache struct { + *tipSetCache + *messageCache + uncachedAPI +} + +func newCache(api EventAPI, gcConfidence abi.ChainEpoch) *cache { + return &cache{ + newTSCache(api, gcConfidence), + newMessageCache(api), + api, + } +} diff --git a/chain/events/events.go b/chain/events/events.go index 02321235b..c68b62a64 100644 --- a/chain/events/events.go +++ b/chain/events/events.go @@ -2,189 +2,66 @@ package events import ( "context" - "sync" - "time" - "github.com/filecoin-project/specs-actors/actors/abi" "github.com/ipfs/go-cid" logging "github.com/ipfs/go-log/v2" - "golang.org/x/xerrors" "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/store" "github.com/filecoin-project/lotus/chain/types" ) var log = logging.Logger("events") -// `curH`-`ts.Height` = `confidence` -type HeightHandler func(ctx context.Context, ts *types.TipSet, curH abi.ChainEpoch) error -type RevertHandler func(ctx context.Context, ts *types.TipSet) error +// HeightHandler `curH`-`ts.Height` = `confidence` +type ( + HeightHandler func(ctx context.Context, ts *types.TipSet, curH abi.ChainEpoch) error + RevertHandler func(ctx context.Context, ts *types.TipSet) error +) -type heightHandler struct { - confidence int - called bool - - handle HeightHandler - revert RevertHandler +// A TipSetObserver receives notifications of tipsets +type TipSetObserver interface { + Apply(ctx context.Context, from, to *types.TipSet) error + Revert(ctx context.Context, from, to *types.TipSet) error } -type eventApi interface { +type EventAPI interface { ChainNotify(context.Context) (<-chan []*api.HeadChange, error) ChainGetBlockMessages(context.Context, cid.Cid) (*api.BlockMessages, error) ChainGetTipSetByHeight(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error) - StateGetReceipt(context.Context, cid.Cid, types.TipSetKey) (*types.MessageReceipt, error) + ChainGetTipSetAfterHeight(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error) + ChainHead(context.Context) (*types.TipSet, error) + StateSearchMsg(ctx context.Context, from types.TipSetKey, msg cid.Cid, limit abi.ChainEpoch, allowReplaced bool) (*api.MsgLookup, error) ChainGetTipSet(context.Context, types.TipSetKey) (*types.TipSet, error) + ChainGetPath(ctx context.Context, from, to types.TipSetKey) ([]*api.HeadChange, error) StateGetActor(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*types.Actor, error) // optional / for CalledMsg } type Events struct { - api eventApi - - tsc *tipSetCache - lk sync.Mutex - - ready sync.WaitGroup - readyOnce sync.Once - - heightEvents - calledEvents + *observer + *heightEvents + *hcEvents } -func NewEvents(ctx context.Context, api eventApi) *Events { +func newEventsWithGCConfidence(ctx context.Context, api EventAPI, gcConfidence abi.ChainEpoch) (*Events, error) { + cache := newCache(api, gcConfidence) + + ob := newObserver(cache, gcConfidence) + if err := ob.start(ctx); err != nil { + return nil, err + } + + he := newHeightEvents(cache, ob, gcConfidence) + headChange := newHCEvents(cache, ob) + + return &Events{ob, he, headChange}, nil +} + +func NewEvents(ctx context.Context, api EventAPI) (*Events, error) { gcConfidence := 2 * build.ForkLengthThreshold - - tsc := newTSCache(gcConfidence, api.ChainGetTipSetByHeight) - - e := &Events{ - api: api, - - tsc: tsc, - - heightEvents: heightEvents{ - tsc: tsc, - ctx: ctx, - gcConfidence: abi.ChainEpoch(gcConfidence), - - heightTriggers: map[uint64]*heightHandler{}, - htTriggerHeights: map[abi.ChainEpoch][]uint64{}, - htHeights: map[abi.ChainEpoch][]uint64{}, - }, - - calledEvents: calledEvents{ - cs: api, - tsc: tsc, - ctx: ctx, - gcConfidence: uint64(gcConfidence), - - confQueue: map[triggerH]map[msgH][]*queuedEvent{}, - revertQueue: map[msgH][]triggerH{}, - triggers: map[triggerId]*callHandler{}, - matchers: map[triggerId][]MatchFunc{}, - timeouts: map[abi.ChainEpoch]map[triggerId]int{}, - }, - } - - e.ready.Add(1) - - go e.listenHeadChanges(ctx) - - e.ready.Wait() - - // TODO: cleanup/gc goroutine - - return e -} - -func (e *Events) listenHeadChanges(ctx context.Context) { - for { - if err := e.listenHeadChangesOnce(ctx); err != nil { - log.Errorf("listen head changes errored: %s", err) - } else { - log.Warn("listenHeadChanges quit") - } - if ctx.Err() != nil { - log.Warnf("not restarting listenHeadChanges: context error: %s", ctx.Err()) - return - } - time.Sleep(time.Second) - log.Info("restarting listenHeadChanges") - } -} - -func (e *Events) listenHeadChangesOnce(ctx context.Context) error { - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - notifs, err := e.api.ChainNotify(ctx) - if err != nil { - // TODO: retry - return xerrors.Errorf("listenHeadChanges ChainNotify call failed: %w", err) - } - - cur, ok := <-notifs // TODO: timeout? - if !ok { - return xerrors.Errorf("notification channel closed") - } - - if len(cur) != 1 { - return xerrors.Errorf("unexpected initial head notification length: %d", len(cur)) - } - - if cur[0].Type != store.HCCurrent { - return xerrors.Errorf("expected first head notification type to be 'current', was '%s'", cur[0].Type) - } - - if err := e.tsc.add(cur[0].Val); err != nil { - log.Warn("tsc.add: adding current tipset failed: %w", err) - } - - e.readyOnce.Do(func() { - e.at = cur[0].Val.Height() - - e.ready.Done() - }) - - for notif := range notifs { - var rev, app []*types.TipSet - for _, notif := range notif { - switch notif.Type { - case store.HCRevert: - rev = append(rev, notif.Val) - case store.HCApply: - app = append(app, notif.Val) - default: - log.Warnf("unexpected head change notification type: '%s'", notif.Type) - } - } - - if err := e.headChange(rev, app); err != nil { - log.Warnf("headChange failed: %s", err) - } - - // sync with fake chainstore (for tests) - if fcs, ok := e.api.(interface{ notifDone() }); ok { - fcs.notifDone() - } - } - - return nil -} - -func (e *Events) headChange(rev, app []*types.TipSet) error { - if len(app) == 0 { - return xerrors.New("events.headChange expected at least one applied tipset") - } - - e.lk.Lock() - defer e.lk.Unlock() - - if err := e.headChangeAt(rev, app); err != nil { - return err - } - - return e.headChangeCalled(rev, app) + return newEventsWithGCConfidence(ctx, api, gcConfidence) } diff --git a/chain/events/events_called.go b/chain/events/events_called.go index 0ddb9476a..3ac02b2f7 100644 --- a/chain/events/events_called.go +++ b/chain/events/events_called.go @@ -5,72 +5,81 @@ import ( "math" "sync" - "github.com/filecoin-project/specs-actors/actors/abi" "github.com/ipfs/go-cid" "golang.org/x/xerrors" + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" ) const NoTimeout = math.MaxInt64 +const NoHeight = abi.ChainEpoch(-1) -type triggerId = uint64 +type triggerID = uint64 // msgH is the block height at which a message was present / event has happened type msgH = abi.ChainEpoch // triggerH is the block height at which the listener will be notified about the -// message (msgH+confidence) +// +// message (msgH+confidence) type triggerH = abi.ChainEpoch -// `ts` is the tipset, in which the `msg` is included. +type eventData interface{} + +// EventHandler arguments: +// `prevTs` is the previous tipset, eg the "from" tipset for a state change. +// `ts` is the event tipset, eg the tipset in which the `msg` is included. // `curH`-`ts.Height` = `confidence` -type CalledHandler func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH abi.ChainEpoch) (more bool, err error) +type EventHandler func(ctx context.Context, data eventData, prevTs, ts *types.TipSet, curH abi.ChainEpoch) (more bool, err error) // CheckFunc is used for atomicity guarantees. If the condition the callbacks // wait for has already happened in tipset `ts` // // If `done` is true, timeout won't be triggered -// If `more` is false, no messages will be sent to CalledHandler (RevertHandler -// may still be called) -type CheckFunc func(ts *types.TipSet) (done bool, more bool, err error) +// If `more` is false, no messages will be sent to EventHandler (RevertHandler +// +// may still be called) +type CheckFunc func(ctx context.Context, ts *types.TipSet) (done bool, more bool, err error) -type MatchFunc func(msg *types.Message) (bool, error) - -type callHandler struct { +// Keep track of information for an event handler +type handlerInfo struct { confidence int timeout abi.ChainEpoch disabled bool // TODO: GC after gcConfidence reached - handle CalledHandler + handle EventHandler revert RevertHandler } +// When a change occurs, a queuedEvent is created and put into a queue +// until the required confidence is reached type queuedEvent struct { - trigger triggerId + trigger triggerID + data eventData - h abi.ChainEpoch - msg *types.Message + prevTipset, tipset *types.TipSet called bool } -type calledEvents struct { - cs eventApi - tsc *tipSetCache - ctx context.Context - gcConfidence uint64 +// Manages chain head change events, which may be forward (new tipset added to +// chain) or backward (chain branch discarded in favour of heavier branch) +type hcEvents struct { + cs EventAPI - at abi.ChainEpoch + lk sync.Mutex + lastTs *types.TipSet - lk sync.Mutex + ctr triggerID - ctr triggerId - - triggers map[triggerId]*callHandler - matchers map[triggerId][]MatchFunc + // TODO: get rid of trigger IDs and just use pointers as keys. + triggers map[triggerID]*handlerInfo + // TODO: instead of scheduling events in the future, look at the chain in the past. We can sip the "confidence" queue entirely. // maps block heights to events // [triggerH][msgH][event] confQueue map[triggerH]map[msgH][]*queuedEvent @@ -78,36 +87,84 @@ type calledEvents struct { // [msgH][triggerH] revertQueue map[msgH][]triggerH - // [timeoutH+confidence][triggerId]{calls} - timeouts map[abi.ChainEpoch]map[triggerId]int + // [timeoutH+confidence][triggerID]{calls} + timeouts map[abi.ChainEpoch]map[triggerID]int + + messageEvents + watcherEvents } -func (e *calledEvents) headChangeCalled(rev, app []*types.TipSet) error { - for _, ts := range rev { - e.handleReverts(ts) - e.at = ts.Height() +func newHCEvents(api EventAPI, obs *observer) *hcEvents { + e := &hcEvents{ + cs: api, + confQueue: map[triggerH]map[msgH][]*queuedEvent{}, + revertQueue: map[msgH][]triggerH{}, + triggers: map[triggerID]*handlerInfo{}, + timeouts: map[abi.ChainEpoch]map[triggerID]int{}, } - for _, ts := range app { - // called triggers - e.checkNewCalls(ts) - for ; e.at <= ts.Height(); e.at++ { - e.applyWithConfidence(ts, e.at) - e.applyTimeouts(ts) + e.messageEvents = newMessageEvents(e, api) + e.watcherEvents = newWatcherEvents(e, api) + + // We need to take the lock as the observer could immediately try calling us. + e.lk.Lock() + e.lastTs = obs.Observe((*hcEventsObserver)(e)) + e.lk.Unlock() + + return e +} + +type hcEventsObserver hcEvents + +func (e *hcEventsObserver) Apply(ctx context.Context, from, to *types.TipSet) error { + e.lk.Lock() + defer e.lk.Unlock() + + defer func() { e.lastTs = to }() + + // Check if the head change caused any state changes that we were + // waiting for + stateChanges := e.checkStateChanges(from, to) + + // Queue up calls until there have been enough blocks to reach + // confidence on the state changes + for tid, data := range stateChanges { + e.queueForConfidence(tid, data, from, to) + } + + // Check if the head change included any new message calls + newCalls := e.checkNewCalls(ctx, from, to) + + // Queue up calls until there have been enough blocks to reach + // confidence on the message calls + for tid, calls := range newCalls { + for _, data := range calls { + e.queueForConfidence(tid, data, nil, to) } } + for at := from.Height() + 1; at <= to.Height(); at++ { + // Apply any queued events and timeouts that were targeted at the + // current chain height + e.applyWithConfidence(ctx, at) + e.applyTimeouts(ctx, at, to) + } return nil } -func (e *calledEvents) handleReverts(ts *types.TipSet) { - reverts, ok := e.revertQueue[ts.Height()] +func (e *hcEventsObserver) Revert(ctx context.Context, from, to *types.TipSet) error { + e.lk.Lock() + defer e.lk.Unlock() + + defer func() { e.lastTs = to }() + + reverts, ok := e.revertQueue[from.Height()] if !ok { - return // nothing to do + return nil // nothing to do } for _, triggerH := range reverts { - toRevert := e.confQueue[triggerH][ts.Height()] + toRevert := e.confQueue[triggerH][from.Height()] for _, event := range toRevert { if !event.called { continue // event wasn't apply()-ied yet @@ -115,50 +172,20 @@ func (e *calledEvents) handleReverts(ts *types.TipSet) { trigger := e.triggers[event.trigger] - if err := trigger.revert(e.ctx, ts); err != nil { - log.Errorf("reverting chain trigger (call %s.%d() @H %d, called @ %d) failed: %s", event.msg.To, event.msg.Method, ts.Height(), triggerH, err) + if err := trigger.revert(ctx, from); err != nil { + log.Errorf("reverting chain trigger (@H %d, triggered @ %d) failed: %s", from.Height(), triggerH, err) } } - delete(e.confQueue[triggerH], ts.Height()) + delete(e.confQueue[triggerH], from.Height()) } - delete(e.revertQueue, ts.Height()) + delete(e.revertQueue, from.Height()) + return nil } -func (e *calledEvents) checkNewCalls(ts *types.TipSet) { - pts, err := e.cs.ChainGetTipSet(e.ctx, ts.Parents()) // we actually care about messages in the parent tipset here - if err != nil { - log.Errorf("getting parent tipset in checkNewCalls: %s", err) - return - } - - e.messagesForTs(pts, func(msg *types.Message) { - // TODO: provide receipts - - for tid, matchFns := range e.matchers { - var matched bool - for _, matchFn := range matchFns { - ok, err := matchFn(msg) - if err != nil { - log.Errorf("event matcher failed: %s", err) - continue - } - matched = ok - - if matched { - break - } - } - - if matched { - e.queueForConfidence(tid, msg, ts) - break - } - } - }) -} - -func (e *calledEvents) queueForConfidence(triggerId uint64, msg *types.Message, ts *types.TipSet) { - trigger := e.triggers[triggerId] +// Queue up events until the chain has reached a height that reflects the +// desired confidence +func (e *hcEventsObserver) queueForConfidence(trigID uint64, data eventData, prevTs, ts *types.TipSet) { + trigger := e.triggers[trigID] appliedH := ts.Height() @@ -171,26 +198,23 @@ func (e *calledEvents) queueForConfidence(triggerId uint64, msg *types.Message, } byOrigH[appliedH] = append(byOrigH[appliedH], &queuedEvent{ - trigger: triggerId, - h: appliedH, - msg: msg, + trigger: trigID, + data: data, + tipset: ts, + prevTipset: prevTs, }) e.revertQueue[appliedH] = append(e.revertQueue[appliedH], triggerH) } -func (e *calledEvents) applyWithConfidence(ts *types.TipSet, height abi.ChainEpoch) { +// Apply any events that were waiting for this chain height for confidence +func (e *hcEventsObserver) applyWithConfidence(ctx context.Context, height abi.ChainEpoch) { byOrigH, ok := e.confQueue[height] if !ok { - return // no triggers at thin height + return // no triggers at this height } for origH, events := range byOrigH { - triggerTs, err := e.tsc.get(origH) - if err != nil { - log.Errorf("events: applyWithConfidence didn't find tipset for event; wanted %d; current %d", origH, height) - } - for _, event := range events { if event.called { continue @@ -201,15 +225,9 @@ func (e *calledEvents) applyWithConfidence(ts *types.TipSet, height abi.ChainEpo continue } - rec, err := e.cs.StateGetReceipt(e.ctx, event.msg.Cid(), ts.Key()) + more, err := trigger.handle(ctx, event.data, event.prevTipset, event.tipset, height) if err != nil { - log.Error(err) - return - } - - more, err := trigger.handle(event.msg, rec, triggerTs, height) - if err != nil { - log.Errorf("chain trigger (call %s.%d() @H %d, called @ %d) failed: %s", event.msg.To, event.msg.Method, origH, height, err) + log.Errorf("chain trigger (@H %d, triggered @ %d) failed: %s", origH, height, err) continue // don't revert failed calls } @@ -225,29 +243,31 @@ func (e *calledEvents) applyWithConfidence(ts *types.TipSet, height abi.ChainEpo } } -func (e *calledEvents) applyTimeouts(ts *types.TipSet) { - triggers, ok := e.timeouts[ts.Height()] +// Apply any timeouts that expire at this height +func (e *hcEventsObserver) applyTimeouts(ctx context.Context, at abi.ChainEpoch, ts *types.TipSet) { + triggers, ok := e.timeouts[at] if !ok { return // nothing to do } - for triggerId, calls := range triggers { + for triggerID, calls := range triggers { if calls > 0 { continue // don't timeout if the method was called } - trigger := e.triggers[triggerId] + trigger := e.triggers[triggerID] if trigger.disabled { continue } - timeoutTs, err := e.tsc.get(ts.Height() - abi.ChainEpoch(trigger.confidence)) + // This should be cached. + timeoutTs, err := e.cs.ChainGetTipSetAfterHeight(ctx, at-abi.ChainEpoch(trigger.confidence), ts.Key()) if err != nil { - log.Errorf("events: applyTimeouts didn't find tipset for event; wanted %d; current %d", ts.Height()-abi.ChainEpoch(trigger.confidence), ts.Height()) + log.Errorf("events: applyTimeouts didn't find tipset for event; wanted %d; current %d", at-abi.ChainEpoch(trigger.confidence), at) } - more, err := trigger.handle(nil, nil, timeoutTs, ts.Height()) + more, err := trigger.handle(ctx, nil, nil, timeoutTs, at) if err != nil { - log.Errorf("chain trigger (call @H %d, called @ %d) failed: %s", timeoutTs.Height(), ts.Height(), err) + log.Errorf("chain trigger (call @H %d, called @ %d) failed: %s", timeoutTs.Height(), at, err) continue // don't revert failed calls } @@ -255,72 +275,20 @@ func (e *calledEvents) applyTimeouts(ts *types.TipSet) { } } -func (e *calledEvents) messagesForTs(ts *types.TipSet, consume func(*types.Message)) { - seen := map[cid.Cid]struct{}{} - - for _, tsb := range ts.Blocks() { - - msgs, err := e.cs.ChainGetBlockMessages(context.TODO(), tsb.Cid()) - if err != nil { - log.Errorf("messagesForTs MessagesForBlock failed (ts.H=%d, Bcid:%s, B.Mcid:%s): %s", ts.Height(), tsb.Cid(), tsb.Messages, err) - // this is quite bad, but probably better than missing all the other updates - continue - } - - for _, m := range msgs.BlsMessages { - _, ok := seen[m.Cid()] - if ok { - continue - } - seen[m.Cid()] = struct{}{} - - consume(m) - } - - for _, m := range msgs.SecpkMessages { - _, ok := seen[m.Message.Cid()] - if ok { - continue - } - seen[m.Message.Cid()] = struct{}{} - - consume(&m.Message) - } - } -} - -// Called registers a callbacks which are triggered when a specified method is -// called on an actor, or a timeout is reached. -// -// * `CheckFunc` callback is invoked immediately with a recent tipset, it -// returns two booleans - `done`, and `more`. -// -// * `done` should be true when some on-chain action we are waiting for has -// happened. When `done` is set to true, timeout trigger is disabled. -// -// * `more` should be false when we don't want to receive new notifications -// through CalledHandler. Note that notifications may still be delivered to -// RevertHandler -// -// * `CalledHandler` is called when the specified event was observed on-chain, -// and a confidence threshold was reached, or the specified `timeout` height -// was reached with no events observed. When this callback is invoked on a -// timeout, `msg` is set to nil. This callback returns a boolean specifying -// whether further notifications should be sent, like `more` return param -// from `CheckFunc` above. -// -// * `RevertHandler` is called after apply handler, when we drop the tipset -// containing the message. The tipset passed as the argument is the tipset -// that is being dropped. Note that the message dropped may be re-applied -// in a different tipset in small amount of time. -func (e *calledEvents) Called(check CheckFunc, hnd CalledHandler, rev RevertHandler, confidence int, timeout abi.ChainEpoch, mf MatchFunc) error { +// Listen for an event +// - CheckFunc: immediately checks if the event already occurred +// - EventHandler: called when the event has occurred, after confidence tipsets +// - RevertHandler: called if the chain head changes causing the event to revert +// - confidence: wait this many tipsets before calling EventHandler +// - timeout: at this chain height, timeout on waiting for this event +func (e *hcEvents) onHeadChanged(ctx context.Context, check CheckFunc, hnd EventHandler, rev RevertHandler, confidence int, timeout abi.ChainEpoch) (triggerID, error) { e.lk.Lock() defer e.lk.Unlock() - ts := e.tsc.best() - done, more, err := check(ts) + // Check if the event has already occurred + done, more, err := check(ctx, e.lastTs) if err != nil { - return xerrors.Errorf("called check error (h: %d): %w", ts.Height(), err) + return 0, xerrors.Errorf("called check error (h: %d): %w", e.lastTs.Height(), err) } if done { timeout = NoTimeout @@ -329,7 +297,7 @@ func (e *calledEvents) Called(check CheckFunc, hnd CalledHandler, rev RevertHand id := e.ctr e.ctr++ - e.triggers[id] = &callHandler{ + e.triggers[id] = &handlerInfo{ confidence: confidence, timeout: timeout + abi.ChainEpoch(confidence), @@ -339,8 +307,7 @@ func (e *calledEvents) Called(check CheckFunc, hnd CalledHandler, rev RevertHand revert: rev, } - e.matchers[id] = append(e.matchers[id], mf) - + // If there's a timeout, set up a timeout check at that height if timeout != NoTimeout { if e.timeouts[timeout+abi.ChainEpoch(confidence)] == nil { e.timeouts[timeout+abi.ChainEpoch(confidence)] = map[uint64]int{} @@ -348,9 +315,256 @@ func (e *calledEvents) Called(check CheckFunc, hnd CalledHandler, rev RevertHand e.timeouts[timeout+abi.ChainEpoch(confidence)][id] = 0 } + return id, nil +} + +// headChangeAPI is used to allow the composed event APIs to call back to hcEvents +// to listen for changes +type headChangeAPI interface { + onHeadChanged(ctx context.Context, check CheckFunc, hnd EventHandler, rev RevertHandler, confidence int, timeout abi.ChainEpoch) (triggerID, error) +} + +// watcherEvents watches for a state change +type watcherEvents struct { + cs EventAPI + hcAPI headChangeAPI + + lk sync.RWMutex + matchers map[triggerID]StateMatchFunc +} + +func newWatcherEvents(hcAPI headChangeAPI, cs EventAPI) watcherEvents { + return watcherEvents{ + cs: cs, + hcAPI: hcAPI, + matchers: make(map[triggerID]StateMatchFunc), + } +} + +// Run each of the matchers against the previous and current state to see if +// there's a change +func (we *watcherEvents) checkStateChanges(oldState, newState *types.TipSet) map[triggerID]eventData { + we.lk.RLock() + defer we.lk.RUnlock() + + res := make(map[triggerID]eventData) + for tid, matchFn := range we.matchers { + ok, data, err := matchFn(oldState, newState) + if err != nil { + log.Errorf("event diff fn failed: %s", err) + continue + } + + if ok { + res[tid] = data + } + } + return res +} + +// StateChange represents a change in state +type StateChange interface{} + +// StateChangeHandler arguments: +// `oldTs` is the state "from" tipset +// `newTs` is the state "to" tipset +// `states` is the change in state +// `curH`-`ts.Height` = `confidence` +type StateChangeHandler func(oldTs, newTs *types.TipSet, states StateChange, curH abi.ChainEpoch) (more bool, err error) + +type StateMatchFunc func(oldTs, newTs *types.TipSet) (bool, StateChange, error) + +// StateChanged registers a callback which is triggered when a specified state +// change occurs or a timeout is reached. +// +// - `CheckFunc` callback is invoked immediately with a recent tipset, it +// returns two booleans - `done`, and `more`. +// +// - `done` should be true when some on-chain state change we are waiting +// for has happened. When `done` is set to true, timeout trigger is disabled. +// +// - `more` should be false when we don't want to receive new notifications +// through StateChangeHandler. Note that notifications may still be delivered to +// RevertHandler +// +// - `StateChangeHandler` is called when the specified state change was observed +// on-chain, and a confidence threshold was reached, or the specified `timeout` +// height was reached with no state change observed. When this callback is +// invoked on a timeout, `oldTs` and `states are set to nil. +// This callback returns a boolean specifying whether further notifications +// should be sent, like `more` return param from `CheckFunc` above. +// +// - `RevertHandler` is called after apply handler, when we drop the tipset +// containing the message. The tipset passed as the argument is the tipset +// that is being dropped. Note that the event dropped may be re-applied +// in a different tipset in small amount of time. +// +// - `StateMatchFunc` is called against each tipset state. If there is a match, +// the state change is queued up until the confidence interval has elapsed (and +// `StateChangeHandler` is called) +func (we *watcherEvents) StateChanged(check CheckFunc, scHnd StateChangeHandler, rev RevertHandler, confidence int, timeout abi.ChainEpoch, mf StateMatchFunc) error { + hnd := func(ctx context.Context, data eventData, prevTs, ts *types.TipSet, height abi.ChainEpoch) (bool, error) { + states, ok := data.(StateChange) + if data != nil && !ok { + panic("expected StateChange") + } + + return scHnd(prevTs, ts, states, height) + } + + id, err := we.hcAPI.onHeadChanged(context.TODO(), check, hnd, rev, confidence, timeout) + if err != nil { + return err + } + + we.lk.Lock() + defer we.lk.Unlock() + we.matchers[id] = mf + return nil } -func (e *calledEvents) CalledMsg(ctx context.Context, hnd CalledHandler, rev RevertHandler, confidence int, timeout abi.ChainEpoch, msg types.ChainMsg) error { - return e.Called(e.CheckMsg(ctx, msg, hnd), hnd, rev, confidence, timeout, e.MatchMsg(msg.VMMessage())) +// messageEvents watches for message calls to actors +type messageEvents struct { + cs EventAPI + hcAPI headChangeAPI + + lk sync.RWMutex + matchers map[triggerID]MsgMatchFunc +} + +func newMessageEvents(hcAPI headChangeAPI, cs EventAPI) messageEvents { + return messageEvents{ + cs: cs, + hcAPI: hcAPI, + matchers: make(map[triggerID]MsgMatchFunc), + } +} + +// Check if there are any new actor calls +func (me *messageEvents) checkNewCalls(ctx context.Context, from, to *types.TipSet) map[triggerID][]eventData { + me.lk.RLock() + defer me.lk.RUnlock() + + // For each message in the tipset + res := make(map[triggerID][]eventData) + me.messagesForTs(from, func(msg *types.Message) { + // TODO: provide receipts + + // Run each trigger's matcher against the message + for tid, matchFn := range me.matchers { + matched, err := matchFn(msg) + if err != nil { + log.Errorf("event matcher failed: %s", err) + continue + } + + // If there was a match, include the message in the results for the + // trigger + if matched { + res[tid] = append(res[tid], msg) + } + } + }) + + return res +} + +// Get the messages in a tipset +func (me *messageEvents) messagesForTs(ts *types.TipSet, consume func(*types.Message)) { + seen := map[cid.Cid]struct{}{} + + for i, tsb := range ts.Cids() { + msgs, err := me.cs.ChainGetBlockMessages(context.TODO(), tsb) + if err != nil { + log.Errorf("messagesForTs MessagesForBlock failed (ts.H=%d, Bcid:%s, B.Mcid:%s): %s", + ts.Height(), tsb, ts.Blocks()[i].Messages, err) + continue + } + for i, c := range msgs.Cids { + // We iterate over the CIDs to avoid having to recompute them. + _, ok := seen[c] + if ok { + continue + } + seen[c] = struct{}{} + if i < len(msgs.BlsMessages) { + consume(msgs.BlsMessages[i]) + } else { + consume(&msgs.SecpkMessages[i-len(msgs.BlsMessages)].Message) + } + } + } +} + +// MsgHandler arguments: +// `ts` is the tipset, in which the `msg` is included. +// `curH`-`ts.Height` = `confidence` +type MsgHandler func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH abi.ChainEpoch) (more bool, err error) + +type MsgMatchFunc func(msg *types.Message) (matched bool, err error) + +// Called registers a callback which is triggered when a specified method is +// +// called on an actor, or a timeout is reached. +// +// - `CheckFunc` callback is invoked immediately with a recent tipset, it +// returns two booleans - `done`, and `more`. +// +// - `done` should be true when some on-chain action we are waiting for has +// happened. When `done` is set to true, timeout trigger is disabled. +// +// - `more` should be false when we don't want to receive new notifications +// through MsgHandler. Note that notifications may still be delivered to +// RevertHandler +// +// - `MsgHandler` is called when the specified event was observed on-chain, +// and a confidence threshold was reached, or the specified `timeout` height +// was reached with no events observed. When this callback is invoked on a +// timeout, `msg` is set to nil. This callback returns a boolean specifying +// whether further notifications should be sent, like `more` return param +// from `CheckFunc` above. +// +// - `RevertHandler` is called after apply handler, when we drop the tipset +// containing the message. The tipset passed as the argument is the tipset +// that is being dropped. Note that the message dropped may be re-applied +// in a different tipset in small amount of time. +// +// - `MsgMatchFunc` is called against each message. If there is a match, the +// message is queued up until the confidence interval has elapsed (and +// `MsgHandler` is called) +func (me *messageEvents) Called(ctx context.Context, check CheckFunc, msgHnd MsgHandler, rev RevertHandler, confidence int, timeout abi.ChainEpoch, mf MsgMatchFunc) error { + hnd := func(ctx context.Context, data eventData, prevTs, ts *types.TipSet, height abi.ChainEpoch) (bool, error) { + msg, ok := data.(*types.Message) + if data != nil && !ok { + panic("expected msg") + } + + ml, err := me.cs.StateSearchMsg(ctx, ts.Key(), msg.Cid(), stmgr.LookbackNoLimit, true) + if err != nil { + return false, err + } + + if ml == nil { + return msgHnd(msg, nil, ts, height) + } + + return msgHnd(msg, &ml.Receipt, ts, height) + } + + id, err := me.hcAPI.onHeadChanged(ctx, check, hnd, rev, confidence, timeout) + if err != nil { + return xerrors.Errorf("on head changed error: %w", err) + } + + me.lk.Lock() + defer me.lk.Unlock() + me.matchers[id] = mf + + return nil +} + +// Convenience function for checking and matching messages +func (me *messageEvents) CalledMsg(ctx context.Context, hnd MsgHandler, rev RevertHandler, confidence int, timeout abi.ChainEpoch, msg types.ChainMsg) error { + return me.Called(ctx, me.CheckMsg(msg, hnd), hnd, rev, confidence, timeout, me.MatchMsg(msg.VMMessage())) } diff --git a/chain/events/events_height.go b/chain/events/events_height.go index 1b89e7bd7..457933fc6 100644 --- a/chain/events/events_height.go +++ b/chain/events/events_height.go @@ -4,189 +4,243 @@ import ( "context" "sync" - "github.com/filecoin-project/specs-actors/actors/abi" "go.opencensus.io/trace" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/lotus/chain/types" ) -type heightEvents struct { - lk sync.Mutex - tsc *tipSetCache - gcConfidence abi.ChainEpoch +type heightHandler struct { + ts *types.TipSet + height abi.ChainEpoch + called bool - ctr triggerId - - heightTriggers map[triggerId]*heightHandler - - htTriggerHeights map[triggerH][]triggerId - htHeights map[msgH][]triggerId - - ctx context.Context + handle HeightHandler + revert RevertHandler } -func (e *heightEvents) headChangeAt(rev, app []*types.TipSet) error { - ctx, span := trace.StartSpan(e.ctx, "events.HeightHeadChange") - defer span.End() - span.AddAttributes(trace.Int64Attribute("endHeight", int64(app[0].Height()))) - span.AddAttributes(trace.Int64Attribute("reverts", int64(len(rev)))) - span.AddAttributes(trace.Int64Attribute("applies", int64(len(app)))) +type heightEvents struct { + api EventAPI + gcConfidence abi.ChainEpoch - for _, ts := range rev { - // TODO: log error if h below gcconfidence - // revert height-based triggers + lk sync.Mutex + head *types.TipSet + tsHeights, triggerHeights map[abi.ChainEpoch][]*heightHandler + lastGc abi.ChainEpoch //nolint:structcheck +} - revert := func(h abi.ChainEpoch, ts *types.TipSet) { - for _, tid := range e.htHeights[h] { - ctx, span := trace.StartSpan(ctx, "events.HeightRevert") - - err := e.heightTriggers[tid].revert(ctx, ts) - e.heightTriggers[tid].called = false - - span.End() - - if err != nil { - log.Errorf("reverting chain trigger (@H %d): %s", h, err) - } - } - } - revert(ts.Height(), ts) - - subh := ts.Height() - 1 - for { - cts, err := e.tsc.get(subh) - if err != nil { - return err - } - - if cts != nil { - break - } - - revert(subh, ts) - subh-- - } - - if err := e.tsc.revert(ts); err != nil { - return err - } +func newHeightEvents(api EventAPI, obs *observer, gcConfidence abi.ChainEpoch) *heightEvents { + he := &heightEvents{ + api: api, + gcConfidence: gcConfidence, + tsHeights: map[abi.ChainEpoch][]*heightHandler{}, + triggerHeights: map[abi.ChainEpoch][]*heightHandler{}, } + he.lk.Lock() + he.head = obs.Observe((*heightEventsObserver)(he)) + he.lk.Unlock() + return he +} - for i := range app { - ts := app[i] +// ChainAt invokes the specified `HeightHandler` when the chain reaches the +// specified height+confidence threshold. If the chain is rolled-back under the +// specified height, `RevertHandler` will be called. +// +// ts passed to handlers is the tipset at the specified epoch, or above if lower tipsets were null. +// +// The context governs cancellations of this call, it won't cancel the event handler. +func (e *heightEvents) ChainAt(ctx context.Context, hnd HeightHandler, rev RevertHandler, confidence int, h abi.ChainEpoch) error { + if abi.ChainEpoch(confidence) > e.gcConfidence { + // Need this to be able to GC effectively. + return xerrors.Errorf("confidence cannot be greater than gcConfidence: %d > %d", confidence, e.gcConfidence) + } + handler := &heightHandler{ + height: h, + handle: hnd, + revert: rev, + } + triggerAt := h + abi.ChainEpoch(confidence) - if err := e.tsc.add(ts); err != nil { - return err - } + // Here we try to jump onto a moving train. To avoid stopping the train, we release the lock + // while calling the API and/or the trigger functions. Unfortunately, it's entirely possible + // (although unlikely) to go back and forth across the trigger heights, so we need to keep + // going back and forth here till we're synced. + // + // TODO: Consider using a worker goroutine so we can just drop the handler in a channel? The + // downside is that we'd either need a tipset cache, or we'd need to potentially fetch + // tipsets in-line inside the event loop. + e.lk.Lock() + for { + head := e.head + if head.Height() >= h { + // Head is past the handler height. We at least need to stash the tipset to + // avoid doing this from the main event loop. + e.lk.Unlock() - // height triggers - - apply := func(h abi.ChainEpoch, ts *types.TipSet) error { - for _, tid := range e.htTriggerHeights[h] { - hnd := e.heightTriggers[tid] - if hnd.called { - return nil + var ts *types.TipSet + if head.Height() == h { + ts = head + } else { + var err error + ts, err = e.api.ChainGetTipSetAfterHeight(ctx, handler.height, head.Key()) + if err != nil { + return xerrors.Errorf("events.ChainAt: failed to get tipset: %s", err) } - hnd.called = true + } - triggerH := h - abi.ChainEpoch(hnd.confidence) + // If we've applied the handler on the wrong tipset, revert. + if handler.called && !ts.Equals(handler.ts) { + ctx, span := trace.StartSpan(ctx, "events.HeightRevert") + span.AddAttributes(trace.BoolAttribute("immediate", true)) + err := handler.revert(ctx, handler.ts) + span.End() + if err != nil { + return err + } + handler.called = false + } - incTs, err := e.tsc.getNonNull(triggerH) + // Save the tipset. + handler.ts = ts + + // If we've reached confidence and haven't called, call. + if !handler.called && head.Height() >= triggerAt { + ctx, span := trace.StartSpan(ctx, "events.HeightApply") + span.AddAttributes(trace.BoolAttribute("immediate", true)) + err := handler.handle(ctx, handler.ts, head.Height()) + span.End() if err != nil { return err } - ctx, span := trace.StartSpan(ctx, "events.HeightApply") - span.AddAttributes(trace.BoolAttribute("immediate", false)) + handler.called = true - err = hnd.handle(ctx, incTs, h) - span.End() - - if err != nil { - log.Errorf("chain trigger (@H %d, called @ %d) failed: %+v", triggerH, ts.Height(), err) + // If we've reached gcConfidence, return without saving anything. + if head.Height() >= h+e.gcConfidence { + return nil } } - return nil - } - if err := apply(ts.Height(), ts); err != nil { - return err - } - subh := ts.Height() - 1 - for { - cts, err := e.tsc.get(subh) + e.lk.Lock() + } else if handler.called { + // We're not passed the head (anymore) but have applied the handler. Revert, try again. + e.lk.Unlock() + ctx, span := trace.StartSpan(ctx, "events.HeightRevert") + span.AddAttributes(trace.BoolAttribute("immediate", true)) + err := handler.revert(ctx, handler.ts) + span.End() if err != nil { return err } + handler.called = false + e.lk.Lock() + } // otherwise, we changed heads but the change didn't matter. - if cts != nil { - break - } - - if err := apply(subh, ts); err != nil { - return err - } - - subh-- + // If we managed to get through this without the head changing, we're finally done. + if head.Equals(e.head) { + e.triggerHeights[triggerAt] = append(e.triggerHeights[triggerAt], handler) + e.tsHeights[h] = append(e.tsHeights[h], handler) + e.lk.Unlock() + return nil } - } - - return nil } -// ChainAt invokes the specified `HeightHandler` when the chain reaches the -// specified height+confidence threshold. If the chain is rolled-back under the -// specified height, `RevertHandler` will be called. -// -// ts passed to handlers is the tipset at the specified, or above, if lower tipsets were null -func (e *heightEvents) ChainAt(hnd HeightHandler, rev RevertHandler, confidence int, h abi.ChainEpoch) error { - - e.lk.Lock() // Tricky locking, check your locks if you modify this function! - - bestH := e.tsc.best().Height() - - if bestH >= h+abi.ChainEpoch(confidence) { - ts, err := e.tsc.getNonNull(h) - if err != nil { - log.Warnf("events.ChainAt: calling HandleFunc with nil tipset, not found in cache: %s", err) - } - - e.lk.Unlock() - ctx, span := trace.StartSpan(e.ctx, "events.HeightApply") - span.AddAttributes(trace.BoolAttribute("immediate", true)) - - err = hnd(ctx, ts, bestH) - span.End() - - if err != nil { - return err - } - - e.lk.Lock() - bestH = e.tsc.best().Height() - } - +// Updates the head and garbage collects if we're 2x over our garbage collection confidence period. +func (e *heightEventsObserver) updateHead(h *types.TipSet) { + e.lk.Lock() defer e.lk.Unlock() + e.head = h - if bestH >= h+abi.ChainEpoch(confidence)+e.gcConfidence { - return nil + if e.head.Height() < e.lastGc+e.gcConfidence*2 { + return } + e.lastGc = h.Height() - triggerAt := h + abi.ChainEpoch(confidence) - - id := e.ctr - e.ctr++ - - e.heightTriggers[id] = &heightHandler{ - confidence: confidence, - - handle: hnd, - revert: rev, + targetGcHeight := e.head.Height() - e.gcConfidence + for h := range e.tsHeights { + if h >= targetGcHeight { + continue + } + delete(e.tsHeights, h) } + for h := range e.triggerHeights { + if h >= targetGcHeight { + continue + } + delete(e.triggerHeights, h) + } +} - e.htHeights[h] = append(e.htHeights[h], id) - e.htTriggerHeights[triggerAt] = append(e.htTriggerHeights[triggerAt], id) +type heightEventsObserver heightEvents +func (e *heightEventsObserver) Revert(ctx context.Context, from, to *types.TipSet) error { + // Update the head first so we don't accidental skip reverting a concurrent call to ChainAt. + e.updateHead(to) + + // Call revert on all hights between the two tipsets, handling empty tipsets. + for h := from.Height(); h > to.Height(); h-- { + e.lk.Lock() + triggers := e.tsHeights[h] + e.lk.Unlock() + + // 1. Triggers are only invoked from the global event loop, we don't need to hold the lock while calling. + // 2. We only ever append to or replace the trigger slice, so it's safe to iterate over it without the lock. + for _, handler := range triggers { + handler.ts = nil // invalidate + if !handler.called { + // We haven't triggered this yet, or there has been a concurrent call to ChainAt. + continue + } + ctx, span := trace.StartSpan(ctx, "events.HeightRevert") + err := handler.revert(ctx, from) + span.End() + + if err != nil { + log.Errorf("reverting chain trigger (@H %d): %s", h, err) + } + handler.called = false + } + } + return nil +} + +func (e *heightEventsObserver) Apply(ctx context.Context, from, to *types.TipSet) error { + // Update the head first so we don't accidental skip applying a concurrent call to ChainAt. + e.updateHead(to) + + for h := from.Height() + 1; h <= to.Height(); h++ { + e.lk.Lock() + triggers := e.triggerHeights[h] + tipsets := e.tsHeights[h] + e.lk.Unlock() + + // Stash the tipset for future triggers. + for _, handler := range tipsets { + handler.ts = to + } + + // Trigger the ready triggers. + for _, handler := range triggers { + if handler.called { + // We may have reverted past the trigger point, but not past the call point. + // Or there has been a concurrent call to ChainAt. + continue + } + + ctx, span := trace.StartSpan(ctx, "events.HeightApply") + span.AddAttributes(trace.BoolAttribute("immediate", false)) + err := handler.handle(ctx, handler.ts, h) + span.End() + + if err != nil { + log.Errorf("chain trigger (@H %d, called @ %d) failed: %+v", h, to.Height(), err) + } + + handler.called = true + } + } return nil } diff --git a/chain/events/events_test.go b/chain/events/events_test.go index aaf3908d0..e2450909c 100644 --- a/chain/events/events_test.go +++ b/chain/events/events_test.go @@ -1,3 +1,4 @@ +// stm: #unit package events import ( @@ -9,10 +10,11 @@ import ( "github.com/ipfs/go-cid" "github.com/multiformats/go-multihash" "github.com/stretchr/testify/require" + "gotest.tools/assert" "github.com/filecoin-project/go-address" - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/crypto" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" @@ -39,26 +41,100 @@ type fakeCS struct { msgs map[cid.Cid]fakeMsg blkMsgs map[cid.Cid]cid.Cid - sync sync.Mutex - tipsets map[types.TipSetKey]*types.TipSet - sub func(rev, app []*types.TipSet) + mu sync.Mutex + waitSub chan struct{} + subCh chan<- []*api.HeadChange + callNumber map[string]int +} + +func newFakeCS(t *testing.T) *fakeCS { + fcs := &fakeCS{ + t: t, + h: 1, + msgs: make(map[cid.Cid]fakeMsg), + blkMsgs: make(map[cid.Cid]cid.Cid), + tipsets: make(map[types.TipSetKey]*types.TipSet), + tsc: newTSCache(nil, 2*build.ForkLengthThreshold), + callNumber: map[string]int{}, + waitSub: make(chan struct{}), + } + require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) + return fcs +} + +func (fcs *fakeCS) ChainHead(ctx context.Context) (*types.TipSet, error) { + fcs.mu.Lock() + defer fcs.mu.Unlock() + fcs.callNumber["ChainHead"] = fcs.callNumber["ChainHead"] + 1 + panic("implement me") +} + +func (fcs *fakeCS) ChainGetPath(ctx context.Context, from, to types.TipSetKey) ([]*api.HeadChange, error) { + fcs.mu.Lock() + fcs.callNumber["ChainGetPath"] = fcs.callNumber["ChainGetPath"] + 1 + fcs.mu.Unlock() + + fromTs, err := fcs.ChainGetTipSet(ctx, from) + if err != nil { + return nil, err + } + + toTs, err := fcs.ChainGetTipSet(ctx, to) + if err != nil { + return nil, err + } + + // copied from the chainstore + revert, apply, err := store.ReorgOps(ctx, func(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error) { + return fcs.ChainGetTipSet(ctx, tsk) + }, fromTs, toTs) + if err != nil { + return nil, err + } + + path := make([]*api.HeadChange, len(revert)+len(apply)) + for i, r := range revert { + path[i] = &api.HeadChange{Type: store.HCRevert, Val: r} + } + for j, i := 0, len(apply)-1; i >= 0; j, i = j+1, i-1 { + path[j+len(revert)] = &api.HeadChange{Type: store.HCApply, Val: apply[i]} + } + return path, nil } func (fcs *fakeCS) ChainGetTipSet(ctx context.Context, key types.TipSetKey) (*types.TipSet, error) { + fcs.mu.Lock() + defer fcs.mu.Unlock() + fcs.callNumber["ChainGetTipSet"] = fcs.callNumber["ChainGetTipSet"] + 1 return fcs.tipsets[key], nil } -func (fcs *fakeCS) StateGetReceipt(context.Context, cid.Cid, types.TipSetKey) (*types.MessageReceipt, error) { +func (fcs *fakeCS) StateSearchMsg(ctx context.Context, from types.TipSetKey, msg cid.Cid, limit abi.ChainEpoch, allowReplaced bool) (*api.MsgLookup, error) { + fcs.mu.Lock() + defer fcs.mu.Unlock() + fcs.callNumber["StateSearchMsg"] = fcs.callNumber["StateSearchMsg"] + 1 return nil, nil } func (fcs *fakeCS) StateGetActor(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*types.Actor, error) { + fcs.mu.Lock() + defer fcs.mu.Unlock() + fcs.callNumber["StateGetActor"] = fcs.callNumber["StateGetActor"] + 1 panic("Not Implemented") } func (fcs *fakeCS) ChainGetTipSetByHeight(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error) { + fcs.mu.Lock() + defer fcs.mu.Unlock() + fcs.callNumber["ChainGetTipSetByHeight"] = fcs.callNumber["ChainGetTipSetByHeight"] + 1 + panic("Not Implemented") +} +func (fcs *fakeCS) ChainGetTipSetAfterHeight(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error) { + fcs.mu.Lock() + defer fcs.mu.Unlock() + fcs.callNumber["ChainGetTipSetAfterHeight"] = fcs.callNumber["ChainGetTipSetAfterHeight"] + 1 panic("Not Implemented") } @@ -98,43 +174,47 @@ func (fcs *fakeCS) makeTs(t *testing.T, parents []cid.Cid, h abi.ChainEpoch, msg }, }) + require.NoError(t, err) + + fcs.mu.Lock() + defer fcs.mu.Unlock() + if fcs.tipsets == nil { fcs.tipsets = map[types.TipSetKey]*types.TipSet{} } fcs.tipsets[ts.Key()] = ts - require.NoError(t, err) - return ts } -func (fcs *fakeCS) ChainNotify(context.Context) (<-chan []*api.HeadChange, error) { +func (fcs *fakeCS) ChainNotify(ctx context.Context) (<-chan []*api.HeadChange, error) { + fcs.mu.Lock() + defer fcs.mu.Unlock() + fcs.callNumber["ChainNotify"] = fcs.callNumber["ChainNotify"] + 1 + out := make(chan []*api.HeadChange, 1) - out <- []*api.HeadChange{{Type: store.HCCurrent, Val: fcs.tsc.best()}} - - fcs.sub = func(rev, app []*types.TipSet) { - notif := make([]*api.HeadChange, len(rev)+len(app)) - - for i, r := range rev { - notif[i] = &api.HeadChange{ - Type: store.HCRevert, - Val: r, - } - } - for i, r := range app { - notif[i+len(rev)] = &api.HeadChange{ - Type: store.HCApply, - Val: r, - } - } - - out <- notif + if fcs.subCh != nil { + close(out) + fcs.t.Error("already subscribed to notifications") + return out, nil } + best, err := fcs.tsc.ChainHead(ctx) + if err != nil { + return nil, err + } + + out <- []*api.HeadChange{{Type: store.HCCurrent, Val: best}} + fcs.subCh = out + close(fcs.waitSub) + return out, nil } func (fcs *fakeCS) ChainGetBlockMessages(ctx context.Context, blk cid.Cid) (*api.BlockMessages, error) { + fcs.mu.Lock() + defer fcs.mu.Unlock() + fcs.callNumber["ChainGetBlockMessages"] = fcs.callNumber["ChainGetBlockMessages"] + 1 messages, ok := fcs.blkMsgs[blk] if !ok { return &api.BlockMessages{}, nil @@ -144,8 +224,16 @@ func (fcs *fakeCS) ChainGetBlockMessages(ctx context.Context, blk cid.Cid) (*api if !ok { return &api.BlockMessages{}, nil } - return &api.BlockMessages{BlsMessages: ms.bmsgs, SecpkMessages: ms.smsgs}, nil + cids := make([]cid.Cid, len(ms.bmsgs)+len(ms.smsgs)) + for i, m := range ms.bmsgs { + cids[i] = m.Cid() + } + for i, m := range ms.smsgs { + cids[i+len(ms.bmsgs)] = m.Cid() + } + + return &api.BlockMessages{BlsMessages: ms.bmsgs, SecpkMessages: ms.smsgs, Cids: cids}, nil } func (fcs *fakeCS) fakeMsgs(m fakeMsg) cid.Cid { @@ -162,11 +250,47 @@ func (fcs *fakeCS) fakeMsgs(m fakeMsg) cid.Cid { return c } -func (fcs *fakeCS) advance(rev, app int, msgs map[int]cid.Cid, nulls ...int) { // todo: allow msgs - if fcs.sub == nil { +func (fcs *fakeCS) dropSub() { + fcs.mu.Lock() + + if fcs.subCh == nil { + fcs.mu.Unlock() fcs.t.Fatal("sub not be nil") } + waitCh := make(chan struct{}) + fcs.waitSub = waitCh + close(fcs.subCh) + fcs.subCh = nil + fcs.mu.Unlock() + + <-waitCh +} + +func (fcs *fakeCS) sub(rev, app []*types.TipSet) { + <-fcs.waitSub + notif := make([]*api.HeadChange, len(rev)+len(app)) + + for i, r := range rev { + notif[i] = &api.HeadChange{ + Type: store.HCRevert, + Val: r, + } + } + for i, r := range app { + notif[i+len(rev)] = &api.HeadChange{ + Type: store.HCApply, + Val: r, + } + } + + fcs.subCh <- notif +} + +func (fcs *fakeCS) advance(rev, app, drop int, msgs map[int]cid.Cid, nulls ...int) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + nullm := map[int]struct{}{} for _, v := range nulls { nullm[v] = struct{}{} @@ -174,11 +298,22 @@ func (fcs *fakeCS) advance(rev, app int, msgs map[int]cid.Cid, nulls ...int) { / var revs []*types.TipSet for i := 0; i < rev; i++ { - ts := fcs.tsc.best() + fcs.t.Log("revert", fcs.h) + from, err := fcs.tsc.ChainHead(ctx) + require.NoError(fcs.t, err) - if _, ok := nullm[int(ts.Height())]; !ok { - revs = append(revs, ts) - require.NoError(fcs.t, fcs.tsc.revert(ts)) + if _, ok := nullm[int(from.Height())]; !ok { + require.NoError(fcs.t, fcs.tsc.revert(from)) + + if drop == 0 { + revs = append(revs, from) + } + } + if drop > 0 { + drop-- + if drop == 0 { + fcs.dropSub() + } } fcs.h-- } @@ -186,55 +321,55 @@ func (fcs *fakeCS) advance(rev, app int, msgs map[int]cid.Cid, nulls ...int) { / var apps []*types.TipSet for i := 0; i < app; i++ { fcs.h++ + fcs.t.Log("apply", fcs.h) mc, hasMsgs := msgs[i] if !hasMsgs { mc = dummyCid } - if _, ok := nullm[int(fcs.h)]; ok { - continue + if _, ok := nullm[int(fcs.h)]; !ok { + best, err := fcs.tsc.ChainHead(ctx) + require.NoError(fcs.t, err) + ts := fcs.makeTs(fcs.t, best.Key().Cids(), fcs.h, mc) + require.NoError(fcs.t, fcs.tsc.add(ts)) + + if hasMsgs { + fcs.blkMsgs[ts.Blocks()[0].Cid()] = mc + } + + if drop == 0 { + apps = append(apps, ts) + } } - ts := fcs.makeTs(fcs.t, fcs.tsc.best().Key().Cids(), fcs.h, mc) - require.NoError(fcs.t, fcs.tsc.add(ts)) - - if hasMsgs { - fcs.blkMsgs[ts.Blocks()[0].Cid()] = mc + if drop > 0 { + drop-- + if drop == 0 { + fcs.dropSub() + } } - - apps = append(apps, ts) } - fcs.sync.Lock() - fcs.sub(revs, apps) - fcs.sync.Lock() - fcs.sync.Unlock() - + // Wait for the last round to finish. + fcs.sub(nil, nil) + fcs.sub(nil, nil) } -func (fcs *fakeCS) notifDone() { - fcs.sync.Unlock() -} - -var _ eventApi = &fakeCS{} +var _ EventAPI = &fakeCS{} func TestAt(t *testing.T) { - fcs := &fakeCS{ - t: t, - h: 1, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) - - events := NewEvents(context.Background(), fcs) + //stm: @EVENTS_HEIGHT_CHAIN_AT_001, @EVENTS_HEIGHT_REVERT_001 + fcs := newFakeCS(t) + events, err := NewEvents(context.Background(), fcs) + require.NoError(t, err) var applied bool var reverted bool - err := events.ChainAt(func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { + err = events.ChainAt(context.Background(), func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { require.Equal(t, 5, int(ts.Height())) require.Equal(t, 8, int(curH)) applied = true @@ -245,103 +380,57 @@ func TestAt(t *testing.T) { }, 3, 5) require.NoError(t, err) - fcs.advance(0, 3, nil) + fcs.advance(0, 3, 0, nil) require.Equal(t, false, applied) require.Equal(t, false, reverted) - fcs.advance(0, 3, nil) + fcs.advance(0, 3, 0, nil) require.Equal(t, false, applied) require.Equal(t, false, reverted) - fcs.advance(0, 3, nil) + fcs.advance(0, 3, 0, nil) require.Equal(t, true, applied) require.Equal(t, false, reverted) applied = false - fcs.advance(0, 3, nil) + fcs.advance(0, 3, 0, nil) require.Equal(t, false, applied) require.Equal(t, false, reverted) - fcs.advance(10, 10, nil) + fcs.advance(10, 10, 0, nil) require.Equal(t, true, applied) require.Equal(t, true, reverted) applied = false reverted = false - fcs.advance(10, 1, nil) + fcs.advance(10, 1, 0, nil) require.Equal(t, false, applied) require.Equal(t, true, reverted) reverted = false - fcs.advance(0, 1, nil) + fcs.advance(0, 1, 0, nil) require.Equal(t, false, applied) require.Equal(t, false, reverted) - fcs.advance(0, 2, nil) + fcs.advance(0, 2, 0, nil) require.Equal(t, false, applied) require.Equal(t, false, reverted) - fcs.advance(0, 1, nil) // 8 + fcs.advance(0, 1, 0, nil) // 8 require.Equal(t, true, applied) require.Equal(t, false, reverted) } -func TestAtDoubleTrigger(t *testing.T) { - fcs := &fakeCS{ - t: t, - h: 1, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) - - events := NewEvents(context.Background(), fcs) - - var applied bool - var reverted bool - - err := events.ChainAt(func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { - require.Equal(t, 5, int(ts.Height())) - require.Equal(t, 8, int(curH)) - applied = true - return nil - }, func(_ context.Context, ts *types.TipSet) error { - reverted = true - return nil - }, 3, 5) - require.NoError(t, err) - - fcs.advance(0, 6, nil) - require.False(t, applied) - require.False(t, reverted) - - fcs.advance(0, 1, nil) - require.True(t, applied) - require.False(t, reverted) - applied = false - - fcs.advance(2, 2, nil) - require.False(t, applied) - require.False(t, reverted) - - fcs.advance(4, 4, nil) - require.True(t, applied) - require.True(t, reverted) -} - func TestAtNullTrigger(t *testing.T) { - fcs := &fakeCS{ - t: t, - h: 1, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) - - events := NewEvents(context.Background(), fcs) + //stm: @EVENTS_HEIGHT_CHAIN_AT_001 + fcs := newFakeCS(t) + events, err := NewEvents(context.Background(), fcs) + require.NoError(t, err) var applied bool var reverted bool - err := events.ChainAt(func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { + err = events.ChainAt(context.Background(), func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { require.Equal(t, abi.ChainEpoch(6), ts.Height()) require.Equal(t, 8, int(curH)) applied = true @@ -352,30 +441,30 @@ func TestAtNullTrigger(t *testing.T) { }, 3, 5) require.NoError(t, err) - fcs.advance(0, 6, nil, 5) + fcs.advance(0, 6, 0, nil, 5) require.Equal(t, false, applied) require.Equal(t, false, reverted) - fcs.advance(0, 3, nil) + fcs.advance(0, 3, 0, nil) require.Equal(t, true, applied) require.Equal(t, false, reverted) applied = false } func TestAtNullConf(t *testing.T) { - fcs := &fakeCS{ - t: t, - h: 1, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) + //stm: @EVENTS_HEIGHT_CHAIN_AT_001, @EVENTS_HEIGHT_REVERT_001 + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() - events := NewEvents(context.Background(), fcs) + fcs := newFakeCS(t) + + events, err := NewEvents(ctx, fcs) + require.NoError(t, err) var applied bool var reverted bool - err := events.ChainAt(func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { + err = events.ChainAt(ctx, func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { require.Equal(t, 5, int(ts.Height())) require.Equal(t, 8, int(curH)) applied = true @@ -386,37 +475,34 @@ func TestAtNullConf(t *testing.T) { }, 3, 5) require.NoError(t, err) - fcs.advance(0, 6, nil) + fcs.advance(0, 6, 0, nil) require.Equal(t, false, applied) require.Equal(t, false, reverted) - fcs.advance(0, 3, nil, 8) + fcs.advance(0, 3, 0, nil, 8) require.Equal(t, true, applied) require.Equal(t, false, reverted) applied = false - fcs.advance(7, 1, nil) + fcs.advance(7, 1, 0, nil) require.Equal(t, false, applied) require.Equal(t, true, reverted) reverted = false } func TestAtStart(t *testing.T) { - fcs := &fakeCS{ - t: t, - h: 1, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) + //stm: @EVENTS_HEIGHT_CHAIN_AT_001 + fcs := newFakeCS(t) - events := NewEvents(context.Background(), fcs) + events, err := NewEvents(context.Background(), fcs) + require.NoError(t, err) - fcs.advance(0, 5, nil) // 6 + fcs.advance(0, 5, 0, nil) // 6 var applied bool var reverted bool - err := events.ChainAt(func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { + err = events.ChainAt(context.Background(), func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { require.Equal(t, 5, int(ts.Height())) require.Equal(t, 8, int(curH)) applied = true @@ -430,27 +516,24 @@ func TestAtStart(t *testing.T) { require.Equal(t, false, applied) require.Equal(t, false, reverted) - fcs.advance(0, 5, nil) // 11 + fcs.advance(0, 5, 0, nil) // 11 require.Equal(t, true, applied) require.Equal(t, false, reverted) } func TestAtStartConfidence(t *testing.T) { - fcs := &fakeCS{ - t: t, - h: 1, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) + //stm: @EVENTS_HEIGHT_CHAIN_AT_001 + fcs := newFakeCS(t) - events := NewEvents(context.Background(), fcs) + events, err := NewEvents(context.Background(), fcs) + require.NoError(t, err) - fcs.advance(0, 10, nil) // 11 + fcs.advance(0, 10, 0, nil) // 11 var applied bool var reverted bool - err := events.ChainAt(func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { + err = events.ChainAt(context.Background(), func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { require.Equal(t, 5, int(ts.Height())) require.Equal(t, 11, int(curH)) applied = true @@ -466,20 +549,17 @@ func TestAtStartConfidence(t *testing.T) { } func TestAtChained(t *testing.T) { - fcs := &fakeCS{ - t: t, - h: 1, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) + //stm: @EVENTS_HEIGHT_CHAIN_AT_001 + fcs := newFakeCS(t) - events := NewEvents(context.Background(), fcs) + events, err := NewEvents(context.Background(), fcs) + require.NoError(t, err) var applied bool var reverted bool - err := events.ChainAt(func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { - return events.ChainAt(func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { + err = events.ChainAt(context.Background(), func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { + return events.ChainAt(context.Background(), func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { require.Equal(t, 10, int(ts.Height())) applied = true return nil @@ -493,29 +573,26 @@ func TestAtChained(t *testing.T) { }, 3, 5) require.NoError(t, err) - fcs.advance(0, 15, nil) + fcs.advance(0, 15, 0, nil) require.Equal(t, true, applied) require.Equal(t, false, reverted) } func TestAtChainedConfidence(t *testing.T) { - fcs := &fakeCS{ - t: t, - h: 1, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) + //stm: @EVENTS_HEIGHT_CHAIN_AT_001 + fcs := newFakeCS(t) - events := NewEvents(context.Background(), fcs) + events, err := NewEvents(context.Background(), fcs) + require.NoError(t, err) - fcs.advance(0, 15, nil) + fcs.advance(0, 15, 0, nil) var applied bool var reverted bool - err := events.ChainAt(func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { - return events.ChainAt(func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { + err = events.ChainAt(context.Background(), func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { + return events.ChainAt(context.Background(), func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { require.Equal(t, 10, int(ts.Height())) applied = true return nil @@ -534,21 +611,18 @@ func TestAtChainedConfidence(t *testing.T) { } func TestAtChainedConfidenceNull(t *testing.T) { - fcs := &fakeCS{ - t: t, - h: 1, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) + //stm: @EVENTS_HEIGHT_CHAIN_AT_001 + fcs := newFakeCS(t) - events := NewEvents(context.Background(), fcs) + events, err := NewEvents(context.Background(), fcs) + require.NoError(t, err) - fcs.advance(0, 15, nil, 5) + fcs.advance(0, 15, 0, nil, 5) var applied bool var reverted bool - err := events.ChainAt(func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { + err = events.ChainAt(context.Background(), func(_ context.Context, ts *types.TipSet, curH abi.ChainEpoch) error { applied = true require.Equal(t, 6, int(ts.Height())) return nil @@ -562,24 +636,18 @@ func TestAtChainedConfidenceNull(t *testing.T) { require.Equal(t, false, reverted) } -func matchAddrMethod(to address.Address, m abi.MethodNum) func(msg *types.Message) (bool, error) { - return func(msg *types.Message) (bool, error) { +func matchAddrMethod(to address.Address, m abi.MethodNum) func(msg *types.Message) (matched bool, err error) { + return func(msg *types.Message) (matched bool, err error) { return to == msg.To && m == msg.Method, nil } } func TestCalled(t *testing.T) { - fcs := &fakeCS{ - t: t, - h: 1, + //stm: @EVENTS_EVENTS_CALLED_001, @EVENTS_HEIGHT_REVERT_001 + fcs := newFakeCS(t) - msgs: map[cid.Cid]fakeMsg{}, - blkMsgs: map[cid.Cid]cid.Cid{}, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) - - events := NewEvents(context.Background(), fcs) + events, err := NewEvents(context.Background(), fcs) + require.NoError(t, err) t0123, err := address.NewFromString("t0123") require.NoError(t, err) @@ -590,7 +658,7 @@ func TestCalled(t *testing.T) { var appliedTs *types.TipSet var appliedH abi.ChainEpoch - err = events.Called(func(ts *types.TipSet) (d bool, m bool, e error) { + err = events.Called(context.Background(), func(ctx context.Context, ts *types.TipSet) (d bool, m bool, e error) { return false, true, nil }, func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH abi.ChainEpoch) (bool, error) { require.Equal(t, false, applied) @@ -607,13 +675,13 @@ func TestCalled(t *testing.T) { // create few blocks to make sure nothing get's randomly called - fcs.advance(0, 4, nil) // H=5 + fcs.advance(0, 4, 0, nil) // H=5 require.Equal(t, false, applied) require.Equal(t, false, reverted) // create blocks with message (but below confidence threshold) - fcs.advance(0, 3, map[int]cid.Cid{ // msg at H=6; H=8 (confidence=2) + fcs.advance(0, 3, 0, map[int]cid.Cid{ // msg at H=6; H=8 (confidence=2) 0: fcs.fakeMsgs(fakeMsg{ bmsgs: []*types.Message{ {To: t0123, From: t0123, Method: 5, Nonce: 1}, @@ -626,14 +694,14 @@ func TestCalled(t *testing.T) { // create additional block so we are above confidence threshold - fcs.advance(0, 2, nil) // H=10 (confidence=3, apply) + fcs.advance(0, 2, 0, nil) // H=10 (confidence=3, apply) require.Equal(t, true, applied) require.Equal(t, false, reverted) applied = false // dip below confidence - fcs.advance(2, 2, nil) // H=10 (confidence=3, apply) + fcs.advance(2, 2, 0, nil) // H=10 (confidence=3, apply) require.Equal(t, false, applied) require.Equal(t, false, reverted) @@ -647,13 +715,13 @@ func TestCalled(t *testing.T) { // revert some blocks, keep the message - fcs.advance(3, 1, nil) // H=8 (confidence=1) + fcs.advance(3, 1, 0, nil) // H=8 (confidence=1) require.Equal(t, false, applied) require.Equal(t, false, reverted) // revert the message - fcs.advance(2, 1, nil) // H=7, we reverted ts with the msg execution, but not the msg itself + fcs.advance(2, 1, 0, nil) // H=7, we reverted ts with the msg execution, but not the msg itself require.Equal(t, false, applied) require.Equal(t, true, reverted) @@ -667,7 +735,7 @@ func TestCalled(t *testing.T) { }, }) - fcs.advance(0, 3, map[int]cid.Cid{ // (n2msg confidence=1) + fcs.advance(0, 3, 0, map[int]cid.Cid{ // (n2msg confidence=1) 0: n2msg, }) @@ -676,7 +744,7 @@ func TestCalled(t *testing.T) { require.Equal(t, abi.ChainEpoch(10), appliedH) applied = false - fcs.advance(0, 2, nil) // (confidence=3) + fcs.advance(0, 2, 0, nil) // (confidence=3) require.Equal(t, true, applied) require.Equal(t, false, reverted) @@ -691,7 +759,7 @@ func TestCalled(t *testing.T) { // revert and apply at different height - fcs.advance(8, 6, map[int]cid.Cid{ // (confidence=3) + fcs.advance(8, 6, 0, map[int]cid.Cid{ // (confidence=3) 1: n2msg, }) @@ -712,7 +780,7 @@ func TestCalled(t *testing.T) { // call method again - fcs.advance(0, 5, map[int]cid.Cid{ + fcs.advance(0, 5, 0, map[int]cid.Cid{ 0: n2msg, }) @@ -721,7 +789,7 @@ func TestCalled(t *testing.T) { applied = false // send and revert below confidence, then cross confidence - fcs.advance(0, 2, map[int]cid.Cid{ + fcs.advance(0, 2, 0, map[int]cid.Cid{ 0: fcs.fakeMsgs(fakeMsg{ bmsgs: []*types.Message{ {To: t0123, From: t0123, Method: 5, Nonce: 3}, @@ -729,14 +797,14 @@ func TestCalled(t *testing.T) { }), }) - fcs.advance(2, 5, nil) // H=19, but message reverted + fcs.advance(2, 5, 0, nil) // H=19, but message reverted require.Equal(t, false, applied) require.Equal(t, false, reverted) // test timeout (it's set to 20 in the call to `events.Called` above) - fcs.advance(0, 6, nil) + fcs.advance(0, 6, 0, nil) require.Equal(t, false, applied) // not calling timeout as we received messages require.Equal(t, false, reverted) @@ -744,7 +812,7 @@ func TestCalled(t *testing.T) { // test unregistering with more more = false - fcs.advance(0, 5, map[int]cid.Cid{ + fcs.advance(0, 5, 0, map[int]cid.Cid{ 0: fcs.fakeMsgs(fakeMsg{ bmsgs: []*types.Message{ {To: t0123, From: t0123, Method: 5, Nonce: 4}, // this signals we don't want more @@ -756,7 +824,7 @@ func TestCalled(t *testing.T) { require.Equal(t, false, reverted) applied = false - fcs.advance(0, 5, map[int]cid.Cid{ + fcs.advance(0, 5, 0, map[int]cid.Cid{ 0: fcs.fakeMsgs(fakeMsg{ bmsgs: []*types.Message{ {To: t0123, From: t0123, Method: 5, Nonce: 5}, @@ -769,36 +837,30 @@ func TestCalled(t *testing.T) { // revert after disabled - fcs.advance(5, 1, nil) // try reverting msg sent after disabling + fcs.advance(5, 1, 0, nil) // try reverting msg sent after disabling require.Equal(t, false, applied) require.Equal(t, false, reverted) - fcs.advance(5, 1, nil) // try reverting msg sent before disabling + fcs.advance(5, 1, 0, nil) // try reverting msg sent before disabling require.Equal(t, false, applied) require.Equal(t, true, reverted) } func TestCalledTimeout(t *testing.T) { - fcs := &fakeCS{ - t: t, - h: 1, + //stm: @EVENTS_EVENTS_CALLED_001, @EVENTS_HEIGHT_REVERT_001 + fcs := newFakeCS(t) - msgs: map[cid.Cid]fakeMsg{}, - blkMsgs: map[cid.Cid]cid.Cid{}, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) - - events := NewEvents(context.Background(), fcs) + events, err := NewEvents(context.Background(), fcs) + require.NoError(t, err) t0123, err := address.NewFromString("t0123") require.NoError(t, err) called := false - err = events.Called(func(ts *types.TipSet) (d bool, m bool, e error) { + err = events.Called(context.Background(), func(ctx context.Context, ts *types.TipSet) (d bool, m bool, e error) { return false, true, nil }, func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH abi.ChainEpoch) (bool, error) { called = true @@ -812,28 +874,21 @@ func TestCalledTimeout(t *testing.T) { }, 3, 20, matchAddrMethod(t0123, 5)) require.NoError(t, err) - fcs.advance(0, 21, nil) + fcs.advance(0, 21, 0, nil) require.False(t, called) - fcs.advance(0, 5, nil) + fcs.advance(0, 5, 0, nil) require.True(t, called) called = false // with check func reporting done - fcs = &fakeCS{ - t: t, - h: 1, + fcs = newFakeCS(t) - msgs: map[cid.Cid]fakeMsg{}, - blkMsgs: map[cid.Cid]cid.Cid{}, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) + events, err = NewEvents(context.Background(), fcs) + require.NoError(t, err) - events = NewEvents(context.Background(), fcs) - - err = events.Called(func(ts *types.TipSet) (d bool, m bool, e error) { + err = events.Called(context.Background(), func(ctx context.Context, ts *types.TipSet) (d bool, m bool, e error) { return true, true, nil }, func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH abi.ChainEpoch) (bool, error) { called = true @@ -847,32 +902,26 @@ func TestCalledTimeout(t *testing.T) { }, 3, 20, matchAddrMethod(t0123, 5)) require.NoError(t, err) - fcs.advance(0, 21, nil) + fcs.advance(0, 21, 0, nil) require.False(t, called) - fcs.advance(0, 5, nil) + fcs.advance(0, 5, 0, nil) require.False(t, called) } func TestCalledOrder(t *testing.T) { - fcs := &fakeCS{ - t: t, - h: 1, + //stm: @EVENTS_EVENTS_CALLED_001, @EVENTS_HEIGHT_REVERT_001 + fcs := newFakeCS(t) - msgs: map[cid.Cid]fakeMsg{}, - blkMsgs: map[cid.Cid]cid.Cid{}, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) - - events := NewEvents(context.Background(), fcs) + events, err := NewEvents(context.Background(), fcs) + require.NoError(t, err) t0123, err := address.NewFromString("t0123") require.NoError(t, err) at := 0 - err = events.Called(func(ts *types.TipSet) (d bool, m bool, e error) { + err = events.Called(context.Background(), func(ctx context.Context, ts *types.TipSet) (d bool, m bool, e error) { return false, true, nil }, func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH abi.ChainEpoch) (bool, error) { switch at { @@ -901,7 +950,7 @@ func TestCalledOrder(t *testing.T) { }, 3, 20, matchAddrMethod(t0123, 5)) require.NoError(t, err) - fcs.advance(0, 10, map[int]cid.Cid{ + fcs.advance(0, 10, 0, map[int]cid.Cid{ 1: fcs.fakeMsgs(fakeMsg{ bmsgs: []*types.Message{ {To: t0123, From: t0123, Method: 5, Nonce: 1}, @@ -914,21 +963,15 @@ func TestCalledOrder(t *testing.T) { }), }) - fcs.advance(9, 1, nil) + fcs.advance(9, 1, 0, nil) } func TestCalledNull(t *testing.T) { - fcs := &fakeCS{ - t: t, - h: 1, + //stm: @EVENTS_EVENTS_CALLED_001, @EVENTS_HEIGHT_REVERT_001 + fcs := newFakeCS(t) - msgs: map[cid.Cid]fakeMsg{}, - blkMsgs: map[cid.Cid]cid.Cid{}, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) - - events := NewEvents(context.Background(), fcs) + events, err := NewEvents(context.Background(), fcs) + require.NoError(t, err) t0123, err := address.NewFromString("t0123") require.NoError(t, err) @@ -936,7 +979,7 @@ func TestCalledNull(t *testing.T) { more := true var applied, reverted bool - err = events.Called(func(ts *types.TipSet) (d bool, m bool, e error) { + err = events.Called(context.Background(), func(ctx context.Context, ts *types.TipSet) (d bool, m bool, e error) { return false, true, nil }, func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH abi.ChainEpoch) (bool, error) { require.Equal(t, false, applied) @@ -950,13 +993,13 @@ func TestCalledNull(t *testing.T) { // create few blocks to make sure nothing get's randomly called - fcs.advance(0, 4, nil) // H=5 + fcs.advance(0, 4, 0, nil) // H=5 require.Equal(t, false, applied) require.Equal(t, false, reverted) // create blocks with message (but below confidence threshold) - fcs.advance(0, 3, map[int]cid.Cid{ // msg at H=6; H=8 (confidence=2) + fcs.advance(0, 3, 0, map[int]cid.Cid{ // msg at H=6; H=8 (confidence=2) 0: fcs.fakeMsgs(fakeMsg{ bmsgs: []*types.Message{ {To: t0123, From: t0123, Method: 5, Nonce: 1}, @@ -970,30 +1013,24 @@ func TestCalledNull(t *testing.T) { // create additional blocks so we are above confidence threshold, but with null tipset at the height // of application - fcs.advance(0, 3, nil, 10) // H=11 (confidence=3, apply) + fcs.advance(0, 3, 0, nil, 10) // H=11 (confidence=3, apply) require.Equal(t, true, applied) require.Equal(t, false, reverted) applied = false - fcs.advance(5, 1, nil, 10) + fcs.advance(5, 1, 0, nil, 10) require.Equal(t, false, applied) require.Equal(t, true, reverted) } func TestRemoveTriggersOnMessage(t *testing.T) { - fcs := &fakeCS{ - t: t, - h: 1, + //stm: @EVENTS_EVENTS_CALLED_001, @EVENTS_HEIGHT_REVERT_001 + fcs := newFakeCS(t) - msgs: map[cid.Cid]fakeMsg{}, - blkMsgs: map[cid.Cid]cid.Cid{}, - tsc: newTSCache(2*build.ForkLengthThreshold, nil), - } - require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) - - events := NewEvents(context.Background(), fcs) + events, err := NewEvents(context.Background(), fcs) + require.NoError(t, err) t0123, err := address.NewFromString("t0123") require.NoError(t, err) @@ -1001,12 +1038,10 @@ func TestRemoveTriggersOnMessage(t *testing.T) { more := true var applied, reverted bool - err = events.Called(func(ts *types.TipSet) (d bool, m bool, e error) { + err = events.Called(context.Background(), func(ctx context.Context, ts *types.TipSet) (d bool, m bool, e error) { return false, true, nil }, func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH abi.ChainEpoch) (bool, error) { require.Equal(t, false, applied) - fmt.Println(msg == nil) - fmt.Println(curH) applied = true return more, nil }, func(_ context.Context, ts *types.TipSet) error { @@ -1017,13 +1052,13 @@ func TestRemoveTriggersOnMessage(t *testing.T) { // create few blocks to make sure nothing get's randomly called - fcs.advance(0, 4, nil) // H=5 + fcs.advance(0, 4, 0, nil) // H=5 require.Equal(t, false, applied) require.Equal(t, false, reverted) // create blocks with message (but below confidence threshold) - fcs.advance(0, 3, map[int]cid.Cid{ // msg occurs at H=5, applied at H=6; H=8 (confidence=2) + fcs.advance(0, 3, 0, map[int]cid.Cid{ // msg occurs at H=5, applied at H=6; H=8 (confidence=2) 0: fcs.fakeMsgs(fakeMsg{ bmsgs: []*types.Message{ {To: t0123, From: t0123, Method: 5, Nonce: 1}, @@ -1035,19 +1070,19 @@ func TestRemoveTriggersOnMessage(t *testing.T) { require.Equal(t, false, reverted) // revert applied TS & message TS - fcs.advance(3, 1, nil) // H=6 (tipset message applied in reverted, AND message reverted) + fcs.advance(3, 1, 0, nil) // H=6 (tipset message applied in reverted, AND message reverted) require.Equal(t, false, applied) require.Equal(t, false, reverted) // create additional blocks so we are above confidence threshold, but message not applied // as it was reverted - fcs.advance(0, 5, nil) // H=11 (confidence=3, apply) + fcs.advance(0, 5, 0, nil) // H=11 (confidence=3, apply) require.Equal(t, false, applied) require.Equal(t, false, reverted) // create blocks with message again (but below confidence threshold) - fcs.advance(0, 3, map[int]cid.Cid{ // msg occurs at H=12, applied at H=13; H=15 (confidence=2) + fcs.advance(0, 3, 0, map[int]cid.Cid{ // msg occurs at H=12, applied at H=13; H=15 (confidence=2) 0: fcs.fakeMsgs(fakeMsg{ bmsgs: []*types.Message{ {To: t0123, From: t0123, Method: 5, Nonce: 2}, @@ -1058,13 +1093,413 @@ func TestRemoveTriggersOnMessage(t *testing.T) { require.Equal(t, false, reverted) // revert applied height TS, but don't remove message trigger - fcs.advance(2, 1, nil) // H=13 (tipset message applied in reverted, by tipset with message not reverted) + fcs.advance(2, 1, 0, nil) // H=13 (tipset message applied in reverted, by tipset with message not reverted) require.Equal(t, false, applied) require.Equal(t, false, reverted) // create additional blocks so we are above confidence threshold - fcs.advance(0, 4, nil) // H=18 (confidence=3, apply) + fcs.advance(0, 4, 0, nil) // H=18 (confidence=3, apply) require.Equal(t, true, applied) require.Equal(t, false, reverted) } + +type testStateChange struct { + from string + to string +} + +func TestStateChanged(t *testing.T) { + //stm: @EVENTS_EVENTS_CALLED_001, @EVENTS_HEIGHT_REVERT_001 + fcs := newFakeCS(t) + + events, err := NewEvents(context.Background(), fcs) + require.NoError(t, err) + + more := true + var applied, reverted bool + var appliedData StateChange + var appliedOldTs *types.TipSet + var appliedNewTs *types.TipSet + var appliedH abi.ChainEpoch + var matchData StateChange + + confidence := 3 + timeout := abi.ChainEpoch(20) + + err = events.StateChanged(func(ctx context.Context, ts *types.TipSet) (d bool, m bool, e error) { + return false, true, nil + }, func(oldTs, newTs *types.TipSet, data StateChange, curH abi.ChainEpoch) (bool, error) { + if data != nil { + require.Equal(t, oldTs.Key(), newTs.Parents()) + } + require.Equal(t, false, applied) + applied = true + appliedData = data + appliedOldTs = oldTs + appliedNewTs = newTs + appliedH = curH + return more, nil + }, func(_ context.Context, ts *types.TipSet) error { + reverted = true + return nil + }, confidence, timeout, func(oldTs, newTs *types.TipSet) (bool, StateChange, error) { + require.Equal(t, oldTs.Key(), newTs.Parents()) + if matchData == nil { + return false, matchData, nil + } + + d := matchData + matchData = nil + return true, d, nil + }) + require.NoError(t, err) + + // create few blocks to make sure nothing get's randomly called + + fcs.advance(0, 4, 0, nil) // H=5 + require.Equal(t, false, applied) + require.Equal(t, false, reverted) + + // create state change (but below confidence threshold) + matchData = testStateChange{from: "a", to: "b"} + fcs.advance(0, 3, 0, nil) + + require.Equal(t, false, applied) + require.Equal(t, false, reverted) + + // create additional block so we are above confidence threshold + + fcs.advance(0, 2, 0, nil) // H=10 (confidence=3, apply) + + require.Equal(t, true, applied) + require.Equal(t, false, reverted) + applied = false + + // dip below confidence (should not apply again) + fcs.advance(2, 2, 0, nil) // H=10 (confidence=3, apply) + + require.Equal(t, false, applied) + require.Equal(t, false, reverted) + + // Change happens from 5 -> 6 + require.Equal(t, abi.ChainEpoch(5), appliedOldTs.Height()) + require.Equal(t, abi.ChainEpoch(6), appliedNewTs.Height()) + + // Actually applied (with confidence) at 9 + require.Equal(t, abi.ChainEpoch(9), appliedH) + + // Make sure the state change was correctly passed through + rcvd := appliedData.(testStateChange) + require.Equal(t, "a", rcvd.from) + require.Equal(t, "b", rcvd.to) +} + +func TestStateChangedRevert(t *testing.T) { + //stm: @EVENTS_EVENTS_CALLED_001, @EVENTS_HEIGHT_REVERT_001 + fcs := newFakeCS(t) + + events, err := NewEvents(context.Background(), fcs) + require.NoError(t, err) + + more := true + var applied, reverted bool + var matchData StateChange + + confidence := 1 + timeout := abi.ChainEpoch(20) + + err = events.StateChanged(func(ctx context.Context, ts *types.TipSet) (d bool, m bool, e error) { + return false, true, nil + }, func(oldTs, newTs *types.TipSet, data StateChange, curH abi.ChainEpoch) (bool, error) { + if data != nil { + require.Equal(t, oldTs.Key(), newTs.Parents()) + } + require.Equal(t, false, applied) + applied = true + return more, nil + }, func(_ context.Context, ts *types.TipSet) error { + reverted = true + return nil + }, confidence, timeout, func(oldTs, newTs *types.TipSet) (bool, StateChange, error) { + require.Equal(t, oldTs.Key(), newTs.Parents()) + + if matchData == nil { + return false, matchData, nil + } + + d := matchData + matchData = nil + return true, d, nil + }) + require.NoError(t, err) + + fcs.advance(0, 2, 0, nil) // H=3 + + // Make a state change from TS at height 3 to TS at height 4 + matchData = testStateChange{from: "a", to: "b"} + fcs.advance(0, 1, 0, nil) // H=4 + + // Haven't yet reached confidence + require.Equal(t, false, applied) + require.Equal(t, false, reverted) + + // Advance to reach confidence level + fcs.advance(0, 1, 0, nil) // H=5 + + // Should now have called the handler + require.Equal(t, true, applied) + require.Equal(t, false, reverted) + applied = false + + // Advance 3 more TS + fcs.advance(0, 3, 0, nil) // H=8 + + require.Equal(t, false, applied) + require.Equal(t, false, reverted) + + // Regress but not so far as to cause a revert + fcs.advance(3, 1, 0, nil) // H=6 + + require.Equal(t, false, applied) + require.Equal(t, false, reverted) + + // Regress back to state where change happened + fcs.advance(3, 1, 0, nil) // H=4 + + // Expect revert to have happened + require.Equal(t, false, applied) + require.Equal(t, true, reverted) +} + +func TestStateChangedTimeout(t *testing.T) { + //stm: @EVENTS_EVENTS_CALLED_001, @EVENTS_HEIGHT_REVERT_001 + timeoutHeight := abi.ChainEpoch(20) + confidence := 3 + + testCases := []struct { + name string + checkFn CheckFunc + nilBlocks []int + expectTimeout bool + }{{ + // Verify that the state changed timeout is called at the expected height + name: "state changed timeout", + checkFn: func(ctx context.Context, ts *types.TipSet) (d bool, m bool, e error) { + return false, true, nil + }, + expectTimeout: true, + }, { + // Verify that the state changed timeout is called even if the timeout + // falls on nil block + name: "state changed timeout falls on nil block", + checkFn: func(ctx context.Context, ts *types.TipSet) (d bool, m bool, e error) { + return false, true, nil + }, + nilBlocks: []int{20, 21, 22, 23}, + expectTimeout: true, + }, { + // Verify that the state changed timeout is not called if the check + // function reports that it's complete + name: "no timeout callback if check func reports done", + checkFn: func(ctx context.Context, ts *types.TipSet) (d bool, m bool, e error) { + return true, true, nil + }, + expectTimeout: false, + }} + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + fcs := newFakeCS(t) + + events, err := NewEvents(context.Background(), fcs) + require.NoError(t, err) + + // Track whether the callback was called + called := false + + // Set up state change tracking that will timeout at the given height + err = events.StateChanged( + tc.checkFn, + func(oldTs, newTs *types.TipSet, data StateChange, curH abi.ChainEpoch) (bool, error) { + // Expect the callback to be called at the timeout height with nil data + called = true + require.Nil(t, data) + require.Equal(t, timeoutHeight, newTs.Height()) + require.Equal(t, timeoutHeight+abi.ChainEpoch(confidence), curH) + return false, nil + }, func(_ context.Context, ts *types.TipSet) error { + t.Fatal("revert on timeout") + return nil + }, confidence, timeoutHeight, func(oldTs, newTs *types.TipSet) (bool, StateChange, error) { + return false, nil, nil + }) + + require.NoError(t, err) + + // Advance to timeout height + fcs.advance(0, int(timeoutHeight)+1, 0, nil) + require.False(t, called) + + // Advance past timeout height + fcs.advance(0, 5, 0, nil, tc.nilBlocks...) + require.Equal(t, tc.expectTimeout, called) + called = false + }) + } +} + +func TestCalledMultiplePerEpoch(t *testing.T) { + //stm: @EVENTS_EVENTS_CALLED_001, @EVENTS_HEIGHT_REVERT_001 + fcs := newFakeCS(t) + + events, err := NewEvents(context.Background(), fcs) + require.NoError(t, err) + + t0123, err := address.NewFromString("t0123") + require.NoError(t, err) + + at := 0 + + err = events.Called(context.Background(), func(ctx context.Context, ts *types.TipSet) (d bool, m bool, e error) { + return false, true, nil + }, func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH abi.ChainEpoch) (bool, error) { + switch at { + case 0: + require.Equal(t, uint64(1), msg.Nonce) + require.Equal(t, abi.ChainEpoch(4), ts.Height()) + case 1: + require.Equal(t, uint64(2), msg.Nonce) + require.Equal(t, abi.ChainEpoch(4), ts.Height()) + default: + t.Fatal("apply should only get called twice, at: ", at) + } + at++ + return true, nil + }, func(_ context.Context, ts *types.TipSet) error { + switch at { + case 2: + require.Equal(t, abi.ChainEpoch(4), ts.Height()) + case 3: + require.Equal(t, abi.ChainEpoch(4), ts.Height()) + default: + t.Fatal("revert should only get called twice, at: ", at) + } + at++ + return nil + }, 3, 20, matchAddrMethod(t0123, 5)) + require.NoError(t, err) + + fcs.advance(0, 10, 0, map[int]cid.Cid{ + 1: fcs.fakeMsgs(fakeMsg{ + bmsgs: []*types.Message{ + {To: t0123, From: t0123, Method: 5, Nonce: 1}, + {To: t0123, From: t0123, Method: 5, Nonce: 2}, + }, + }), + }) + + fcs.advance(9, 1, 0, nil) +} + +func TestCachedSameBlock(t *testing.T) { + //stm: @EVENTS_EVENTS_CALLED_001, @EVENTS_HEIGHT_REVERT_001 + fcs := newFakeCS(t) + + _, err := NewEvents(context.Background(), fcs) + require.NoError(t, err) + + fcs.advance(0, 10, 0, map[int]cid.Cid{}) + assert.Assert(t, fcs.callNumber["ChainGetBlockMessages"] == 20, "expect call ChainGetBlockMessages %d but got ", 20, fcs.callNumber["ChainGetBlockMessages"]) + + fcs.advance(5, 10, 0, map[int]cid.Cid{}) + assert.Assert(t, fcs.callNumber["ChainGetBlockMessages"] == 30, "expect call ChainGetBlockMessages %d but got ", 30, fcs.callNumber["ChainGetBlockMessages"]) +} + +type testObserver struct { + t *testing.T + head *types.TipSet +} + +func (t *testObserver) Apply(_ context.Context, from, to *types.TipSet) error { + if t.head != nil { + require.True(t.t, t.head.Equals(from)) + } + t.head = to + return nil +} + +func (t *testObserver) Revert(_ context.Context, from, to *types.TipSet) error { + if t.head != nil { + require.True(t.t, t.head.Equals(from)) + } + t.head = to + return nil +} + +func TestReconnect(t *testing.T) { + //stm: @EVENTS_EVENTS_CALLED_001, @EVENTS_HEIGHT_REVERT_001 + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + fcs := newFakeCS(t) + + events, err := NewEvents(ctx, fcs) + require.NoError(t, err) + + fcs.advance(0, 1, 0, nil) + + events.Observe(&testObserver{t: t}) + + fcs.advance(0, 3, 0, nil) + + // Drop on apply + fcs.advance(0, 6, 2, nil) + require.True(t, fcs.callNumber["ChainGetPath"] == 1) + + // drop across revert/apply boundary + fcs.advance(4, 2, 3, nil) + require.True(t, fcs.callNumber["ChainGetPath"] == 2) + fcs.advance(0, 6, 0, nil) + + // drop on revert + fcs.advance(3, 0, 2, nil) + require.True(t, fcs.callNumber["ChainGetPath"] == 3) + + // drop with nulls + fcs.advance(0, 5, 2, nil, 0, 1, 3) + require.True(t, fcs.callNumber["ChainGetPath"] == 4) +} + +func TestUnregister(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + fcs := newFakeCS(t) + + events, err := NewEvents(ctx, fcs) + require.NoError(t, err) + + tsObs := &testObserver{t: t} + events.Observe(tsObs) + + // observer receives heads as the chain advances + fcs.advance(0, 1, 0, nil) + headBeforeDeregister := events.lastTs + require.Equal(t, tsObs.head, headBeforeDeregister) + + // observer unregistered successfully + found := events.Unregister(tsObs) + require.True(t, found) + + // observer stops receiving heads as the chain advances + fcs.advance(0, 1, 0, nil) + require.Equal(t, tsObs.head, headBeforeDeregister) + require.NotEqual(t, tsObs.head, events.lastTs) + + // unregistering an invalid observer returns false + dneObs := &testObserver{t: t} + found = events.Unregister(dneObs) + require.False(t, found) +} diff --git a/chain/events/filter/event.go b/chain/events/filter/event.go new file mode 100644 index 000000000..b821a2f83 --- /dev/null +++ b/chain/events/filter/event.go @@ -0,0 +1,479 @@ +package filter + +import ( + "bytes" + "context" + "math" + "sync" + "time" + + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + amt4 "github.com/filecoin-project/go-amt-ipld/v4" + "github.com/filecoin-project/go-state-types/abi" + blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" + + cstore "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/chain/types" +) + +func isIndexedValue(b uint8) bool { + // currently we mark the full entry as indexed if either the key + // or the value are indexed; in the future we will need finer-grained + // management of indices + return b&(types.EventFlagIndexedKey|types.EventFlagIndexedValue) > 0 +} + +type EventFilter struct { + id types.FilterID + minHeight abi.ChainEpoch // minimum epoch to apply filter or -1 if no minimum + maxHeight abi.ChainEpoch // maximum epoch to apply filter or -1 if no maximum + tipsetCid cid.Cid + addresses []address.Address // list of f4 actor addresses that are extpected to emit the event + keys map[string][][]byte // map of key names to a list of alternate values that may match + maxResults int // maximum number of results to collect, 0 is unlimited + + mu sync.Mutex + collected []*CollectedEvent + lastTaken time.Time + ch chan<- interface{} +} + +var _ Filter = (*EventFilter)(nil) + +type CollectedEvent struct { + Entries []types.EventEntry + EmitterAddr address.Address // f4 address of emitter + EventIdx int // index of the event within the list of emitted events + Reverted bool + Height abi.ChainEpoch + TipSetKey types.TipSetKey // tipset that contained the message + MsgIdx int // index of the message in the tipset + MsgCid cid.Cid // cid of message that produced event +} + +func (f *EventFilter) ID() types.FilterID { + return f.id +} + +func (f *EventFilter) SetSubChannel(ch chan<- interface{}) { + f.mu.Lock() + defer f.mu.Unlock() + f.ch = ch + f.collected = nil +} + +func (f *EventFilter) ClearSubChannel() { + f.mu.Lock() + defer f.mu.Unlock() + f.ch = nil +} + +func (f *EventFilter) CollectEvents(ctx context.Context, te *TipSetEvents, revert bool, resolver func(ctx context.Context, emitter abi.ActorID, ts *types.TipSet) (address.Address, bool)) error { + if !f.matchTipset(te) { + return nil + } + + // cache of lookups between actor id and f4 address + addressLookups := make(map[abi.ActorID]address.Address) + + ems, err := te.messages(ctx) + if err != nil { + return xerrors.Errorf("load executed messages: %w", err) + } + for msgIdx, em := range ems { + for evIdx, ev := range em.Events() { + // lookup address corresponding to the actor id + addr, found := addressLookups[ev.Emitter] + if !found { + var ok bool + addr, ok = resolver(ctx, ev.Emitter, te.rctTs) + if !ok { + // not an address we will be able to match against + continue + } + addressLookups[ev.Emitter] = addr + } + + if !f.matchAddress(addr) { + continue + } + if !f.matchKeys(ev.Entries) { + continue + } + + // event matches filter, so record it + cev := &CollectedEvent{ + Entries: ev.Entries, + EmitterAddr: addr, + EventIdx: evIdx, + Reverted: revert, + Height: te.msgTs.Height(), + TipSetKey: te.msgTs.Key(), + MsgCid: em.Message().Cid(), + MsgIdx: msgIdx, + } + + f.mu.Lock() + // if we have a subscription channel then push event to it + if f.ch != nil { + f.ch <- cev + f.mu.Unlock() + continue + } + + if f.maxResults > 0 && len(f.collected) == f.maxResults { + copy(f.collected, f.collected[1:]) + f.collected = f.collected[:len(f.collected)-1] + } + f.collected = append(f.collected, cev) + f.mu.Unlock() + } + } + + return nil +} + +func (f *EventFilter) setCollectedEvents(ces []*CollectedEvent) { + f.mu.Lock() + f.collected = ces + f.mu.Unlock() +} + +func (f *EventFilter) TakeCollectedEvents(ctx context.Context) []*CollectedEvent { + f.mu.Lock() + collected := f.collected + f.collected = nil + f.lastTaken = time.Now().UTC() + f.mu.Unlock() + + return collected +} + +func (f *EventFilter) LastTaken() time.Time { + f.mu.Lock() + defer f.mu.Unlock() + return f.lastTaken +} + +// matchTipset reports whether this filter matches the given tipset +func (f *EventFilter) matchTipset(te *TipSetEvents) bool { + if f.tipsetCid != cid.Undef { + tsCid, err := te.Cid() + if err != nil { + return false + } + return f.tipsetCid.Equals(tsCid) + } + + if f.minHeight >= 0 && f.minHeight > te.Height() { + return false + } + if f.maxHeight >= 0 && f.maxHeight < te.Height() { + return false + } + return true +} + +func (f *EventFilter) matchAddress(o address.Address) bool { + if len(f.addresses) == 0 { + return true + } + + // Assume short lists of addresses + // TODO: binary search for longer lists or restrict list length + for _, a := range f.addresses { + if a == o { + return true + } + } + return false +} + +func (f *EventFilter) matchKeys(ees []types.EventEntry) bool { + if len(f.keys) == 0 { + return true + } + // TODO: optimize this naive algorithm + // tracked in https://github.com/filecoin-project/lotus/issues/9987 + + // Note keys names may be repeated so we may have multiple opportunities to match + + matched := map[string]bool{} + for _, ee := range ees { + // Skip an entry that is not indexable + if !isIndexedValue(ee.Flags) { + continue + } + + keyname := ee.Key + + // skip if we have already matched this key + if matched[keyname] { + continue + } + + wantlist, ok := f.keys[keyname] + if !ok || len(wantlist) == 0 { + continue + } + + for _, w := range wantlist { + if bytes.Equal(w, ee.Value) { + matched[keyname] = true + break + } + } + + if len(matched) == len(f.keys) { + // all keys have been matched + return true + } + + } + + return false +} + +type TipSetEvents struct { + rctTs *types.TipSet // rctTs is the tipset containing the receipts of executed messages + msgTs *types.TipSet // msgTs is the tipset containing the messages that have been executed + + load func(ctx context.Context, msgTs, rctTs *types.TipSet) ([]executedMessage, error) + + once sync.Once // for lazy population of ems + ems []executedMessage + err error +} + +func (te *TipSetEvents) Height() abi.ChainEpoch { + return te.msgTs.Height() +} + +func (te *TipSetEvents) Cid() (cid.Cid, error) { + return te.msgTs.Key().Cid() +} + +func (te *TipSetEvents) messages(ctx context.Context) ([]executedMessage, error) { + te.once.Do(func() { + // populate executed message list + ems, err := te.load(ctx, te.msgTs, te.rctTs) + if err != nil { + te.err = err + return + } + te.ems = ems + }) + return te.ems, te.err +} + +type executedMessage struct { + msg types.ChainMsg + rct *types.MessageReceipt + // events extracted from receipt + evs []*types.Event +} + +func (e *executedMessage) Message() types.ChainMsg { + return e.msg +} + +func (e *executedMessage) Receipt() *types.MessageReceipt { + return e.rct +} + +func (e *executedMessage) Events() []*types.Event { + return e.evs +} + +type EventFilterManager struct { + ChainStore *cstore.ChainStore + AddressResolver func(ctx context.Context, emitter abi.ActorID, ts *types.TipSet) (address.Address, bool) + MaxFilterResults int + EventIndex *EventIndex + + mu sync.Mutex // guards mutations to filters + filters map[types.FilterID]*EventFilter + currentHeight abi.ChainEpoch +} + +func (m *EventFilterManager) Apply(ctx context.Context, from, to *types.TipSet) error { + m.mu.Lock() + defer m.mu.Unlock() + m.currentHeight = to.Height() + + if len(m.filters) == 0 && m.EventIndex == nil { + return nil + } + + tse := &TipSetEvents{ + msgTs: from, + rctTs: to, + load: m.loadExecutedMessages, + } + + if m.EventIndex != nil { + if err := m.EventIndex.CollectEvents(ctx, tse, false, m.AddressResolver); err != nil { + return err + } + } + + // TODO: could run this loop in parallel with errgroup if there are many filters + for _, f := range m.filters { + if err := f.CollectEvents(ctx, tse, false, m.AddressResolver); err != nil { + return err + } + } + + return nil +} + +func (m *EventFilterManager) Revert(ctx context.Context, from, to *types.TipSet) error { + m.mu.Lock() + defer m.mu.Unlock() + m.currentHeight = to.Height() + + if len(m.filters) == 0 && m.EventIndex == nil { + return nil + } + + tse := &TipSetEvents{ + msgTs: to, + rctTs: from, + load: m.loadExecutedMessages, + } + + if m.EventIndex != nil { + if err := m.EventIndex.CollectEvents(ctx, tse, true, m.AddressResolver); err != nil { + return err + } + } + + // TODO: could run this loop in parallel with errgroup if there are many filters + for _, f := range m.filters { + if err := f.CollectEvents(ctx, tse, true, m.AddressResolver); err != nil { + return err + } + } + + return nil +} + +func (m *EventFilterManager) Install(ctx context.Context, minHeight, maxHeight abi.ChainEpoch, tipsetCid cid.Cid, addresses []address.Address, keys map[string][][]byte) (*EventFilter, error) { + m.mu.Lock() + currentHeight := m.currentHeight + m.mu.Unlock() + + if m.EventIndex == nil && minHeight != -1 && minHeight < currentHeight { + return nil, xerrors.Errorf("historic event index disabled") + } + + id, err := newFilterID() + if err != nil { + return nil, xerrors.Errorf("new filter id: %w", err) + } + + f := &EventFilter{ + id: id, + minHeight: minHeight, + maxHeight: maxHeight, + tipsetCid: tipsetCid, + addresses: addresses, + keys: keys, + maxResults: m.MaxFilterResults, + } + + if m.EventIndex != nil && minHeight != -1 && minHeight < currentHeight { + // Filter needs historic events + if err := m.EventIndex.PrefillFilter(ctx, f); err != nil { + return nil, err + } + } + + m.mu.Lock() + if m.filters == nil { + m.filters = make(map[types.FilterID]*EventFilter) + } + m.filters[id] = f + m.mu.Unlock() + + return f, nil +} + +func (m *EventFilterManager) Remove(ctx context.Context, id types.FilterID) error { + m.mu.Lock() + defer m.mu.Unlock() + if _, found := m.filters[id]; !found { + return ErrFilterNotFound + } + delete(m.filters, id) + return nil +} + +func (m *EventFilterManager) loadExecutedMessages(ctx context.Context, msgTs, rctTs *types.TipSet) ([]executedMessage, error) { + msgs, err := m.ChainStore.MessagesForTipset(ctx, msgTs) + if err != nil { + return nil, xerrors.Errorf("read messages: %w", err) + } + + st := m.ChainStore.ActorStore(ctx) + + arr, err := blockadt.AsArray(st, rctTs.Blocks()[0].ParentMessageReceipts) + if err != nil { + return nil, xerrors.Errorf("load receipts amt: %w", err) + } + + if uint64(len(msgs)) != arr.Length() { + return nil, xerrors.Errorf("mismatching message and receipt counts (%d msgs, %d rcts)", len(msgs), arr.Length()) + } + + ems := make([]executedMessage, len(msgs)) + + for i := 0; i < len(msgs); i++ { + ems[i].msg = msgs[i] + + var rct types.MessageReceipt + found, err := arr.Get(uint64(i), &rct) + if err != nil { + return nil, xerrors.Errorf("load receipt: %w", err) + } + if !found { + return nil, xerrors.Errorf("receipt %d not found", i) + } + ems[i].rct = &rct + + if rct.EventsRoot == nil { + continue + } + + evtArr, err := amt4.LoadAMT(ctx, st, *rct.EventsRoot, amt4.UseTreeBitWidth(types.EventAMTBitwidth)) + if err != nil { + return nil, xerrors.Errorf("load events amt: %w", err) + } + + ems[i].evs = make([]*types.Event, evtArr.Len()) + var evt types.Event + err = evtArr.ForEach(ctx, func(u uint64, deferred *cbg.Deferred) error { + if u > math.MaxInt { + return xerrors.Errorf("too many events") + } + if err := evt.UnmarshalCBOR(bytes.NewReader(deferred.Raw)); err != nil { + return err + } + + cpy := evt + ems[i].evs[int(u)] = &cpy //nolint:scopelint + return nil + }) + + if err != nil { + return nil, xerrors.Errorf("read events: %w", err) + } + + } + + return ems, nil +} diff --git a/chain/events/filter/event_test.go b/chain/events/filter/event_test.go new file mode 100644 index 000000000..329573bc1 --- /dev/null +++ b/chain/events/filter/event_test.go @@ -0,0 +1,433 @@ +package filter + +import ( + "context" + pseudo "math/rand" + "testing" + + "github.com/ipfs/go-cid" + cbor "github.com/ipfs/go-ipld-cbor" + mh "github.com/multiformats/go-multihash" + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + builtintypes "github.com/filecoin-project/go-state-types/builtin" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/go-state-types/exitcode" + blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" + + "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/types" +) + +func TestEventFilterCollectEvents(t *testing.T) { + rng := pseudo.New(pseudo.NewSource(299792458)) + a1 := randomF4Addr(t, rng) + a2 := randomF4Addr(t, rng) + + a1ID := abi.ActorID(1) + a2ID := abi.ActorID(2) + + addrMap := addressMap{} + addrMap.add(a1ID, a1) + addrMap.add(a2ID, a2) + + ev1 := fakeEvent( + a1ID, + []kv{ + {k: "type", v: []byte("approval")}, + {k: "signer", v: []byte("addr1")}, + }, + []kv{ + {k: "amount", v: []byte("2988181")}, + }, + ) + + st := newStore() + events := []*types.Event{ev1} + em := executedMessage{ + msg: fakeMessage(randomF4Addr(t, rng), randomF4Addr(t, rng)), + rct: fakeReceipt(t, rng, st, events), + evs: events, + } + + events14000 := buildTipSetEvents(t, rng, 14000, em) + cid14000, err := events14000.msgTs.Key().Cid() + require.NoError(t, err, "tipset cid") + + noCollectedEvents := []*CollectedEvent{} + oneCollectedEvent := []*CollectedEvent{ + { + Entries: ev1.Entries, + EmitterAddr: a1, + EventIdx: 0, + Reverted: false, + Height: 14000, + TipSetKey: events14000.msgTs.Key(), + MsgIdx: 0, + MsgCid: em.msg.Cid(), + }, + } + + testCases := []struct { + name string + filter *EventFilter + te *TipSetEvents + want []*CollectedEvent + }{ + { + name: "nomatch tipset min height", + filter: &EventFilter{ + minHeight: 14001, + maxHeight: -1, + }, + te: events14000, + want: noCollectedEvents, + }, + { + name: "nomatch tipset max height", + filter: &EventFilter{ + minHeight: -1, + maxHeight: 13999, + }, + te: events14000, + want: noCollectedEvents, + }, + { + name: "match tipset min height", + filter: &EventFilter{ + minHeight: 14000, + maxHeight: -1, + }, + te: events14000, + want: oneCollectedEvent, + }, + { + name: "match tipset cid", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + tipsetCid: cid14000, + }, + te: events14000, + want: oneCollectedEvent, + }, + { + name: "nomatch address", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + addresses: []address.Address{a2}, + }, + te: events14000, + want: noCollectedEvents, + }, + { + name: "match address", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + addresses: []address.Address{a1}, + }, + te: events14000, + want: oneCollectedEvent, + }, + { + name: "match one entry", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + keys: map[string][][]byte{ + "type": { + []byte("approval"), + }, + }, + }, + te: events14000, + want: oneCollectedEvent, + }, + { + name: "match one entry with alternate values", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + keys: map[string][][]byte{ + "type": { + []byte("cancel"), + []byte("propose"), + []byte("approval"), + }, + }, + }, + te: events14000, + want: oneCollectedEvent, + }, + { + name: "nomatch one entry by missing value", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + keys: map[string][][]byte{ + "type": { + []byte("cancel"), + []byte("propose"), + }, + }, + }, + te: events14000, + want: noCollectedEvents, + }, + { + name: "nomatch one entry by missing key", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + keys: map[string][][]byte{ + "method": { + []byte("approval"), + }, + }, + }, + te: events14000, + want: noCollectedEvents, + }, + { + name: "match one entry with multiple keys", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + keys: map[string][][]byte{ + "type": { + []byte("approval"), + }, + "signer": { + []byte("addr1"), + }, + }, + }, + te: events14000, + want: oneCollectedEvent, + }, + { + name: "nomatch one entry with one mismatching key", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + keys: map[string][][]byte{ + "type": { + []byte("approval"), + }, + "approver": { + []byte("addr1"), + }, + }, + }, + te: events14000, + want: noCollectedEvents, + }, + { + name: "nomatch one entry with one mismatching value", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + keys: map[string][][]byte{ + "type": { + []byte("approval"), + }, + "signer": { + []byte("addr2"), + }, + }, + }, + te: events14000, + want: noCollectedEvents, + }, + { + name: "nomatch one entry with one unindexed key", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + keys: map[string][][]byte{ + "amount": { + []byte("2988181"), + }, + }, + }, + te: events14000, + want: noCollectedEvents, + }, + } + + for _, tc := range testCases { + tc := tc // appease lint + t.Run(tc.name, func(t *testing.T) { + if err := tc.filter.CollectEvents(context.Background(), tc.te, false, addrMap.ResolveAddress); err != nil { + require.NoError(t, err, "collect events") + } + + coll := tc.filter.TakeCollectedEvents(context.Background()) + require.ElementsMatch(t, coll, tc.want) + }) + } +} + +type kv struct { + k string + v []byte +} + +func fakeEvent(emitter abi.ActorID, indexed []kv, unindexed []kv) *types.Event { + ev := &types.Event{ + Emitter: emitter, + } + + for _, in := range indexed { + ev.Entries = append(ev.Entries, types.EventEntry{ + Flags: 0x01, + Key: in.k, + Codec: cid.Raw, + Value: in.v, + }) + } + + for _, in := range unindexed { + ev.Entries = append(ev.Entries, types.EventEntry{ + Flags: 0x00, + Key: in.k, + Codec: cid.Raw, + Value: in.v, + }) + } + + return ev +} + +func randomF4Addr(tb testing.TB, rng *pseudo.Rand) address.Address { + tb.Helper() + addr, err := address.NewDelegatedAddress(builtintypes.EthereumAddressManagerActorID, randomBytes(32, rng)) + require.NoError(tb, err) + + return addr +} + +func randomIDAddr(tb testing.TB, rng *pseudo.Rand) address.Address { + tb.Helper() + addr, err := address.NewIDAddress(uint64(rng.Int63())) + require.NoError(tb, err) + return addr +} + +func randomCid(tb testing.TB, rng *pseudo.Rand) cid.Cid { + tb.Helper() + cb := cid.V1Builder{Codec: cid.Raw, MhType: mh.IDENTITY} + c, err := cb.Sum(randomBytes(10, rng)) + require.NoError(tb, err) + return c +} + +func randomBytes(n int, rng *pseudo.Rand) []byte { + buf := make([]byte, n) + rng.Read(buf) + return buf +} + +func fakeMessage(to, from address.Address) *types.Message { + return &types.Message{ + To: to, + From: from, + Nonce: 197, + Method: 1, + Params: []byte("some random bytes"), + GasLimit: 126723, + GasPremium: types.NewInt(4), + GasFeeCap: types.NewInt(120), + } +} + +func fakeReceipt(tb testing.TB, rng *pseudo.Rand, st adt.Store, events []*types.Event) *types.MessageReceipt { + arr := blockadt.MakeEmptyArray(st) + for _, ev := range events { + err := arr.AppendContinuous(ev) + require.NoError(tb, err, "append event") + } + eventsRoot, err := arr.Root() + require.NoError(tb, err, "flush events amt") + + rec := types.NewMessageReceiptV1(exitcode.Ok, randomBytes(32, rng), rng.Int63(), &eventsRoot) + return &rec +} + +func fakeTipSet(tb testing.TB, rng *pseudo.Rand, h abi.ChainEpoch, parents []cid.Cid) *types.TipSet { + tb.Helper() + ts, err := types.NewTipSet([]*types.BlockHeader{ + { + Height: h, + Miner: randomIDAddr(tb, rng), + + Parents: parents, + + Ticket: &types.Ticket{VRFProof: []byte{byte(h % 2)}}, + + ParentStateRoot: randomCid(tb, rng), + Messages: randomCid(tb, rng), + ParentMessageReceipts: randomCid(tb, rng), + + BlockSig: &crypto.Signature{Type: crypto.SigTypeBLS}, + BLSAggregate: &crypto.Signature{Type: crypto.SigTypeBLS}, + }, + { + Height: h, + Miner: randomIDAddr(tb, rng), + + Parents: parents, + + Ticket: &types.Ticket{VRFProof: []byte{byte((h + 1) % 2)}}, + + ParentStateRoot: randomCid(tb, rng), + Messages: randomCid(tb, rng), + ParentMessageReceipts: randomCid(tb, rng), + + BlockSig: &crypto.Signature{Type: crypto.SigTypeBLS}, + BLSAggregate: &crypto.Signature{Type: crypto.SigTypeBLS}, + }, + }) + + require.NoError(tb, err) + + return ts +} + +func newStore() adt.Store { + ctx := context.Background() + bs := blockstore.NewMemorySync() + store := cbor.NewCborStore(bs) + return adt.WrapStore(ctx, store) +} + +func buildTipSetEvents(tb testing.TB, rng *pseudo.Rand, h abi.ChainEpoch, em executedMessage) *TipSetEvents { + tb.Helper() + + msgTs := fakeTipSet(tb, rng, h, []cid.Cid{}) + rctTs := fakeTipSet(tb, rng, h+1, msgTs.Cids()) + + return &TipSetEvents{ + msgTs: msgTs, + rctTs: rctTs, + load: func(ctx context.Context, msgTs, rctTs *types.TipSet) ([]executedMessage, error) { + return []executedMessage{em}, nil + }, + } +} + +type addressMap map[abi.ActorID]address.Address + +func (a addressMap) add(actorID abi.ActorID, addr address.Address) { + a[actorID] = addr +} + +func (a addressMap) ResolveAddress(ctx context.Context, emitter abi.ActorID, ts *types.TipSet) (address.Address, bool) { + ra, ok := a[emitter] + return ra, ok +} diff --git a/chain/events/filter/index.go b/chain/events/filter/index.go new file mode 100644 index 000000000..ab4e24493 --- /dev/null +++ b/chain/events/filter/index.go @@ -0,0 +1,406 @@ +package filter + +import ( + "context" + "database/sql" + "errors" + "fmt" + "sort" + "strings" + + "github.com/ipfs/go-cid" + _ "github.com/mattn/go-sqlite3" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/chain/types" +) + +var pragmas = []string{ + "PRAGMA synchronous = normal", + "PRAGMA temp_store = memory", + "PRAGMA mmap_size = 30000000000", + "PRAGMA page_size = 32768", + "PRAGMA auto_vacuum = NONE", + "PRAGMA automatic_index = OFF", + "PRAGMA journal_mode = WAL", + "PRAGMA read_uncommitted = ON", +} + +var ddls = []string{ + `CREATE TABLE IF NOT EXISTS event ( + id INTEGER PRIMARY KEY, + height INTEGER NOT NULL, + tipset_key BLOB NOT NULL, + tipset_key_cid BLOB NOT NULL, + emitter_addr BLOB NOT NULL, + event_index INTEGER NOT NULL, + message_cid BLOB NOT NULL, + message_index INTEGER NOT NULL, + reverted INTEGER NOT NULL + )`, + + `CREATE TABLE IF NOT EXISTS event_entry ( + event_id INTEGER, + indexed INTEGER NOT NULL, + flags BLOB NOT NULL, + key TEXT NOT NULL, + codec INTEGER, + value BLOB NOT NULL + )`, + + // metadata containing version of schema + `CREATE TABLE IF NOT EXISTS _meta ( + version UINT64 NOT NULL UNIQUE + )`, + + // version 1. + `INSERT OR IGNORE INTO _meta (version) VALUES (1)`, +} + +const schemaVersion = 1 + +const ( + insertEvent = `INSERT OR IGNORE INTO event + (height, tipset_key, tipset_key_cid, emitter_addr, event_index, message_cid, message_index, reverted) + VALUES(?, ?, ?, ?, ?, ?, ?, ?)` + + insertEntry = `INSERT OR IGNORE INTO event_entry + (event_id, indexed, flags, key, codec, value) + VALUES(?, ?, ?, ?, ?, ?)` +) + +type EventIndex struct { + db *sql.DB +} + +func NewEventIndex(path string) (*EventIndex, error) { + db, err := sql.Open("sqlite3", path+"?mode=rwc") + if err != nil { + return nil, xerrors.Errorf("open sqlite3 database: %w", err) + } + + for _, pragma := range pragmas { + if _, err := db.Exec(pragma); err != nil { + _ = db.Close() + return nil, xerrors.Errorf("exec pragma %q: %w", pragma, err) + } + } + + q, err := db.Query("SELECT name FROM sqlite_master WHERE type='table' AND name='_meta';") + if err == sql.ErrNoRows || !q.Next() { + // empty database, create the schema + for _, ddl := range ddls { + if _, err := db.Exec(ddl); err != nil { + _ = db.Close() + return nil, xerrors.Errorf("exec ddl %q: %w", ddl, err) + } + } + } else if err != nil { + _ = db.Close() + return nil, xerrors.Errorf("looking for _meta table: %w", err) + } else { + // Ensure we don't open a database from a different schema version + + row := db.QueryRow("SELECT max(version) FROM _meta") + var version int + err := row.Scan(&version) + if err != nil { + _ = db.Close() + return nil, xerrors.Errorf("invalid database version: no version found") + } + if version != schemaVersion { + _ = db.Close() + return nil, xerrors.Errorf("invalid database version: got %d, expected %d", version, schemaVersion) + } + } + + return &EventIndex{ + db: db, + }, nil +} + +func (ei *EventIndex) Close() error { + if ei.db == nil { + return nil + } + return ei.db.Close() +} + +func (ei *EventIndex) CollectEvents(ctx context.Context, te *TipSetEvents, revert bool, resolver func(ctx context.Context, emitter abi.ActorID, ts *types.TipSet) (address.Address, bool)) error { + // cache of lookups between actor id and f4 address + + addressLookups := make(map[abi.ActorID]address.Address) + + ems, err := te.messages(ctx) + if err != nil { + return xerrors.Errorf("load executed messages: %w", err) + } + + tx, err := ei.db.Begin() + if err != nil { + return xerrors.Errorf("begin transaction: %w", err) + } + stmtEvent, err := tx.Prepare(insertEvent) + if err != nil { + return xerrors.Errorf("prepare insert event: %w", err) + } + stmtEntry, err := tx.Prepare(insertEntry) + if err != nil { + return xerrors.Errorf("prepare insert entry: %w", err) + } + + for msgIdx, em := range ems { + for evIdx, ev := range em.Events() { + addr, found := addressLookups[ev.Emitter] + if !found { + var ok bool + addr, ok = resolver(ctx, ev.Emitter, te.rctTs) + if !ok { + // not an address we will be able to match against + continue + } + addressLookups[ev.Emitter] = addr + } + + tsKeyCid, err := te.msgTs.Key().Cid() + if err != nil { + return xerrors.Errorf("tipset key cid: %w", err) + } + + res, err := stmtEvent.Exec( + te.msgTs.Height(), // height + te.msgTs.Key().Bytes(), // tipset_key + tsKeyCid.Bytes(), // tipset_key_cid + addr.Bytes(), // emitter_addr + evIdx, // event_index + em.Message().Cid().Bytes(), // message_cid + msgIdx, // message_index + revert, // reverted + ) + if err != nil { + return xerrors.Errorf("exec insert event: %w", err) + } + + lastID, err := res.LastInsertId() + if err != nil { + return xerrors.Errorf("get last row id: %w", err) + } + + for _, entry := range ev.Entries { + _, err := stmtEntry.Exec( + lastID, // event_id + isIndexedValue(entry.Flags), // indexed + []byte{entry.Flags}, // flags + entry.Key, // key + entry.Codec, // codec + entry.Value, // value + ) + if err != nil { + return xerrors.Errorf("exec insert entry: %w", err) + } + } + } + } + + if err := tx.Commit(); err != nil { + return xerrors.Errorf("commit transaction: %w", err) + } + + return nil +} + +// PrefillFilter fills a filter's collection of events from the historic index +func (ei *EventIndex) PrefillFilter(ctx context.Context, f *EventFilter) error { + clauses := []string{} + values := []any{} + joins := []string{} + + if f.tipsetCid != cid.Undef { + clauses = append(clauses, "event.tipset_key_cid=?") + values = append(values, f.tipsetCid.Bytes()) + } else { + if f.minHeight >= 0 { + clauses = append(clauses, "event.height>=?") + values = append(values, f.minHeight) + } + if f.maxHeight >= 0 { + clauses = append(clauses, "event.height<=?") + values = append(values, f.maxHeight) + } + } + + if len(f.addresses) > 0 { + subclauses := []string{} + for _, addr := range f.addresses { + subclauses = append(subclauses, "emitter_addr=?") + values = append(values, addr.Bytes()) + } + clauses = append(clauses, "("+strings.Join(subclauses, " OR ")+")") + } + + if len(f.keys) > 0 { + join := 0 + for key, vals := range f.keys { + if len(vals) > 0 { + join++ + joinAlias := fmt.Sprintf("ee%d", join) + joins = append(joins, fmt.Sprintf("event_entry %s on event.id=%[1]s.event_id", joinAlias)) + clauses = append(clauses, fmt.Sprintf("%s.indexed=1 AND %[1]s.key=?", joinAlias)) + values = append(values, key) + subclauses := []string{} + for _, val := range vals { + subclauses = append(subclauses, fmt.Sprintf("%s.value=?", joinAlias)) + values = append(values, val) + } + clauses = append(clauses, "("+strings.Join(subclauses, " OR ")+")") + } + } + } + + s := `SELECT + event.id, + event.height, + event.tipset_key, + event.tipset_key_cid, + event.emitter_addr, + event.event_index, + event.message_cid, + event.message_index, + event.reverted, + event_entry.flags, + event_entry.key, + event_entry.codec, + event_entry.value + FROM event JOIN event_entry ON event.id=event_entry.event_id` + + if len(joins) > 0 { + s = s + ", " + strings.Join(joins, ", ") + } + + if len(clauses) > 0 { + s = s + " WHERE " + strings.Join(clauses, " AND ") + } + + s += " ORDER BY event.height DESC" + + stmt, err := ei.db.Prepare(s) + if err != nil { + return xerrors.Errorf("prepare prefill query: %w", err) + } + + q, err := stmt.QueryContext(ctx, values...) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil + } + return xerrors.Errorf("exec prefill query: %w", err) + } + + var ces []*CollectedEvent + var currentID int64 = -1 + var ce *CollectedEvent + + for q.Next() { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + + var row struct { + id int64 + height uint64 + tipsetKey []byte + tipsetKeyCid []byte + emitterAddr []byte + eventIndex int + messageCid []byte + messageIndex int + reverted bool + flags []byte + key string + codec uint64 + value []byte + } + + if err := q.Scan( + &row.id, + &row.height, + &row.tipsetKey, + &row.tipsetKeyCid, + &row.emitterAddr, + &row.eventIndex, + &row.messageCid, + &row.messageIndex, + &row.reverted, + &row.flags, + &row.key, + &row.codec, + &row.value, + ); err != nil { + return xerrors.Errorf("read prefill row: %w", err) + } + + if row.id != currentID { + if ce != nil { + ces = append(ces, ce) + ce = nil + // Unfortunately we can't easily incorporate the max results limit into the query due to the + // unpredictable number of rows caused by joins + // Break here to stop collecting rows + if f.maxResults > 0 && len(ces) >= f.maxResults { + break + } + } + + currentID = row.id + ce = &CollectedEvent{ + EventIdx: row.eventIndex, + Reverted: row.reverted, + Height: abi.ChainEpoch(row.height), + MsgIdx: row.messageIndex, + } + + ce.EmitterAddr, err = address.NewFromBytes(row.emitterAddr) + if err != nil { + return xerrors.Errorf("parse emitter addr: %w", err) + } + + ce.TipSetKey, err = types.TipSetKeyFromBytes(row.tipsetKey) + if err != nil { + return xerrors.Errorf("parse tipsetkey: %w", err) + } + + ce.MsgCid, err = cid.Cast(row.messageCid) + if err != nil { + return xerrors.Errorf("parse message cid: %w", err) + } + } + + ce.Entries = append(ce.Entries, types.EventEntry{ + Flags: row.flags[0], + Key: row.key, + Codec: row.codec, + Value: row.value, + }) + + } + + if ce != nil { + ces = append(ces, ce) + } + + if len(ces) == 0 { + return nil + } + + // collected event list is in inverted order since we selected only the most recent events + // sort it into height order + sort.Slice(ces, func(i, j int) bool { return ces[i].Height < ces[j].Height }) + f.setCollectedEvents(ces) + + return nil +} diff --git a/chain/events/filter/index_test.go b/chain/events/filter/index_test.go new file mode 100644 index 000000000..ee2ae8611 --- /dev/null +++ b/chain/events/filter/index_test.go @@ -0,0 +1,283 @@ +package filter + +import ( + "context" + pseudo "math/rand" + "os" + "path/filepath" + "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/types" +) + +func TestEventIndexPrefillFilter(t *testing.T) { + rng := pseudo.New(pseudo.NewSource(299792458)) + a1 := randomF4Addr(t, rng) + a2 := randomF4Addr(t, rng) + + a1ID := abi.ActorID(1) + a2ID := abi.ActorID(2) + + addrMap := addressMap{} + addrMap.add(a1ID, a1) + addrMap.add(a2ID, a2) + + ev1 := fakeEvent( + a1ID, + []kv{ + {k: "type", v: []byte("approval")}, + {k: "signer", v: []byte("addr1")}, + }, + []kv{ + {k: "amount", v: []byte("2988181")}, + }, + ) + + st := newStore() + events := []*types.Event{ev1} + em := executedMessage{ + msg: fakeMessage(randomF4Addr(t, rng), randomF4Addr(t, rng)), + rct: fakeReceipt(t, rng, st, events), + evs: events, + } + + events14000 := buildTipSetEvents(t, rng, 14000, em) + cid14000, err := events14000.msgTs.Key().Cid() + require.NoError(t, err, "tipset cid") + + noCollectedEvents := []*CollectedEvent{} + oneCollectedEvent := []*CollectedEvent{ + { + Entries: ev1.Entries, + EmitterAddr: a1, + EventIdx: 0, + Reverted: false, + Height: 14000, + TipSetKey: events14000.msgTs.Key(), + MsgIdx: 0, + MsgCid: em.msg.Cid(), + }, + } + + workDir, err := os.MkdirTemp("", "lotusevents") + require.NoError(t, err, "create temporary work directory") + + defer func() { + _ = os.RemoveAll(workDir) + }() + t.Logf("using work dir %q", workDir) + + dbPath := filepath.Join(workDir, "actorevents.db") + + ei, err := NewEventIndex(dbPath) + require.NoError(t, err, "create event index") + if err := ei.CollectEvents(context.Background(), events14000, false, addrMap.ResolveAddress); err != nil { + require.NoError(t, err, "collect events") + } + + testCases := []struct { + name string + filter *EventFilter + te *TipSetEvents + want []*CollectedEvent + }{ + { + name: "nomatch tipset min height", + filter: &EventFilter{ + minHeight: 14001, + maxHeight: -1, + }, + te: events14000, + want: noCollectedEvents, + }, + { + name: "nomatch tipset max height", + filter: &EventFilter{ + minHeight: -1, + maxHeight: 13999, + }, + te: events14000, + want: noCollectedEvents, + }, + { + name: "match tipset min height", + filter: &EventFilter{ + minHeight: 14000, + maxHeight: -1, + }, + te: events14000, + want: oneCollectedEvent, + }, + { + name: "match tipset cid", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + tipsetCid: cid14000, + }, + te: events14000, + want: oneCollectedEvent, + }, + { + name: "nomatch address", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + addresses: []address.Address{a2}, + }, + te: events14000, + want: noCollectedEvents, + }, + { + name: "match address", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + addresses: []address.Address{a1}, + }, + te: events14000, + want: oneCollectedEvent, + }, + { + name: "match one entry", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + keys: map[string][][]byte{ + "type": { + []byte("approval"), + }, + }, + }, + te: events14000, + want: oneCollectedEvent, + }, + { + name: "match one entry with alternate values", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + keys: map[string][][]byte{ + "type": { + []byte("cancel"), + []byte("propose"), + []byte("approval"), + }, + }, + }, + te: events14000, + want: oneCollectedEvent, + }, + { + name: "nomatch one entry by missing value", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + keys: map[string][][]byte{ + "type": { + []byte("cancel"), + []byte("propose"), + }, + }, + }, + te: events14000, + want: noCollectedEvents, + }, + { + name: "nomatch one entry by missing key", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + keys: map[string][][]byte{ + "method": { + []byte("approval"), + }, + }, + }, + te: events14000, + want: noCollectedEvents, + }, + { + name: "match one entry with multiple keys", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + keys: map[string][][]byte{ + "type": { + []byte("approval"), + }, + "signer": { + []byte("addr1"), + }, + }, + }, + te: events14000, + want: oneCollectedEvent, + }, + { + name: "nomatch one entry with one mismatching key", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + keys: map[string][][]byte{ + "type": { + []byte("approval"), + }, + "approver": { + []byte("addr1"), + }, + }, + }, + te: events14000, + want: noCollectedEvents, + }, + { + name: "nomatch one entry with one mismatching value", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + keys: map[string][][]byte{ + "type": { + []byte("approval"), + }, + "signer": { + []byte("addr2"), + }, + }, + }, + te: events14000, + want: noCollectedEvents, + }, + { + name: "nomatch one entry with one unindexed key", + filter: &EventFilter{ + minHeight: -1, + maxHeight: -1, + keys: map[string][][]byte{ + "amount": { + []byte("2988181"), + }, + }, + }, + te: events14000, + want: noCollectedEvents, + }, + } + + for _, tc := range testCases { + tc := tc // appease lint + t.Run(tc.name, func(t *testing.T) { + if err := ei.PrefillFilter(context.Background(), tc.filter); err != nil { + require.NoError(t, err, "prefill filter events") + } + + coll := tc.filter.TakeCollectedEvents(context.Background()) + require.ElementsMatch(t, coll, tc.want) + }) + } +} diff --git a/chain/events/filter/mempool.go b/chain/events/filter/mempool.go new file mode 100644 index 000000000..39ccf12c2 --- /dev/null +++ b/chain/events/filter/mempool.go @@ -0,0 +1,142 @@ +package filter + +import ( + "context" + "sync" + "time" + + "golang.org/x/xerrors" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/types" +) + +type MemPoolFilter struct { + id types.FilterID + maxResults int // maximum number of results to collect, 0 is unlimited + ch chan<- interface{} + + mu sync.Mutex + collected []*types.SignedMessage + lastTaken time.Time +} + +var _ Filter = (*MemPoolFilter)(nil) + +func (f *MemPoolFilter) ID() types.FilterID { + return f.id +} + +func (f *MemPoolFilter) SetSubChannel(ch chan<- interface{}) { + f.mu.Lock() + defer f.mu.Unlock() + f.ch = ch + f.collected = nil +} + +func (f *MemPoolFilter) ClearSubChannel() { + f.mu.Lock() + defer f.mu.Unlock() + f.ch = nil +} + +func (f *MemPoolFilter) CollectMessage(ctx context.Context, msg *types.SignedMessage) { + f.mu.Lock() + defer f.mu.Unlock() + + // if we have a subscription channel then push message to it + if f.ch != nil { + f.ch <- msg + return + } + + if f.maxResults > 0 && len(f.collected) == f.maxResults { + copy(f.collected, f.collected[1:]) + f.collected = f.collected[:len(f.collected)-1] + } + f.collected = append(f.collected, msg) +} + +func (f *MemPoolFilter) TakeCollectedMessages(context.Context) []*types.SignedMessage { + f.mu.Lock() + collected := f.collected + f.collected = nil + f.lastTaken = time.Now().UTC() + f.mu.Unlock() + + return collected +} + +func (f *MemPoolFilter) LastTaken() time.Time { + f.mu.Lock() + defer f.mu.Unlock() + return f.lastTaken +} + +type MemPoolFilterManager struct { + MaxFilterResults int + + mu sync.Mutex // guards mutations to filters + filters map[types.FilterID]*MemPoolFilter +} + +func (m *MemPoolFilterManager) WaitForMpoolUpdates(ctx context.Context, ch <-chan api.MpoolUpdate) { + for { + select { + case <-ctx.Done(): + return + case u := <-ch: + m.processUpdate(ctx, u) + } + } +} + +func (m *MemPoolFilterManager) processUpdate(ctx context.Context, u api.MpoolUpdate) { + // only process added messages + if u.Type == api.MpoolRemove { + return + } + + m.mu.Lock() + defer m.mu.Unlock() + + if len(m.filters) == 0 { + return + } + + // TODO: could run this loop in parallel with errgroup if we expect large numbers of filters + for _, f := range m.filters { + f.CollectMessage(ctx, u.Message) + } +} + +func (m *MemPoolFilterManager) Install(ctx context.Context) (*MemPoolFilter, error) { + id, err := newFilterID() + if err != nil { + return nil, xerrors.Errorf("new filter id: %w", err) + } + + f := &MemPoolFilter{ + id: id, + maxResults: m.MaxFilterResults, + } + + m.mu.Lock() + if m.filters == nil { + m.filters = make(map[types.FilterID]*MemPoolFilter) + } + m.filters[id] = f + m.mu.Unlock() + + return f, nil +} + +func (m *MemPoolFilterManager) Remove(ctx context.Context, id types.FilterID) error { + m.mu.Lock() + defer m.mu.Unlock() + if _, found := m.filters[id]; !found { + return ErrFilterNotFound + } + delete(m.filters, id) + return nil +} diff --git a/chain/events/filter/store.go b/chain/events/filter/store.go new file mode 100644 index 000000000..d3c173ec0 --- /dev/null +++ b/chain/events/filter/store.go @@ -0,0 +1,108 @@ +package filter + +import ( + "context" + "errors" + "sync" + "time" + + "github.com/google/uuid" + "golang.org/x/xerrors" + + "github.com/filecoin-project/lotus/chain/types" +) + +type Filter interface { + ID() types.FilterID + LastTaken() time.Time + SetSubChannel(chan<- interface{}) + ClearSubChannel() +} + +type FilterStore interface { + Add(context.Context, Filter) error + Get(context.Context, types.FilterID) (Filter, error) + Remove(context.Context, types.FilterID) error + NotTakenSince(when time.Time) []Filter // returns a list of filters that have not had their collected results taken +} + +var ( + ErrFilterAlreadyRegistered = errors.New("filter already registered") + ErrFilterNotFound = errors.New("filter not found") + ErrMaximumNumberOfFilters = errors.New("maximum number of filters registered") +) + +func newFilterID() (types.FilterID, error) { + rawid, err := uuid.NewRandom() + if err != nil { + return types.FilterID{}, xerrors.Errorf("new uuid: %w", err) + } + id := types.FilterID{} + copy(id[:], rawid[:]) // uuid is 16 bytes, the last 16 bytes are zeroed + return id, nil +} + +type memFilterStore struct { + max int + mu sync.Mutex + filters map[types.FilterID]Filter +} + +var _ FilterStore = (*memFilterStore)(nil) + +func NewMemFilterStore(maxFilters int) FilterStore { + return &memFilterStore{ + max: maxFilters, + filters: make(map[types.FilterID]Filter), + } +} + +func (m *memFilterStore) Add(_ context.Context, f Filter) error { + m.mu.Lock() + defer m.mu.Unlock() + + if len(m.filters) >= m.max { + return ErrMaximumNumberOfFilters + } + + if _, exists := m.filters[f.ID()]; exists { + return ErrFilterAlreadyRegistered + } + m.filters[f.ID()] = f + return nil +} + +func (m *memFilterStore) Get(_ context.Context, id types.FilterID) (Filter, error) { + m.mu.Lock() + f, found := m.filters[id] + m.mu.Unlock() + if !found { + return nil, ErrFilterNotFound + } + return f, nil +} + +func (m *memFilterStore) Remove(_ context.Context, id types.FilterID) error { + m.mu.Lock() + defer m.mu.Unlock() + + if _, exists := m.filters[id]; !exists { + return ErrFilterNotFound + } + delete(m.filters, id) + return nil +} + +func (m *memFilterStore) NotTakenSince(when time.Time) []Filter { + m.mu.Lock() + defer m.mu.Unlock() + + var res []Filter + for _, f := range m.filters { + if f.LastTaken().Before(when) { + res = append(res, f) + } + } + + return res +} diff --git a/chain/events/filter/tipset.go b/chain/events/filter/tipset.go new file mode 100644 index 000000000..be734c6f7 --- /dev/null +++ b/chain/events/filter/tipset.go @@ -0,0 +1,130 @@ +package filter + +import ( + "context" + "sync" + "time" + + "golang.org/x/xerrors" + + "github.com/filecoin-project/lotus/chain/types" +) + +type TipSetFilter struct { + id types.FilterID + maxResults int // maximum number of results to collect, 0 is unlimited + ch chan<- interface{} + + mu sync.Mutex + collected []types.TipSetKey + lastTaken time.Time +} + +var _ Filter = (*TipSetFilter)(nil) + +func (f *TipSetFilter) ID() types.FilterID { + return f.id +} + +func (f *TipSetFilter) SetSubChannel(ch chan<- interface{}) { + f.mu.Lock() + defer f.mu.Unlock() + f.ch = ch + f.collected = nil +} + +func (f *TipSetFilter) ClearSubChannel() { + f.mu.Lock() + defer f.mu.Unlock() + f.ch = nil +} + +func (f *TipSetFilter) CollectTipSet(ctx context.Context, ts *types.TipSet) { + f.mu.Lock() + defer f.mu.Unlock() + + // if we have a subscription channel then push tipset to it + if f.ch != nil { + f.ch <- ts + return + } + + if f.maxResults > 0 && len(f.collected) == f.maxResults { + copy(f.collected, f.collected[1:]) + f.collected = f.collected[:len(f.collected)-1] + } + f.collected = append(f.collected, ts.Key()) +} + +func (f *TipSetFilter) TakeCollectedTipSets(context.Context) []types.TipSetKey { + f.mu.Lock() + collected := f.collected + f.collected = nil + f.lastTaken = time.Now().UTC() + f.mu.Unlock() + + return collected +} + +func (f *TipSetFilter) LastTaken() time.Time { + f.mu.Lock() + defer f.mu.Unlock() + return f.lastTaken +} + +type TipSetFilterManager struct { + MaxFilterResults int + + mu sync.Mutex // guards mutations to filters + filters map[types.FilterID]*TipSetFilter +} + +func (m *TipSetFilterManager) Apply(ctx context.Context, from, to *types.TipSet) error { + m.mu.Lock() + defer m.mu.Unlock() + if len(m.filters) == 0 { + return nil + } + + // TODO: could run this loop in parallel with errgroup + for _, f := range m.filters { + f.CollectTipSet(ctx, to) + } + + return nil +} + +func (m *TipSetFilterManager) Revert(ctx context.Context, from, to *types.TipSet) error { + return nil +} + +func (m *TipSetFilterManager) Install(ctx context.Context) (*TipSetFilter, error) { + id, err := newFilterID() + if err != nil { + return nil, xerrors.Errorf("new filter id: %w", err) + } + + f := &TipSetFilter{ + id: id, + maxResults: m.MaxFilterResults, + } + + m.mu.Lock() + if m.filters == nil { + m.filters = make(map[types.FilterID]*TipSetFilter) + } + m.filters[id] = f + m.mu.Unlock() + + return f, nil +} + +func (m *TipSetFilterManager) Remove(ctx context.Context, id types.FilterID) error { + m.mu.Lock() + defer m.mu.Unlock() + if _, found := m.filters[id]; !found { + return ErrFilterNotFound + } + delete(m.filters, id) + return nil +} diff --git a/chain/events/message_cache.go b/chain/events/message_cache.go new file mode 100644 index 000000000..d47d3a168 --- /dev/null +++ b/chain/events/message_cache.go @@ -0,0 +1,43 @@ +package events + +import ( + "context" + "sync" + + lru "github.com/hashicorp/golang-lru/v2" + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/lotus/api" +) + +type messageCache struct { + api EventAPI + + blockMsgLk sync.Mutex + blockMsgCache *lru.ARCCache[cid.Cid, *api.BlockMessages] +} + +func newMessageCache(a EventAPI) *messageCache { + blsMsgCache, _ := lru.NewARC[cid.Cid, *api.BlockMessages](500) + + return &messageCache{ + api: a, + blockMsgCache: blsMsgCache, + } +} + +func (c *messageCache) ChainGetBlockMessages(ctx context.Context, blkCid cid.Cid) (*api.BlockMessages, error) { + c.blockMsgLk.Lock() + defer c.blockMsgLk.Unlock() + + msgs, ok := c.blockMsgCache.Get(blkCid) + var err error + if !ok { + msgs, err = c.api.ChainGetBlockMessages(ctx, blkCid) + if err != nil { + return nil, err + } + c.blockMsgCache.Add(blkCid, msgs) + } + return msgs, nil +} diff --git a/chain/events/observer.go b/chain/events/observer.go new file mode 100644 index 000000000..446218585 --- /dev/null +++ b/chain/events/observer.go @@ -0,0 +1,280 @@ +package events + +import ( + "context" + "sync" + "time" + + "go.opencensus.io/trace" + "golang.org/x/xerrors" + + "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/store" + "github.com/filecoin-project/lotus/chain/types" +) + +type observer struct { + api EventAPI + + gcConfidence abi.ChainEpoch + + ready chan struct{} + + lk sync.Mutex + head *types.TipSet + maxHeight abi.ChainEpoch + observers []TipSetObserver +} + +func newObserver(api *cache, gcConfidence abi.ChainEpoch) *observer { + obs := &observer{ + api: api, + gcConfidence: gcConfidence, + + ready: make(chan struct{}), + observers: []TipSetObserver{}, + } + obs.Observe(api.observer()) + return obs +} + +func (o *observer) start(ctx context.Context) error { + go o.listenHeadChanges(ctx) + + // Wait for the first tipset to be seen or bail if shutting down + select { + case <-o.ready: + return nil + case <-ctx.Done(): + return ctx.Err() + } +} + +func (o *observer) listenHeadChanges(ctx context.Context) { + for { + if err := o.listenHeadChangesOnce(ctx); err != nil { + log.Errorf("listen head changes errored: %s", err) + } else { + log.Warn("listenHeadChanges quit") + } + select { + case <-build.Clock.After(time.Second): + case <-ctx.Done(): + log.Warnf("not restarting listenHeadChanges: context error: %s", ctx.Err()) + return + } + + log.Info("restarting listenHeadChanges") + } +} + +func (o *observer) listenHeadChangesOnce(ctx context.Context) error { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + notifs, err := o.api.ChainNotify(ctx) + if err != nil { + // Retry is handled by caller + return xerrors.Errorf("listenHeadChanges ChainNotify call failed: %w", err) + } + + var cur []*api.HeadChange + var ok bool + + // Wait for first tipset or bail + select { + case cur, ok = <-notifs: + if !ok { + return xerrors.Errorf("notification channel closed") + } + case <-ctx.Done(): + return ctx.Err() + } + + if len(cur) != 1 { + return xerrors.Errorf("unexpected initial head notification length: %d", len(cur)) + } + + if cur[0].Type != store.HCCurrent { + return xerrors.Errorf("expected first head notification type to be 'current', was '%s'", cur[0].Type) + } + + curHead := cur[0].Val + + o.lk.Lock() + if o.head == nil { + o.head = curHead + close(o.ready) + } + startHead := o.head + o.lk.Unlock() + + if !startHead.Equals(curHead) { + changes, err := o.api.ChainGetPath(ctx, startHead.Key(), curHead.Key()) + if err != nil { + return xerrors.Errorf("failed to get path from last applied tipset to head: %w", err) + } + + if err := o.applyChanges(ctx, changes); err != nil { + return xerrors.Errorf("failed catch-up head changes: %w", err) + } + } + + for changes := range notifs { + if err := o.applyChanges(ctx, changes); err != nil { + return xerrors.Errorf("failed to apply a change notification: %w", err) + } + } + + return nil +} + +func (o *observer) applyChanges(ctx context.Context, changes []*api.HeadChange) error { + // Used to wait for a prior notification round to finish (by tests) + if len(changes) == 0 { + return nil + } + + var rev, app []*types.TipSet + for _, changes := range changes { + switch changes.Type { + case store.HCRevert: + rev = append(rev, changes.Val) + case store.HCApply: + app = append(app, changes.Val) + default: + log.Errorf("unexpected head change notification type: '%s'", changes.Type) + } + } + + if err := o.headChange(ctx, rev, app); err != nil { + return xerrors.Errorf("failed to apply head changes: %w", err) + } + return nil +} + +func (o *observer) headChange(ctx context.Context, rev, app []*types.TipSet) error { + ctx, span := trace.StartSpan(ctx, "events.HeadChange") + span.AddAttributes(trace.Int64Attribute("reverts", int64(len(rev)))) + span.AddAttributes(trace.Int64Attribute("applies", int64(len(app)))) + + o.lk.Lock() + head := o.head + o.lk.Unlock() + + defer func() { + span.AddAttributes(trace.Int64Attribute("endHeight", int64(head.Height()))) + span.End() + }() + + // NOTE: bailing out here if the head isn't what we expected is fine. We'll re-start the + // entire process and handle any strange reorgs. + for i, from := range rev { + if !from.Equals(head) { + return xerrors.Errorf( + "expected to revert %s (%d), reverting %s (%d)", + head.Key(), head.Height(), from.Key(), from.Height(), + ) + } + var to *types.TipSet + if i+1 < len(rev) { + // If we have more reverts, the next revert is the next head. + to = rev[i+1] + } else { + // At the end of the revert sequenece, we need to lookup the joint tipset + // between the revert sequence and the apply sequence. + var err error + to, err = o.api.ChainGetTipSet(ctx, from.Parents()) + if err != nil { + // Well, this sucks. We'll bail and restart. + return xerrors.Errorf("failed to get tipset when reverting due to a SetHeead: %w", err) + } + } + + // Get the current observers and atomically set the head. + // + // 1. We need to get the observers every time in case some registered/deregistered. + // 2. We need to atomically set the head so new observers don't see events twice or + // skip them. + o.lk.Lock() + observers := o.observers + o.head = to + o.lk.Unlock() + + for _, obs := range observers { + if err := obs.Revert(ctx, from, to); err != nil { + log.Errorf("observer %T failed to apply tipset %s (%d) with: %s", obs, from.Key(), from.Height(), err) + } + } + + if to.Height() < o.maxHeight-o.gcConfidence { + log.Errorf("reverted past finality, from %d to %d", o.maxHeight, to.Height()) + } + + head = to + } + + for _, to := range app { + if to.Parents() != head.Key() { + return xerrors.Errorf( + "cannot apply %s (%d) with parents %s on top of %s (%d)", + to.Key(), to.Height(), to.Parents(), head.Key(), head.Height(), + ) + } + + o.lk.Lock() + observers := o.observers + o.head = to + o.lk.Unlock() + + for _, obs := range observers { + if err := obs.Apply(ctx, head, to); err != nil { + log.Errorf("observer %T failed to revert tipset %s (%d) with: %s", obs, to.Key(), to.Height(), err) + } + } + if to.Height() > o.maxHeight { + o.maxHeight = to.Height() + } + + head = to + } + return nil +} + +// Observe registers the observer, and returns the current tipset. The observer is guaranteed to +// observe events starting at this tipset. +// +// Returns nil if the observer hasn't started yet (but still registers). +func (o *observer) Observe(obs TipSetObserver) *types.TipSet { + o.lk.Lock() + defer o.lk.Unlock() + o.observers = append(o.observers, obs) + return o.head +} + +// Unregister unregisters an observer. Returns true if we successfully removed the observer. +// +// NOTE: The observer _may_ be called after being removed. Observers MUST handle this case +// internally. +func (o *observer) Unregister(obs TipSetObserver) (found bool) { + o.lk.Lock() + defer o.lk.Unlock() + // We _copy_ the observers list because we may be concurrently reading it from a headChange + // handler. + // + // This should happen infrequently, so it's fine if we spend a bit of time here. + newObservers := make([]TipSetObserver, 0, len(o.observers)) + for _, existingObs := range o.observers { + if existingObs == obs { + found = true + continue + } + newObservers = append(newObservers, existingObs) + } + + o.observers = newObservers + return found +} diff --git a/chain/events/state/ctxstore.go b/chain/events/state/ctxstore.go new file mode 100644 index 000000000..12b45e425 --- /dev/null +++ b/chain/events/state/ctxstore.go @@ -0,0 +1,25 @@ +package state + +import ( + "context" + + "github.com/ipfs/go-cid" + cbor "github.com/ipfs/go-ipld-cbor" +) + +type contextStore struct { + ctx context.Context + cst *cbor.BasicIpldStore +} + +func (cs *contextStore) Context() context.Context { + return cs.ctx +} + +func (cs *contextStore) Get(ctx context.Context, c cid.Cid, out interface{}) error { + return cs.cst.Get(ctx, c, out) +} + +func (cs *contextStore) Put(ctx context.Context, v interface{}) (cid.Cid, error) { + return cs.cst.Put(ctx, v) +} diff --git a/chain/events/state/fastapi.go b/chain/events/state/fastapi.go new file mode 100644 index 000000000..9375d9d78 --- /dev/null +++ b/chain/events/state/fastapi.go @@ -0,0 +1,34 @@ +package state + +import ( + "context" + + "github.com/filecoin-project/go-address" + + "github.com/filecoin-project/lotus/chain/types" +) + +type FastChainApiAPI interface { + ChainAPI + + ChainGetTipSet(context.Context, types.TipSetKey) (*types.TipSet, error) +} + +type fastAPI struct { + FastChainApiAPI +} + +func WrapFastAPI(api FastChainApiAPI) ChainAPI { + return &fastAPI{ + api, + } +} + +func (a *fastAPI) StateGetActor(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*types.Actor, error) { + ts, err := a.FastChainApiAPI.ChainGetTipSet(ctx, tsk) + if err != nil { + return nil, err + } + + return a.FastChainApiAPI.StateGetActor(ctx, actor, ts.Parents()) +} diff --git a/chain/events/state/mock/api.go b/chain/events/state/mock/api.go new file mode 100644 index 000000000..cdec42659 --- /dev/null +++ b/chain/events/state/mock/api.go @@ -0,0 +1,76 @@ +package test + +import ( + "context" + "sync" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + + "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/chain/types" +) + +type MockAPI struct { + bs blockstore.Blockstore + + lk sync.Mutex + ts map[types.TipSetKey]*types.Actor + stateGetActorCalled int +} + +func NewMockAPI(bs blockstore.Blockstore) *MockAPI { + return &MockAPI{ + bs: bs, + ts: make(map[types.TipSetKey]*types.Actor), + } +} + +func (m *MockAPI) ChainHasObj(ctx context.Context, c cid.Cid) (bool, error) { + return m.bs.Has(ctx, c) +} + +func (m *MockAPI) ChainReadObj(ctx context.Context, c cid.Cid) ([]byte, error) { + blk, err := m.bs.Get(ctx, c) + if err != nil { + return nil, xerrors.Errorf("blockstore get: %w", err) + } + + return blk.RawData(), nil +} + +func (m *MockAPI) ChainPutObj(ctx context.Context, block blocks.Block) error { + return m.bs.Put(ctx, block) +} + +func (m *MockAPI) StateGetActor(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*types.Actor, error) { + m.lk.Lock() + defer m.lk.Unlock() + + m.stateGetActorCalled++ + return m.ts[tsk], nil +} + +func (m *MockAPI) StateGetActorCallCount() int { + m.lk.Lock() + defer m.lk.Unlock() + + return m.stateGetActorCalled +} + +func (m *MockAPI) ResetCallCounts() { + m.lk.Lock() + defer m.lk.Unlock() + + m.stateGetActorCalled = 0 +} + +func (m *MockAPI) SetActor(tsk types.TipSetKey, act *types.Actor) { + m.lk.Lock() + defer m.lk.Unlock() + + m.ts[tsk] = act +} diff --git a/chain/events/state/mock/state.go b/chain/events/state/mock/state.go new file mode 100644 index 000000000..e3c6dde57 --- /dev/null +++ b/chain/events/state/mock/state.go @@ -0,0 +1,32 @@ +package test + +import ( + "context" + "testing" + + "github.com/ipfs/go-cid" + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/specs-actors/v2/actors/builtin/market" + "github.com/filecoin-project/specs-actors/v2/actors/util/adt" +) + +func CreateEmptyMarketState(t *testing.T, store adt.Store) *market.State { + emptyArrayCid, err := adt.MakeEmptyArray(store).Root() + require.NoError(t, err) + emptyMap, err := adt.MakeEmptyMap(store).Root() + require.NoError(t, err) + return market.ConstructState(emptyArrayCid, emptyMap, emptyMap) +} + +func CreateDealAMT(ctx context.Context, t *testing.T, store adt.Store, deals map[abi.DealID]*market.DealState) cid.Cid { + root := adt.MakeEmptyArray(store) + for dealID, dealState := range deals { + err := root.Set(uint64(dealID), dealState) + require.NoError(t, err) + } + rootCid, err := root.Root() + require.NoError(t, err) + return rootCid +} diff --git a/chain/events/state/mock/tipset.go b/chain/events/state/mock/tipset.go new file mode 100644 index 000000000..0d25b8790 --- /dev/null +++ b/chain/events/state/mock/tipset.go @@ -0,0 +1,29 @@ +package test + +import ( + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/crypto" + + "github.com/filecoin-project/lotus/chain/types" +) + +var dummyCid cid.Cid + +func init() { + dummyCid, _ = cid.Parse("bafkqaaa") +} + +func MockTipset(minerAddr address.Address, timestamp uint64) (*types.TipSet, error) { + return types.NewTipSet([]*types.BlockHeader{{ + Miner: minerAddr, + Height: 5, + ParentStateRoot: dummyCid, + Messages: dummyCid, + ParentMessageReceipts: dummyCid, + BlockSig: &crypto.Signature{Type: crypto.SigTypeBLS}, + BLSAggregate: &crypto.Signature{Type: crypto.SigTypeBLS}, + Timestamp: timestamp, + }}) +} diff --git a/chain/events/state/predicates.go b/chain/events/state/predicates.go new file mode 100644 index 000000000..ff05156a6 --- /dev/null +++ b/chain/events/state/predicates.go @@ -0,0 +1,434 @@ +package state + +import ( + "context" + + cbor "github.com/ipfs/go-ipld-cbor" + + "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/lotus/api" + "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/chain/actors/adt" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/actors/builtin/market" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/builtin/paych" + "github.com/filecoin-project/lotus/chain/types" +) + +// UserData is the data returned from the DiffTipSetKeyFunc +type UserData interface{} + +// ChainAPI abstracts out calls made by this class to external APIs +type ChainAPI interface { + api.ChainIO + StateGetActor(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*types.Actor, error) +} + +// StatePredicates has common predicates for responding to state changes +type StatePredicates struct { + api ChainAPI + cst *cbor.BasicIpldStore +} + +func NewStatePredicates(api ChainAPI) *StatePredicates { + return &StatePredicates{ + api: api, + cst: cbor.NewCborStore(blockstore.NewAPIBlockstore(api)), + } +} + +// DiffTipSetKeyFunc check if there's a change form oldState to newState, and returns +// - changed: was there a change +// - user: user-defined data representing the state change +// - err +type DiffTipSetKeyFunc func(ctx context.Context, oldState, newState types.TipSetKey) (changed bool, user UserData, err error) + +type DiffActorStateFunc func(ctx context.Context, oldActorState *types.Actor, newActorState *types.Actor) (changed bool, user UserData, err error) + +// OnActorStateChanged calls diffStateFunc when the state changes for the given actor +func (sp *StatePredicates) OnActorStateChanged(addr address.Address, diffStateFunc DiffActorStateFunc) DiffTipSetKeyFunc { + return func(ctx context.Context, oldState, newState types.TipSetKey) (changed bool, user UserData, err error) { + oldActor, err := sp.api.StateGetActor(ctx, addr, oldState) + if err != nil { + return false, nil, err + } + newActor, err := sp.api.StateGetActor(ctx, addr, newState) + if err != nil { + return false, nil, err + } + + if oldActor.Head.Equals(newActor.Head) { + return false, nil, nil + } + return diffStateFunc(ctx, oldActor, newActor) + } +} + +type DiffStorageMarketStateFunc func(ctx context.Context, oldState market.State, newState market.State) (changed bool, user UserData, err error) + +// OnStorageMarketActorChanged calls diffStorageMarketState when the state changes for the market actor +func (sp *StatePredicates) OnStorageMarketActorChanged(diffStorageMarketState DiffStorageMarketStateFunc) DiffTipSetKeyFunc { + return sp.OnActorStateChanged(market.Address, func(ctx context.Context, oldActorState, newActorState *types.Actor) (changed bool, user UserData, err error) { + oldState, err := market.Load(adt.WrapStore(ctx, sp.cst), oldActorState) + if err != nil { + return false, nil, err + } + newState, err := market.Load(adt.WrapStore(ctx, sp.cst), newActorState) + if err != nil { + return false, nil, err + } + return diffStorageMarketState(ctx, oldState, newState) + }) +} + +type BalanceTables struct { + EscrowTable market.BalanceTable + LockedTable market.BalanceTable +} + +// DiffBalanceTablesFunc compares two balance tables +type DiffBalanceTablesFunc func(ctx context.Context, oldBalanceTable, newBalanceTable BalanceTables) (changed bool, user UserData, err error) + +// OnBalanceChanged runs when the escrow table for available balances changes +func (sp *StatePredicates) OnBalanceChanged(diffBalances DiffBalanceTablesFunc) DiffStorageMarketStateFunc { + return func(ctx context.Context, oldState market.State, newState market.State) (changed bool, user UserData, err error) { + bc, err := oldState.BalancesChanged(newState) + if err != nil { + return false, nil, err + } + + if !bc { + return false, nil, nil + } + + oldEscrowRoot, err := oldState.EscrowTable() + if err != nil { + return false, nil, err + } + + oldLockedRoot, err := oldState.LockedTable() + if err != nil { + return false, nil, err + } + + newEscrowRoot, err := newState.EscrowTable() + if err != nil { + return false, nil, err + } + + newLockedRoot, err := newState.LockedTable() + if err != nil { + return false, nil, err + } + + return diffBalances(ctx, BalanceTables{oldEscrowRoot, oldLockedRoot}, BalanceTables{newEscrowRoot, newLockedRoot}) + } +} + +type DiffDealStatesFunc func(ctx context.Context, oldDealStateRoot, newDealStateRoot market.DealStates) (changed bool, user UserData, err error) +type DiffDealProposalsFunc func(ctx context.Context, oldDealStateRoot, newDealStateRoot market.DealProposals) (changed bool, user UserData, err error) +type DiffAdtArraysFunc func(ctx context.Context, oldDealStateRoot, newDealStateRoot adt.Array) (changed bool, user UserData, err error) + +// OnDealStateChanged calls diffDealStates when the market deal state changes +func (sp *StatePredicates) OnDealStateChanged(diffDealStates DiffDealStatesFunc) DiffStorageMarketStateFunc { + return func(ctx context.Context, oldState market.State, newState market.State) (changed bool, user UserData, err error) { + sc, err := oldState.StatesChanged(newState) + if err != nil { + return false, nil, err + } + + if !sc { + return false, nil, nil + } + + oldRoot, err := oldState.States() + if err != nil { + return false, nil, err + } + newRoot, err := newState.States() + if err != nil { + return false, nil, err + } + + return diffDealStates(ctx, oldRoot, newRoot) + } +} + +// OnDealProposalChanged calls diffDealProps when the market proposal state changes +func (sp *StatePredicates) OnDealProposalChanged(diffDealProps DiffDealProposalsFunc) DiffStorageMarketStateFunc { + return func(ctx context.Context, oldState market.State, newState market.State) (changed bool, user UserData, err error) { + pc, err := oldState.ProposalsChanged(newState) + if err != nil { + return false, nil, err + } + + if !pc { + return false, nil, nil + } + + oldRoot, err := oldState.Proposals() + if err != nil { + return false, nil, err + } + newRoot, err := newState.Proposals() + if err != nil { + return false, nil, err + } + + return diffDealProps(ctx, oldRoot, newRoot) + } +} + +// OnDealProposalAmtChanged detects changes in the deal proposal AMT for all deal proposals and returns a MarketProposalsChanges structure containing: +// - Added Proposals +// - Modified Proposals +// - Removed Proposals +func (sp *StatePredicates) OnDealProposalAmtChanged() DiffDealProposalsFunc { + return func(ctx context.Context, oldDealProps, newDealProps market.DealProposals) (changed bool, user UserData, err error) { + proposalChanges, err := market.DiffDealProposals(oldDealProps, newDealProps) + if err != nil { + return false, nil, err + } + + if len(proposalChanges.Added)+len(proposalChanges.Removed) == 0 { + return false, nil, nil + } + + return true, proposalChanges, nil + } +} + +// OnDealStateAmtChanged detects changes in the deal state AMT for all deal states and returns a MarketDealStateChanges structure containing: +// - Added Deals +// - Modified Deals +// - Removed Deals +func (sp *StatePredicates) OnDealStateAmtChanged() DiffDealStatesFunc { + return func(ctx context.Context, oldDealStates, newDealStates market.DealStates) (changed bool, user UserData, err error) { + dealStateChanges, err := market.DiffDealStates(oldDealStates, newDealStates) + if err != nil { + return false, nil, err + } + + if len(dealStateChanges.Added)+len(dealStateChanges.Modified)+len(dealStateChanges.Removed) == 0 { + return false, nil, nil + } + + return true, dealStateChanges, nil + } +} + +// ChangedDeals is a set of changes to deal state +type ChangedDeals map[abi.DealID]market.DealStateChange + +// DealStateChangedForIDs detects changes in the deal state AMT for the given deal IDs +func (sp *StatePredicates) DealStateChangedForIDs(dealIds []abi.DealID) DiffDealStatesFunc { + return func(ctx context.Context, oldDealStates, newDealStates market.DealStates) (changed bool, user UserData, err error) { + changedDeals := make(ChangedDeals) + for _, dealID := range dealIds { + + // If the deal has been removed, we just set it to nil + oldDeal, oldFound, err := oldDealStates.Get(dealID) + if err != nil { + return false, nil, err + } + + newDeal, newFound, err := newDealStates.Get(dealID) + if err != nil { + return false, nil, err + } + + existenceChanged := oldFound != newFound + valueChanged := (oldFound && newFound) && *oldDeal != *newDeal + if existenceChanged || valueChanged { + changedDeals[dealID] = market.DealStateChange{ID: dealID, From: oldDeal, To: newDeal} + } + } + if len(changedDeals) > 0 { + return true, changedDeals, nil + } + return false, nil, nil + } +} + +// ChangedBalances is a set of changes to deal state +type ChangedBalances map[address.Address]BalanceChange + +// BalanceChange is a change in balance from -> to +type BalanceChange struct { + From abi.TokenAmount + To abi.TokenAmount +} + +// AvailableBalanceChangedForAddresses detects changes in the escrow table for the given addresses +func (sp *StatePredicates) AvailableBalanceChangedForAddresses(getAddrs func() []address.Address) DiffBalanceTablesFunc { + return func(ctx context.Context, oldBalances, newBalances BalanceTables) (changed bool, user UserData, err error) { + changedBalances := make(ChangedBalances) + addrs := getAddrs() + for _, addr := range addrs { + // If the deal has been removed, we just set it to nil + oldEscrowBalance, err := oldBalances.EscrowTable.Get(addr) + if err != nil { + return false, nil, err + } + + oldLockedBalance, err := oldBalances.LockedTable.Get(addr) + if err != nil { + return false, nil, err + } + + oldBalance := big.Sub(oldEscrowBalance, oldLockedBalance) + + newEscrowBalance, err := newBalances.EscrowTable.Get(addr) + if err != nil { + return false, nil, err + } + + newLockedBalance, err := newBalances.LockedTable.Get(addr) + if err != nil { + return false, nil, err + } + + newBalance := big.Sub(newEscrowBalance, newLockedBalance) + + if !oldBalance.Equals(newBalance) { + changedBalances[addr] = BalanceChange{oldBalance, newBalance} + } + } + if len(changedBalances) > 0 { + return true, changedBalances, nil + } + return false, nil, nil + } +} + +type DiffMinerActorStateFunc func(ctx context.Context, oldState miner.State, newState miner.State) (changed bool, user UserData, err error) + +func (sp *StatePredicates) OnInitActorChange(diffInitActorState DiffInitActorStateFunc) DiffTipSetKeyFunc { + return sp.OnActorStateChanged(init_.Address, func(ctx context.Context, oldActorState, newActorState *types.Actor) (changed bool, user UserData, err error) { + oldState, err := init_.Load(adt.WrapStore(ctx, sp.cst), oldActorState) + if err != nil { + return false, nil, err + } + newState, err := init_.Load(adt.WrapStore(ctx, sp.cst), newActorState) + if err != nil { + return false, nil, err + } + return diffInitActorState(ctx, oldState, newState) + }) + +} + +func (sp *StatePredicates) OnMinerActorChange(minerAddr address.Address, diffMinerActorState DiffMinerActorStateFunc) DiffTipSetKeyFunc { + return sp.OnActorStateChanged(minerAddr, func(ctx context.Context, oldActorState, newActorState *types.Actor) (changed bool, user UserData, err error) { + oldState, err := miner.Load(adt.WrapStore(ctx, sp.cst), oldActorState) + if err != nil { + return false, nil, err + } + newState, err := miner.Load(adt.WrapStore(ctx, sp.cst), newActorState) + if err != nil { + return false, nil, err + } + return diffMinerActorState(ctx, oldState, newState) + }) +} + +func (sp *StatePredicates) OnMinerSectorChange() DiffMinerActorStateFunc { + return func(ctx context.Context, oldState, newState miner.State) (changed bool, user UserData, err error) { + sectorChanges, err := miner.DiffSectors(oldState, newState) + if err != nil { + return false, nil, err + } + // nothing changed + if len(sectorChanges.Added)+len(sectorChanges.Extended)+len(sectorChanges.Removed) == 0 { + return false, nil, nil + } + + return true, sectorChanges, nil + } +} + +func (sp *StatePredicates) OnMinerPreCommitChange() DiffMinerActorStateFunc { + return func(ctx context.Context, oldState, newState miner.State) (changed bool, user UserData, err error) { + precommitChanges, err := miner.DiffPreCommits(oldState, newState) + if err != nil { + return false, nil, err + } + + if len(precommitChanges.Added)+len(precommitChanges.Removed) == 0 { + return false, nil, nil + } + + return true, precommitChanges, nil + } +} + +// DiffPaymentChannelStateFunc is function that compares two states for the payment channel +type DiffPaymentChannelStateFunc func(ctx context.Context, oldState paych.State, newState paych.State) (changed bool, user UserData, err error) + +// OnPaymentChannelActorChanged calls diffPaymentChannelState when the state changes for the the payment channel actor +func (sp *StatePredicates) OnPaymentChannelActorChanged(paychAddr address.Address, diffPaymentChannelState DiffPaymentChannelStateFunc) DiffTipSetKeyFunc { + return sp.OnActorStateChanged(paychAddr, func(ctx context.Context, oldActorState, newActorState *types.Actor) (changed bool, user UserData, err error) { + oldState, err := paych.Load(adt.WrapStore(ctx, sp.cst), oldActorState) + if err != nil { + return false, nil, err + } + newState, err := paych.Load(adt.WrapStore(ctx, sp.cst), newActorState) + if err != nil { + return false, nil, err + } + return diffPaymentChannelState(ctx, oldState, newState) + }) +} + +// PayChToSendChange is a difference in the amount to send on a payment channel when the money is collected +type PayChToSendChange struct { + OldToSend abi.TokenAmount + NewToSend abi.TokenAmount +} + +// OnToSendAmountChanges monitors changes on the total amount to send from one party to the other on a payment channel +func (sp *StatePredicates) OnToSendAmountChanges() DiffPaymentChannelStateFunc { + return func(ctx context.Context, oldState paych.State, newState paych.State) (changed bool, user UserData, err error) { + ots, err := oldState.ToSend() + if err != nil { + return false, nil, err + } + + nts, err := newState.ToSend() + if err != nil { + return false, nil, err + } + + if ots.Equals(nts) { + return false, nil, nil + } + return true, &PayChToSendChange{ + OldToSend: ots, + NewToSend: nts, + }, nil + } +} + +type AddressPair struct { + ID address.Address + PK address.Address +} + +type DiffInitActorStateFunc func(ctx context.Context, oldState init_.State, newState init_.State) (changed bool, user UserData, err error) + +func (sp *StatePredicates) OnAddressMapChange() DiffInitActorStateFunc { + return func(ctx context.Context, oldState, newState init_.State) (changed bool, user UserData, err error) { + addressChanges, err := init_.DiffAddressMap(oldState, newState) + if err != nil { + return false, nil, err + } + if len(addressChanges.Added)+len(addressChanges.Modified)+len(addressChanges.Removed) == 0 { + return false, nil, nil + } + return true, addressChanges, nil + } +} diff --git a/chain/events/state/predicates_test.go b/chain/events/state/predicates_test.go new file mode 100644 index 000000000..52fc2668a --- /dev/null +++ b/chain/events/state/predicates_test.go @@ -0,0 +1,585 @@ +// stm: #unit +package state + +import ( + "context" + "testing" + + "github.com/ipfs/go-cid" + cbornode "github.com/ipfs/go-ipld-cbor" + "github.com/stretchr/testify/require" + + "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/big" + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market" + miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/miner" + adt2 "github.com/filecoin-project/specs-actors/v2/actors/util/adt" + tutils "github.com/filecoin-project/specs-actors/v5/support/testing" + + bstore "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/chain/actors/builtin/market" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + test "github.com/filecoin-project/lotus/chain/events/state/mock" + "github.com/filecoin-project/lotus/chain/types" +) + +var dummyCid cid.Cid + +func init() { + dummyCid, _ = cid.Parse("bafkqaaa") +} + +func TestMarketPredicates(t *testing.T) { + //stm: @EVENTS_PREDICATES_ON_ACTOR_STATE_CHANGED_001, @EVENTS_PREDICATES_DEAL_STATE_CHANGED_001 + //stm: @EVENTS_PREDICATES_DEAL_CHANGED_FOR_IDS + + //stm: @EVENTS_PREDICATES_ON_BALANCE_CHANGED_001, @EVENTS_PREDICATES_BALANCE_CHANGED_FOR_ADDRESS_001 + //stm: @EVENTS_PREDICATES_ON_DEAL_PROPOSAL_CHANGED_001, @EVENTS_PREDICATES_PROPOSAL_AMT_CHANGED_001 + //stm: @EVENTS_PREDICATES_DEAL_STATE_CHANGED_001, @EVENTS_PREDICATES_DEAL_AMT_CHANGED_001 + ctx := context.Background() + bs := bstore.NewMemorySync() + store := adt2.WrapStore(ctx, cbornode.NewCborStore(bs)) + + oldDeal1 := &market2.DealState{ + SectorStartEpoch: 1, + LastUpdatedEpoch: 2, + SlashEpoch: 0, + } + oldDeal2 := &market2.DealState{ + SectorStartEpoch: 4, + LastUpdatedEpoch: 5, + SlashEpoch: 0, + } + oldDeals := map[abi.DealID]*market2.DealState{ + abi.DealID(1): oldDeal1, + abi.DealID(2): oldDeal2, + } + + oldProp1 := &market2.DealProposal{ + PieceCID: dummyCid, + PieceSize: 0, + VerifiedDeal: false, + Client: tutils.NewIDAddr(t, 1), + Provider: tutils.NewIDAddr(t, 1), + StartEpoch: 1, + EndEpoch: 2, + StoragePricePerEpoch: big.Zero(), + ProviderCollateral: big.Zero(), + ClientCollateral: big.Zero(), + } + oldProp2 := &market2.DealProposal{ + PieceCID: dummyCid, + PieceSize: 0, + VerifiedDeal: false, + Client: tutils.NewIDAddr(t, 1), + Provider: tutils.NewIDAddr(t, 1), + StartEpoch: 2, + EndEpoch: 3, + StoragePricePerEpoch: big.Zero(), + ProviderCollateral: big.Zero(), + ClientCollateral: big.Zero(), + } + oldProps := map[abi.DealID]*market2.DealProposal{ + abi.DealID(1): oldProp1, + abi.DealID(2): oldProp2, + } + + oldBalances := map[address.Address]balance{ + tutils.NewIDAddr(t, 1): {abi.NewTokenAmount(1000), abi.NewTokenAmount(1000)}, + tutils.NewIDAddr(t, 2): {abi.NewTokenAmount(2000), abi.NewTokenAmount(500)}, + tutils.NewIDAddr(t, 3): {abi.NewTokenAmount(3000), abi.NewTokenAmount(2000)}, + tutils.NewIDAddr(t, 5): {abi.NewTokenAmount(3000), abi.NewTokenAmount(1000)}, + } + + oldStateC := createMarketState(ctx, t, store, oldDeals, oldProps, oldBalances) + + newDeal1 := &market2.DealState{ + SectorStartEpoch: 1, + LastUpdatedEpoch: 3, + SlashEpoch: 0, + } + + // deal 2 removed + + // added + newDeal3 := &market2.DealState{ + SectorStartEpoch: 1, + LastUpdatedEpoch: 2, + SlashEpoch: 3, + } + newDeals := map[abi.DealID]*market2.DealState{ + abi.DealID(1): newDeal1, + // deal 2 was removed + abi.DealID(3): newDeal3, + } + + // added + newProp3 := &market2.DealProposal{ + PieceCID: dummyCid, + PieceSize: 0, + VerifiedDeal: false, + Client: tutils.NewIDAddr(t, 1), + Provider: tutils.NewIDAddr(t, 1), + StartEpoch: 4, + EndEpoch: 4, + StoragePricePerEpoch: big.Zero(), + ProviderCollateral: big.Zero(), + ClientCollateral: big.Zero(), + } + newProps := map[abi.DealID]*market2.DealProposal{ + abi.DealID(1): oldProp1, // 1 was persisted + // prop 2 was removed + abi.DealID(3): newProp3, // new + // NB: DealProposals cannot be modified, so don't test that case. + } + newBalances := map[address.Address]balance{ + tutils.NewIDAddr(t, 1): {abi.NewTokenAmount(3000), abi.NewTokenAmount(0)}, + tutils.NewIDAddr(t, 2): {abi.NewTokenAmount(2000), abi.NewTokenAmount(500)}, + tutils.NewIDAddr(t, 4): {abi.NewTokenAmount(5000), abi.NewTokenAmount(0)}, + tutils.NewIDAddr(t, 5): {abi.NewTokenAmount(1000), abi.NewTokenAmount(3000)}, + } + + newStateC := createMarketState(ctx, t, store, newDeals, newProps, newBalances) + + minerAddr, err := address.NewFromString("t00") + require.NoError(t, err) + oldState, err := test.MockTipset(minerAddr, 1) + require.NoError(t, err) + newState, err := test.MockTipset(minerAddr, 2) + require.NoError(t, err) + + api := test.NewMockAPI(bs) + api.SetActor(oldState.Key(), &types.Actor{Code: builtin2.StorageMarketActorCodeID, Head: oldStateC}) + api.SetActor(newState.Key(), &types.Actor{Code: builtin2.StorageMarketActorCodeID, Head: newStateC}) + + t.Run("deal ID predicate", func(t *testing.T) { + preds := NewStatePredicates(api) + + dealIds := []abi.DealID{abi.DealID(1), abi.DealID(2)} + diffIDFn := preds.OnStorageMarketActorChanged(preds.OnDealStateChanged(preds.DealStateChangedForIDs(dealIds))) + + // Diff a state against itself: expect no change + changed, _, err := diffIDFn(ctx, oldState.Key(), oldState.Key()) + require.NoError(t, err) + require.False(t, changed) + + // Diff old state against new state + changed, valIDs, err := diffIDFn(ctx, oldState.Key(), newState.Key()) + require.NoError(t, err) + require.True(t, changed) + + changedDealIDs, ok := valIDs.(ChangedDeals) + require.True(t, ok) + require.Len(t, changedDealIDs, 2) + require.Contains(t, changedDealIDs, abi.DealID(1)) + require.Contains(t, changedDealIDs, abi.DealID(2)) + deal1 := changedDealIDs[abi.DealID(1)] + if deal1.From.LastUpdatedEpoch != 2 || deal1.To.LastUpdatedEpoch != 3 { + t.Fatal("Unexpected change to LastUpdatedEpoch") + } + deal2 := changedDealIDs[abi.DealID(2)] + if deal2.From.LastUpdatedEpoch != 5 || deal2.To != nil { + t.Fatal("Expected To to be nil") + } + + // Diff with non-existent deal. + noDeal := []abi.DealID{4} + diffNoDealFn := preds.OnStorageMarketActorChanged(preds.OnDealStateChanged(preds.DealStateChangedForIDs(noDeal))) + changed, _, err = diffNoDealFn(ctx, oldState.Key(), newState.Key()) + require.NoError(t, err) + require.False(t, changed) + + // Test that OnActorStateChanged does not call the callback if the state has not changed + mockAddr, err := address.NewFromString("t01") + require.NoError(t, err) + actorDiffFn := preds.OnActorStateChanged(mockAddr, func(context.Context, *types.Actor, *types.Actor) (bool, UserData, error) { + t.Fatal("No state change so this should not be called") + return false, nil, nil + }) + changed, _, err = actorDiffFn(ctx, oldState.Key(), oldState.Key()) + require.NoError(t, err) + require.False(t, changed) + + // Test that OnDealStateChanged does not call the callback if the state has not changed + diffDealStateFn := preds.OnDealStateChanged(func(context.Context, market.DealStates, market.DealStates) (bool, UserData, error) { + t.Fatal("No state change so this should not be called") + return false, nil, nil + }) + marketState0 := test.CreateEmptyMarketState(t, store) + marketCid, err := store.Put(ctx, marketState0) + require.NoError(t, err) + marketState, err := market.Load(store, &types.Actor{ + Code: builtin2.StorageMarketActorCodeID, + Head: marketCid, + }) + require.NoError(t, err) + changed, _, err = diffDealStateFn(ctx, marketState, marketState) + require.NoError(t, err) + require.False(t, changed) + }) + + t.Run("deal state array predicate", func(t *testing.T) { + preds := NewStatePredicates(api) + diffArrFn := preds.OnStorageMarketActorChanged(preds.OnDealStateChanged(preds.OnDealStateAmtChanged())) + + changed, _, err := diffArrFn(ctx, oldState.Key(), oldState.Key()) + require.NoError(t, err) + require.False(t, changed) + + changed, valArr, err := diffArrFn(ctx, oldState.Key(), newState.Key()) + require.NoError(t, err) + require.True(t, changed) + + changedDeals, ok := valArr.(*market.DealStateChanges) + require.True(t, ok) + require.Len(t, changedDeals.Added, 1) + require.Equal(t, abi.DealID(3), changedDeals.Added[0].ID) + require.True(t, dealEquality(*newDeal3, changedDeals.Added[0].Deal)) + + require.Len(t, changedDeals.Removed, 1) + + require.Len(t, changedDeals.Modified, 1) + require.Equal(t, abi.DealID(1), changedDeals.Modified[0].ID) + require.True(t, dealEquality(*newDeal1, *changedDeals.Modified[0].To)) + require.True(t, dealEquality(*oldDeal1, *changedDeals.Modified[0].From)) + + require.Equal(t, abi.DealID(2), changedDeals.Removed[0].ID) + }) + + t.Run("deal proposal array predicate", func(t *testing.T) { + preds := NewStatePredicates(api) + diffArrFn := preds.OnStorageMarketActorChanged(preds.OnDealProposalChanged(preds.OnDealProposalAmtChanged())) + changed, _, err := diffArrFn(ctx, oldState.Key(), oldState.Key()) + require.NoError(t, err) + require.False(t, changed) + + changed, valArr, err := diffArrFn(ctx, oldState.Key(), newState.Key()) + require.NoError(t, err) + require.True(t, changed) + + changedProps, ok := valArr.(*market.DealProposalChanges) + require.True(t, ok) + require.Len(t, changedProps.Added, 1) + require.Equal(t, abi.DealID(3), changedProps.Added[0].ID) + + // proposals cannot be modified -- no modified testing + + require.Len(t, changedProps.Removed, 1) + require.Equal(t, abi.DealID(2), changedProps.Removed[0].ID) + }) + + t.Run("balances predicate", func(t *testing.T) { + preds := NewStatePredicates(api) + + getAddresses := func() []address.Address { + return []address.Address{tutils.NewIDAddr(t, 1), tutils.NewIDAddr(t, 2), tutils.NewIDAddr(t, 3), tutils.NewIDAddr(t, 4)} + } + diffBalancesFn := preds.OnStorageMarketActorChanged(preds.OnBalanceChanged(preds.AvailableBalanceChangedForAddresses(getAddresses))) + + // Diff a state against itself: expect no change + changed, _, err := diffBalancesFn(ctx, oldState.Key(), oldState.Key()) + require.NoError(t, err) + require.False(t, changed) + + // Diff old state against new state + changed, valIDs, err := diffBalancesFn(ctx, oldState.Key(), newState.Key()) + require.NoError(t, err) + require.True(t, changed) + + changedBalances, ok := valIDs.(ChangedBalances) + require.True(t, ok) + require.Len(t, changedBalances, 3) + require.Contains(t, changedBalances, tutils.NewIDAddr(t, 1)) + require.Contains(t, changedBalances, tutils.NewIDAddr(t, 3)) + require.Contains(t, changedBalances, tutils.NewIDAddr(t, 4)) + + balance1 := changedBalances[tutils.NewIDAddr(t, 1)] + if !balance1.From.Equals(abi.NewTokenAmount(1000)) || !balance1.To.Equals(abi.NewTokenAmount(3000)) { + t.Fatal("Unexpected change to balance") + } + balance3 := changedBalances[tutils.NewIDAddr(t, 3)] + if !balance3.From.Equals(abi.NewTokenAmount(3000)) || !balance3.To.Equals(abi.NewTokenAmount(0)) { + t.Fatal("Unexpected change to balance") + } + balance4 := changedBalances[tutils.NewIDAddr(t, 4)] + if !balance4.From.Equals(abi.NewTokenAmount(0)) || !balance4.To.Equals(abi.NewTokenAmount(5000)) { + t.Fatal("Unexpected change to balance") + } + + // Diff with non-existent address. + getNoAddress := func() []address.Address { return []address.Address{tutils.NewIDAddr(t, 6)} } + diffNoAddressFn := preds.OnStorageMarketActorChanged(preds.OnBalanceChanged(preds.AvailableBalanceChangedForAddresses(getNoAddress))) + changed, _, err = diffNoAddressFn(ctx, oldState.Key(), newState.Key()) + require.NoError(t, err) + require.False(t, changed) + + // Test that OnBalanceChanged does not call the callback if the state has not changed + diffDealBalancesFn := preds.OnBalanceChanged(func(context.Context, BalanceTables, BalanceTables) (bool, UserData, error) { + t.Fatal("No state change so this should not be called") + return false, nil, nil + }) + marketState0 := test.CreateEmptyMarketState(t, store) + marketCid, err := store.Put(ctx, marketState0) + require.NoError(t, err) + marketState, err := market.Load(store, &types.Actor{ + Code: builtin2.StorageMarketActorCodeID, + Head: marketCid, + }) + require.NoError(t, err) + changed, _, err = diffDealBalancesFn(ctx, marketState, marketState) + require.NoError(t, err) + require.False(t, changed) + }) + +} + +func TestMinerSectorChange(t *testing.T) { + //stm: @EVENTS_PREDICATES_ON_ACTOR_STATE_CHANGED_001, @EVENTS_PREDICATES_MINER_ACTOR_CHANGE_001 + //stm: @EVENTS_PREDICATES_MINER_SECTOR_CHANGE_001 + ctx := context.Background() + bs := bstore.NewMemorySync() + store := adt2.WrapStore(ctx, cbornode.NewCborStore(bs)) + + nextID := uint64(0) + nextIDAddrF := func() address.Address { + defer func() { nextID++ }() + return tutils.NewIDAddr(t, nextID) + } + + owner, worker := nextIDAddrF(), nextIDAddrF() + si0 := newSectorOnChainInfo(0, tutils.MakeCID("0", &miner2.SealedCIDPrefix), big.NewInt(0), abi.ChainEpoch(0), abi.ChainEpoch(10)) + si1 := newSectorOnChainInfo(1, tutils.MakeCID("1", &miner2.SealedCIDPrefix), big.NewInt(1), abi.ChainEpoch(1), abi.ChainEpoch(11)) + si2 := newSectorOnChainInfo(2, tutils.MakeCID("2", &miner2.SealedCIDPrefix), big.NewInt(2), abi.ChainEpoch(2), abi.ChainEpoch(11)) + oldMinerC := createMinerState(ctx, t, store, owner, worker, []miner.SectorOnChainInfo{si0, si1, si2}) + + si3 := newSectorOnChainInfo(3, tutils.MakeCID("3", &miner2.SealedCIDPrefix), big.NewInt(3), abi.ChainEpoch(3), abi.ChainEpoch(12)) + // 0 delete + // 1 extend + // 2 same + // 3 added + si1Ext := si1 + si1Ext.Expiration++ + newMinerC := createMinerState(ctx, t, store, owner, worker, []miner.SectorOnChainInfo{si1Ext, si2, si3}) + + minerAddr := nextIDAddrF() + oldState, err := test.MockTipset(minerAddr, 1) + require.NoError(t, err) + newState, err := test.MockTipset(minerAddr, 2) + require.NoError(t, err) + + api := test.NewMockAPI(bs) + api.SetActor(oldState.Key(), &types.Actor{Head: oldMinerC, Code: builtin2.StorageMinerActorCodeID}) + api.SetActor(newState.Key(), &types.Actor{Head: newMinerC, Code: builtin2.StorageMinerActorCodeID}) + + preds := NewStatePredicates(api) + + minerDiffFn := preds.OnMinerActorChange(minerAddr, preds.OnMinerSectorChange()) + change, val, err := minerDiffFn(ctx, oldState.Key(), newState.Key()) + require.NoError(t, err) + require.True(t, change) + require.NotNil(t, val) + + sectorChanges, ok := val.(*miner.SectorChanges) + require.True(t, ok) + + require.Equal(t, len(sectorChanges.Added), 1) + require.Equal(t, 1, len(sectorChanges.Added)) + require.Equal(t, si3, sectorChanges.Added[0]) + + require.Equal(t, 1, len(sectorChanges.Removed)) + require.Equal(t, si0, sectorChanges.Removed[0]) + + require.Equal(t, 1, len(sectorChanges.Extended)) + require.Equal(t, si1, sectorChanges.Extended[0].From) + require.Equal(t, si1Ext, sectorChanges.Extended[0].To) + + change, val, err = minerDiffFn(ctx, oldState.Key(), oldState.Key()) + require.NoError(t, err) + require.False(t, change) + require.Nil(t, val) + + change, val, err = minerDiffFn(ctx, newState.Key(), oldState.Key()) + require.NoError(t, err) + require.True(t, change) + require.NotNil(t, val) + + sectorChanges, ok = val.(*miner.SectorChanges) + require.True(t, ok) + + require.Equal(t, 1, len(sectorChanges.Added)) + require.Equal(t, si0, sectorChanges.Added[0]) + + require.Equal(t, 1, len(sectorChanges.Removed)) + require.Equal(t, si3, sectorChanges.Removed[0]) + + require.Equal(t, 1, len(sectorChanges.Extended)) + require.Equal(t, si1, sectorChanges.Extended[0].To) + require.Equal(t, si1Ext, sectorChanges.Extended[0].From) +} + +type balance struct { + available abi.TokenAmount + locked abi.TokenAmount +} + +func createMarketState(ctx context.Context, t *testing.T, store adt2.Store, deals map[abi.DealID]*market2.DealState, props map[abi.DealID]*market2.DealProposal, balances map[address.Address]balance) cid.Cid { + dealRootCid := test.CreateDealAMT(ctx, t, store, deals) + propRootCid := createProposalAMT(ctx, t, store, props) + balancesCids := createBalanceTable(ctx, t, store, balances) + state := test.CreateEmptyMarketState(t, store) + state.States = dealRootCid + state.Proposals = propRootCid + state.EscrowTable = balancesCids[0] + state.LockedTable = balancesCids[1] + + stateC, err := store.Put(ctx, state) + require.NoError(t, err) + return stateC +} + +func createProposalAMT(ctx context.Context, t *testing.T, store adt2.Store, props map[abi.DealID]*market2.DealProposal) cid.Cid { + root := adt2.MakeEmptyArray(store) + for dealID, prop := range props { + err := root.Set(uint64(dealID), prop) + require.NoError(t, err) + } + rootCid, err := root.Root() + require.NoError(t, err) + return rootCid +} + +func createBalanceTable(ctx context.Context, t *testing.T, store adt2.Store, balances map[address.Address]balance) [2]cid.Cid { + escrowMapRoot := adt2.MakeEmptyMap(store) + escrowMapRootCid, err := escrowMapRoot.Root() + require.NoError(t, err) + escrowRoot, err := adt2.AsBalanceTable(store, escrowMapRootCid) + require.NoError(t, err) + lockedMapRoot := adt2.MakeEmptyMap(store) + lockedMapRootCid, err := lockedMapRoot.Root() + require.NoError(t, err) + lockedRoot, err := adt2.AsBalanceTable(store, lockedMapRootCid) + require.NoError(t, err) + + for addr, balance := range balances { + err := escrowRoot.Add(addr, big.Add(balance.available, balance.locked)) + require.NoError(t, err) + err = lockedRoot.Add(addr, balance.locked) + require.NoError(t, err) + + } + escrowRootCid, err := escrowRoot.Root() + require.NoError(t, err) + lockedRootCid, err := lockedRoot.Root() + require.NoError(t, err) + return [2]cid.Cid{escrowRootCid, lockedRootCid} +} + +func createMinerState(ctx context.Context, t *testing.T, store adt2.Store, owner, worker address.Address, sectors []miner.SectorOnChainInfo) cid.Cid { + rootCid := createSectorsAMT(ctx, t, store, sectors) + + state := createEmptyMinerState(ctx, t, store, owner, worker) + state.Sectors = rootCid + + stateC, err := store.Put(ctx, state) + require.NoError(t, err) + return stateC +} + +func createEmptyMinerState(ctx context.Context, t *testing.T, store adt2.Store, owner, worker address.Address) *miner2.State { + emptyArrayCid, err := adt2.MakeEmptyArray(store).Root() + require.NoError(t, err) + emptyMap, err := adt2.MakeEmptyMap(store).Root() + require.NoError(t, err) + + emptyDeadline, err := store.Put(store.Context(), miner2.ConstructDeadline(emptyArrayCid)) + require.NoError(t, err) + + emptyVestingFunds := miner2.ConstructVestingFunds() + emptyVestingFundsCid, err := store.Put(store.Context(), emptyVestingFunds) + require.NoError(t, err) + + emptyDeadlines := miner2.ConstructDeadlines(emptyDeadline) + emptyDeadlinesCid, err := store.Put(store.Context(), emptyDeadlines) + require.NoError(t, err) + + minerInfo := emptyMap + + emptyBitfield := bitfield.NewFromSet(nil) + emptyBitfieldCid, err := store.Put(store.Context(), emptyBitfield) + require.NoError(t, err) + + state, err := miner2.ConstructState(minerInfo, 123, 4, emptyBitfieldCid, emptyArrayCid, emptyMap, emptyDeadlinesCid, emptyVestingFundsCid) + require.NoError(t, err) + return state + +} + +func createSectorsAMT(ctx context.Context, t *testing.T, store adt2.Store, sectors []miner.SectorOnChainInfo) cid.Cid { + root := adt2.MakeEmptyArray(store) + for _, sector := range sectors { + sector := miner2.SectorOnChainInfo{ + SectorNumber: sector.SectorNumber, + SealProof: sector.SealProof, + SealedCID: sector.SealedCID, + DealIDs: sector.DealIDs, + Activation: sector.Activation, + Expiration: sector.Expiration, + DealWeight: sector.DealWeight, + VerifiedDealWeight: sector.VerifiedDealWeight, + InitialPledge: sector.InitialPledge, + ExpectedDayReward: sector.ExpectedDayReward, + ExpectedStoragePledge: sector.ExpectedStoragePledge, + ReplacedSectorAge: 0, + ReplacedDayReward: big.NewInt(0), + } + err := root.Set(uint64(sector.SectorNumber), §or) + require.NoError(t, err) + } + rootCid, err := root.Root() + require.NoError(t, err) + return rootCid +} + +// returns a unique SectorOnChainInfo with each invocation with SectorNumber set to `sectorNo`. +func newSectorOnChainInfo(sectorNo abi.SectorNumber, sealed cid.Cid, weight big.Int, activation, expiration abi.ChainEpoch) miner.SectorOnChainInfo { + info := newSectorPreCommitInfo(sectorNo, sealed, expiration) + return miner.SectorOnChainInfo{ + SectorNumber: info.SectorNumber, + SealProof: info.SealProof, + SealedCID: info.SealedCID, + DealIDs: info.DealIDs, + Expiration: info.Expiration, + + Activation: activation, + DealWeight: weight, + VerifiedDealWeight: weight, + InitialPledge: big.Zero(), + ExpectedDayReward: big.Zero(), + ExpectedStoragePledge: big.Zero(), + } +} + +const ( + sectorSealRandEpochValue = abi.ChainEpoch(1) +) + +// returns a unique SectorPreCommitInfo with each invocation with SectorNumber set to `sectorNo`. +func newSectorPreCommitInfo(sectorNo abi.SectorNumber, sealed cid.Cid, expiration abi.ChainEpoch) *miner2.SectorPreCommitInfo { + return &miner2.SectorPreCommitInfo{ + SealProof: abi.RegisteredSealProof_StackedDrg32GiBV1, + SectorNumber: sectorNo, + SealedCID: sealed, + SealRandEpoch: sectorSealRandEpochValue, + DealIDs: nil, + Expiration: expiration, + } +} + +func dealEquality(expected market2.DealState, actual market.DealState) bool { + return expected.LastUpdatedEpoch == actual.LastUpdatedEpoch && + expected.SectorStartEpoch == actual.SectorStartEpoch && + expected.SlashEpoch == actual.SlashEpoch +} diff --git a/chain/events/tscache.go b/chain/events/tscache.go index 3852c9930..ed19a5f41 100644 --- a/chain/events/tscache.go +++ b/chain/events/tscache.go @@ -2,126 +2,214 @@ package events import ( "context" + "sync" - "github.com/filecoin-project/specs-actors/actors/abi" "golang.org/x/xerrors" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/chain/types" ) -type tsByHFunc func(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error) +type tsCacheAPI interface { + ChainGetTipSetAfterHeight(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error) + ChainGetTipSetByHeight(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error) + ChainGetTipSet(context.Context, types.TipSetKey) (*types.TipSet, error) + ChainHead(context.Context) (*types.TipSet, error) +} // tipSetCache implements a simple ring-buffer cache to keep track of recent // tipsets type tipSetCache struct { - cache []*types.TipSet - start int - len int + mu sync.RWMutex - storage tsByHFunc + byKey map[types.TipSetKey]*types.TipSet + byHeight []*types.TipSet + start int // chain head (end) + len int + + storage tsCacheAPI } -func newTSCache(cap abi.ChainEpoch, storage tsByHFunc) *tipSetCache { +func newTSCache(storage tsCacheAPI, cap abi.ChainEpoch) *tipSetCache { return &tipSetCache{ - cache: make([]*types.TipSet, cap), - start: 0, - len: 0, + byKey: make(map[types.TipSetKey]*types.TipSet, cap), + byHeight: make([]*types.TipSet, cap), + start: 0, + len: 0, storage: storage, } } +func (tsc *tipSetCache) ChainGetTipSet(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error) { + if ts, ok := tsc.byKey[tsk]; ok { + return ts, nil + } + return tsc.storage.ChainGetTipSet(ctx, tsk) +} -func (tsc *tipSetCache) add(ts *types.TipSet) error { - if tsc.len > 0 { - if tsc.cache[tsc.start].Height() >= ts.Height() { - return xerrors.Errorf("tipSetCache.add: expected new tipset height to be at least %d, was %d", tsc.cache[tsc.start].Height()+1, ts.Height()) +func (tsc *tipSetCache) ChainGetTipSetByHeight(ctx context.Context, height abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error) { + return tsc.get(ctx, height, tsk, true) +} + +func (tsc *tipSetCache) ChainGetTipSetAfterHeight(ctx context.Context, height abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error) { + return tsc.get(ctx, height, tsk, false) +} + +func (tsc *tipSetCache) get(ctx context.Context, height abi.ChainEpoch, tsk types.TipSetKey, prev bool) (*types.TipSet, error) { + fallback := tsc.storage.ChainGetTipSetAfterHeight + if prev { + fallback = tsc.storage.ChainGetTipSetByHeight + } + tsc.mu.RLock() + + // Nothing in the cache? + if tsc.len == 0 { + tsc.mu.RUnlock() + log.Warnf("tipSetCache.get: cache is empty, requesting from storage (h=%d)", height) + return fallback(ctx, height, tsk) + } + + // Resolve the head. + head := tsc.byHeight[tsc.start] + if !tsk.IsEmpty() { + // Not on this chain? + var ok bool + head, ok = tsc.byKey[tsk] + if !ok { + tsc.mu.RUnlock() + return fallback(ctx, height, tsk) } } - nextH := ts.Height() + headH := head.Height() + tailH := headH - abi.ChainEpoch(tsc.len) + + if headH == height { + tsc.mu.RUnlock() + return head, nil + } else if headH < height { + tsc.mu.RUnlock() + // If the user doesn't pass a tsk, we assume "head" is the last tipset we processed. + return nil, xerrors.Errorf("requested epoch is in the future") + } else if height < tailH { + log.Warnf("tipSetCache.get: requested tipset not in cache, requesting from storage (h=%d; tail=%d)", height, tailH) + tsc.mu.RUnlock() + return fallback(ctx, height, head.Key()) + } + + direction := 1 + if prev { + direction = -1 + } + var ts *types.TipSet + for i := 0; i < tsc.len && ts == nil; i += direction { + ts = tsc.byHeight[normalModulo(tsc.start-int(headH-height)+i, len(tsc.byHeight))] + } + tsc.mu.RUnlock() + return ts, nil +} + +func (tsc *tipSetCache) ChainHead(ctx context.Context) (*types.TipSet, error) { + tsc.mu.RLock() + best := tsc.byHeight[tsc.start] + tsc.mu.RUnlock() + if best == nil { + return tsc.storage.ChainHead(ctx) + } + return best, nil +} + +func (tsc *tipSetCache) add(to *types.TipSet) error { + tsc.mu.Lock() + defer tsc.mu.Unlock() + if tsc.len > 0 { - nextH = tsc.cache[tsc.start].Height() + 1 + best := tsc.byHeight[tsc.start] + if best.Height() >= to.Height() { + return xerrors.Errorf("tipSetCache.add: expected new tipset height to be at least %d, was %d", tsc.byHeight[tsc.start].Height()+1, to.Height()) + } + if best.Key() != to.Parents() { + return xerrors.Errorf( + "tipSetCache.add: expected new tipset %s (%d) to follow %s (%d), its parents are %s", + to.Key(), to.Height(), best.Key(), best.Height(), best.Parents(), + ) + } + } + + nextH := to.Height() + if tsc.len > 0 { + nextH = tsc.byHeight[tsc.start].Height() + 1 } // fill null blocks - for nextH != ts.Height() { - tsc.start = normalModulo(tsc.start+1, len(tsc.cache)) - tsc.cache[tsc.start] = nil - if tsc.len < len(tsc.cache) { + for nextH != to.Height() { + tsc.start = normalModulo(tsc.start+1, len(tsc.byHeight)) + was := tsc.byHeight[tsc.start] + if was != nil { + tsc.byHeight[tsc.start] = nil + delete(tsc.byKey, was.Key()) + } + if tsc.len < len(tsc.byHeight) { tsc.len++ } nextH++ } - tsc.start = normalModulo(tsc.start+1, len(tsc.cache)) - tsc.cache[tsc.start] = ts - if tsc.len < len(tsc.cache) { + tsc.start = normalModulo(tsc.start+1, len(tsc.byHeight)) + was := tsc.byHeight[tsc.start] + if was != nil { + delete(tsc.byKey, was.Key()) + } + tsc.byHeight[tsc.start] = to + if tsc.len < len(tsc.byHeight) { tsc.len++ } + tsc.byKey[to.Key()] = to return nil } -func (tsc *tipSetCache) revert(ts *types.TipSet) error { +func (tsc *tipSetCache) revert(from *types.TipSet) error { + tsc.mu.Lock() + defer tsc.mu.Unlock() + + return tsc.revertUnlocked(from) +} + +func (tsc *tipSetCache) revertUnlocked(ts *types.TipSet) error { if tsc.len == 0 { return nil // this can happen, and it's fine } - if !tsc.cache[tsc.start].Equals(ts) { + was := tsc.byHeight[tsc.start] + + if !was.Equals(ts) { return xerrors.New("tipSetCache.revert: revert tipset didn't match cache head") } + delete(tsc.byKey, was.Key()) - tsc.cache[tsc.start] = nil - tsc.start = normalModulo(tsc.start-1, len(tsc.cache)) + tsc.byHeight[tsc.start] = nil + tsc.start = normalModulo(tsc.start-1, len(tsc.byHeight)) tsc.len-- - _ = tsc.revert(nil) // revert null block gap + _ = tsc.revertUnlocked(nil) // revert null block gap return nil } -func (tsc *tipSetCache) getNonNull(height abi.ChainEpoch) (*types.TipSet, error) { - for { - ts, err := tsc.get(height) - if err != nil { - return nil, err - } - if ts != nil { - return ts, nil - } - height++ - } +func (tsc *tipSetCache) observer() TipSetObserver { + return (*tipSetCacheObserver)(tsc) } -func (tsc *tipSetCache) get(height abi.ChainEpoch) (*types.TipSet, error) { - if tsc.len == 0 { - log.Warnf("tipSetCache.get: cache is empty, requesting from storage (h=%d)", height) - return tsc.storage(context.TODO(), height, types.EmptyTSK) - } +type tipSetCacheObserver tipSetCache - headH := tsc.cache[tsc.start].Height() +var _ TipSetObserver = new(tipSetCacheObserver) - if height > headH { - return nil, xerrors.Errorf("tipSetCache.get: requested tipset not in cache (req: %d, cache head: %d)", height, headH) - } - - clen := len(tsc.cache) - var tail *types.TipSet - for i := 1; i <= tsc.len; i++ { - tail = tsc.cache[normalModulo(tsc.start-tsc.len+i, clen)] - if tail != nil { - break - } - } - - if height < tail.Height() { - log.Warnf("tipSetCache.get: requested tipset not in cache, requesting from storage (h=%d; tail=%d)", height, tail.Height()) - return tsc.storage(context.TODO(), height, tail.Key()) - } - - return tsc.cache[normalModulo(tsc.start-int(headH-height), clen)], nil +func (tsc *tipSetCacheObserver) Apply(_ context.Context, _, to *types.TipSet) error { + return (*tipSetCache)(tsc).add(to) } -func (tsc *tipSetCache) best() *types.TipSet { - return tsc.cache[tsc.start] +func (tsc *tipSetCacheObserver) Revert(ctx context.Context, from, _ *types.TipSet) error { + return (*tipSetCache)(tsc).revert(from) } func normalModulo(n, m int) int { diff --git a/chain/events/tscache_test.go b/chain/events/tscache_test.go index 1278e58e9..ec312740b 100644 --- a/chain/events/tscache_test.go +++ b/chain/events/tscache_test.go @@ -1,122 +1,217 @@ +// stm: #unit package events import ( "context" "testing" - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/crypto" + "github.com/ipfs/go-cid" "github.com/stretchr/testify/require" "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/lotus/chain/types" ) -func TestTsCache(t *testing.T) { - tsc := newTSCache(50, func(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error) { - t.Fatal("storage call") - return &types.TipSet{}, nil - }) +type tsCacheAPIFailOnStorageCall struct { + t *testing.T +} - h := abi.ChainEpoch(75) +func (tc *tsCacheAPIFailOnStorageCall) ChainGetTipSetAfterHeight(ctx context.Context, epoch abi.ChainEpoch, key types.TipSetKey) (*types.TipSet, error) { + tc.t.Fatal("storage call") + return &types.TipSet{}, nil +} - a, _ := address.NewFromString("t00") +func (tc *tsCacheAPIFailOnStorageCall) ChainGetTipSetByHeight(ctx context.Context, epoch abi.ChainEpoch, key types.TipSetKey) (*types.TipSet, error) { + tc.t.Fatal("storage call") + return &types.TipSet{}, nil +} +func (tc *tsCacheAPIFailOnStorageCall) ChainHead(ctx context.Context) (*types.TipSet, error) { + tc.t.Fatal("storage call") + return &types.TipSet{}, nil +} +func (tc *tsCacheAPIFailOnStorageCall) ChainGetTipSet(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error) { + tc.t.Fatal("storage call") + return &types.TipSet{}, nil +} - add := func() { - ts, err := types.NewTipSet([]*types.BlockHeader{{ - Miner: a, - Height: h, - ParentStateRoot: dummyCid, - Messages: dummyCid, - ParentMessageReceipts: dummyCid, - BlockSig: &crypto.Signature{Type: crypto.SigTypeBLS}, - BLSAggregate: &crypto.Signature{Type: crypto.SigTypeBLS}, - }}) - if err != nil { - t.Fatal(err) - } - if err := tsc.add(ts); err != nil { - t.Fatal(err) - } - h++ +type cacheHarness struct { + t *testing.T + + miner address.Address + tsc *tipSetCache + height abi.ChainEpoch +} + +func newCacheharness(t *testing.T) *cacheHarness { + a, err := address.NewFromString("t00") + require.NoError(t, err) + + h := &cacheHarness{ + t: t, + tsc: newTSCache(&tsCacheAPIFailOnStorageCall{t: t}, 50), + height: 75, + miner: a, } + h.addWithParents(nil) + return h +} + +func (h *cacheHarness) addWithParents(parents []cid.Cid) { + ts, err := types.NewTipSet([]*types.BlockHeader{{ + Miner: h.miner, + Height: h.height, + ParentStateRoot: dummyCid, + Messages: dummyCid, + ParentMessageReceipts: dummyCid, + BlockSig: &crypto.Signature{Type: crypto.SigTypeBLS}, + BLSAggregate: &crypto.Signature{Type: crypto.SigTypeBLS}, + Parents: parents, + }}) + require.NoError(h.t, err) + require.NoError(h.t, h.tsc.add(ts)) + h.height++ +} + +func (h *cacheHarness) add() { + last, err := h.tsc.ChainHead(context.Background()) + require.NoError(h.t, err) + h.addWithParents(last.Cids()) +} + +func (h *cacheHarness) revert() { + best, err := h.tsc.ChainHead(context.Background()) + require.NoError(h.t, err) + err = h.tsc.revert(best) + require.NoError(h.t, err) + h.height-- +} + +func (h *cacheHarness) skip(n abi.ChainEpoch) { + h.height += n +} + +func TestTsCache(t *testing.T) { + //stm: @EVENTS_CACHE_GET_CHAIN_HEAD_001, @EVENTS_CACHE_GET_001, @EVENTS_CACHE_ADD_001 + h := newCacheharness(t) for i := 0; i < 9000; i++ { if i%90 > 60 { - if err := tsc.revert(tsc.best()); err != nil { - t.Fatal(err, "; i:", i) - return - } - h-- + h.revert() } else { - add() + h.add() } } - } func TestTsCacheNulls(t *testing.T) { - tsc := newTSCache(50, func(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error) { - t.Fatal("storage call") - return &types.TipSet{}, nil - }) + //stm: @EVENTS_CACHE_GET_CHAIN_HEAD_001, @EVENTS_CACHE_GET_CHAIN_TIPSET_BEFORE_001, @EVENTS_CACHE_GET_CHAIN_TIPSET_AFTER_001 + //stm: @EVENTS_CACHE_GET_001, @EVENTS_CACHE_ADD_001 + ctx := context.Background() + h := newCacheharness(t) - h := abi.ChainEpoch(75) + h.add() + h.add() + h.add() + h.skip(5) - a, _ := address.NewFromString("t00") - add := func() { - ts, err := types.NewTipSet([]*types.BlockHeader{{ - Miner: a, - Height: h, - ParentStateRoot: dummyCid, - Messages: dummyCid, - ParentMessageReceipts: dummyCid, - BlockSig: &crypto.Signature{Type: crypto.SigTypeBLS}, - BLSAggregate: &crypto.Signature{Type: crypto.SigTypeBLS}, - }}) - if err != nil { - t.Fatal(err) - } - if err := tsc.add(ts); err != nil { - t.Fatal(err) - } - h++ - } + h.add() + h.add() - add() - add() - add() - h += 5 - - add() - add() - - require.Equal(t, h-1, tsc.best().Height()) - - ts, err := tsc.get(h - 1) + best, err := h.tsc.ChainHead(ctx) require.NoError(t, err) - require.Equal(t, h-1, ts.Height()) + require.Equal(t, h.height-1, best.Height()) - ts, err = tsc.get(h - 2) + ts, err := h.tsc.ChainGetTipSetByHeight(ctx, h.height-1, types.EmptyTSK) require.NoError(t, err) - require.Equal(t, h-2, ts.Height()) + require.Equal(t, h.height-1, ts.Height()) - ts, err = tsc.get(h - 3) + ts, err = h.tsc.ChainGetTipSetByHeight(ctx, h.height-2, types.EmptyTSK) require.NoError(t, err) - require.Nil(t, ts) + require.Equal(t, h.height-2, ts.Height()) - ts, err = tsc.get(h - 8) + // Should skip the nulls and walk back to the last tipset. + ts, err = h.tsc.ChainGetTipSetByHeight(ctx, h.height-3, types.EmptyTSK) require.NoError(t, err) - require.Equal(t, h-8, ts.Height()) + require.Equal(t, h.height-8, ts.Height()) - require.NoError(t, tsc.revert(tsc.best())) - require.NoError(t, tsc.revert(tsc.best())) - require.Equal(t, h-8, tsc.best().Height()) - - h += 50 - add() - - ts, err = tsc.get(h - 1) + ts, err = h.tsc.ChainGetTipSetByHeight(ctx, h.height-8, types.EmptyTSK) require.NoError(t, err) - require.Equal(t, h-1, ts.Height()) + require.Equal(t, h.height-8, ts.Height()) + + best, err = h.tsc.ChainHead(ctx) + require.NoError(t, err) + require.NoError(t, h.tsc.revert(best)) + + best, err = h.tsc.ChainHead(ctx) + require.NoError(t, err) + require.NoError(t, h.tsc.revert(best)) + + best, err = h.tsc.ChainHead(ctx) + require.NoError(t, err) + require.Equal(t, h.height-8, best.Height()) + + h.skip(50) + h.add() + + ts, err = h.tsc.ChainGetTipSetByHeight(ctx, h.height-1, types.EmptyTSK) + require.NoError(t, err) + require.Equal(t, h.height-1, ts.Height()) +} + +type tsCacheAPIStorageCallCounter struct { + t *testing.T + chainGetTipSetByHeight int + chainGetTipSetAfterHeight int + chainGetTipSet int + chainHead int +} + +func (tc *tsCacheAPIStorageCallCounter) ChainGetTipSetByHeight(ctx context.Context, epoch abi.ChainEpoch, key types.TipSetKey) (*types.TipSet, error) { + tc.chainGetTipSetByHeight++ + return &types.TipSet{}, nil +} +func (tc *tsCacheAPIStorageCallCounter) ChainGetTipSetAfterHeight(ctx context.Context, epoch abi.ChainEpoch, key types.TipSetKey) (*types.TipSet, error) { + tc.chainGetTipSetAfterHeight++ + return &types.TipSet{}, nil +} +func (tc *tsCacheAPIStorageCallCounter) ChainHead(ctx context.Context) (*types.TipSet, error) { + tc.chainHead++ + return &types.TipSet{}, nil +} +func (tc *tsCacheAPIStorageCallCounter) ChainGetTipSet(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error) { + tc.chainGetTipSet++ + return &types.TipSet{}, nil +} + +func TestTsCacheEmpty(t *testing.T) { + //stm: @EVENTS_CACHE_GET_CHAIN_HEAD_001 + // Calling best on an empty cache should just call out to the chain API + callCounter := &tsCacheAPIStorageCallCounter{t: t} + tsc := newTSCache(callCounter, 50) + _, err := tsc.ChainHead(context.Background()) + require.NoError(t, err) + require.Equal(t, 1, callCounter.chainHead) +} + +func TestTsCacheSkip(t *testing.T) { + //stm: @EVENTS_CACHE_GET_CHAIN_HEAD_001, @EVENTS_CACHE_GET_001, @EVENTS_CACHE_ADD_001 + h := newCacheharness(t) + + ts, err := types.NewTipSet([]*types.BlockHeader{{ + Miner: h.miner, + Height: h.height, + ParentStateRoot: dummyCid, + Messages: dummyCid, + ParentMessageReceipts: dummyCid, + BlockSig: &crypto.Signature{Type: crypto.SigTypeBLS}, + BLSAggregate: &crypto.Signature{Type: crypto.SigTypeBLS}, + // With parents that don't match the last block. + Parents: nil, + }}) + require.NoError(h.t, err) + err = h.tsc.add(ts) + require.Error(t, err) } diff --git a/chain/events/utils.go b/chain/events/utils.go index 1f4ca381b..ac8d02009 100644 --- a/chain/events/utils.go +++ b/chain/events/utils.go @@ -5,14 +5,15 @@ import ( "golang.org/x/xerrors" + "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" ) -func (e *calledEvents) CheckMsg(ctx context.Context, smsg types.ChainMsg, hnd CalledHandler) CheckFunc { +func (me *messageEvents) CheckMsg(smsg types.ChainMsg, hnd MsgHandler) CheckFunc { msg := smsg.VMMessage() - return func(ts *types.TipSet) (done bool, more bool, err error) { - fa, err := e.cs.StateGetActor(ctx, msg.From, ts.Key()) + return func(ctx context.Context, ts *types.TipSet) (done bool, more bool, err error) { + fa, err := me.cs.StateGetActor(ctx, msg.From, ts.Key()) if err != nil { return false, true, err } @@ -22,21 +23,25 @@ func (e *calledEvents) CheckMsg(ctx context.Context, smsg types.ChainMsg, hnd Ca return false, true, nil } - rec, err := e.cs.StateGetReceipt(ctx, smsg.VMMessage().Cid(), ts.Key()) + ml, err := me.cs.StateSearchMsg(ctx, ts.Key(), msg.Cid(), stmgr.LookbackNoLimit, true) if err != nil { return false, true, xerrors.Errorf("getting receipt in CheckMsg: %w", err) } - more, err = hnd(msg, rec, ts, ts.Height()) + if ml == nil { + more, err = hnd(msg, nil, ts, ts.Height()) + } else { + more, err = hnd(msg, &ml.Receipt, ts, ts.Height()) + } return true, more, err } } -func (e *calledEvents) MatchMsg(inmsg *types.Message) MatchFunc { - return func(msg *types.Message) (bool, error) { +func (me *messageEvents) MatchMsg(inmsg *types.Message) MsgMatchFunc { + return func(msg *types.Message) (matched bool, err error) { if msg.From == inmsg.From && msg.Nonce == inmsg.Nonce && !inmsg.Equals(msg) { - return false, xerrors.Errorf("matching msg %s from %s, nonce %d: got duplicate origin/nonce msg %s", inmsg.Cid(), inmsg.From, inmsg.Nonce, msg.Nonce) + return false, xerrors.Errorf("matching msg %s from %s, nonce %d: got duplicate origin/nonce msg %d", inmsg.Cid(), inmsg.From, inmsg.Nonce, msg.Nonce) } return inmsg.Equals(msg), nil diff --git a/chain/exchange/cbor_gen.go b/chain/exchange/cbor_gen.go new file mode 100644 index 000000000..d1eb271e9 --- /dev/null +++ b/chain/exchange/cbor_gen.go @@ -0,0 +1,674 @@ +// Code generated by github.com/whyrusleeping/cbor-gen. DO NOT EDIT. + +package exchange + +import ( + "fmt" + "io" + "math" + "sort" + + cid "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + xerrors "golang.org/x/xerrors" + + types "github.com/filecoin-project/lotus/chain/types" +) + +var _ = xerrors.Errorf +var _ = cid.Undef +var _ = math.E +var _ = sort.Sort + +var lengthBufRequest = []byte{131} + +func (t *Request) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufRequest); err != nil { + return err + } + + // t.Head ([]cid.Cid) (slice) + if len(t.Head) > cbg.MaxLength { + return xerrors.Errorf("Slice value in field t.Head was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Head))); err != nil { + return err + } + for _, v := range t.Head { + if err := cbg.WriteCid(w, v); err != nil { + return xerrors.Errorf("failed writing cid field t.Head: %w", err) + } + } + + // t.Length (uint64) (uint64) + + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Length)); err != nil { + return err + } + + // t.Options (uint64) (uint64) + + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Options)); err != nil { + return err + } + + return nil +} + +func (t *Request) UnmarshalCBOR(r io.Reader) (err error) { + *t = Request{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != 3 { + return fmt.Errorf("cbor input had wrong number of fields") + } + + // t.Head ([]cid.Cid) (slice) + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > cbg.MaxLength { + return fmt.Errorf("t.Head: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.Head = make([]cid.Cid, extra) + } + + for i := 0; i < int(extra); i++ { + + c, err := cbg.ReadCid(cr) + if err != nil { + return xerrors.Errorf("reading cid field t.Head failed: %w", err) + } + t.Head[i] = c + } + + // t.Length (uint64) (uint64) + + { + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.Length = uint64(extra) + + } + // t.Options (uint64) (uint64) + + { + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.Options = uint64(extra) + + } + return nil +} + +var lengthBufResponse = []byte{131} + +func (t *Response) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufResponse); err != nil { + return err + } + + // t.Status (exchange.status) (uint64) + + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Status)); err != nil { + return err + } + + // t.ErrorMessage (string) (string) + if len(t.ErrorMessage) > cbg.MaxLength { + return xerrors.Errorf("Value in field t.ErrorMessage was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.ErrorMessage))); err != nil { + return err + } + if _, err := io.WriteString(w, string(t.ErrorMessage)); err != nil { + return err + } + + // t.Chain ([]*exchange.BSTipSet) (slice) + if len(t.Chain) > cbg.MaxLength { + return xerrors.Errorf("Slice value in field t.Chain was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Chain))); err != nil { + return err + } + for _, v := range t.Chain { + if err := v.MarshalCBOR(cw); err != nil { + return err + } + } + return nil +} + +func (t *Response) UnmarshalCBOR(r io.Reader) (err error) { + *t = Response{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != 3 { + return fmt.Errorf("cbor input had wrong number of fields") + } + + // t.Status (exchange.status) (uint64) + + { + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.Status = status(extra) + + } + // t.ErrorMessage (string) (string) + + { + sval, err := cbg.ReadString(cr) + if err != nil { + return err + } + + t.ErrorMessage = string(sval) + } + // t.Chain ([]*exchange.BSTipSet) (slice) + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > cbg.MaxLength { + return fmt.Errorf("t.Chain: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.Chain = make([]*BSTipSet, extra) + } + + for i := 0; i < int(extra); i++ { + + var v BSTipSet + if err := v.UnmarshalCBOR(cr); err != nil { + return err + } + + t.Chain[i] = &v + } + + return nil +} + +var lengthBufCompactedMessages = []byte{132} + +func (t *CompactedMessages) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufCompactedMessages); err != nil { + return err + } + + // t.Bls ([]*types.Message) (slice) + if len(t.Bls) > cbg.MaxLength { + return xerrors.Errorf("Slice value in field t.Bls was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Bls))); err != nil { + return err + } + for _, v := range t.Bls { + if err := v.MarshalCBOR(cw); err != nil { + return err + } + } + + // t.BlsIncludes ([][]uint64) (slice) + if len(t.BlsIncludes) > cbg.MaxLength { + return xerrors.Errorf("Slice value in field t.BlsIncludes was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.BlsIncludes))); err != nil { + return err + } + for _, v := range t.BlsIncludes { + if len(v) > cbg.MaxLength { + return xerrors.Errorf("Slice value in field v was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(v))); err != nil { + return err + } + for _, v := range v { + if err := cw.CborWriteHeader(cbg.MajUnsignedInt, uint64(v)); err != nil { + return err + } + } + } + + // t.Secpk ([]*types.SignedMessage) (slice) + if len(t.Secpk) > cbg.MaxLength { + return xerrors.Errorf("Slice value in field t.Secpk was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Secpk))); err != nil { + return err + } + for _, v := range t.Secpk { + if err := v.MarshalCBOR(cw); err != nil { + return err + } + } + + // t.SecpkIncludes ([][]uint64) (slice) + if len(t.SecpkIncludes) > cbg.MaxLength { + return xerrors.Errorf("Slice value in field t.SecpkIncludes was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.SecpkIncludes))); err != nil { + return err + } + for _, v := range t.SecpkIncludes { + if len(v) > cbg.MaxLength { + return xerrors.Errorf("Slice value in field v was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(v))); err != nil { + return err + } + for _, v := range v { + if err := cw.CborWriteHeader(cbg.MajUnsignedInt, uint64(v)); err != nil { + return err + } + } + } + return nil +} + +func (t *CompactedMessages) UnmarshalCBOR(r io.Reader) (err error) { + *t = CompactedMessages{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != 4 { + return fmt.Errorf("cbor input had wrong number of fields") + } + + // t.Bls ([]*types.Message) (slice) + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > cbg.MaxLength { + return fmt.Errorf("t.Bls: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.Bls = make([]*types.Message, extra) + } + + for i := 0; i < int(extra); i++ { + + var v types.Message + if err := v.UnmarshalCBOR(cr); err != nil { + return err + } + + t.Bls[i] = &v + } + + // t.BlsIncludes ([][]uint64) (slice) + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > cbg.MaxLength { + return fmt.Errorf("t.BlsIncludes: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.BlsIncludes = make([][]uint64, extra) + } + + for i := 0; i < int(extra); i++ { + { + var maj byte + var extra uint64 + var err error + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > cbg.MaxLength { + return fmt.Errorf("t.BlsIncludes[i]: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.BlsIncludes[i] = make([]uint64, extra) + } + + for j := 0; j < int(extra); j++ { + + maj, val, err := cr.ReadHeader() + if err != nil { + return xerrors.Errorf("failed to read uint64 for t.BlsIncludes[i] slice: %w", err) + } + + if maj != cbg.MajUnsignedInt { + return xerrors.Errorf("value read for array t.BlsIncludes[i] was not a uint, instead got %d", maj) + } + + t.BlsIncludes[i][j] = uint64(val) + } + + } + } + + // t.Secpk ([]*types.SignedMessage) (slice) + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > cbg.MaxLength { + return fmt.Errorf("t.Secpk: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.Secpk = make([]*types.SignedMessage, extra) + } + + for i := 0; i < int(extra); i++ { + + var v types.SignedMessage + if err := v.UnmarshalCBOR(cr); err != nil { + return err + } + + t.Secpk[i] = &v + } + + // t.SecpkIncludes ([][]uint64) (slice) + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > cbg.MaxLength { + return fmt.Errorf("t.SecpkIncludes: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.SecpkIncludes = make([][]uint64, extra) + } + + for i := 0; i < int(extra); i++ { + { + var maj byte + var extra uint64 + var err error + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > cbg.MaxLength { + return fmt.Errorf("t.SecpkIncludes[i]: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.SecpkIncludes[i] = make([]uint64, extra) + } + + for j := 0; j < int(extra); j++ { + + maj, val, err := cr.ReadHeader() + if err != nil { + return xerrors.Errorf("failed to read uint64 for t.SecpkIncludes[i] slice: %w", err) + } + + if maj != cbg.MajUnsignedInt { + return xerrors.Errorf("value read for array t.SecpkIncludes[i] was not a uint, instead got %d", maj) + } + + t.SecpkIncludes[i][j] = uint64(val) + } + + } + } + + return nil +} + +var lengthBufBSTipSet = []byte{130} + +func (t *BSTipSet) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufBSTipSet); err != nil { + return err + } + + // t.Blocks ([]*types.BlockHeader) (slice) + if len(t.Blocks) > cbg.MaxLength { + return xerrors.Errorf("Slice value in field t.Blocks was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Blocks))); err != nil { + return err + } + for _, v := range t.Blocks { + if err := v.MarshalCBOR(cw); err != nil { + return err + } + } + + // t.Messages (exchange.CompactedMessages) (struct) + if err := t.Messages.MarshalCBOR(cw); err != nil { + return err + } + return nil +} + +func (t *BSTipSet) UnmarshalCBOR(r io.Reader) (err error) { + *t = BSTipSet{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != 2 { + return fmt.Errorf("cbor input had wrong number of fields") + } + + // t.Blocks ([]*types.BlockHeader) (slice) + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > cbg.MaxLength { + return fmt.Errorf("t.Blocks: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.Blocks = make([]*types.BlockHeader, extra) + } + + for i := 0; i < int(extra); i++ { + + var v types.BlockHeader + if err := v.UnmarshalCBOR(cr); err != nil { + return err + } + + t.Blocks[i] = &v + } + + // t.Messages (exchange.CompactedMessages) (struct) + + { + + b, err := cr.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { + return err + } + t.Messages = new(CompactedMessages) + if err := t.Messages.UnmarshalCBOR(cr); err != nil { + return xerrors.Errorf("unmarshaling t.Messages pointer: %w", err) + } + } + + } + return nil +} diff --git a/chain/exchange/client.go b/chain/exchange/client.go new file mode 100644 index 000000000..db39628be --- /dev/null +++ b/chain/exchange/client.go @@ -0,0 +1,496 @@ +package exchange + +import ( + "bufio" + "context" + "fmt" + "math/rand" + "time" + + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/network" + "github.com/libp2p/go-libp2p/core/peer" + "go.opencensus.io/trace" + "go.uber.org/fx" + "golang.org/x/xerrors" + + cborutil "github.com/filecoin-project/go-cbor-util" + + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/chain/types" + incrt "github.com/filecoin-project/lotus/lib/increadtimeout" + "github.com/filecoin-project/lotus/lib/peermgr" +) + +// client implements exchange.Client, using the libp2p ChainExchange protocol +// as the fetching mechanism. +type client struct { + // Connection manager used to contact the server. + // FIXME: We should have a reduced interface here, initialized + // just with our protocol ID, we shouldn't be able to open *any* + // connection. + host host.Host + + peerTracker *bsPeerTracker +} + +var _ Client = (*client)(nil) + +// NewClient creates a new libp2p-based exchange.Client that uses the libp2p +// ChainExhange protocol as the fetching mechanism. +func NewClient(lc fx.Lifecycle, host host.Host, pmgr peermgr.MaybePeerMgr) Client { + return &client{ + host: host, + peerTracker: newPeerTracker(lc, host, pmgr.Mgr), + } +} + +// Main logic of the client request service. The provided `Request` +// is sent to the `singlePeer` if one is indicated or to all available +// ones otherwise. The response is processed and validated according +// to the `Request` options. Either a `validatedResponse` is returned +// (which can be safely accessed), or an `error` that may represent +// either a response error status, a failed validation or an internal +// error. +// +// This is the internal single point of entry for all external-facing +// APIs, currently we have 3 very heterogeneous services exposed: +// * GetBlocks: Headers +// * GetFullTipSet: Headers | Messages +// * GetChainMessages: Messages +// This function handles all the different combinations of the available +// request options without disrupting external calls. In the future the +// consumers should be forced to use a more standardized service and +// adhere to a single API derived from this function. +func (c *client) doRequest( + ctx context.Context, + req *Request, + singlePeer *peer.ID, + // In the `GetChainMessages` case, we won't request the headers but we still + // need them to check the integrity of the `CompactedMessages` in the response + // so the tipset blocks need to be provided by the caller. + tipsets []*types.TipSet, +) (*validatedResponse, error) { + // Validate request. + if req.Length == 0 { + return nil, xerrors.Errorf("invalid request of length 0") + } + if req.Length > MaxRequestLength { + return nil, xerrors.Errorf("request length (%d) above maximum (%d)", + req.Length, MaxRequestLength) + } + if req.Options == 0 { + return nil, xerrors.Errorf("request with no options set") + } + + // Generate the list of peers to be queried, either the + // `singlePeer` indicated or all peers available (sorted + // by an internal peer tracker with some randomness injected). + var peers []peer.ID + if singlePeer != nil { + peers = []peer.ID{*singlePeer} + } else { + peers = c.getShuffledPeers() + if len(peers) == 0 { + return nil, xerrors.Errorf("no peers available") + } + } + + // Try the request for each peer in the list, + // return on the first successful response. + // FIXME: Doing this serially isn't great, but fetching in parallel + // may not be a good idea either. Think about this more. + globalTime := build.Clock.Now() + // Global time used to track what is the expected time we will need to get + // a response if a client fails us. + for _, peer := range peers { + select { + case <-ctx.Done(): + return nil, xerrors.Errorf("context cancelled: %w", ctx.Err()) + default: + } + + // Send request, read response. + res, err := c.sendRequestToPeer(ctx, peer, req) + if err != nil { + if !xerrors.Is(err, network.ErrNoConn) { + log.Warnf("could not send request to peer %s: %s", + peer.String(), err) + } + continue + } + + // Process and validate response. + validRes, err := c.processResponse(req, res, tipsets) + if err != nil { + log.Warnf("processing peer %s response failed: %s", + peer.String(), err) + continue + } + + c.peerTracker.logGlobalSuccess(build.Clock.Since(globalTime)) + c.host.ConnManager().TagPeer(peer, "bsync", SuccessPeerTagValue) + return validRes, nil + } + + errString := "doRequest failed for all peers" + if singlePeer != nil { + errString = fmt.Sprintf("doRequest failed for single peer %s", *singlePeer) + } + return nil, xerrors.Errorf(errString) +} + +// Process and validate response. Check the status, the integrity of the +// information returned, and that it matches the request. Extract the information +// into a `validatedResponse` for the external-facing APIs to select what they +// need. +// +// We are conflating in the single error returned both status and validation +// errors. Peer penalization should happen here then, before returning, so +// we can apply the correct penalties depending on the cause of the error. +// FIXME: Add the `peer` as argument once we implement penalties. +func (c *client) processResponse(req *Request, res *Response, tipsets []*types.TipSet) (r *validatedResponse, err error) { + err = res.statusToError() + if err != nil { + return nil, xerrors.Errorf("status error: %s", err) + } + + defer func() { + if rerr := recover(); rerr != nil { + log.Errorf("process response error: %s", rerr) + err = xerrors.Errorf("process response error: %s", rerr) + return + } + }() + + options := parseOptions(req.Options) + if options.noOptionsSet() { + // Safety check: this shouldn't have been sent, and even if it did + // it should have been caught by the peer in its error status. + return nil, xerrors.Errorf("nothing was requested") + } + + // Verify that the chain segment returned is in the valid range. + // Note that the returned length might be less than requested. + resLength := len(res.Chain) + if resLength == 0 { + return nil, xerrors.Errorf("got no chain in successful response") + } + if resLength > int(req.Length) { + return nil, xerrors.Errorf("got longer response (%d) than requested (%d)", + resLength, req.Length) + } + if resLength < int(req.Length) && res.Status != Partial { + return nil, xerrors.Errorf("got less than requested without a proper status: %d", res.Status) + } + + validRes := &validatedResponse{} + if options.IncludeHeaders { + // Check for valid block sets and extract them into `TipSet`s. + validRes.tipsets = make([]*types.TipSet, resLength) + for i := 0; i < resLength; i++ { + if res.Chain[i] == nil { + return nil, xerrors.Errorf("response with nil tipset in pos %d", i) + } + for blockIdx, block := range res.Chain[i].Blocks { + if block == nil { + return nil, xerrors.Errorf("tipset with nil block in pos %d", blockIdx) + // FIXME: Maybe we should move this check to `NewTipSet`. + } + } + + validRes.tipsets[i], err = types.NewTipSet(res.Chain[i].Blocks) + if err != nil { + return nil, xerrors.Errorf("invalid tipset blocks at height (head - %d): %w", i, err) + } + } + + // Check that the returned head matches the one requested. + if !types.CidArrsEqual(validRes.tipsets[0].Cids(), req.Head) { + return nil, xerrors.Errorf("returned chain head does not match request") + } + + // Check `TipSet`s are connected (valid chain). + for i := 0; i < len(validRes.tipsets)-1; i++ { + if validRes.tipsets[i].IsChildOf(validRes.tipsets[i+1]) == false { + return nil, fmt.Errorf("tipsets are not connected at height (head - %d)/(head - %d)", + i, i+1) + // FIXME: Maybe give more information here, like CIDs. + } + } + } + + if options.IncludeMessages { + validRes.messages = make([]*CompactedMessages, resLength) + for i := 0; i < resLength; i++ { + if res.Chain[i].Messages == nil { + return nil, xerrors.Errorf("no messages included for tipset at height (head - %d)", i) + } + validRes.messages[i] = res.Chain[i].Messages + } + + if options.IncludeHeaders { + // If the headers were also returned check that the compression + // indexes are valid before `toFullTipSets()` is called by the + // consumer. + err := c.validateCompressedIndices(res.Chain) + if err != nil { + return nil, err + } + } else { + // If we didn't request the headers they should have been provided + // by the caller. + if len(tipsets) < len(res.Chain) { + return nil, xerrors.Errorf("not enought tipsets provided for message response validation, needed %d, have %d", len(res.Chain), len(tipsets)) + } + chain := make([]*BSTipSet, 0, resLength) + for i, resChain := range res.Chain { + next := &BSTipSet{ + Blocks: tipsets[i].Blocks(), + Messages: resChain.Messages, + } + chain = append(chain, next) + } + + err := c.validateCompressedIndices(chain) + if err != nil { + return nil, err + } + } + } + + return validRes, nil +} + +func (c *client) validateCompressedIndices(chain []*BSTipSet) error { + resLength := len(chain) + for tipsetIdx := 0; tipsetIdx < resLength; tipsetIdx++ { + msgs := chain[tipsetIdx].Messages + blocksNum := len(chain[tipsetIdx].Blocks) + + if len(msgs.BlsIncludes) != blocksNum { + return xerrors.Errorf("BlsIncludes (%d) does not match number of blocks (%d)", + len(msgs.BlsIncludes), blocksNum) + } + + if len(msgs.SecpkIncludes) != blocksNum { + return xerrors.Errorf("SecpkIncludes (%d) does not match number of blocks (%d)", + len(msgs.SecpkIncludes), blocksNum) + } + + for blockIdx := 0; blockIdx < blocksNum; blockIdx++ { + for _, mi := range msgs.BlsIncludes[blockIdx] { + if int(mi) >= len(msgs.Bls) { + return xerrors.Errorf("index in BlsIncludes (%d) exceeds number of messages (%d)", + mi, len(msgs.Bls)) + } + } + + for _, mi := range msgs.SecpkIncludes[blockIdx] { + if int(mi) >= len(msgs.Secpk) { + return xerrors.Errorf("index in SecpkIncludes (%d) exceeds number of messages (%d)", + mi, len(msgs.Secpk)) + } + } + } + } + + return nil +} + +// GetBlocks implements Client.GetBlocks(). Refer to the godocs there. +func (c *client) GetBlocks(ctx context.Context, tsk types.TipSetKey, count int) ([]*types.TipSet, error) { + ctx, span := trace.StartSpan(ctx, "bsync.GetBlocks") + defer span.End() + if span.IsRecordingEvents() { + span.AddAttributes( + trace.StringAttribute("tipset", fmt.Sprint(tsk.Cids())), + trace.Int64Attribute("count", int64(count)), + ) + } + + req := &Request{ + Head: tsk.Cids(), + Length: uint64(count), + Options: Headers, + } + + validRes, err := c.doRequest(ctx, req, nil, nil) + if err != nil { + return nil, err + } + + return validRes.tipsets, nil +} + +// GetFullTipSet implements Client.GetFullTipSet(). Refer to the godocs there. +func (c *client) GetFullTipSet(ctx context.Context, peer peer.ID, tsk types.TipSetKey) (*store.FullTipSet, error) { + // TODO: round robin through these peers on error + + req := &Request{ + Head: tsk.Cids(), + Length: 1, + Options: Headers | Messages, + } + + validRes, err := c.doRequest(ctx, req, &peer, nil) + if err != nil { + return nil, err + } + + return validRes.toFullTipSets()[0], nil + // If `doRequest` didn't fail we are guaranteed to have at least + // *one* tipset here, so it's safe to index directly. +} + +// GetChainMessages implements Client.GetChainMessages(). Refer to the godocs there. +func (c *client) GetChainMessages(ctx context.Context, tipsets []*types.TipSet) ([]*CompactedMessages, error) { + head := tipsets[0] + length := uint64(len(tipsets)) + + ctx, span := trace.StartSpan(ctx, "GetChainMessages") + if span.IsRecordingEvents() { + span.AddAttributes( + trace.StringAttribute("tipset", fmt.Sprint(head.Cids())), + trace.Int64Attribute("count", int64(length)), + ) + } + defer span.End() + + req := &Request{ + Head: head.Cids(), + Length: length, + Options: Messages, + } + + validRes, err := c.doRequest(ctx, req, nil, tipsets) + if err != nil { + return nil, err + } + + return validRes.messages, nil +} + +// Send a request to a peer. Write request in the stream and read the +// response back. We do not do any processing of the request/response +// here. +func (c *client) sendRequestToPeer(ctx context.Context, peer peer.ID, req *Request) (_ *Response, err error) { + // Trace code. + ctx, span := trace.StartSpan(ctx, "sendRequestToPeer") + defer span.End() + if span.IsRecordingEvents() { + span.AddAttributes( + trace.StringAttribute("peer", peer.Pretty()), + ) + } + defer func() { + if err != nil { + if span.IsRecordingEvents() { + span.SetStatus(trace.Status{ + Code: 5, + Message: err.Error(), + }) + } + } + }() + // -- TRACE -- + + supported, err := c.host.Peerstore().SupportsProtocols(peer, ChainExchangeProtocolID) + if err != nil { + c.RemovePeer(peer) + return nil, xerrors.Errorf("failed to get protocols for peer: %w", err) + } + if len(supported) == 0 || (supported[0] != ChainExchangeProtocolID) { + return nil, xerrors.Errorf("peer %s does not support protocols %s", + peer, []string{ChainExchangeProtocolID}) + } + + connectionStart := build.Clock.Now() + + // Open stream to peer. + stream, err := c.host.NewStream( + network.WithNoDial(ctx, "should already have connection"), + peer, + ChainExchangeProtocolID) + if err != nil { + c.RemovePeer(peer) + return nil, xerrors.Errorf("failed to open stream to peer: %w", err) + } + + defer stream.Close() //nolint:errcheck + + // Write request. + _ = stream.SetWriteDeadline(time.Now().Add(WriteReqDeadline)) + if err := cborutil.WriteCborRPC(stream, req); err != nil { + _ = stream.SetWriteDeadline(time.Time{}) + c.peerTracker.logFailure(peer, build.Clock.Since(connectionStart), req.Length) + // FIXME: Should we also remove peer here? + return nil, err + } + _ = stream.SetWriteDeadline(time.Time{}) // clear deadline // FIXME: Needs + // its own API (https://github.com/libp2p/go-libp2p/core/issues/162). + if err := stream.CloseWrite(); err != nil { + log.Warnw("CloseWrite err", "error", err) + } + + // Read response. + var res Response + err = cborutil.ReadCborRPC( + bufio.NewReader(incrt.New(stream, ReadResMinSpeed, ReadResDeadline)), + &res) + if err != nil { + c.peerTracker.logFailure(peer, build.Clock.Since(connectionStart), req.Length) + return nil, xerrors.Errorf("failed to read chainxchg response: %w", err) + } + + // FIXME: Move all this together at the top using a defer as done elsewhere. + // Maybe we need to declare `res` in the signature. + if span.IsRecordingEvents() { + span.AddAttributes( + trace.Int64Attribute("resp_status", int64(res.Status)), + trace.StringAttribute("msg", res.ErrorMessage), + trace.Int64Attribute("chain_len", int64(len(res.Chain))), + ) + } + + c.peerTracker.logSuccess(peer, build.Clock.Since(connectionStart), uint64(len(res.Chain))) + // FIXME: We should really log a success only after we validate the response. + // It might be a bit hard to do. + return &res, nil +} + +// AddPeer implements Client.AddPeer(). Refer to the godocs there. +func (c *client) AddPeer(p peer.ID) { + c.peerTracker.addPeer(p) +} + +// RemovePeer implements Client.RemovePeer(). Refer to the godocs there. +func (c *client) RemovePeer(p peer.ID) { + c.peerTracker.removePeer(p) +} + +// getShuffledPeers returns a preference-sorted set of peers (by latency +// and failure counting), shuffling the first few peers so we don't always +// pick the same peer. +// FIXME: Consider merging with `shufflePrefix()s`. +func (c *client) getShuffledPeers() []peer.ID { + peers := c.peerTracker.prefSortedPeers() + shufflePrefix(peers) + return peers +} + +func shufflePrefix(peers []peer.ID) { + prefix := ShufflePeersPrefix + if len(peers) < prefix { + prefix = len(peers) + } + + buf := make([]peer.ID, prefix) + perm := rand.Perm(prefix) + for i, v := range perm { + buf[i] = peers[v] + } + + copy(peers, buf) +} diff --git a/chain/exchange/doc.go b/chain/exchange/doc.go new file mode 100644 index 000000000..21abc38c4 --- /dev/null +++ b/chain/exchange/doc.go @@ -0,0 +1,19 @@ +// Package exchange contains the ChainExchange server and client components. +// +// ChainExchange is the basic chain synchronization protocol of Filecoin. +// ChainExchange is an RPC-oriented protocol, with a single operation to +// request blocks for now. +// +// A request contains a start anchor block (referred to with a CID), and a +// amount of blocks requested beyond the anchor (including the anchor itself). +// +// A client can also pass options, encoded as a 64-bit bitfield. Lotus supports +// two options at the moment: +// +// - include block contents +// - include block messages +// +// The response will include a status code, an optional message, and the +// response payload in case of success. The payload is a slice of serialized +// tipsets. +package exchange diff --git a/chain/exchange/interfaces.go b/chain/exchange/interfaces.go new file mode 100644 index 000000000..c95127929 --- /dev/null +++ b/chain/exchange/interfaces.go @@ -0,0 +1,50 @@ +package exchange + +import ( + "context" + + "github.com/libp2p/go-libp2p/core/network" + "github.com/libp2p/go-libp2p/core/peer" + + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/chain/types" +) + +// Server is the responder side of the ChainExchange protocol. It accepts +// requests from clients and services them by returning the requested +// chain data. +type Server interface { + // HandleStream is the protocol handler to be registered on a libp2p + // protocol router. + // + // In the current version of the protocol, streams are single-use. The + // server will read a single Request, and will respond with a single + // Response. It will dispose of the stream straight after. + HandleStream(stream network.Stream) +} + +// Client is the requesting side of the ChainExchange protocol. It acts as +// a proxy for other components to request chain data from peers. It is chiefly +// used by the Syncer. +type Client interface { + // GetBlocks fetches block headers from the network, from the provided + // tipset *backwards*, returning as many tipsets as the count parameter, + // or less. + GetBlocks(ctx context.Context, tsk types.TipSetKey, count int) ([]*types.TipSet, error) + + // GetChainMessages fetches messages from the network, starting from the first provided tipset + // and returning messages from as many tipsets as requested or less. + GetChainMessages(ctx context.Context, tipsets []*types.TipSet) ([]*CompactedMessages, error) + + // GetFullTipSet fetches a full tipset from a given peer. If successful, + // the fetched object contains block headers and all messages in full form. + GetFullTipSet(ctx context.Context, peer peer.ID, tsk types.TipSetKey) (*store.FullTipSet, error) + + // AddPeer adds a peer to the pool of peers that the Client requests + // data from. + AddPeer(peer peer.ID) + + // RemovePeer removes a peer from the pool of peers that the Client + // requests data from. + RemovePeer(peer peer.ID) +} diff --git a/chain/exchange/peer_tracker.go b/chain/exchange/peer_tracker.go new file mode 100644 index 000000000..00b919d23 --- /dev/null +++ b/chain/exchange/peer_tracker.go @@ -0,0 +1,195 @@ +package exchange + +// FIXME: This needs to be reviewed. + +import ( + "context" + "sort" + "sync" + "time" + + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" + "go.uber.org/fx" + + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/lib/peermgr" +) + +type peerStats struct { + successes int + failures int + firstSeen time.Time + averageTime time.Duration +} + +type bsPeerTracker struct { + lk sync.Mutex + + peers map[peer.ID]*peerStats + avgGlobalTime time.Duration + + pmgr *peermgr.PeerMgr +} + +func newPeerTracker(lc fx.Lifecycle, h host.Host, pmgr *peermgr.PeerMgr) *bsPeerTracker { + bsPt := &bsPeerTracker{ + peers: make(map[peer.ID]*peerStats), + pmgr: pmgr, + } + + evtSub, err := h.EventBus().Subscribe(new(peermgr.FilPeerEvt)) + if err != nil { + panic(err) + } + + go func() { + for evt := range evtSub.Out() { + pEvt := evt.(peermgr.FilPeerEvt) + switch pEvt.Type { + case peermgr.AddFilPeerEvt: + bsPt.addPeer(pEvt.ID) + case peermgr.RemoveFilPeerEvt: + bsPt.removePeer(pEvt.ID) + } + } + }() + + lc.Append(fx.Hook{ + OnStop: func(ctx context.Context) error { + return evtSub.Close() + }, + }) + + return bsPt +} + +func (bpt *bsPeerTracker) addPeer(p peer.ID) { + bpt.lk.Lock() + defer bpt.lk.Unlock() + if _, ok := bpt.peers[p]; ok { + return + } + bpt.peers[p] = &peerStats{ + firstSeen: build.Clock.Now(), + } + +} + +const ( + // newPeerMul is how much better than average is the new peer assumed to be + // less than one to encourouge trying new peers + newPeerMul = 0.9 +) + +func (bpt *bsPeerTracker) prefSortedPeers() []peer.ID { + // TODO: this could probably be cached, but as long as its not too many peers, fine for now + bpt.lk.Lock() + defer bpt.lk.Unlock() + out := make([]peer.ID, 0, len(bpt.peers)) + for p := range bpt.peers { + out = append(out, p) + } + + // sort by 'expected cost' of requesting data from that peer + // additionally handle edge cases where not enough data is available + sort.Slice(out, func(i, j int) bool { + pi := bpt.peers[out[i]] + pj := bpt.peers[out[j]] + + var costI, costJ float64 + + getPeerInitLat := func(p peer.ID) float64 { + return float64(bpt.avgGlobalTime) * newPeerMul + } + + if pi.successes+pi.failures > 0 { + failRateI := float64(pi.failures) / float64(pi.failures+pi.successes) + costI = float64(pi.averageTime) + failRateI*float64(bpt.avgGlobalTime) + } else { + costI = getPeerInitLat(out[i]) + } + + if pj.successes+pj.failures > 0 { + failRateJ := float64(pj.failures) / float64(pj.failures+pj.successes) + costJ = float64(pj.averageTime) + failRateJ*float64(bpt.avgGlobalTime) + } else { + costJ = getPeerInitLat(out[j]) + } + + return costI < costJ + }) + + return out +} + +const ( + // xInvAlpha = (N+1)/2 + + localInvAlpha = 10 // 86% of the value is the last 19 + globalInvAlpha = 25 // 86% of the value is the last 49 +) + +func (bpt *bsPeerTracker) logGlobalSuccess(dur time.Duration) { + bpt.lk.Lock() + defer bpt.lk.Unlock() + + if bpt.avgGlobalTime == 0 { + bpt.avgGlobalTime = dur + return + } + delta := (dur - bpt.avgGlobalTime) / globalInvAlpha + bpt.avgGlobalTime += delta +} + +func logTime(pi *peerStats, dur time.Duration) { + if pi.averageTime == 0 { + pi.averageTime = dur + return + } + delta := (dur - pi.averageTime) / localInvAlpha + pi.averageTime += delta + +} + +func (bpt *bsPeerTracker) logSuccess(p peer.ID, dur time.Duration, reqSize uint64) { + bpt.lk.Lock() + defer bpt.lk.Unlock() + + var pi *peerStats + var ok bool + if pi, ok = bpt.peers[p]; !ok { + log.Warnw("log success called on peer not in tracker", "peerid", p.String()) + return + } + + pi.successes++ + if reqSize == 0 { + reqSize = 1 + } + logTime(pi, dur/time.Duration(reqSize)) +} + +func (bpt *bsPeerTracker) logFailure(p peer.ID, dur time.Duration, reqSize uint64) { + bpt.lk.Lock() + defer bpt.lk.Unlock() + + var pi *peerStats + var ok bool + if pi, ok = bpt.peers[p]; !ok { + log.Warn("log failure called on peer not in tracker", "peerid", p.String()) + return + } + + pi.failures++ + if reqSize == 0 { + reqSize = 1 + } + logTime(pi, dur/time.Duration(reqSize)) +} + +func (bpt *bsPeerTracker) removePeer(p peer.ID) { + bpt.lk.Lock() + defer bpt.lk.Unlock() + delete(bpt.peers, p) +} diff --git a/chain/exchange/protocol.go b/chain/exchange/protocol.go new file mode 100644 index 000000000..5e12d31cc --- /dev/null +++ b/chain/exchange/protocol.go @@ -0,0 +1,204 @@ +package exchange + +import ( + "time" + + "github.com/ipfs/go-cid" + logging "github.com/ipfs/go-log/v2" + "golang.org/x/xerrors" + + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/chain/types" +) + +var log = logging.Logger("chainxchg") + +const ( + // ChainExchangeProtocolID is the protocol ID of the chain exchange + // protocol. + ChainExchangeProtocolID = "/fil/chain/xchg/0.0.1" +) + +// FIXME: Bumped from original 800 to this to accommodate `syncFork()` +// +// use of `GetBlocks()`. It seems the expectation of that API is to +// fetch any amount of blocks leaving it to the internal logic here +// to partition and reassemble the requests if they go above the maximum. +// (Also as a consequence of this temporarily removing the `const` +// qualifier to avoid "const initializer [...] is not a constant" error.) +var MaxRequestLength = uint64(build.ForkLengthThreshold) + +const ( + // Extracted constants from the code. + // FIXME: Should be reviewed and confirmed. + SuccessPeerTagValue = 25 + WriteReqDeadline = 5 * time.Second + ReadResDeadline = WriteReqDeadline + ReadResMinSpeed = 50 << 10 + ShufflePeersPrefix = 16 + WriteResDeadline = 60 * time.Second +) + +// FIXME: Rename. Make private. +type Request struct { + // List of ordered CIDs comprising a `TipSetKey` from where to start + // fetching backwards. + // FIXME: Consider using `TipSetKey` now (introduced after the creation + // of this protocol) instead of converting back and forth. + Head []cid.Cid + // Number of block sets to fetch from `Head` (inclusive, should always + // be in the range `[1, MaxRequestLength]`). + Length uint64 + // Request options, see `Options` type for more details. Compressed + // in a single `uint64` to save space. + Options uint64 +} + +// `Request` processed and validated to query the tipsets needed. +type validatedRequest struct { + head types.TipSetKey + length uint64 + options *parsedOptions +} + +// Request options. When fetching the chain segment we can fetch +// either block headers, messages, or both. +const ( + Headers = 1 << iota + Messages +) + +// Decompressed options into separate struct members for easy access +// during internal processing.. +type parsedOptions struct { + IncludeHeaders bool + IncludeMessages bool +} + +func (options *parsedOptions) noOptionsSet() bool { + return options.IncludeHeaders == false && + options.IncludeMessages == false +} + +func parseOptions(optfield uint64) *parsedOptions { + return &parsedOptions{ + IncludeHeaders: optfield&(uint64(Headers)) != 0, + IncludeMessages: optfield&(uint64(Messages)) != 0, + } +} + +// FIXME: Rename. Make private. +type Response struct { + Status status + // String that complements the error status when converting to an + // internal error (see `statusToError()`). + ErrorMessage string + + Chain []*BSTipSet +} + +type status uint64 + +const ( + Ok status = 0 + // We could not fetch all blocks requested (but at least we returned + // the `Head` requested). Not considered an error. + Partial = 101 + + // Errors + NotFound = 201 + GoAway = 202 + InternalError = 203 + BadRequest = 204 +) + +// Convert status to internal error. +func (res *Response) statusToError() error { + switch res.Status { + case Ok, Partial: + return nil + // FIXME: Consider if we want to not process `Partial` responses + // and return an error instead. + case NotFound: + return xerrors.Errorf("not found") + case GoAway: + return xerrors.Errorf("not handling 'go away' chainxchg responses yet") + case InternalError: + return xerrors.Errorf("block sync peer errored: %s", res.ErrorMessage) + case BadRequest: + return xerrors.Errorf("block sync request invalid: %s", res.ErrorMessage) + default: + return xerrors.Errorf("unrecognized response code: %d", res.Status) + } +} + +// FIXME: Rename. +type BSTipSet struct { + // List of blocks belonging to a single tipset to which the + // `CompactedMessages` are linked. + Blocks []*types.BlockHeader + Messages *CompactedMessages +} + +// All messages of a single tipset compacted together instead +// of grouped by block to save space, since there are normally +// many repeated messages per tipset in different blocks. +// +// `BlsIncludes`/`SecpkIncludes` matches `Bls`/`Secpk` messages +// to blocks in the tipsets with the format: +// `BlsIncludes[BI][MI]` +// - BI: block index in the tipset. +// - MI: message index in `Bls` list +// +// FIXME: The logic to decompress this structure should belong +// +// to itself, not to the consumer. +type CompactedMessages struct { + Bls []*types.Message + BlsIncludes [][]uint64 + + Secpk []*types.SignedMessage + SecpkIncludes [][]uint64 +} + +// Response that has been validated according to the protocol +// and can be safely accessed. +type validatedResponse struct { + tipsets []*types.TipSet + // List of all messages per tipset (grouped by tipset, + // not by block, hence a single index like `tipsets`). + messages []*CompactedMessages +} + +// Decompress messages and form full tipsets with them. The headers +// need to have been requested as well. +func (res *validatedResponse) toFullTipSets() []*store.FullTipSet { + if len(res.tipsets) == 0 || len(res.tipsets) != len(res.messages) { + // This decompression can only be done if both headers and + // messages are returned in the response. (The second check + // is already implied by the guarantees of `validatedResponse`, + // added here just for completeness.) + return nil + } + ftsList := make([]*store.FullTipSet, len(res.tipsets)) + for tipsetIdx := range res.tipsets { + fts := &store.FullTipSet{} // FIXME: We should use the `NewFullTipSet` API. + msgs := res.messages[tipsetIdx] + for blockIdx, b := range res.tipsets[tipsetIdx].Blocks() { + fb := &types.FullBlock{ + Header: b, + } + for _, mi := range msgs.BlsIncludes[blockIdx] { + fb.BlsMessages = append(fb.BlsMessages, msgs.Bls[mi]) + } + for _, mi := range msgs.SecpkIncludes[blockIdx] { + fb.SecpkMessages = append(fb.SecpkMessages, msgs.Secpk[mi]) + } + + fts.Blocks = append(fts.Blocks, fb) + } + ftsList[tipsetIdx] = fts + } + return ftsList +} diff --git a/chain/exchange/server.go b/chain/exchange/server.go new file mode 100644 index 000000000..03dcf0ed7 --- /dev/null +++ b/chain/exchange/server.go @@ -0,0 +1,250 @@ +package exchange + +import ( + "bufio" + "context" + "fmt" + "time" + + "github.com/ipfs/go-cid" + inet "github.com/libp2p/go-libp2p/core/network" + "go.opencensus.io/trace" + "golang.org/x/xerrors" + + cborutil "github.com/filecoin-project/go-cbor-util" + + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/chain/types" +) + +// server implements exchange.Server. It services requests for the +// libp2p ChainExchange protocol. +type server struct { + cs *store.ChainStore +} + +var _ Server = (*server)(nil) + +// NewServer creates a new libp2p-based exchange.Server. It services requests +// for the libp2p ChainExchange protocol. +func NewServer(cs *store.ChainStore) Server { + return &server{ + cs: cs, + } +} + +// HandleStream implements Server.HandleStream. Refer to the godocs there. +func (s *server) HandleStream(stream inet.Stream) { + ctx, span := trace.StartSpan(context.Background(), "chainxchg.HandleStream") + defer span.End() + + defer stream.Close() //nolint:errcheck + + var req Request + if err := cborutil.ReadCborRPC(bufio.NewReader(stream), &req); err != nil { + log.Warnf("failed to read block sync request: %s", err) + return + } + log.Debugw("block sync request", + "start", req.Head, "len", req.Length) + + resp, err := s.processRequest(ctx, &req) + if err != nil { + log.Warn("failed to process request: ", err) + return + } + + _ = stream.SetDeadline(time.Now().Add(WriteResDeadline)) + buffered := bufio.NewWriter(stream) + if err = cborutil.WriteCborRPC(buffered, resp); err == nil { + err = buffered.Flush() + } + if err != nil { + _ = stream.SetDeadline(time.Time{}) + log.Warnw("failed to write back response for handle stream", + "err", err, "peer", stream.Conn().RemotePeer()) + return + } + _ = stream.SetDeadline(time.Time{}) +} + +// Validate and service the request. We return either a protocol +// response or an internal error. +func (s *server) processRequest(ctx context.Context, req *Request) (*Response, error) { + validReq, errResponse := validateRequest(ctx, req) + if errResponse != nil { + // The request did not pass validation, return the response + // indicating it. + return errResponse, nil + } + + return s.serviceRequest(ctx, validReq) +} + +// Validate request. We either return a `validatedRequest`, or an error +// `Response` indicating why we can't process it. We do not return any +// internal errors here, we just signal protocol ones. +func validateRequest(ctx context.Context, req *Request) (*validatedRequest, *Response) { + _, span := trace.StartSpan(ctx, "chainxchg.ValidateRequest") + defer span.End() + + validReq := validatedRequest{} + + validReq.options = parseOptions(req.Options) + if validReq.options.noOptionsSet() { + return nil, &Response{ + Status: BadRequest, + ErrorMessage: "no options set", + } + } + + validReq.length = req.Length + if validReq.length > MaxRequestLength { + return nil, &Response{ + Status: BadRequest, + ErrorMessage: fmt.Sprintf("request length over maximum allowed (%d)", + MaxRequestLength), + } + } + if validReq.length == 0 { + return nil, &Response{ + Status: BadRequest, + ErrorMessage: "invalid request length of zero", + } + } + + if len(req.Head) == 0 { + return nil, &Response{ + Status: BadRequest, + ErrorMessage: "no cids in request", + } + } + validReq.head = types.NewTipSetKey(req.Head...) + + // FIXME: Add as a defer at the start. + span.AddAttributes( + trace.BoolAttribute("blocks", validReq.options.IncludeHeaders), + trace.BoolAttribute("messages", validReq.options.IncludeMessages), + trace.Int64Attribute("reqlen", int64(validReq.length)), + ) + + return &validReq, nil +} + +func (s *server) serviceRequest(ctx context.Context, req *validatedRequest) (*Response, error) { + _, span := trace.StartSpan(ctx, "chainxchg.ServiceRequest") + defer span.End() + + chain, err := collectChainSegment(ctx, s.cs, req) + if err != nil { + log.Warn("block sync request: collectChainSegment failed: ", err) + return &Response{ + Status: InternalError, + ErrorMessage: err.Error(), + }, nil + } + + status := Ok + if len(chain) < int(req.length) { + status = Partial + } + + return &Response{ + Chain: chain, + Status: status, + }, nil +} + +func collectChainSegment(ctx context.Context, cs *store.ChainStore, req *validatedRequest) ([]*BSTipSet, error) { + var bstips []*BSTipSet + + cur := req.head + for { + var bst BSTipSet + ts, err := cs.LoadTipSet(ctx, cur) + if err != nil { + return nil, xerrors.Errorf("failed loading tipset %s: %w", cur, err) + } + + if req.options.IncludeHeaders { + bst.Blocks = ts.Blocks() + } + + if req.options.IncludeMessages { + bmsgs, bmincl, smsgs, smincl, err := gatherMessages(ctx, cs, ts) + if err != nil { + return nil, xerrors.Errorf("gather messages failed: %w", err) + } + + // FIXME: Pass the response to `gatherMessages()` and set all this there. + bst.Messages = &CompactedMessages{} + bst.Messages.Bls = bmsgs + bst.Messages.BlsIncludes = bmincl + bst.Messages.Secpk = smsgs + bst.Messages.SecpkIncludes = smincl + } + + bstips = append(bstips, &bst) + + // If we collected the length requested or if we reached the + // start (genesis), then stop. + if uint64(len(bstips)) >= req.length || ts.Height() == 0 { + return bstips, nil + } + + cur = ts.Parents() + } +} + +func gatherMessages(ctx context.Context, cs *store.ChainStore, ts *types.TipSet) ([]*types.Message, [][]uint64, []*types.SignedMessage, [][]uint64, error) { + blsmsgmap := make(map[cid.Cid]uint64) + secpkmsgmap := make(map[cid.Cid]uint64) + var secpkincl, blsincl [][]uint64 + + var blscids, secpkcids []cid.Cid + for _, block := range ts.Blocks() { + bc, sc, err := cs.ReadMsgMetaCids(ctx, block.Messages) + if err != nil { + return nil, nil, nil, nil, err + } + + // FIXME: DRY. Use `chain.Message` interface. + bmi := make([]uint64, 0, len(bc)) + for _, m := range bc { + i, ok := blsmsgmap[m] + if !ok { + i = uint64(len(blscids)) + blscids = append(blscids, m) + blsmsgmap[m] = i + } + + bmi = append(bmi, i) + } + blsincl = append(blsincl, bmi) + + smi := make([]uint64, 0, len(sc)) + for _, m := range sc { + i, ok := secpkmsgmap[m] + if !ok { + i = uint64(len(secpkcids)) + secpkcids = append(secpkcids, m) + secpkmsgmap[m] = i + } + + smi = append(smi, i) + } + secpkincl = append(secpkincl, smi) + } + + blsmsgs, err := cs.LoadMessagesFromCids(ctx, blscids) + if err != nil { + return nil, nil, nil, nil, err + } + + secpkmsgs, err := cs.LoadSignedMessagesFromCids(ctx, secpkcids) + if err != nil { + return nil, nil, nil, nil, err + } + + return blsmsgs, blsincl, secpkmsgs, secpkincl, nil +} diff --git a/chain/gen/gen.go b/chain/gen/gen.go index c9ebebcef..98610cd6c 100644 --- a/chain/gen/gen.go +++ b/chain/gen/gen.go @@ -4,31 +4,38 @@ import ( "bytes" "context" "fmt" - "io/ioutil" + "io" + "os" "sync/atomic" "time" - "github.com/filecoin-project/go-address" - commcid "github.com/filecoin-project/go-fil-commcid" - "github.com/filecoin-project/specs-actors/actors/abi" - saminer "github.com/filecoin-project/specs-actors/actors/builtin/miner" - "github.com/filecoin-project/specs-actors/actors/crypto" - block "github.com/ipfs/go-block-format" + "github.com/google/uuid" "github.com/ipfs/go-blockservice" "github.com/ipfs/go-cid" - blockstore "github.com/ipfs/go-ipfs-blockstore" offline "github.com/ipfs/go-ipfs-exchange-offline" format "github.com/ipfs/go-ipld-format" logging "github.com/ipfs/go-log/v2" "github.com/ipfs/go-merkledag" "github.com/ipld/go-car" - "go.opencensus.io/trace" "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/crypto" + "github.com/filecoin-project/go-state-types/network" + proof7 "github.com/filecoin-project/specs-actors/v7/actors/runtime/proof" + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/blockstore" "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/beacon" + "github.com/filecoin-project/lotus/chain/consensus" + "github.com/filecoin-project/lotus/chain/consensus/filcns" genesis2 "github.com/filecoin-project/lotus/chain/gen/genesis" + "github.com/filecoin-project/lotus/chain/index" + "github.com/filecoin-project/lotus/chain/rand" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" @@ -36,14 +43,20 @@ import ( "github.com/filecoin-project/lotus/chain/wallet" "github.com/filecoin-project/lotus/cmd/lotus-seed/seed" "github.com/filecoin-project/lotus/genesis" - "github.com/filecoin-project/lotus/lib/sigs" + "github.com/filecoin-project/lotus/journal" "github.com/filecoin-project/lotus/node/repo" - "github.com/filecoin-project/sector-storage/ffiwrapper" + "github.com/filecoin-project/lotus/storage/sealer/ffiwrapper" + "github.com/filecoin-project/lotus/storage/sealer/storiface" ) +const msgsPerBlock = 20 + +//nolint:deadcode,varcheck var log = logging.Logger("gen") -const msgsPerBlock = 20 +var ValidWpostForTesting = []proof7.PoStProof{{ + ProofBytes: []byte("valid proof"), +}} type ChainGen struct { msgsPerBlock int @@ -52,7 +65,7 @@ type ChainGen struct { cs *store.ChainStore - beacon beacon.RandomBeacon + beacon beacon.Schedule sm *stmgr.StateManager @@ -63,11 +76,12 @@ type ChainGen struct { GetMessages func(*ChainGen) ([]*types.SignedMessage, error) - w *wallet.Wallet + w *wallet.LocalWallet - eppProvs map[address.Address]WinningPoStProver - Miners []address.Address - receivers []address.Address + eppProvs map[address.Address]WinningPoStProver + Miners []address.Address + receivers []address.Address + // a SecP address banker address.Address bankerNonce uint64 @@ -75,23 +89,35 @@ type ChainGen struct { lr repo.LockedRepo } -type mybs struct { - blockstore.Blockstore +var rootkeyMultisig = genesis.MultisigMeta{ + Signers: []address.Address{remAccTestKey}, + Threshold: 1, + VestingDuration: 0, + VestingStart: 0, } -func (m mybs) Get(c cid.Cid) (block.Block, error) { - b, err := m.Blockstore.Get(c) - if err != nil { - return nil, err - } - - return b, nil +var DefaultVerifregRootkeyActor = genesis.Actor{ + Type: genesis.TMultisig, + Balance: big.NewInt(0), + Meta: rootkeyMultisig.ActorMeta(), } -func NewGeneratorWithSectors(numSectors int) (*ChainGen, error) { - saminer.SupportedProofTypes = map[abi.RegisteredProof]struct{}{ - abi.RegisteredProof_StackedDRG2KiBSeal: {}, - } +var remAccTestKey, _ = address.NewFromString("t1ceb34gnsc6qk5dt6n7xg6ycwzasjhbxm3iylkiy") +var remAccMeta = genesis.MultisigMeta{ + Signers: []address.Address{remAccTestKey}, + Threshold: 1, +} + +var DefaultRemainderAccountActor = genesis.Actor{ + Type: genesis.TMultisig, + Balance: big.NewInt(0), + Meta: remAccMeta.ActorMeta(), +} + +func NewGeneratorWithSectorsAndUpgradeSchedule(numSectors int, us stmgr.UpgradeSchedule) (*ChainGen, error) { + j := journal.NilJournal() + // TODO: we really shouldn't modify a global variable here. + policy.SetSupportedProofTypes(abi.RegisteredSealProof_StackedDrg2KiBV1) mr := repo.NewMemory(nil) lr, err := mr.Lock(repo.StorageMiner) @@ -99,17 +125,23 @@ func NewGeneratorWithSectors(numSectors int) (*ChainGen, error) { return nil, xerrors.Errorf("taking mem-repo lock failed: %w", err) } - ds, err := lr.Datastore("/metadata") + ds, err := lr.Datastore(context.TODO(), "/metadata") if err != nil { return nil, xerrors.Errorf("failed to get metadata datastore: %w", err) } - bds, err := lr.Datastore("/blocks") + bs, err := lr.Blockstore(context.TODO(), repo.UniversalBlockstore) if err != nil { - return nil, xerrors.Errorf("failed to get blocks datastore: %w", err) + return nil, err } - bs := mybs{blockstore.NewIdStore(blockstore.NewBlockstore(bds))} + defer func() { + if c, ok := bs.(io.Closer); ok { + if err := c.Close(); err != nil { + log.Warnf("failed to close blockstore: %s", err) + } + } + }() ks, err := lr.KeyStore() if err != nil { @@ -121,14 +153,14 @@ func NewGeneratorWithSectors(numSectors int) (*ChainGen, error) { return nil, xerrors.Errorf("creating memrepo wallet failed: %w", err) } - banker, err := w.GenerateKey(crypto.SigTypeSecp256k1) + banker, err := w.WalletNew(context.Background(), types.KTSecp256k1) if err != nil { return nil, xerrors.Errorf("failed to generate banker key: %w", err) } receievers := make([]address.Address, msgsPerBlock) for r := range receievers { - receievers[r], err = w.GenerateKey(crypto.SigTypeBLS) + receievers[r], err = w.WalletNew(context.Background(), types.KTBLS) if err != nil { return nil, xerrors.Errorf("failed to generate receiver key: %w", err) } @@ -136,33 +168,33 @@ func NewGeneratorWithSectors(numSectors int) (*ChainGen, error) { maddr1 := genesis2.MinerAddress(0) - m1temp, err := ioutil.TempDir("", "preseal") + m1temp, err := os.MkdirTemp("", "preseal") if err != nil { return nil, err } - genm1, k1, err := seed.PreSeal(maddr1, abi.RegisteredProof_StackedDRG2KiBPoSt, 0, numSectors, m1temp, []byte("some randomness"), nil) + genm1, k1, err := seed.PreSeal(maddr1, abi.RegisteredSealProof_StackedDrg2KiBV1, 0, numSectors, m1temp, []byte("some randomness"), nil, true) if err != nil { return nil, err } maddr2 := genesis2.MinerAddress(1) - m2temp, err := ioutil.TempDir("", "preseal") + m2temp, err := os.MkdirTemp("", "preseal") if err != nil { return nil, err } - genm2, k2, err := seed.PreSeal(maddr2, abi.RegisteredProof_StackedDRG2KiBPoSt, 0, numSectors, m2temp, []byte("some randomness"), nil) + genm2, k2, err := seed.PreSeal(maddr2, abi.RegisteredSealProof_StackedDrg2KiBV1, 0, numSectors, m2temp, []byte("some randomness"), nil, true) if err != nil { return nil, err } - mk1, err := w.Import(k1) + mk1, err := w.WalletImport(context.Background(), k1) if err != nil { return nil, err } - mk2, err := w.Import(k2) + mk2, err := w.WalletImport(context.Background(), k2) if err != nil { return nil, err } @@ -170,15 +202,16 @@ func NewGeneratorWithSectors(numSectors int) (*ChainGen, error) { sys := vm.Syscalls(&genFakeVerifier{}) tpl := genesis.Template{ + NetworkVersion: network.Version0, Accounts: []genesis.Actor{ { Type: genesis.TAccount, - Balance: types.FromFil(40000), + Balance: types.FromFil(20_000_000), Meta: (&genesis.AccountMeta{Owner: mk1}).ActorMeta(), }, { Type: genesis.TAccount, - Balance: types.FromFil(40000), + Balance: types.FromFil(20_000_000), Meta: (&genesis.AccountMeta{Owner: mk2}).ActorMeta(), }, { @@ -191,21 +224,23 @@ func NewGeneratorWithSectors(numSectors int) (*ChainGen, error) { *genm1, *genm2, }, - NetworkName: "", - Timestamp: uint64(time.Now().Add(-500 * build.BlockDelay * time.Second).Unix()), + VerifregRootKey: DefaultVerifregRootkeyActor, + RemainderAccount: DefaultRemainderAccountActor, + NetworkName: uuid.New().String(), + Timestamp: uint64(build.Clock.Now().Add(-500 * time.Duration(build.BlockDelaySecs) * time.Second).Unix()), } - genb, err := genesis2.MakeGenesisBlock(context.TODO(), bs, sys, tpl) + genb, err := genesis2.MakeGenesisBlock(context.TODO(), j, bs, sys, tpl) if err != nil { return nil, xerrors.Errorf("make genesis block failed: %w", err) } - cs := store.NewChainStore(bs, ds, sys) + cs := store.NewChainStore(bs, bs, ds, filcns.Weight, j) genfb := &types.FullBlock{Header: genb.Genesis} gents := store.NewFullTipSet([]*types.FullBlock{genfb}) - if err := cs.SetGenesis(genb.Genesis); err != nil { + if err := cs.SetGenesis(context.TODO(), genb.Genesis); err != nil { return nil, xerrors.Errorf("set genesis failed: %w", err) } @@ -214,11 +249,18 @@ func NewGeneratorWithSectors(numSectors int) (*ChainGen, error) { mgen[genesis2.MinerAddress(uint64(i))] = &wppProvider{} } - sm := stmgr.NewStateManager(cs) - miners := []address.Address{maddr1, maddr2} - beac := beacon.NewMockBeacon(time.Second) + beac := beacon.Schedule{{Start: 0, Beacon: beacon.NewMockBeacon(time.Second)}} + //beac, err := drand.NewDrandBeacon(tpl.Timestamp, build.BlockDelaySecs) + //if err != nil { + //return nil, xerrors.Errorf("creating drand beacon: %w", err) + //} + + sm, err := stmgr.NewStateManager(cs, consensus.NewTipSetExecutor(filcns.RewardFunc), sys, us, beac, ds, index.DummyMsgIndex) + if err != nil { + return nil, xerrors.Errorf("initing stmgr: %w", err) + } gen := &ChainGen{ bs: bs, @@ -248,6 +290,22 @@ func NewGenerator() (*ChainGen, error) { return NewGeneratorWithSectors(1) } +func NewGeneratorWithSectors(numSectors int) (*ChainGen, error) { + return NewGeneratorWithSectorsAndUpgradeSchedule(numSectors, filcns.DefaultUpgradeSchedule()) +} + +func NewGeneratorWithUpgradeSchedule(us stmgr.UpgradeSchedule) (*ChainGen, error) { + return NewGeneratorWithSectorsAndUpgradeSchedule(1, us) +} + +func (cg *ChainGen) Blockstore() blockstore.Blockstore { + return cg.bs +} + +func (cg *ChainGen) StateManager() *stmgr.StateManager { + return cg.sm +} + func (cg *ChainGen) SetStateManager(sm *stmgr.StateManager) { cg.sm = sm } @@ -256,6 +314,10 @@ func (cg *ChainGen) ChainStore() *store.ChainStore { return cg.cs } +func (cg *ChainGen) BeaconSchedule() beacon.Schedule { + return cg.beacon +} + func (cg *ChainGen) Genesis() *types.BlockHeader { return cg.genesis } @@ -276,7 +338,8 @@ func (cg *ChainGen) GenesisCar() ([]byte, error) { func CarWalkFunc(nd format.Node) (out []*format.Link, err error) { for _, link := range nd.Links() { - if link.Cid.Prefix().MhType == uint64(commcid.FC_SEALED_V1) || link.Cid.Prefix().MhType == uint64(commcid.FC_UNSEALED_V1) { + pref := link.Cid.Prefix() + if pref.Codec == cid.FilCommitmentSealed || pref.Codec == cid.FilCommitmentUnsealed { continue } out = append(out, link) @@ -293,14 +356,8 @@ func (cg *ChainGen) nextBlockProof(ctx context.Context, pts *types.TipSet, m add return nil, nil, nil, xerrors.Errorf("get miner base info: %w", err) } - prev := mbi.PrevBeaconEntry - - entries, err := beacon.BeaconEntriesForBlock(ctx, cg.beacon, round, prev) - if err != nil { - return nil, nil, nil, xerrors.Errorf("get beacon entries for block: %w", err) - } - - rbase := prev + entries := mbi.BeaconEntries + rbase := mbi.PrevBeaconEntry if len(entries) > 0 { rbase = entries[len(entries)-1] } @@ -315,11 +372,11 @@ func (cg *ChainGen) nextBlockProof(ctx context.Context, pts *types.TipSet, m add return nil, nil, nil, xerrors.Errorf("failed to cbor marshal address: %w", err) } - if len(entries) == 0 { + if round > build.UpgradeSmokeHeight { buf.Write(pts.MinTicket().VRFProof) } - ticketRand, err := store.DrawRandomness(rbase.Data, crypto.DomainSeparationTag_TicketProduction, round-build.TicketRandomnessLookback, buf.Bytes()) + ticketRand, err := rand.DrawRandomness(rbase.Data, crypto.DomainSeparationTag_TicketProduction, round-build.TicketRandomnessLookback, buf.Bytes()) if err != nil { return nil, nil, nil, err } @@ -331,7 +388,13 @@ func (cg *ChainGen) nextBlockProof(ctx context.Context, pts *types.TipSet, m add return nil, nil, nil, xerrors.Errorf("get miner worker: %w", err) } - vrfout, err := ComputeVRF(ctx, cg.w.Sign, worker, ticketRand) + sf := func(ctx context.Context, a address.Address, i []byte) (*crypto.Signature, error) { + return cg.w.WalletSign(ctx, a, i, api.MsgMeta{ + Type: api.MTUnknown, + }) + } + + vrfout, err := ComputeVRF(ctx, sf, worker, ticketRand) if err != nil { return nil, nil, nil, xerrors.Errorf("compute VRF: %w", err) } @@ -345,25 +408,51 @@ type MinedTipSet struct { } func (cg *ChainGen) NextTipSet() (*MinedTipSet, error) { - mts, err := cg.NextTipSetFromMiners(cg.CurTipset.TipSet(), cg.Miners) + return cg.NextTipSetWithNulls(0) +} + +func (cg *ChainGen) NextTipSetWithNulls(nulls abi.ChainEpoch) (*MinedTipSet, error) { + mts, err := cg.NextTipSetFromMiners(cg.CurTipset.TipSet(), cg.Miners, nulls) if err != nil { return nil, err } - cg.CurTipset = mts.TipSet return mts, nil } -func (cg *ChainGen) NextTipSetFromMiners(base *types.TipSet, miners []address.Address) (*MinedTipSet, error) { - var blks []*types.FullBlock +func (cg *ChainGen) SetWinningPoStProver(m address.Address, wpp WinningPoStProver) { + cg.eppProvs[m] = wpp +} - msgs, err := cg.GetMessages(cg) +func (cg *ChainGen) NextTipSetFromMiners(base *types.TipSet, miners []address.Address, nulls abi.ChainEpoch) (*MinedTipSet, error) { + ms, err := cg.GetMessages(cg) if err != nil { return nil, xerrors.Errorf("get random messages: %w", err) } - for round := base.Height() + 1; len(blks) == 0; round++ { - for _, m := range miners { + msgs := make([][]*types.SignedMessage, len(miners)) + for i := range msgs { + msgs[i] = ms + } + + fts, err := cg.NextTipSetFromMinersWithMessagesAndNulls(base, miners, msgs, nulls) + if err != nil { + return nil, err + } + + cg.CurTipset = fts + + return &MinedTipSet{ + TipSet: fts, + Messages: ms, + }, nil +} + +func (cg *ChainGen) NextTipSetFromMinersWithMessagesAndNulls(base *types.TipSet, miners []address.Address, msgs [][]*types.SignedMessage, nulls abi.ChainEpoch) (*store.FullTipSet, error) { + var blks []*types.FullBlock + + for round := base.Height() + nulls + 1; len(blks) == 0; round++ { + for mi, m := range miners { bvals, et, ticket, err := cg.nextBlockProof(context.TODO(), base, m, round) if err != nil { return nil, xerrors.Errorf("next block proof: %w", err) @@ -371,45 +460,43 @@ func (cg *ChainGen) NextTipSetFromMiners(base *types.TipSet, miners []address.Ad if et != nil { // TODO: maybe think about passing in more real parameters to this? - wpost, err := cg.eppProvs[m].ComputeProof(context.TODO(), nil, nil) + wpost, err := cg.eppProvs[m].ComputeProof(context.TODO(), nil, nil, round, network.Version0) if err != nil { return nil, err } - fblk, err := cg.makeBlock(base, m, ticket, et, bvals, round, wpost, msgs) + fblk, err := cg.makeBlock(base, m, ticket, et, bvals, round, wpost, msgs[mi]) if err != nil { return nil, xerrors.Errorf("making a block for next tipset failed: %w", err) } - if err := cg.cs.PersistBlockHeaders(fblk.Header); err != nil { - return nil, xerrors.Errorf("chainstore AddBlock: %w", err) - } - blks = append(blks, fblk) } } } fts := store.NewFullTipSet(blks) + if err := cg.cs.PutTipSet(context.TODO(), fts.TipSet()); err != nil { + return nil, err + } - return &MinedTipSet{ - TipSet: fts, - Messages: msgs, - }, nil + cg.CurTipset = fts + + return fts, nil } func (cg *ChainGen) makeBlock(parents *types.TipSet, m address.Address, vrfticket *types.Ticket, eticket *types.ElectionProof, bvals []types.BeaconEntry, height abi.ChainEpoch, - wpost []abi.PoStProof, msgs []*types.SignedMessage) (*types.FullBlock, error) { + wpost []proof7.PoStProof, msgs []*types.SignedMessage) (*types.FullBlock, error) { var ts uint64 if cg.Timestamper != nil { ts = cg.Timestamper(parents, height-parents.Height()) } else { - ts = parents.MinTimestamp() + uint64((height-parents.Height())*build.BlockDelay) + ts = parents.MinTimestamp() + uint64(height-parents.Height())*build.BlockDelaySecs } - fblk, err := MinerCreateBlock(context.TODO(), cg.sm, cg.w, &api.BlockTemplate{ + fblk, err := filcns.NewFilecoinExpectedConsensus(cg.sm, nil, nil, nil).CreateBlock(context.TODO(), cg.w, &api.BlockTemplate{ Miner: m, Parents: parents.Key(), Ticket: vrfticket, @@ -427,15 +514,19 @@ func (cg *ChainGen) makeBlock(parents *types.TipSet, m address.Address, vrfticke return fblk, err } -// This function is awkward. It's used to deal with messages made when +// ResyncBankerNonce is used for dealing with messages made when // simulating forks func (cg *ChainGen) ResyncBankerNonce(ts *types.TipSet) error { - act, err := cg.sm.GetActor(cg.banker, ts) + st, err := cg.sm.ParentState(ts) + if err != nil { + return err + } + act, err := st.GetActor(cg.banker) if err != nil { return err } - cg.bankerNonce = act.Nonce + return nil } @@ -443,7 +534,7 @@ func (cg *ChainGen) Banker() address.Address { return cg.banker } -func (cg *ChainGen) Wallet() *wallet.Wallet { +func (cg *ChainGen) Wallet() *wallet.LocalWallet { return cg.w } @@ -460,11 +551,14 @@ func getRandomMessages(cg *ChainGen) ([]*types.SignedMessage, error) { Method: 0, - GasLimit: 10000, - GasPrice: types.NewInt(0), + GasLimit: 100_000_000, + GasFeeCap: types.NewInt(0), + GasPremium: types.NewInt(0), } - sig, err := cg.w.Sign(context.TODO(), cg.banker, msg.Cid().Bytes()) + sig, err := cg.w.WalletSign(context.TODO(), cg.banker, msg.Cid().Bytes(), api.MsgMeta{ + Type: api.MTUnknown, // testing + }) if err != nil { return nil, err } @@ -486,7 +580,8 @@ func (cg *ChainGen) YieldRepo() (repo.Repo, error) { } type MiningCheckAPI interface { - ChainGetRandomness(ctx context.Context, tsk types.TipSetKey, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) (abi.Randomness, error) + StateGetRandomnessFromBeacon(ctx context.Context, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte, tsk types.TipSetKey) (abi.Randomness, error) + StateGetRandomnessFromTickets(ctx context.Context, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte, tsk types.TipSetKey) (abi.Randomness, error) MinerGetBaseInfo(context.Context, address.Address, abi.ChainEpoch, types.TipSetKey) (*api.MiningBaseInfo, error) @@ -494,19 +589,18 @@ type MiningCheckAPI interface { } type mca struct { - w *wallet.Wallet + w *wallet.LocalWallet sm *stmgr.StateManager - pv ffiwrapper.Verifier - bcn beacon.RandomBeacon + pv storiface.Verifier + bcn beacon.Schedule } -func (mca mca) ChainGetRandomness(ctx context.Context, tsk types.TipSetKey, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) (abi.Randomness, error) { - pts, err := mca.sm.ChainStore().LoadTipSet(tsk) - if err != nil { - return nil, xerrors.Errorf("loading tipset key: %w", err) - } +func (mca mca) StateGetRandomnessFromTickets(ctx context.Context, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte, tsk types.TipSetKey) (abi.Randomness, error) { + return mca.sm.GetRandomnessFromTickets(ctx, personalization, randEpoch, entropy, tsk) +} - return mca.sm.ChainStore().GetRandomness(ctx, pts.Cids(), personalization, randEpoch, entropy) +func (mca mca) StateGetRandomnessFromBeacon(ctx context.Context, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte, tsk types.TipSetKey) (abi.Randomness, error) { + return mca.sm.GetRandomnessFromBeacon(ctx, personalization, randEpoch, entropy, tsk) } func (mca mca) MinerGetBaseInfo(ctx context.Context, maddr address.Address, epoch abi.ChainEpoch, tsk types.TipSetKey) (*api.MiningBaseInfo, error) { @@ -514,12 +608,14 @@ func (mca mca) MinerGetBaseInfo(ctx context.Context, maddr address.Address, epoc } func (mca mca) WalletSign(ctx context.Context, a address.Address, v []byte) (*crypto.Signature, error) { - return mca.w.Sign(ctx, a, v) + return mca.w.WalletSign(ctx, a, v, api.MsgMeta{ + Type: api.MTUnknown, + }) } type WinningPoStProver interface { GenerateCandidates(context.Context, abi.PoStRandomness, uint64) ([]uint64, error) - ComputeProof(context.Context, []abi.SectorInfo, abi.PoStRandomness) ([]abi.PoStProof, error) + ComputeProof(context.Context, []proof7.ExtendedSectorInfo, abi.PoStRandomness, abi.ChainEpoch, network.Version) ([]proof7.PoStProof, error) } type wppProvider struct{} @@ -528,17 +624,8 @@ func (wpp *wppProvider) GenerateCandidates(ctx context.Context, _ abi.PoStRandom return []uint64{0}, nil } -func (wpp *wppProvider) ComputeProof(context.Context, []abi.SectorInfo, abi.PoStRandomness) ([]abi.PoStProof, error) { - return []abi.PoStProof{{ - ProofBytes: []byte("valid proof"), - }}, nil -} - -type ProofInput struct { - sectors []abi.SectorInfo - hvrf []byte - challengedSectors []uint64 - vrfout []byte +func (wpp *wppProvider) ComputeProof(context.Context, []proof7.ExtendedSectorInfo, abi.PoStRandomness, abi.ChainEpoch, network.Version) ([]proof7.PoStProof, error) { + return ValidWpostForTesting, nil } func IsRoundWinner(ctx context.Context, ts *types.TipSet, round abi.ChainEpoch, @@ -546,10 +633,10 @@ func IsRoundWinner(ctx context.Context, ts *types.TipSet, round abi.ChainEpoch, buf := new(bytes.Buffer) if err := miner.MarshalCBOR(buf); err != nil { - return nil, xerrors.Errorf("failed to cbor marshal address: %w") + return nil, xerrors.Errorf("failed to cbor marshal address: %w", err) } - electionRand, err := store.DrawRandomness(brand.Data, crypto.DomainSeparationTag_ElectionProofProduction, round, buf.Bytes()) + electionRand, err := rand.DrawRandomness(brand.Data, crypto.DomainSeparationTag_ElectionProofProduction, round, buf.Bytes()) if err != nil { return nil, xerrors.Errorf("failed to draw randomness: %w", err) } @@ -559,32 +646,18 @@ func IsRoundWinner(ctx context.Context, ts *types.TipSet, round abi.ChainEpoch, return nil, xerrors.Errorf("failed to compute VRF: %w", err) } - // TODO: wire in real power - if !types.IsTicketWinner(vrfout, mbi.MinerPower, mbi.NetworkPower) { + ep := &types.ElectionProof{VRFProof: vrfout} + j := ep.ComputeWinCount(mbi.MinerPower, mbi.NetworkPower) + ep.WinCount = j + if j < 1 { return nil, nil } - return &types.ElectionProof{VRFProof: vrfout}, nil + return ep, nil } type SignFunc func(context.Context, address.Address, []byte) (*crypto.Signature, error) -func VerifyVRF(ctx context.Context, worker address.Address, vrfBase, vrfproof []byte) error { - _, span := trace.StartSpan(ctx, "VerifyVRF") - defer span.End() - - sig := &crypto.Signature{ - Type: crypto.SigTypeBLS, - Data: vrfproof, - } - - if err := sigs.Verify(sig, worker, vrfBase); err != nil { - return xerrors.Errorf("vrf was invalid: %w", err) - } - - return nil -} - func ComputeVRF(ctx context.Context, sign SignFunc, worker address.Address, sigInput []byte) ([]byte, error) { sig, err := sign(ctx, worker, sigInput) if err != nil { @@ -600,20 +673,28 @@ func ComputeVRF(ctx context.Context, sign SignFunc, worker address.Address, sigI type genFakeVerifier struct{} -var _ ffiwrapper.Verifier = (*genFakeVerifier)(nil) +var _ storiface.Verifier = (*genFakeVerifier)(nil) -func (m genFakeVerifier) VerifySeal(svi abi.SealVerifyInfo) (bool, error) { +func (m genFakeVerifier) VerifySeal(svi proof7.SealVerifyInfo) (bool, error) { return true, nil } -func (m genFakeVerifier) VerifyWinningPoSt(ctx context.Context, info abi.WinningPoStVerifyInfo) (bool, error) { +func (m genFakeVerifier) VerifyAggregateSeals(aggregate proof7.AggregateSealVerifyProofAndInfos) (bool, error) { panic("not supported") } -func (m genFakeVerifier) VerifyWindowPoSt(ctx context.Context, info abi.WindowPoStVerifyInfo) (bool, error) { +func (m genFakeVerifier) VerifyReplicaUpdate(update proof7.ReplicaUpdateInfo) (bool, error) { panic("not supported") } -func (m genFakeVerifier) GenerateWinningPoStSectorChallenge(ctx context.Context, proof abi.RegisteredProof, id abi.ActorID, randomness abi.PoStRandomness, u uint64) ([]uint64, error) { +func (m genFakeVerifier) VerifyWinningPoSt(ctx context.Context, info proof7.WinningPoStVerifyInfo) (bool, error) { + panic("not supported") +} + +func (m genFakeVerifier) VerifyWindowPoSt(ctx context.Context, info proof7.WindowPoStVerifyInfo) (bool, error) { + panic("not supported") +} + +func (m genFakeVerifier) GenerateWinningPoStSectorChallenge(ctx context.Context, proof abi.RegisteredPoStProof, id abi.ActorID, randomness abi.PoStRandomness, u uint64) ([]uint64, error) { panic("not supported") } diff --git a/chain/gen/gen_test.go b/chain/gen/gen_test.go index 69c8587ef..d04e55a26 100644 --- a/chain/gen/gen_test.go +++ b/chain/gen/gen_test.go @@ -1,24 +1,20 @@ +// stm: #unit package gen import ( "testing" - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/abi/big" - "github.com/filecoin-project/specs-actors/actors/builtin/miner" - "github.com/filecoin-project/specs-actors/actors/builtin/power" - "github.com/filecoin-project/specs-actors/actors/builtin/verifreg" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/chain/actors/policy" _ "github.com/filecoin-project/lotus/lib/sigs/bls" _ "github.com/filecoin-project/lotus/lib/sigs/secp" ) func init() { - miner.SupportedProofTypes = map[abi.RegisteredProof]struct{}{ - abi.RegisteredProof_StackedDRG2KiBSeal: {}, - } - power.ConsensusMinerMinPower = big.NewInt(2048) - verifreg.MinVerifiedDealSize = big.NewInt(256) + policy.SetSupportedProofTypes(abi.RegisteredSealProof_StackedDrg2KiBV1) + policy.SetConsensusMinerMinPower(abi.NewStoragePower(2048)) + policy.SetMinVerifiedDealSize(abi.NewStoragePower(256)) } func testGeneration(t testing.TB, n int, msgs int, sectors int) { @@ -39,8 +35,9 @@ func testGeneration(t testing.TB, n int, msgs int, sectors int) { } func TestChainGeneration(t *testing.T) { - testGeneration(t, 10, 20, 1) - testGeneration(t, 10, 20, 25) + //stm: @CHAIN_GEN_NEW_GEN_WITH_SECTORS_001, @CHAIN_GEN_NEXT_TIPSET_001 + t.Run("10-20-1", func(t *testing.T) { testGeneration(t, 10, 20, 1) }) + t.Run("10-20-25", func(t *testing.T) { testGeneration(t, 10, 20, 25) }) } func BenchmarkChainGeneration(b *testing.B) { diff --git a/chain/gen/genesis/f00_system.go b/chain/gen/genesis/f00_system.go new file mode 100644 index 000000000..5c6ecacbf --- /dev/null +++ b/chain/gen/genesis/f00_system.go @@ -0,0 +1,63 @@ +package genesis + +import ( + "context" + + "github.com/ipfs/go-cid" + cbor "github.com/ipfs/go-ipld-cbor" + "golang.org/x/xerrors" + + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/manifest" + + bstore "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin/system" + "github.com/filecoin-project/lotus/chain/types" +) + +func SetupSystemActor(ctx context.Context, bs bstore.Blockstore, av actorstypes.Version) (*types.Actor, error) { + + cst := cbor.NewCborStore(bs) + // TODO pass in built-in actors cid for V8 and later + st, err := system.MakeState(adt.WrapStore(ctx, cst), av, cid.Undef) + if err != nil { + return nil, err + } + + if av >= actorstypes.Version8 { + mfCid, ok := actors.GetManifest(av) + if !ok { + return nil, xerrors.Errorf("missing manifest for actors version %d", av) + } + + mf := manifest.Manifest{} + if err := cst.Get(ctx, mfCid, &mf); err != nil { + return nil, xerrors.Errorf("loading manifest for actors version %d: %w", av, err) + } + + if err := st.SetBuiltinActors(mf.Data); err != nil { + return nil, xerrors.Errorf("failed to set manifest data: %w", err) + } + } + + statecid, err := cst.Put(ctx, st.GetState()) + if err != nil { + return nil, err + } + + actcid, ok := actors.GetActorCodeID(av, manifest.SystemKey) + if !ok { + return nil, xerrors.Errorf("failed to get system actor code ID for actors version %d", av) + } + + act := &types.Actor{ + 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 new file mode 100644 index 000000000..706328d21 --- /dev/null +++ b/chain/gen/genesis/f01_init.go @@ -0,0 +1,190 @@ +package genesis + +import ( + "context" + "encoding/json" + "fmt" + + cbor "github.com/ipfs/go-ipld-cbor" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/manifest" + "github.com/filecoin-project/specs-actors/actors/util/adt" + + bstore "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/chain/actors" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/genesis" +) + +func SetupInitActor(ctx context.Context, bs bstore.Blockstore, netname string, initialActors []genesis.Actor, rootVerifier genesis.Actor, remainder genesis.Actor, av actorstypes.Version) (int64, *types.Actor, map[address.Address]address.Address, error) { + if len(initialActors) > MaxAccounts { + return 0, nil, nil, xerrors.New("too many initial actors") + } + + cst := cbor.NewCborStore(bs) + ist, err := init_.MakeState(adt.WrapStore(ctx, cst), av, netname) + if err != nil { + return 0, nil, nil, err + } + + if err = ist.SetNextID(MinerStart); err != nil { + return 0, nil, nil, err + } + + amap, err := ist.AddressMap() + if err != nil { + return 0, nil, nil, err + } + + keyToId := map[address.Address]address.Address{} + counter := int64(AccountStart) + + for _, a := range initialActors { + if a.Type == genesis.TMultisig { + var ainfo genesis.MultisigMeta + if err := json.Unmarshal(a.Meta, &ainfo); err != nil { + return 0, nil, nil, xerrors.Errorf("unmarshaling account meta: %w", err) + } + for _, e := range ainfo.Signers { + + if _, ok := keyToId[e]; ok { + continue + } + + fmt.Printf("init set %s t0%d\n", e, counter) + + value := cbg.CborInt(counter) + if err := amap.Put(abi.AddrKey(e), &value); err != nil { + return 0, nil, nil, err + } + counter = counter + 1 + var err error + keyToId[e], err = address.NewIDAddress(uint64(value)) + if err != nil { + return 0, nil, nil, err + } + + } + // Need to add actors for all multisigs too + continue + } + + if a.Type != genesis.TAccount { + return 0, nil, nil, xerrors.Errorf("unsupported account type: %s", a.Type) + } + + var ainfo genesis.AccountMeta + if err := json.Unmarshal(a.Meta, &ainfo); err != nil { + return 0, nil, nil, xerrors.Errorf("unmarshaling account meta: %w", err) + } + + fmt.Printf("init set %s t0%d\n", ainfo.Owner, counter) + + value := cbg.CborInt(counter) + if err := amap.Put(abi.AddrKey(ainfo.Owner), &value); err != nil { + return 0, nil, nil, err + } + counter = counter + 1 + + var err error + keyToId[ainfo.Owner], err = address.NewIDAddress(uint64(value)) + if err != nil { + return 0, nil, nil, err + } + } + + setupMsig := func(meta json.RawMessage) error { + var ainfo genesis.MultisigMeta + if err := json.Unmarshal(meta, &ainfo); err != nil { + return xerrors.Errorf("unmarshaling account meta: %w", err) + } + for _, e := range ainfo.Signers { + if _, ok := keyToId[e]; ok { + continue + } + fmt.Printf("init set %s t0%d\n", e, counter) + + value := cbg.CborInt(counter) + if err := amap.Put(abi.AddrKey(e), &value); err != nil { + return err + } + counter = counter + 1 + var err error + keyToId[e], err = address.NewIDAddress(uint64(value)) + if err != nil { + return err + } + + } + + return nil + } + + if rootVerifier.Type == genesis.TAccount { + var ainfo genesis.AccountMeta + if err := json.Unmarshal(rootVerifier.Meta, &ainfo); err != nil { + return 0, nil, nil, xerrors.Errorf("unmarshaling account meta: %w", err) + } + value := cbg.CborInt(80) + if err := amap.Put(abi.AddrKey(ainfo.Owner), &value); err != nil { + return 0, nil, nil, err + } + } else if rootVerifier.Type == genesis.TMultisig { + err := setupMsig(rootVerifier.Meta) + if err != nil { + return 0, nil, nil, xerrors.Errorf("setting up root verifier msig: %w", err) + } + } + + if remainder.Type == genesis.TAccount { + var ainfo genesis.AccountMeta + if err := json.Unmarshal(remainder.Meta, &ainfo); err != nil { + return 0, nil, nil, xerrors.Errorf("unmarshaling account meta: %w", err) + } + + // TODO: Use builtin.ReserveAddress... + value := cbg.CborInt(90) + if err := amap.Put(abi.AddrKey(ainfo.Owner), &value); err != nil { + return 0, nil, nil, err + } + } else if remainder.Type == genesis.TMultisig { + err := setupMsig(remainder.Meta) + if err != nil { + return 0, nil, nil, xerrors.Errorf("setting up remainder msig: %w", err) + } + } + + amapaddr, err := amap.Root() + if err != nil { + return 0, nil, nil, err + } + + if err = ist.SetAddressMap(amapaddr); err != nil { + return 0, nil, nil, err + } + + statecid, err := cst.Put(ctx, ist.GetState()) + if err != nil { + return 0, nil, nil, err + } + + actcid, ok := actors.GetActorCodeID(av, manifest.InitKey) + if !ok { + return 0, nil, nil, xerrors.Errorf("failed to get init actor code ID for actors version %d", av) + } + + act := &types.Actor{ + Code: actcid, + Head: statecid, + Balance: big.Zero(), + } + + return counter, act, keyToId, nil +} diff --git a/chain/gen/genesis/f02_reward.go b/chain/gen/genesis/f02_reward.go new file mode 100644 index 000000000..db32517f9 --- /dev/null +++ b/chain/gen/genesis/f02_reward.go @@ -0,0 +1,45 @@ +package genesis + +import ( + "context" + + cbor "github.com/ipfs/go-ipld-cbor" + "golang.org/x/xerrors" + + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/manifest" + + bstore "github.com/filecoin-project/lotus/blockstore" + "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/reward" + "github.com/filecoin-project/lotus/chain/types" +) + +func SetupRewardActor(ctx context.Context, bs bstore.Blockstore, qaPower big.Int, av actorstypes.Version) (*types.Actor, error) { + cst := cbor.NewCborStore(bs) + rst, err := reward.MakeState(adt.WrapStore(ctx, cst), av, qaPower) + if err != nil { + return nil, err + } + + statecid, err := cst.Put(ctx, rst.GetState()) + if err != nil { + return nil, err + } + + actcid, ok := actors.GetActorCodeID(av, manifest.RewardKey) + if !ok { + return nil, xerrors.Errorf("failed to get reward actor code ID for actors version %d", av) + } + + act := &types.Actor{ + Code: actcid, + Balance: types.BigInt{Int: build.InitialRewardBalance}, + Head: statecid, + } + + return act, nil +} diff --git a/chain/gen/genesis/f03_cron.go b/chain/gen/genesis/f03_cron.go new file mode 100644 index 000000000..4c377b191 --- /dev/null +++ b/chain/gen/genesis/f03_cron.go @@ -0,0 +1,44 @@ +package genesis + +import ( + "context" + + cbor "github.com/ipfs/go-ipld-cbor" + "golang.org/x/xerrors" + + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/manifest" + + bstore "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin/cron" + "github.com/filecoin-project/lotus/chain/types" +) + +func SetupCronActor(ctx context.Context, bs bstore.Blockstore, av actorstypes.Version) (*types.Actor, error) { + cst := cbor.NewCborStore(bs) + st, err := cron.MakeState(adt.WrapStore(ctx, cbor.NewCborStore(bs)), av) + if err != nil { + return nil, err + } + + statecid, err := cst.Put(ctx, st.GetState()) + if err != nil { + return nil, err + } + + actcid, ok := actors.GetActorCodeID(av, manifest.CronKey) + if !ok { + return nil, xerrors.Errorf("failed to get cron actor code ID for actors version %d", av) + } + + act := &types.Actor{ + 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 new file mode 100644 index 000000000..385cc97d2 --- /dev/null +++ b/chain/gen/genesis/f04_power.go @@ -0,0 +1,45 @@ +package genesis + +import ( + "context" + + cbor "github.com/ipfs/go-ipld-cbor" + "golang.org/x/xerrors" + + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/manifest" + "github.com/filecoin-project/specs-actors/actors/util/adt" + + bstore "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/builtin/power" + "github.com/filecoin-project/lotus/chain/types" +) + +func SetupStoragePowerActor(ctx context.Context, bs bstore.Blockstore, av actorstypes.Version) (*types.Actor, error) { + + cst := cbor.NewCborStore(bs) + pst, err := power.MakeState(adt.WrapStore(ctx, cbor.NewCborStore(bs)), av) + if err != nil { + return nil, err + } + + statecid, err := cst.Put(ctx, pst.GetState()) + if err != nil { + return nil, err + } + + actcid, ok := actors.GetActorCodeID(av, manifest.PowerKey) + if !ok { + return nil, xerrors.Errorf("failed to get power actor code ID for actors version %d", av) + } + + act := &types.Actor{ + 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 new file mode 100644 index 000000000..59c61a3ae --- /dev/null +++ b/chain/gen/genesis/f05_market.go @@ -0,0 +1,44 @@ +package genesis + +import ( + "context" + + cbor "github.com/ipfs/go-ipld-cbor" + "golang.org/x/xerrors" + + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/manifest" + + bstore "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin/market" + "github.com/filecoin-project/lotus/chain/types" +) + +func SetupStorageMarketActor(ctx context.Context, bs bstore.Blockstore, av actorstypes.Version) (*types.Actor, error) { + cst := cbor.NewCborStore(bs) + mst, err := market.MakeState(adt.WrapStore(ctx, cbor.NewCborStore(bs)), av) + if err != nil { + return nil, err + } + + statecid, err := cst.Put(ctx, mst.GetState()) + if err != nil { + return nil, err + } + + actcid, ok := actors.GetActorCodeID(av, manifest.MarketKey) + if !ok { + return nil, xerrors.Errorf("failed to get market actor code ID for actors version %d", av) + } + + act := &types.Actor{ + 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 new file mode 100644 index 000000000..ffddc814f --- /dev/null +++ b/chain/gen/genesis/f06_vreg.go @@ -0,0 +1,57 @@ +package genesis + +import ( + "context" + + cbor "github.com/ipfs/go-ipld-cbor" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/manifest" + "github.com/filecoin-project/specs-actors/actors/util/adt" + + bstore "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/builtin/verifreg" + "github.com/filecoin-project/lotus/chain/types" +) + +var RootVerifierID address.Address + +func init() { + + idk, err := address.NewFromString("t080") + if err != nil { + panic(err) + } + + RootVerifierID = idk +} + +func SetupVerifiedRegistryActor(ctx context.Context, bs bstore.Blockstore, av actorstypes.Version) (*types.Actor, error) { + cst := cbor.NewCborStore(bs) + vst, err := verifreg.MakeState(adt.WrapStore(ctx, cbor.NewCborStore(bs)), av, RootVerifierID) + if err != nil { + return nil, err + } + + statecid, err := cst.Put(ctx, vst.GetState()) + if err != nil { + return nil, err + } + + actcid, ok := actors.GetActorCodeID(av, manifest.VerifregKey) + if !ok { + return nil, xerrors.Errorf("failed to get verifreg actor code ID for actors version %d", av) + } + + act := &types.Actor{ + Code: actcid, + Head: statecid, + Balance: big.Zero(), + } + + return act, nil +} diff --git a/chain/gen/genesis/f07_dcap.go b/chain/gen/genesis/f07_dcap.go new file mode 100644 index 000000000..6d8e3258e --- /dev/null +++ b/chain/gen/genesis/f07_dcap.go @@ -0,0 +1,57 @@ +package genesis + +import ( + "context" + + cbor "github.com/ipfs/go-ipld-cbor" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/builtin" + "github.com/filecoin-project/go-state-types/manifest" + "github.com/filecoin-project/specs-actors/actors/util/adt" + + bstore "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/builtin/datacap" + "github.com/filecoin-project/lotus/chain/types" +) + +var GovernorId address.Address + +func init() { + idk, err := address.NewFromString("t06") + if err != nil { + panic(err) + } + + GovernorId = idk +} + +func SetupDatacapActor(ctx context.Context, bs bstore.Blockstore, av actorstypes.Version) (*types.Actor, error) { + cst := cbor.NewCborStore(bs) + dst, err := datacap.MakeState(adt.WrapStore(ctx, cbor.NewCborStore(bs)), av, GovernorId, builtin.DefaultTokenActorBitwidth) + if err != nil { + return nil, err + } + + statecid, err := cst.Put(ctx, dst.GetState()) + if err != nil { + return nil, err + } + + actcid, ok := actors.GetActorCodeID(av, manifest.DatacapKey) + if !ok { + return nil, xerrors.Errorf("failed to get datacap actor code ID for actors version %d", av) + } + + act := &types.Actor{ + Code: actcid, + Head: statecid, + Balance: big.Zero(), + } + + return act, nil +} diff --git a/chain/gen/genesis/genblock.go b/chain/gen/genesis/genblock.go new file mode 100644 index 000000000..f26659cdf --- /dev/null +++ b/chain/gen/genesis/genblock.go @@ -0,0 +1,41 @@ +package genesis + +import ( + "encoding/hex" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + "github.com/multiformats/go-multihash" +) + +const genesisMultihashString = "1220107d821c25dc0735200249df94a8bebc9c8e489744f86a4ca8919e81f19dcd72" +const genesisBlockHex = "a5684461746574696d6573323031372d30352d30352030313a32373a3531674e6574776f726b6846696c65636f696e65546f6b656e6846696c65636f696e6c546f6b656e416d6f756e7473a36b546f74616c537570706c796d322c3030302c3030302c303030664d696e6572736d312c3430302c3030302c3030306c50726f746f636f6c4c616273a36b446576656c6f706d656e746b3330302c3030302c3030306b46756e6472616973696e676b3230302c3030302c3030306a466f756e646174696f6e6b3130302c3030302c303030674d657373616765784854686973206973207468652047656e6573697320426c6f636b206f66207468652046696c65636f696e20446563656e7472616c697a65642053746f72616765204e6574776f726b2e" + +var cidBuilder = cid.V1Builder{Codec: cid.DagCBOR, MhType: multihash.SHA2_256} + +func expectedCid() cid.Cid { + mh, err := multihash.FromHexString(genesisMultihashString) + if err != nil { + panic(err) + } + return cid.NewCidV1(cidBuilder.Codec, mh) +} + +func getGenesisBlock() (blocks.Block, error) { + genesisBlockData, err := hex.DecodeString(genesisBlockHex) + if err != nil { + return nil, err + } + + genesisCid, err := cidBuilder.Sum(genesisBlockData) + if err != nil { + return nil, err + } + + block, err := blocks.NewBlockWithCid(genesisBlockData, genesisCid) + if err != nil { + return nil, err + } + + return block, nil +} diff --git a/chain/gen/genesis/genesis.go b/chain/gen/genesis/genesis.go index 203d8529e..3e8848021 100644 --- a/chain/gen/genesis/genesis.go +++ b/chain/gen/genesis/genesis.go @@ -2,28 +2,51 @@ package genesis import ( "context" + "crypto/rand" "encoding/json" + "fmt" - "github.com/filecoin-project/go-amt-ipld/v2" - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/builtin" - "github.com/filecoin-project/specs-actors/actors/builtin/account" - "github.com/filecoin-project/specs-actors/actors/builtin/verifreg" - "github.com/filecoin-project/specs-actors/actors/runtime" "github.com/ipfs/go-cid" "github.com/ipfs/go-datastore" - bstore "github.com/ipfs/go-ipfs-blockstore" cbor "github.com/ipfs/go-ipld-cbor" logging "github.com/ipfs/go-log/v2" "golang.org/x/xerrors" "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/go-state-types/manifest" + "github.com/filecoin-project/go-state-types/network" + builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" + verifreg0 "github.com/filecoin-project/specs-actors/actors/builtin/verifreg" + adt0 "github.com/filecoin-project/specs-actors/actors/util/adt" + bstore "github.com/filecoin-project/lotus/blockstore" + "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/actors/builtin/cron" + "github.com/filecoin-project/lotus/chain/actors/builtin/datacap" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/actors/builtin/market" + "github.com/filecoin-project/lotus/chain/actors/builtin/multisig" + "github.com/filecoin-project/lotus/chain/actors/builtin/power" + "github.com/filecoin-project/lotus/chain/actors/builtin/reward" + "github.com/filecoin-project/lotus/chain/actors/builtin/system" + "github.com/filecoin-project/lotus/chain/actors/builtin/verifreg" + "github.com/filecoin-project/lotus/chain/consensus" "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/vm" "github.com/filecoin-project/lotus/genesis" + "github.com/filecoin-project/lotus/journal" + "github.com/filecoin-project/lotus/lib/sigs" + "github.com/filecoin-project/lotus/node/bundle" ) const AccountStart = 100 @@ -59,8 +82,15 @@ The process: - power.CreateMiner, set msg value to PowerBalance - market.AddFunds with correct value - market.PublishDeals for related sectors - - Set precommits - - Commit presealed sectors + - Set network power in the power actor to what we'll have after genesis creation + - Recreate reward actor state with the right power + - For each precommitted sector + - Get deal weight + - Calculate QA Power + - Remove fake power from the power actor + - Calculate pledge + - Precommit + - Confirm valid Data Types: @@ -91,188 +121,418 @@ Genesis: { */ -func MakeInitialStateTree(ctx context.Context, bs bstore.Blockstore, template genesis.Template) (*state.StateTree, error) { +func MakeInitialStateTree(ctx context.Context, bs bstore.Blockstore, template genesis.Template) (*state.StateTree, map[address.Address]address.Address, error) { // Create empty state tree - cst := cbor.NewCborStore(bs) - _, err := cst.Put(context.TODO(), []struct{}{}) + + sv, err := state.VersionForNetwork(template.NetworkVersion) if err != nil { - return nil, xerrors.Errorf("putting empty object: %w", err) + return nil, nil, xerrors.Errorf("getting state tree version: %w", err) } - state, err := state.NewStateTree(cst) + state, err := state.NewStateTree(cst, sv) if err != nil { - return nil, xerrors.Errorf("making new state tree: %w", err) + return nil, nil, xerrors.Errorf("making new state tree: %w", err) } - emptyobject, err := cst.Put(context.TODO(), []struct{}{}) + av, err := actorstypes.VersionForNetwork(template.NetworkVersion) if err != nil { - return nil, xerrors.Errorf("failed putting empty object: %w", err) + return nil, nil, xerrors.Errorf("getting network version: %w", err) + } + + if err := bundle.LoadBundles(ctx, bs, av); err != nil { + return nil, nil, xerrors.Errorf("loading actors for genesis block: %w", err) } // Create system actor - sysact, err := SetupSystemActor(bs) + sysact, err := SetupSystemActor(ctx, bs, av) if err != nil { - return nil, xerrors.Errorf("setup init actor: %w", err) + return nil, nil, xerrors.Errorf("setup system actor: %w", err) } - if err := state.SetActor(builtin.SystemActorAddr, sysact); err != nil { - return nil, xerrors.Errorf("set init actor: %w", err) + if err := state.SetActor(system.Address, sysact); err != nil { + return nil, nil, xerrors.Errorf("set system actor: %w", err) } // Create init actor - initact, err := SetupInitActor(bs, template.NetworkName, template.Accounts) + idStart, initact, keyIDs, err := SetupInitActor(ctx, bs, template.NetworkName, template.Accounts, template.VerifregRootKey, template.RemainderAccount, av) if err != nil { - return nil, xerrors.Errorf("setup init actor: %w", err) + return nil, nil, xerrors.Errorf("setup init actor: %w", err) } - if err := state.SetActor(builtin.InitActorAddr, initact); err != nil { - return nil, xerrors.Errorf("set init actor: %w", err) + if err := state.SetActor(init_.Address, initact); err != nil { + return nil, nil, xerrors.Errorf("set init actor: %w", err) } // Setup reward - rewact, err := SetupRewardActor(bs) + // RewardActor's state is overwritten by SetupStorageMiners, but needs to exist for miner creation messages + rewact, err := SetupRewardActor(ctx, bs, big.Zero(), av) if err != nil { - return nil, xerrors.Errorf("setup init actor: %w", err) + return nil, nil, xerrors.Errorf("setup reward actor: %w", err) } - err = state.SetActor(builtin.RewardActorAddr, rewact) + err = state.SetActor(reward.Address, rewact) if err != nil { - return nil, xerrors.Errorf("set network account actor: %w", err) + return nil, nil, xerrors.Errorf("set reward actor: %w", err) } // Setup cron - cronact, err := SetupCronActor(bs) + cronact, err := SetupCronActor(ctx, bs, av) if err != nil { - return nil, xerrors.Errorf("setup cron actor: %w", err) + return nil, nil, xerrors.Errorf("setup cron actor: %w", err) } - if err := state.SetActor(builtin.CronActorAddr, cronact); err != nil { - return nil, xerrors.Errorf("set cron actor: %w", err) + if err := state.SetActor(cron.Address, cronact); err != nil { + return nil, nil, xerrors.Errorf("set cron actor: %w", err) } // Create empty power actor - spact, err := SetupStoragePowerActor(bs) + spact, err := SetupStoragePowerActor(ctx, bs, av) if err != nil { - return nil, xerrors.Errorf("setup storage market actor: %w", err) + return nil, nil, xerrors.Errorf("setup storage power actor: %w", err) } - if err := state.SetActor(builtin.StoragePowerActorAddr, spact); err != nil { - return nil, xerrors.Errorf("set storage market actor: %w", err) + if err := state.SetActor(power.Address, spact); err != nil { + return nil, nil, xerrors.Errorf("set storage power actor: %w", err) } // Create empty market actor - marketact, err := SetupStorageMarketActor(bs) + marketact, err := SetupStorageMarketActor(ctx, bs, av) if err != nil { - return nil, xerrors.Errorf("setup storage market actor: %w", err) + return nil, nil, xerrors.Errorf("setup storage market actor: %w", err) } - if err := state.SetActor(builtin.StorageMarketActorAddr, marketact); err != nil { - return nil, xerrors.Errorf("set market actor: %w", err) + if err := state.SetActor(market.Address, marketact); err != nil { + return nil, nil, xerrors.Errorf("set storage market actor: %w", err) } // Create verified registry - verifact, err := SetupVerifiedRegistryActor(bs) + verifact, err := SetupVerifiedRegistryActor(ctx, bs, av) if err != nil { - return nil, xerrors.Errorf("setup storage market actor: %w", err) + return nil, nil, xerrors.Errorf("setup verified registry market actor: %w", err) } - if err := state.SetActor(builtin.VerifiedRegistryActorAddr, verifact); err != nil { - return nil, xerrors.Errorf("set market actor: %w", err) + if err := state.SetActor(verifreg.Address, verifact); err != nil { + return nil, nil, xerrors.Errorf("set verified registry actor: %w", err) } - // Setup burnt-funds - err = state.SetActor(builtin.BurntFundsActorAddr, &types.Actor{ - Code: builtin.AccountActorCodeID, - Balance: types.NewInt(0), - Head: emptyobject, - }) + // Create datacap actor + if av >= 9 { + dcapact, err := SetupDatacapActor(ctx, bs, av) + if err != nil { + return nil, nil, xerrors.Errorf("setup datacap actor: %w", err) + } + if err := state.SetActor(datacap.Address, dcapact); err != nil { + return nil, nil, xerrors.Errorf("set datacap actor: %w", err) + } + } + + bact, err := MakeAccountActor(ctx, cst, av, builtin.BurntFundsActorAddr, big.Zero()) if err != nil { - return nil, xerrors.Errorf("set burnt funds account actor: %w", err) + return nil, nil, xerrors.Errorf("setup burnt funds actor state: %w", err) + } + if err := state.SetActor(builtin.BurntFundsActorAddr, bact); err != nil { + return nil, nil, xerrors.Errorf("set burnt funds actor: %w", err) } // Create accounts - for id, info := range template.Accounts { - if info.Type != genesis.TAccount { - return nil, xerrors.New("unsupported account type") // TODO: msigs + for _, info := range template.Accounts { + switch info.Type { + case genesis.TAccount: + if err := CreateAccountActor(ctx, cst, state, info, keyIDs, av); err != nil { + return nil, nil, xerrors.Errorf("failed to create account actor: %w", err) + } + case genesis.TMultisig: + ida, err := address.NewIDAddress(uint64(idStart)) + if err != nil { + return nil, nil, err + } + idStart++ + + if err := CreateMultisigAccount(ctx, cst, state, ida, info, keyIDs, av); err != nil { + return nil, nil, err + } + default: + return nil, nil, xerrors.New("unsupported account type") } - ida, err := address.NewIDAddress(uint64(AccountStart + id)) - if err != nil { - return nil, err - } + } + switch template.VerifregRootKey.Type { + case genesis.TAccount: var ainfo genesis.AccountMeta - if err := json.Unmarshal(info.Meta, &ainfo); err != nil { - return nil, xerrors.Errorf("unmarshaling account meta: %w", err) + if err := json.Unmarshal(template.VerifregRootKey.Meta, &ainfo); err != nil { + return nil, nil, xerrors.Errorf("unmarshaling account meta: %w", err) } - st, err := cst.Put(ctx, &account.State{Address: ainfo.Owner}) + _, ok := keyIDs[ainfo.Owner] + if ok { + return nil, nil, fmt.Errorf("rootkey account has already been declared, cannot be assigned 80: %s", ainfo.Owner) + } + + vact, err := MakeAccountActor(ctx, cst, av, ainfo.Owner, template.VerifregRootKey.Balance) if err != nil { - return nil, err + return nil, nil, xerrors.Errorf("setup verifreg rootkey account state: %w", err) } - - err = state.SetActor(ida, &types.Actor{ - Code: builtin.AccountActorCodeID, - Balance: info.Balance, - Head: st, - }) - if err != nil { - return nil, xerrors.Errorf("setting account from actmap: %w", err) + if err = state.SetActor(builtin.RootVerifierAddress, vact); err != nil { + return nil, nil, xerrors.Errorf("set verifreg rootkey account actor: %w", err) } + case genesis.TMultisig: + if err = CreateMultisigAccount(ctx, cst, state, builtin.RootVerifierAddress, template.VerifregRootKey, keyIDs, av); err != nil { + return nil, nil, xerrors.Errorf("failed to set up verified registry signer: %w", err) + } + default: + return nil, nil, xerrors.Errorf("unknown account type for verifreg rootkey: %w", err) } - vregroot, err := address.NewIDAddress(80) + // Setup the first verifier as ID-address 81 + // TODO: remove this + skBytes, err := sigs.Generate(crypto.SigTypeBLS) if err != nil { - return nil, err + return nil, nil, xerrors.Errorf("creating random verifier secret key: %w", err) } - vrst, err := cst.Put(ctx, &account.State{Address: RootVerifierAddr}) + verifierPk, err := sigs.ToPublic(crypto.SigTypeBLS, skBytes) if err != nil { - return nil, err + return nil, nil, xerrors.Errorf("creating random verifier public key: %w", err) } - err = state.SetActor(vregroot, &types.Actor{ - Code: builtin.AccountActorCodeID, - Balance: types.NewInt(0), - Head: vrst, + verifierAd, err := address.NewBLSAddress(verifierPk) + if err != nil { + return nil, nil, xerrors.Errorf("creating random verifier address: %w", err) + } + + verifierId, err := address.NewIDAddress(81) + if err != nil { + return nil, nil, err + } + + verifierAct, err := MakeAccountActor(ctx, cst, av, verifierAd, big.Zero()) + if err != nil { + return nil, nil, xerrors.Errorf("setup first verifier state: %w", err) + } + + if err = state.SetActor(verifierId, verifierAct); err != nil { + return nil, nil, xerrors.Errorf("set first verifier actor: %w", err) + } + + totalFilAllocated := big.Zero() + + 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 }) - if err != nil { - return nil, xerrors.Errorf("setting account from actmap: %w", err) + return nil, nil, xerrors.Errorf("summing account balances in state tree: %w", err) } - return state, nil + totalFil := big.Mul(big.NewInt(int64(build.FilBase)), big.NewInt(int64(build.FilecoinPrecision))) + remainingFil := big.Sub(totalFil, totalFilAllocated) + if remainingFil.Sign() < 0 { + return nil, nil, xerrors.Errorf("somehow overallocated filecoin (allocated = %s)", types.FIL(totalFilAllocated)) + } + + template.RemainderAccount.Balance = remainingFil + + switch template.RemainderAccount.Type { + case genesis.TAccount: + var ainfo genesis.AccountMeta + if err := json.Unmarshal(template.RemainderAccount.Meta, &ainfo); err != nil { + return nil, nil, xerrors.Errorf("unmarshaling account meta: %w", err) + } + + _, ok := keyIDs[ainfo.Owner] + if ok { + return nil, nil, fmt.Errorf("remainder account has already been declared, cannot be assigned 90: %s", ainfo.Owner) + } + + keyIDs[ainfo.Owner] = builtin.ReserveAddress + err = CreateAccountActor(ctx, cst, state, template.RemainderAccount, keyIDs, av) + if err != nil { + return nil, nil, xerrors.Errorf("creating remainder acct: %w", err) + } + + case genesis.TMultisig: + if err = CreateMultisigAccount(ctx, cst, state, builtin.ReserveAddress, template.RemainderAccount, keyIDs, av); err != nil { + return nil, nil, xerrors.Errorf("failed to set up remainder: %w", err) + } + default: + return nil, nil, xerrors.Errorf("unknown account type for remainder: %w", err) + } + + return state, keyIDs, nil } -func VerifyPreSealedData(ctx context.Context, cs *store.ChainStore, stateroot cid.Cid, template genesis.Template) (cid.Cid, error) { +func MakeAccountActor(ctx context.Context, cst cbor.IpldStore, av actorstypes.Version, addr address.Address, bal types.BigInt) (*types.Actor, error) { + ast, err := account.MakeState(adt.WrapStore(ctx, cst), av, addr) + if err != nil { + return nil, err + } + + statecid, err := cst.Put(ctx, ast.GetState()) + if err != nil { + return nil, err + } + + actcid, ok := actors.GetActorCodeID(av, manifest.AccountKey) + if !ok { + return nil, xerrors.Errorf("failed to get account actor code ID for actors version %d", av) + } + + act := &types.Actor{ + Code: actcid, + Head: statecid, + Balance: bal, + Address: &addr, + } + + return act, nil +} + +func CreateAccountActor(ctx context.Context, cst cbor.IpldStore, state *state.StateTree, info genesis.Actor, keyIDs map[address.Address]address.Address, av actorstypes.Version) error { + var ainfo genesis.AccountMeta + if err := json.Unmarshal(info.Meta, &ainfo); err != nil { + return xerrors.Errorf("unmarshaling account meta: %w", err) + } + + aa, err := MakeAccountActor(ctx, cst, av, ainfo.Owner, info.Balance) + if err != nil { + return err + } + + ida, ok := keyIDs[ainfo.Owner] + if !ok { + return fmt.Errorf("no registered ID for account actor: %s", ainfo.Owner) + } + + err = state.SetActor(ida, aa) + if err != nil { + return xerrors.Errorf("setting account from actmap: %w", err) + } + return nil +} + +func CreateMultisigAccount(ctx context.Context, cst cbor.IpldStore, state *state.StateTree, ida address.Address, info genesis.Actor, keyIDs map[address.Address]address.Address, av actorstypes.Version) error { + if info.Type != genesis.TMultisig { + return fmt.Errorf("can only call CreateMultisigAccount with multisig Actor info") + } + var ainfo genesis.MultisigMeta + if err := json.Unmarshal(info.Meta, &ainfo); err != nil { + return xerrors.Errorf("unmarshaling account meta: %w", err) + } + + var signers []address.Address + + for _, e := range ainfo.Signers { + idAddress, ok := keyIDs[e] + if !ok { + return fmt.Errorf("no registered key ID for signer: %s", e) + } + + // Check if actor already exists + _, err := state.GetActor(e) + if err == nil { + signers = append(signers, idAddress) + continue + } + + aa, err := MakeAccountActor(ctx, cst, av, e, big.Zero()) + if err != nil { + return err + } + + if err = state.SetActor(idAddress, aa); err != nil { + return xerrors.Errorf("setting account from actmap: %w", err) + } + signers = append(signers, idAddress) + } + + mst, err := multisig.MakeState(adt.WrapStore(ctx, cst), av, signers, uint64(ainfo.Threshold), abi.ChainEpoch(ainfo.VestingStart), abi.ChainEpoch(ainfo.VestingDuration), info.Balance) + if err != nil { + return err + } + + statecid, err := cst.Put(ctx, mst.GetState()) + if err != nil { + return err + } + + actcid, ok := actors.GetActorCodeID(av, manifest.MultisigKey) + if !ok { + return xerrors.Errorf("failed to get multisig code ID for actors version %d", av) + } + + err = state.SetActor(ida, &types.Actor{ + Code: actcid, + Balance: info.Balance, + Head: statecid, + }) + if err != nil { + return xerrors.Errorf("setting account from actmap: %w", err) + } + + return nil +} + +func VerifyPreSealedData(ctx context.Context, cs *store.ChainStore, sys vm.SyscallBuilder, stateroot cid.Cid, template genesis.Template, keyIDs map[address.Address]address.Address, nv network.Version) (cid.Cid, error) { verifNeeds := make(map[address.Address]abi.PaddedPieceSize) var sum abi.PaddedPieceSize - for _, m := range template.Miners { - for _, s := range m.Sectors { + + csc := func(context.Context, abi.ChainEpoch, *state.StateTree) (abi.TokenAmount, error) { + return big.Zero(), nil + } + + vmopt := vm.VMOpts{ + StateBase: stateroot, + Epoch: 0, + Rand: &fakeRand{}, + Bstore: cs.StateBlockstore(), + Actors: consensus.NewActorRegistry(), + Syscalls: mkFakedSigSyscalls(sys), + CircSupplyCalc: csc, + NetworkVersion: nv, + BaseFee: big.Zero(), + } + vm, err := vm.NewVM(ctx, &vmopt) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to create VM: %w", err) + } + + for mi, m := range template.Miners { + for si, s := range m.Sectors { + if s.Deal.Provider != m.ID { + return cid.Undef, xerrors.Errorf("Sector %d in miner %d in template had mismatch in provider and miner ID: %s != %s", si, mi, s.Deal.Provider, m.ID) + } + amt := s.Deal.PieceSize - verifNeeds[s.Deal.Client] += amt + verifNeeds[keyIDs[s.Deal.Client]] += amt sum += amt } } - verifier, err := address.NewIDAddress(80) + verifregRoot, err := address.NewIDAddress(80) if err != nil { return cid.Undef, err } - vm, err := vm.NewVM(stateroot, 0, &fakeRand{}, cs.Blockstore(), &fakedSigSyscalls{cs.VMSys()}) + verifier, err := address.NewIDAddress(81) if err != nil { - return cid.Undef, xerrors.Errorf("failed to create NewVM: %w", err) + return cid.Undef, err } - _, err = doExecValue(ctx, vm, builtin.VerifiedRegistryActorAddr, RootVerifierAddr, types.NewInt(0), builtin.MethodsVerifiedRegistry.AddVerifier, mustEnc(&verifreg.AddVerifierParams{ + // Note: This is brittle, if the methodNum / param changes, it could break things + _, err = doExecValue(ctx, vm, verifreg.Address, verifregRoot, types.NewInt(0), builtin0.MethodsVerifiedRegistry.AddVerifier, mustEnc(&verifreg0.AddVerifierParams{ + Address: verifier, Allowance: abi.NewStoragePower(int64(sum)), // eh, close enough })) if err != nil { - return cid.Undef, xerrors.Errorf("failed to failed to create verifier: %w", err) + return cid.Undef, xerrors.Errorf("failed to create verifier: %w", err) } for c, amt := range verifNeeds { - _, err := doExecValue(ctx, vm, builtin.VerifiedRegistryActorAddr, verifier, types.NewInt(0), builtin.MethodsVerifiedRegistry.AddVerifiedClient, mustEnc(&verifreg.AddVerifiedClientParams{ + // Note: This is brittle, if the methodNum / param changes, it could break things + _, err := doExecValue(ctx, vm, verifreg.Address, verifier, types.NewInt(0), builtin0.MethodsVerifiedRegistry.AddVerifiedClient, mustEnc(&verifreg0.AddVerifiedClientParams{ Address: c, Allowance: abi.NewStoragePower(int64(amt)), })) @@ -281,37 +541,65 @@ func VerifyPreSealedData(ctx context.Context, cs *store.ChainStore, stateroot ci } } - return vm.Flush(ctx) + st, err := vm.Flush(ctx) + if err != nil { + return cid.Cid{}, xerrors.Errorf("vm flush: %w", err) + } + + return st, nil } -func MakeGenesisBlock(ctx context.Context, bs bstore.Blockstore, sys runtime.Syscalls, template genesis.Template) (*GenesisBootstrap, error) { - st, err := MakeInitialStateTree(ctx, bs, template) +func MakeGenesisBlock(ctx context.Context, j journal.Journal, bs bstore.Blockstore, sys vm.SyscallBuilder, template genesis.Template) (*GenesisBootstrap, error) { + if j == nil { + j = journal.NilJournal() + } + st, keyIDs, err := MakeInitialStateTree(ctx, bs, template) if err != nil { return nil, xerrors.Errorf("make initial state tree failed: %w", err) } + // Set up the Ethereum Address Manager + if err = SetupEAM(ctx, st, template.NetworkVersion); err != nil { + return nil, xerrors.Errorf("failed to setup EAM: %w", err) + } + stateroot, err := st.Flush(ctx) if err != nil { return nil, xerrors.Errorf("flush state tree failed: %w", err) } // temp chainstore - cs := store.NewChainStore(bs, datastore.NewMapDatastore(), sys) + cs := store.NewChainStore(bs, bs, datastore.NewMapDatastore(), nil, j) // Verify PreSealed Data - stateroot, err = VerifyPreSealedData(ctx, cs, stateroot, template) + stateroot, err = VerifyPreSealedData(ctx, cs, sys, stateroot, template, keyIDs, template.NetworkVersion) if err != nil { return nil, xerrors.Errorf("failed to verify presealed data: %w", err) } - stateroot, err = SetupStorageMiners(ctx, cs, stateroot, template.Miners) + // setup Storage Miners + stateroot, err = SetupStorageMiners(ctx, cs, sys, stateroot, template.Miners, template.NetworkVersion) if err != nil { - return nil, xerrors.Errorf("setup storage miners failed: %w", err) + return nil, xerrors.Errorf("setup miners failed: %w", err) } - cst := cbor.NewCborStore(bs) + st, err = state.LoadStateTree(st.Store, stateroot) + if err != nil { + return nil, xerrors.Errorf("failed to load updated state tree: %w", err) + } - emptyroot, err := amt.FromArray(ctx, cst, nil) + // Set up Eth null addresses. + if _, err := SetupEthNullAddresses(ctx, st, template.NetworkVersion); err != nil { + return nil, xerrors.Errorf("failed to set up Eth null addresses: %w", err) + } + + stateroot, err = st.Flush(ctx) + if err != nil { + return nil, xerrors.Errorf("failed to flush state tree: %w", err) + } + + store := adt.WrapStore(ctx, cbor.NewCborStore(bs)) + emptyroot, err := adt0.MakeEmptyArray(store).Root() if err != nil { return nil, xerrors.Errorf("amt build failed: %w", err) } @@ -324,20 +612,44 @@ func MakeGenesisBlock(ctx context.Context, bs bstore.Blockstore, sys runtime.Sys if err != nil { return nil, xerrors.Errorf("serializing msgmeta failed: %w", err) } - if err := bs.Put(mmb); err != nil { + if err := bs.Put(ctx, mmb); err != nil { return nil, xerrors.Errorf("putting msgmeta block to blockstore: %w", err) } log.Infof("Empty Genesis root: %s", emptyroot) + tickBuf := make([]byte, 32) + _, _ = rand.Read(tickBuf) genesisticket := &types.Ticket{ - VRFProof: []byte("vrf proof0000000vrf proof0000000"), + VRFProof: tickBuf, + } + + filecoinGenesisCid, err := cid.Decode("bafyreiaqpwbbyjo4a42saasj36kkrpv4tsherf2e7bvezkert2a7dhonoi") + if err != nil { + return nil, xerrors.Errorf("failed to decode filecoin genesis block CID: %w", err) + } + + if !expectedCid().Equals(filecoinGenesisCid) { + return nil, xerrors.Errorf("expectedCid != filecoinGenesisCid") + } + + gblk, err := getGenesisBlock() + if err != nil { + return nil, xerrors.Errorf("failed to construct filecoin genesis block: %w", err) + } + + if !filecoinGenesisCid.Equals(gblk.Cid()) { + return nil, xerrors.Errorf("filecoinGenesisCid != gblk.Cid") + } + + if err := bs.Put(ctx, gblk); err != nil { + return nil, xerrors.Errorf("failed writing filecoin genesis block to blockstore: %w", err) } b := &types.BlockHeader{ - Miner: builtin.SystemActorAddr, + Miner: system.Address, Ticket: genesisticket, - Parents: []cid.Cid{}, + Parents: []cid.Cid{filecoinGenesisCid}, Height: 0, ParentWeight: types.NewInt(0), ParentStateRoot: stateroot, @@ -353,6 +665,7 @@ func MakeGenesisBlock(ctx context.Context, bs bstore.Blockstore, sys runtime.Sys Data: make([]byte, 32), }, }, + ParentBaseFee: abi.NewTokenAmount(build.InitialBaseFee), } sb, err := b.ToStorageBlock() @@ -360,7 +673,7 @@ func MakeGenesisBlock(ctx context.Context, bs bstore.Blockstore, sys runtime.Sys return nil, xerrors.Errorf("serializing block header failed: %w", err) } - if err := bs.Put(sb); err != nil { + if err := bs.Put(ctx, sb); err != nil { return nil, xerrors.Errorf("putting header to blockstore: %w", err) } diff --git a/chain/gen/genesis/genesis_eth.go b/chain/gen/genesis/genesis_eth.go new file mode 100644 index 000000000..d5aa2f0b5 --- /dev/null +++ b/chain/gen/genesis/genesis_eth.go @@ -0,0 +1,139 @@ +package genesis + +import ( + "context" + "fmt" + + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/builtin" + "github.com/filecoin-project/go-state-types/manifest" + "github.com/filecoin-project/go-state-types/network" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/state" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/ethtypes" + "github.com/filecoin-project/lotus/chain/vm" +) + +// EthNullAddresses are the Ethereum addresses we want to create zero-balanced EthAccounts in. +// We may want to add null addresses for precompiles going forward. +var EthNullAddresses = []string{ + "0x0000000000000000000000000000000000000000", +} + +func SetupEAM(_ context.Context, nst *state.StateTree, nv network.Version) error { + av, err := actorstypes.VersionForNetwork(nv) + if err != nil { + return fmt.Errorf("failed to get actors version for network version %d: %w", nv, err) + } + + if av < actorstypes.Version10 { + // Not defined before version 10; migration has to create. + return nil + } + + codecid, ok := actors.GetActorCodeID(av, manifest.EamKey) + if !ok { + return fmt.Errorf("failed to get CodeCID for EAM during genesis") + } + + header := &types.Actor{ + Code: codecid, + Head: vm.EmptyObjectCid, + Balance: big.Zero(), + Address: &builtin.EthereumAddressManagerActorAddr, // so that it can create ETH0 + } + return nst.SetActor(builtin.EthereumAddressManagerActorAddr, header) +} + +// MakeEthNullAddressActor creates a null address actor at the specified Ethereum address. +func MakeEthNullAddressActor(av actorstypes.Version, addr address.Address) (*types.Actor, error) { + actcid, ok := actors.GetActorCodeID(av, manifest.EthAccountKey) + if !ok { + return nil, xerrors.Errorf("failed to get EthAccount actor code ID for actors version %d", av) + } + + act := &types.Actor{ + Code: actcid, + Head: vm.EmptyObjectCid, + Nonce: 0, + Balance: big.Zero(), + Address: &addr, + } + + return act, nil +} + +func SetupEthNullAddresses(ctx context.Context, st *state.StateTree, nv network.Version) ([]address.Address, error) { + av, err := actorstypes.VersionForNetwork(nv) + if err != nil { + return nil, xerrors.Errorf("failed to resolve actors version for network version %d: %w", av, err) + } + + if av < actorstypes.Version10 { + // Not defined before version 10. + return nil, nil + } + + var ethAddresses []ethtypes.EthAddress + for _, addr := range EthNullAddresses { + a, err := ethtypes.ParseEthAddress(addr) + if err != nil { + return nil, xerrors.Errorf("failed to represent the 0x0 as an EthAddress: %w", err) + } + ethAddresses = append(ethAddresses, a) + } + + initAct, err := st.GetActor(builtin.InitActorAddr) + if err != nil { + return nil, xerrors.Errorf("failed to load init actor: %w", err) + } + + initState, err := init_.Load(adt.WrapStore(ctx, st.Store), initAct) + if err != nil { + return nil, xerrors.Errorf("failed to load init actor state: %w", err) + } + + var ret []address.Address + for _, ethAddr := range ethAddresses { + // Place an EthAccount at the 0x0 Eth Null Address. + f4Addr, err := ethAddr.ToFilecoinAddress() + if err != nil { + return nil, xerrors.Errorf("failed to compute Filecoin address for Eth addr 0x0: %w", err) + } + + idAddr, err := initState.MapAddressToNewID(f4Addr) + if err != nil { + return nil, xerrors.Errorf("failed to map addr in init actor: %w", err) + } + + actState, err := MakeEthNullAddressActor(av, f4Addr) + if err != nil { + return nil, xerrors.Errorf("failed to create EthAccount actor for null address: %w", err) + } + + if err := st.SetActor(idAddr, actState); err != nil { + return nil, xerrors.Errorf("failed to set Eth Null Address EthAccount actor state: %w", err) + } + + ret = append(ret, idAddr) + } + + initAct.Head, err = st.Store.Put(ctx, initState) + if err != nil { + return nil, xerrors.Errorf("failed to add init actor state to store: %w", err) + } + + if err := st.SetActor(builtin.InitActorAddr, initAct); err != nil { + return nil, xerrors.Errorf("failed to set updated state for init actor: %w", err) + } + + return ret, nil +} diff --git a/chain/gen/genesis/miners.go b/chain/gen/genesis/miners.go index 51a128d54..c083f4fda 100644 --- a/chain/gen/genesis/miners.go +++ b/chain/gen/genesis/miners.go @@ -12,21 +12,44 @@ import ( "golang.org/x/xerrors" "github.com/filecoin-project/go-address" - "github.com/filecoin-project/sector-storage/ffiwrapper" - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/abi/big" - "github.com/filecoin-project/specs-actors/actors/builtin" - "github.com/filecoin-project/specs-actors/actors/builtin/market" - "github.com/filecoin-project/specs-actors/actors/builtin/miner" - "github.com/filecoin-project/specs-actors/actors/builtin/power" - "github.com/filecoin-project/specs-actors/actors/crypto" - "github.com/filecoin-project/specs-actors/actors/runtime" + cborutil "github.com/filecoin-project/go-cbor-util" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + builtintypes "github.com/filecoin-project/go-state-types/builtin" + power11 "github.com/filecoin-project/go-state-types/builtin/v11/power" + minertypes "github.com/filecoin-project/go-state-types/builtin/v8/miner" + markettypes "github.com/filecoin-project/go-state-types/builtin/v9/market" + miner9 "github.com/filecoin-project/go-state-types/builtin/v9/miner" + smoothing9 "github.com/filecoin-project/go-state-types/builtin/v9/util/smoothing" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/go-state-types/network" + builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" + miner0 "github.com/filecoin-project/specs-actors/actors/builtin/miner" + power0 "github.com/filecoin-project/specs-actors/actors/builtin/power" + reward0 "github.com/filecoin-project/specs-actors/actors/builtin/reward" + power2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/power" + reward2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/reward" + power4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/power" + reward4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/reward" + builtin6 "github.com/filecoin-project/specs-actors/v6/actors/builtin" + runtime7 "github.com/filecoin-project/specs-actors/v7/actors/runtime" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/actors/builtin/market" + "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/builtin/reward" + "github.com/filecoin-project/lotus/chain/actors/policy" + "github.com/filecoin-project/lotus/chain/consensus" "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/vm" + "github.com/filecoin-project/lotus/chain/wallet/key" "github.com/filecoin-project/lotus/genesis" + "github.com/filecoin-project/lotus/lib/sigs" ) func MinerAddress(genesisIndex uint64) address.Address { @@ -39,47 +62,113 @@ func MinerAddress(genesisIndex uint64) address.Address { } type fakedSigSyscalls struct { - runtime.Syscalls + runtime7.Syscalls } func (fss *fakedSigSyscalls) VerifySignature(signature crypto.Signature, signer address.Address, plaintext []byte) error { return nil } -func SetupStorageMiners(ctx context.Context, cs *store.ChainStore, sroot cid.Cid, miners []genesis.Miner) (cid.Cid, error) { - vm, err := vm.NewVM(sroot, 0, &fakeRand{}, cs.Blockstore(), &fakedSigSyscalls{cs.VMSys()}) +func mkFakedSigSyscalls(base vm.SyscallBuilder) vm.SyscallBuilder { + return func(ctx context.Context, rt *vm.Runtime) runtime7.Syscalls { + return &fakedSigSyscalls{ + base(ctx, rt), + } + } +} + +// Note: Much of this is brittle, if the methodNum / param / return changes, it will break things +func SetupStorageMiners(ctx context.Context, cs *store.ChainStore, sys vm.SyscallBuilder, sroot cid.Cid, miners []genesis.Miner, nv network.Version) (cid.Cid, error) { + + cst := cbor.NewCborStore(cs.StateBlockstore()) + av, err := actorstypes.VersionForNetwork(nv) if err != nil { - return cid.Undef, xerrors.Errorf("failed to create NewVM: %w", err) + return cid.Undef, xerrors.Errorf("failed to get network version: %w", err) + } + + csc := func(context.Context, abi.ChainEpoch, *state.StateTree) (abi.TokenAmount, error) { + return big.Zero(), nil + } + + newVM := func(base cid.Cid) (vm.Interface, error) { + vmopt := &vm.VMOpts{ + StateBase: base, + Epoch: 0, + Rand: &fakeRand{}, + Bstore: cs.StateBlockstore(), + Actors: consensus.NewActorRegistry(), + Syscalls: mkFakedSigSyscalls(sys), + CircSupplyCalc: csc, + NetworkVersion: nv, + BaseFee: big.Zero(), + } + + return vm.NewVM(ctx, vmopt) + } + + genesisVm, err := newVM(sroot) + if err != nil { + return cid.Undef, fmt.Errorf("creating vm: %w", err) } if len(miners) == 0 { return cid.Undef, xerrors.New("no genesis miners") } + minerInfos := make([]struct { + maddr address.Address + + presealExp abi.ChainEpoch + + dealIDs []abi.DealID + sectorWeight []abi.StoragePower + }, len(miners)) + + maxPeriods := policy.GetMaxSectorExpirationExtension() / minertypes.WPoStProvingPeriod + rawPow, qaPow := big.NewInt(0), big.NewInt(0) for i, m := range miners { // Create miner through power actor + i := i + m := m - spt, err := ffiwrapper.SealProofTypeFromSectorSize(m.SectorSize) + spt, err := miner.SealProofTypeFromSectorSize(m.SectorSize, nv) if err != nil { return cid.Undef, err } - var maddr address.Address { - constructorParams := &power.CreateMinerParams{ - Owner: m.Worker, - Worker: m.Worker, - Peer: m.PeerId, - SealProofType: spt, + var params []byte + if nv <= network.Version10 { + constructorParams := &power2.CreateMinerParams{ + Owner: m.Worker, + Worker: m.Worker, + Peer: []byte(m.PeerId), + SealProofType: spt, + } + + params = mustEnc(constructorParams) + } else { + ppt, err := spt.RegisteredWindowPoStProofByNetworkVersion(nv) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to convert spt to wpt: %w", err) + } + + constructorParams := &power11.CreateMinerParams{ + Owner: m.Worker, + Worker: m.Worker, + Peer: []byte(m.PeerId), + WindowPoStProofType: ppt, + } + + params = mustEnc(constructorParams) } - params := mustEnc(constructorParams) - rval, err := doExecValue(ctx, vm, builtin.StoragePowerActorAddr, m.Owner, m.PowerBalance, builtin.MethodsPower.CreateMiner, params) + rval, err := doExecValue(ctx, genesisVm, power.Address, m.Owner, m.PowerBalance, power.Methods.CreateMiner, params) if err != nil { return cid.Undef, xerrors.Errorf("failed to create genesis miner: %w", err) } - var ma power.CreateMinerReturn + var ma power0.CreateMinerReturn if err := ma.UnmarshalCBOR(bytes.NewReader(rval)); err != nil { return cid.Undef, xerrors.Errorf("unmarshaling CreateMinerReturn: %w", err) } @@ -88,61 +177,111 @@ func SetupStorageMiners(ctx context.Context, cs *store.ChainStore, sroot cid.Cid if ma.IDAddress != expma { return cid.Undef, xerrors.Errorf("miner assigned wrong address: %s != %s", ma.IDAddress, expma) } - maddr = ma.IDAddress + minerInfos[i].maddr = ma.IDAddress + + nh, err := genesisVm.Flush(ctx) + if err != nil { + return cid.Undef, xerrors.Errorf("flushing vm: %w", err) + } + + nst, err := state.LoadStateTree(cst, nh) + if err != nil { + return cid.Undef, xerrors.Errorf("loading new state tree: %w", err) + } + + mact, err := nst.GetActor(minerInfos[i].maddr) + if err != nil { + return cid.Undef, xerrors.Errorf("getting newly created miner actor: %w", err) + } + + mst, err := miner.Load(adt.WrapStore(ctx, cst), mact) + if err != nil { + return cid.Undef, xerrors.Errorf("getting newly created miner state: %w", err) + } + + pps, err := mst.GetProvingPeriodStart() + if err != nil { + return cid.Undef, xerrors.Errorf("getting newly created miner proving period start: %w", err) + } + + minerInfos[i].presealExp = (maxPeriods-1)*miner0.WPoStProvingPeriod + pps - 1 } // Add market funds - { - params := mustEnc(&maddr) - _, err := doExecValue(ctx, vm, builtin.StorageMarketActorAddr, m.Worker, m.MarketBalance, builtin.MethodsMarket.AddBalance, params) + if m.MarketBalance.GreaterThan(big.Zero()) { + params := mustEnc(&minerInfos[i].maddr) + _, err := doExecValue(ctx, genesisVm, market.Address, m.Worker, m.MarketBalance, market.Methods.AddBalance, params) if err != nil { - return cid.Undef, xerrors.Errorf("failed to create genesis miner: %w", err) - } - } - { - params := mustEnc(&m.Worker) - _, err := doExecValue(ctx, vm, builtin.StorageMarketActorAddr, m.Worker, big.Zero(), builtin.MethodsMarket.AddBalance, params) - if err != nil { - return cid.Undef, xerrors.Errorf("failed to create genesis miner: %w", err) + return cid.Undef, xerrors.Errorf("failed to create genesis miner (add balance): %w", err) } } - // Publish preseal deals + // Publish preseal deals, and calculate the QAPower - var dealIDs []abi.DealID { - publish := func(params *market.PublishStorageDealsParams) error { + publish := func(params *markettypes.PublishStorageDealsParams) error { fmt.Printf("publishing %d storage deals on miner %s with worker %s\n", len(params.Deals), params.Deals[0].Proposal.Provider, m.Worker) - ret, err := doExecValue(ctx, vm, builtin.StorageMarketActorAddr, m.Worker, big.Zero(), builtin.MethodsMarket.PublishStorageDeals, mustEnc(params)) + ret, err := doExecValue(ctx, genesisVm, market.Address, m.Worker, big.Zero(), builtin0.MethodsMarket.PublishStorageDeals, mustEnc(params)) if err != nil { - return xerrors.Errorf("failed to create genesis miner: %w", err) + return xerrors.Errorf("failed to create genesis miner (publish deals): %w", err) } - var ids market.PublishStorageDealsReturn - if err := ids.UnmarshalCBOR(bytes.NewReader(ret)); err != nil { - return xerrors.Errorf("unmarsahling publishStorageDeals result: %w", err) + retval, err := market.DecodePublishStorageDealsReturn(ret, nv) + if err != nil { + return xerrors.Errorf("failed to create genesis miner (decoding published deals): %w", err) } - dealIDs = append(dealIDs, ids.IDs...) + ids, err := retval.DealIDs() + if err != nil { + return xerrors.Errorf("failed to create genesis miner (getting published dealIDs): %w", err) + } + + if len(ids) != len(params.Deals) { + return xerrors.Errorf("failed to create genesis miner (at least one deal was invalid on publication") + } + + minerInfos[i].dealIDs = append(minerInfos[i].dealIDs, ids...) return nil } - params := &market.PublishStorageDealsParams{} + params := &markettypes.PublishStorageDealsParams{} for _, preseal := range m.Sectors { preseal.Deal.VerifiedDeal = true - params.Deals = append(params.Deals, market.ClientDealProposal{ + preseal.Deal.EndEpoch = minerInfos[i].presealExp + p := markettypes.ClientDealProposal{ Proposal: preseal.Deal, - ClientSignature: crypto.Signature{Type: crypto.SigTypeBLS}, // TODO: do we want to sign these? Or do we want to fake signatures for genesis setup? - }) + ClientSignature: crypto.Signature{Type: crypto.SigTypeBLS}, + } + + if av >= actorstypes.Version8 { + buf, err := cborutil.Dump(&preseal.Deal) + if err != nil { + return cid.Undef, fmt.Errorf("failed to marshal proposal: %w", err) + } + + sig, err := sigs.Sign(key.ActSigType(preseal.DealClientKey.Type), preseal.DealClientKey.PrivateKey, buf) + if err != nil { + return cid.Undef, fmt.Errorf("failed to sign proposal: %w", err) + } + + p.ClientSignature = *sig + } + + params.Deals = append(params.Deals, p) if len(params.Deals) == cbg.MaxLength { if err := publish(params); err != nil { return cid.Undef, err } - params = &market.PublishStorageDealsParams{} + params = &markettypes.PublishStorageDealsParams{} } + + rawPow = big.Add(rawPow, big.NewInt(int64(m.SectorSize))) + sectorWeight := builtin.QAPowerForWeight(m.SectorSize, minerInfos[i].presealExp, big.Zero(), markettypes.DealWeight(&preseal.Deal)) + minerInfos[i].sectorWeight = append(minerInfos[i].sectorWeight, sectorWeight) + qaPow = big.Add(qaPow, sectorWeight) } if len(params.Deals) > 0 { @@ -151,107 +290,365 @@ func SetupStorageMiners(ctx context.Context, cs *store.ChainStore, sroot cid.Cid } } } + } + { + nh, err := genesisVm.Flush(ctx) + if err != nil { + return cid.Undef, xerrors.Errorf("flushing vm: %w", err) + } + + if err != nil { + return cid.Undef, xerrors.Errorf("flushing vm: %w", err) + } + + nst, err := state.LoadStateTree(cst, nh) + if err != nil { + return cid.Undef, xerrors.Errorf("loading new state tree: %w", err) + } + + pact, err := nst.GetActor(power.Address) + if err != nil { + return cid.Undef, xerrors.Errorf("getting power actor: %w", err) + } + + pst, err := power.Load(adt.WrapStore(ctx, cst), pact) + if err != nil { + return cid.Undef, xerrors.Errorf("getting power state: %w", err) + } + + if err = pst.SetTotalQualityAdjPower(qaPow); err != nil { + return cid.Undef, xerrors.Errorf("setting TotalQualityAdjPower in power state: %w", err) + } + + if err = pst.SetTotalRawBytePower(rawPow); err != nil { + return cid.Undef, xerrors.Errorf("setting TotalRawBytePower in power state: %w", err) + } + + if err = pst.SetThisEpochQualityAdjPower(qaPow); err != nil { + return cid.Undef, xerrors.Errorf("setting ThisEpochQualityAdjPower in power state: %w", err) + } + + if err = pst.SetThisEpochRawBytePower(rawPow); err != nil { + return cid.Undef, xerrors.Errorf("setting ThisEpochRawBytePower in power state: %w", err) + } + + pcid, err := cst.Put(ctx, pst.GetState()) + if err != nil { + return cid.Undef, xerrors.Errorf("putting power state: %w", err) + } + + pact.Head = pcid + + if err = nst.SetActor(power.Address, pact); err != nil { + return cid.Undef, xerrors.Errorf("setting power state: %w", err) + } + + rewact, err := SetupRewardActor(ctx, cs.StateBlockstore(), big.Zero(), av) + if err != nil { + return cid.Undef, xerrors.Errorf("setup reward actor: %w", err) + } + + if err = nst.SetActor(reward.Address, rewact); err != nil { + return cid.Undef, xerrors.Errorf("set reward actor: %w", err) + } + + nh, err = nst.Flush(ctx) + if err != nil { + return cid.Undef, xerrors.Errorf("flushing state tree: %w", err) + } + + genesisVm, err = newVM(nh) + if err != nil { + return cid.Undef, fmt.Errorf("creating new vm: %w", err) + } + } + + for i, m := range miners { // Commit sectors - for pi, preseal := range m.Sectors { - // TODO: Maybe check seal (Can just be snark inputs, doesn't go into the genesis file) - - // check deals, get dealWeight - var dealWeight market.VerifyDealsOnSectorProveCommitReturn - { - params := &market.VerifyDealsOnSectorProveCommitParams{ - DealIDs: []abi.DealID{dealIDs[pi]}, - SectorExpiry: preseal.Deal.EndEpoch, + { + for pi, preseal := range m.Sectors { + params := &minertypes.SectorPreCommitInfo{ + SealProof: preseal.ProofType, + SectorNumber: preseal.SectorID, + SealedCID: preseal.CommR, + SealRandEpoch: -1, + DealIDs: []abi.DealID{minerInfos[i].dealIDs[pi]}, + Expiration: minerInfos[i].presealExp, // TODO: Allow setting externally! } - ret, err := doExecValue(ctx, vm, builtin.StorageMarketActorAddr, maddr, big.Zero(), builtin.MethodsMarket.VerifyDealsOnSectorProveCommit, mustEnc(params)) + sectorWeight := minerInfos[i].sectorWeight[pi] + + // we've added fake power for this sector above, remove it now + + nh, err := genesisVm.Flush(ctx) if err != nil { - return cid.Undef, xerrors.Errorf("failed to verify preseal deals miner: %w", err) + return cid.Undef, xerrors.Errorf("flushing vm: %w", err) } - if err := dealWeight.UnmarshalCBOR(bytes.NewReader(ret)); err != nil { - return cid.Undef, xerrors.Errorf("unmarshaling market onProveCommit result: %w", err) - } - } - // update power claims - { - err = vm.MutateState(ctx, builtin.StoragePowerActorAddr, func(cst cbor.IpldStore, st *power.State) error { - weight := &power.SectorStorageWeightDesc{ - SectorSize: m.SectorSize, - Duration: preseal.Deal.Duration(), - DealWeight: dealWeight.DealWeight, - VerifiedDealWeight: dealWeight.VerifiedDealWeight, + nst, err := state.LoadStateTree(cst, nh) + if err != nil { + return cid.Undef, xerrors.Errorf("loading new state tree: %w", err) + } + + pact, err := nst.GetActor(power.Address) + if err != nil { + return cid.Undef, xerrors.Errorf("getting power actor: %w", err) + } + + pst, err := power.Load(adt.WrapStore(ctx, cst), pact) + if err != nil { + return cid.Undef, xerrors.Errorf("getting power state: %w", err) + } + + pc, err := pst.TotalPower() + if err != nil { + return cid.Undef, xerrors.Errorf("getting total power: %w", err) + } + + if err = pst.SetTotalRawBytePower(types.BigSub(pc.RawBytePower, types.NewInt(uint64(m.SectorSize)))); err != nil { + return cid.Undef, xerrors.Errorf("setting TotalRawBytePower in power state: %w", err) + } + + if err = pst.SetTotalQualityAdjPower(types.BigSub(pc.QualityAdjPower, sectorWeight)); err != nil { + return cid.Undef, xerrors.Errorf("setting TotalQualityAdjPower in power state: %w", err) + } + + pcid, err := cst.Put(ctx, pst.GetState()) + if err != nil { + return cid.Undef, xerrors.Errorf("putting power state: %w", err) + } + + pact.Head = pcid + + if err = nst.SetActor(power.Address, pact); err != nil { + return cid.Undef, xerrors.Errorf("setting power state: %w", err) + } + + nh, err = nst.Flush(ctx) + if err != nil { + return cid.Undef, xerrors.Errorf("flushing state tree: %w", err) + } + + genesisVm, err = newVM(nh) + if err != nil { + return cid.Undef, fmt.Errorf("creating new vm: %w", err) + } + + baselinePower, rewardSmoothed, err := currentEpochBlockReward(ctx, genesisVm, minerInfos[i].maddr, av) + if err != nil { + return cid.Undef, xerrors.Errorf("getting current epoch reward: %w", err) + } + + tpow, err := currentTotalPower(ctx, genesisVm, minerInfos[i].maddr) + if err != nil { + return cid.Undef, xerrors.Errorf("getting current total power: %w", err) + } + + pcd := miner9.PreCommitDepositForPower(smoothing9.FilterEstimate(rewardSmoothed), smoothing9.FilterEstimate(*tpow.QualityAdjPowerSmoothed), miner9.QAPowerMax(m.SectorSize)) + + pledge := miner9.InitialPledgeForPower( + sectorWeight, + baselinePower, + smoothing9.FilterEstimate(rewardSmoothed), + smoothing9.FilterEstimate(*tpow.QualityAdjPowerSmoothed), + big.Zero(), + ) + + pledge = big.Add(pcd, pledge) + + _, err = doExecValue(ctx, genesisVm, minerInfos[i].maddr, m.Worker, pledge, builtintypes.MethodsMiner.PreCommitSector, mustEnc(params)) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to confirm presealed sectors: %w", err) + } + + // Commit one-by-one, otherwise pledge math tends to explode + var paramBytes []byte + + if av >= actorstypes.Version6 { + // TODO: fixup + confirmParams := &builtin6.ConfirmSectorProofsParams{ + Sectors: []abi.SectorNumber{preseal.SectorID}, } - qapower := power.QAPowerForWeight(weight) + paramBytes = mustEnc(confirmParams) + } else { + confirmParams := &builtin0.ConfirmSectorProofsParams{ + Sectors: []abi.SectorNumber{preseal.SectorID}, + } - err := st.AddToClaim(&state.AdtStore{cst}, maddr, types.NewInt(uint64(weight.SectorSize)), qapower) + paramBytes = mustEnc(confirmParams) + } + + _, err = doExecValue(ctx, genesisVm, minerInfos[i].maddr, power.Address, big.Zero(), builtintypes.MethodsMiner.ConfirmSectorProofsValid, paramBytes) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to confirm presealed sectors: %w", err) + } + + if av >= actorstypes.Version2 { + // post v0, we need to explicitly Claim this power since ConfirmSectorProofsValid doesn't do it anymore + claimParams := &power4.UpdateClaimedPowerParams{ + RawByteDelta: types.NewInt(uint64(m.SectorSize)), + QualityAdjustedDelta: sectorWeight, + } + + _, err = doExecValue(ctx, genesisVm, power.Address, minerInfos[i].maddr, big.Zero(), power.Methods.UpdateClaimedPower, mustEnc(claimParams)) if err != nil { - return xerrors.Errorf("add to claim: %w", err) - } - fmt.Println("Added weight to claim: ", st.TotalRawBytePower, st.TotalQualityAdjPower) - return nil - }) - if err != nil { - return cid.Undef, xerrors.Errorf("register power claim in power actor: %w", err) - } - } - - // Put sectors to miner sector sets - { - newSectorInfo := &miner.SectorOnChainInfo{ - Info: miner.SectorPreCommitInfo{ - RegisteredProof: preseal.ProofType, - SectorNumber: preseal.SectorID, - SealedCID: preseal.CommR, - SealRandEpoch: 0, - DealIDs: []abi.DealID{dealIDs[pi]}, - Expiration: preseal.Deal.EndEpoch, - }, - ActivationEpoch: 0, - DealWeight: dealWeight.DealWeight, - VerifiedDealWeight: dealWeight.VerifiedDealWeight, - } - - err = vm.MutateState(ctx, maddr, func(cst cbor.IpldStore, st *miner.State) error { - store := &state.AdtStore{cst} - - if err = st.PutSector(store, newSectorInfo); err != nil { - return xerrors.Errorf("failed to put sector: %v", err) + return cid.Undef, xerrors.Errorf("failed to confirm presealed sectors: %w", err) } - if err := st.AddNewSectors(newSectorInfo.Info.SectorNumber); err != nil { - return xerrors.Errorf("failed to add NewSector: %w", err) + nh, err := genesisVm.Flush(ctx) + if err != nil { + return cid.Undef, xerrors.Errorf("flushing vm: %w", err) } - return nil - }) - if err != nil { - return cid.Cid{}, xerrors.Errorf("put to sset: %w", err) + nst, err := state.LoadStateTree(cst, nh) + if err != nil { + return cid.Undef, xerrors.Errorf("loading new state tree: %w", err) + } + + mact, err := nst.GetActor(minerInfos[i].maddr) + if err != nil { + return cid.Undef, xerrors.Errorf("getting miner actor: %w", err) + } + + mst, err := miner.Load(adt.WrapStore(ctx, cst), mact) + if err != nil { + return cid.Undef, xerrors.Errorf("getting miner state: %w", err) + } + + if err = mst.EraseAllUnproven(); err != nil { + return cid.Undef, xerrors.Errorf("failed to erase unproven sectors: %w", err) + } + + mcid, err := cst.Put(ctx, mst.GetState()) + if err != nil { + return cid.Undef, xerrors.Errorf("putting miner state: %w", err) + } + + mact.Head = mcid + + if err = nst.SetActor(minerInfos[i].maddr, mact); err != nil { + return cid.Undef, xerrors.Errorf("setting miner state: %w", err) + } + + nh, err = nst.Flush(ctx) + if err != nil { + return cid.Undef, xerrors.Errorf("flushing state tree: %w", err) + } + + genesisVm, err = newVM(nh) + if err != nil { + return cid.Undef, fmt.Errorf("creating new vm: %w", err) + } } } } - } - // TODO: to avoid division by zero, we set the initial power actor power to 1, this adjusts that back down so the accounting is accurate. - err = vm.MutateState(ctx, builtin.StoragePowerActorAddr, func(cst cbor.IpldStore, st *power.State) error { - st.TotalQualityAdjPower = big.Sub(st.TotalQualityAdjPower, big.NewInt(1)) - return nil - }) + // Sanity-check total network power + nh, err := genesisVm.Flush(ctx) + if err != nil { + return cid.Undef, fmt.Errorf("flushing vm: %w", err) + } - c, err := vm.Flush(ctx) + nst, err := state.LoadStateTree(cst, nh) + if err != nil { + return cid.Undef, xerrors.Errorf("loading new state tree: %w", err) + } + + pact, err := nst.GetActor(power.Address) + if err != nil { + return cid.Undef, xerrors.Errorf("getting power actor: %w", err) + } + + pst, err := power.Load(adt.WrapStore(ctx, cst), pact) + if err != nil { + return cid.Undef, xerrors.Errorf("getting power state: %w", err) + } + + pc, err := pst.TotalPower() + if err != nil { + return cid.Undef, xerrors.Errorf("getting total power: %w", err) + } + + if !pc.RawBytePower.Equals(rawPow) { + return cid.Undef, xerrors.Errorf("TotalRawBytePower (%s) doesn't match previously calculated rawPow (%s)", pc.RawBytePower, rawPow) + } + + if !pc.QualityAdjPower.Equals(qaPow) { + return cid.Undef, xerrors.Errorf("QualityAdjPower (%s) doesn't match previously calculated qaPow (%s)", pc.QualityAdjPower, qaPow) + } + + // TODO: Should we re-ConstructState for the reward actor using rawPow as currRealizedPower here? + + c, err := genesisVm.Flush(ctx) if err != nil { return cid.Undef, xerrors.Errorf("flushing vm: %w", err) } + return c, nil } // TODO: copied from actors test harness, deduplicate or remove from here type fakeRand struct{} -func (fr *fakeRand) GetRandomness(ctx context.Context, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) ([]byte, error) { +func (fr *fakeRand) GetChainRandomness(ctx context.Context, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) ([]byte, error) { out := make([]byte, 32) - _, _ = rand.New(rand.NewSource(int64(randEpoch))).Read(out) + _, _ = rand.New(rand.NewSource(int64(randEpoch * 1000))).Read(out) //nolint return out, nil } + +func (fr *fakeRand) GetBeaconRandomness(ctx context.Context, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) ([]byte, error) { + out := make([]byte, 32) + _, _ = rand.New(rand.NewSource(int64(randEpoch))).Read(out) //nolint + return out, nil +} + +func currentTotalPower(ctx context.Context, vm vm.Interface, maddr address.Address) (*power0.CurrentTotalPowerReturn, error) { + pwret, err := doExecValue(ctx, vm, power.Address, maddr, big.Zero(), builtin0.MethodsPower.CurrentTotalPower, nil) + if err != nil { + return nil, err + } + var pwr power0.CurrentTotalPowerReturn + if err := pwr.UnmarshalCBOR(bytes.NewReader(pwret)); err != nil { + return nil, err + } + + return &pwr, nil +} + +func currentEpochBlockReward(ctx context.Context, vm vm.Interface, maddr address.Address, av actorstypes.Version) (abi.StoragePower, builtin.FilterEstimate, error) { + rwret, err := doExecValue(ctx, vm, reward.Address, maddr, big.Zero(), reward.Methods.ThisEpochReward, nil) + if err != nil { + return big.Zero(), builtin.FilterEstimate{}, err + } + + // TODO: This hack should move to reward actor wrapper + switch av { + case actorstypes.Version0: + var epochReward reward0.ThisEpochRewardReturn + + if err := epochReward.UnmarshalCBOR(bytes.NewReader(rwret)); err != nil { + return big.Zero(), builtin.FilterEstimate{}, err + } + + return epochReward.ThisEpochBaselinePower, builtin.FilterEstimate(*epochReward.ThisEpochRewardSmoothed), nil + case actorstypes.Version2: + var epochReward reward2.ThisEpochRewardReturn + + if err := epochReward.UnmarshalCBOR(bytes.NewReader(rwret)); err != nil { + return big.Zero(), builtin.FilterEstimate{}, err + } + + return epochReward.ThisEpochBaselinePower, builtin.FilterEstimate(epochReward.ThisEpochRewardSmoothed), nil + } + + var epochReward reward4.ThisEpochRewardReturn + + if err := epochReward.UnmarshalCBOR(bytes.NewReader(rwret)); err != nil { + return big.Zero(), builtin.FilterEstimate{}, err + } + + return epochReward.ThisEpochBaselinePower, builtin.FilterEstimate(epochReward.ThisEpochRewardSmoothed), nil +} diff --git a/chain/gen/genesis/t00_system.go b/chain/gen/genesis/t00_system.go deleted file mode 100644 index 581d7e788..000000000 --- a/chain/gen/genesis/t00_system.go +++ /dev/null @@ -1,29 +0,0 @@ -package genesis - -import ( - "context" - "github.com/filecoin-project/specs-actors/actors/builtin/system" - - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/specs-actors/actors/builtin" - bstore "github.com/ipfs/go-ipfs-blockstore" - cbor "github.com/ipfs/go-ipld-cbor" -) - -func SetupSystemActor(bs bstore.Blockstore) (*types.Actor, error) { - var st system.State - - cst := cbor.NewCborStore(bs) - - statecid, err := cst.Put(context.TODO(), &st) - if err != nil { - return nil, err - } - - act := &types.Actor{ - Code: builtin.SystemActorCodeID, - Head: statecid, - } - - return act, nil -} diff --git a/chain/gen/genesis/t01_init.go b/chain/gen/genesis/t01_init.go deleted file mode 100644 index 9f0efb0c6..000000000 --- a/chain/gen/genesis/t01_init.go +++ /dev/null @@ -1,74 +0,0 @@ -package genesis - -import ( - "context" - "encoding/json" - "fmt" - - "github.com/filecoin-project/specs-actors/actors/builtin" - - init_ "github.com/filecoin-project/specs-actors/actors/builtin/init" - "github.com/ipfs/go-hamt-ipld" - bstore "github.com/ipfs/go-ipfs-blockstore" - cbor "github.com/ipfs/go-ipld-cbor" - "golang.org/x/xerrors" - - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/genesis" -) - -func SetupInitActor(bs bstore.Blockstore, netname string, initialActors []genesis.Actor) (*types.Actor, error) { - if len(initialActors) > MaxAccounts { - return nil, xerrors.New("too many initial actors") - } - - var ias init_.State - ias.NextID = MinerStart - ias.NetworkName = netname - - cst := cbor.NewCborStore(bs) - amap := hamt.NewNode(cst, hamt.UseTreeBitWidth(5)) // TODO: use spec adt map - - for i, a := range initialActors { - if a.Type != genesis.TAccount { - return nil, xerrors.Errorf("unsupported account type: %s", a.Type) // TODO: Support msig (skip here) - } - - var ainfo genesis.AccountMeta - if err := json.Unmarshal(a.Meta, &ainfo); err != nil { - return nil, xerrors.Errorf("unmarshaling account meta: %w", err) - } - - fmt.Printf("init set %s t0%d\n", ainfo.Owner, AccountStart+uint64(i)) - - if err := amap.Set(context.TODO(), string(ainfo.Owner.Bytes()), AccountStart+uint64(i)); err != nil { - return nil, err - } - } - - if err := amap.Set(context.TODO(), string(RootVerifierAddr.Bytes()), 80); err != nil { - return nil, err - } - - if err := amap.Flush(context.TODO()); err != nil { - return nil, err - } - amapcid, err := cst.Put(context.TODO(), amap) - if err != nil { - return nil, err - } - - ias.AddressMap = amapcid - - statecid, err := cst.Put(context.TODO(), &ias) - if err != nil { - return nil, err - } - - act := &types.Actor{ - Code: builtin.InitActorCodeID, - Head: statecid, - } - - return act, nil -} diff --git a/chain/gen/genesis/t02_reward.go b/chain/gen/genesis/t02_reward.go deleted file mode 100644 index 96feff671..000000000 --- a/chain/gen/genesis/t02_reward.go +++ /dev/null @@ -1,30 +0,0 @@ -package genesis - -import ( - "context" - - "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/specs-actors/actors/builtin" - "github.com/filecoin-project/specs-actors/actors/builtin/reward" - bstore "github.com/ipfs/go-ipfs-blockstore" - cbor "github.com/ipfs/go-ipld-cbor" -) - -func SetupRewardActor(bs bstore.Blockstore) (*types.Actor, error) { - cst := cbor.NewCborStore(bs) - - st := reward.ConstructState() - st.LastPerEpochReward = types.FromFil(100) - - hcid, err := cst.Put(context.TODO(), st) - if err != nil { - return nil, err - } - - return &types.Actor{ - Code: builtin.RewardActorCodeID, - Balance: types.BigInt{Int: build.InitialRewardBalance}, - Head: hcid, - }, nil -} diff --git a/chain/gen/genesis/t03_cron.go b/chain/gen/genesis/t03_cron.go deleted file mode 100644 index e61a88d18..000000000 --- a/chain/gen/genesis/t03_cron.go +++ /dev/null @@ -1,29 +0,0 @@ -package genesis - -import ( - "context" - - "github.com/filecoin-project/specs-actors/actors/builtin" - "github.com/filecoin-project/specs-actors/actors/builtin/cron" - bstore "github.com/ipfs/go-ipfs-blockstore" - cbor "github.com/ipfs/go-ipld-cbor" - - "github.com/filecoin-project/lotus/chain/types" -) - -func SetupCronActor(bs bstore.Blockstore) (*types.Actor, error) { - cst := cbor.NewCborStore(bs) - cas := cron.ConstructState(cron.BuiltInEntries()) - - stcid, err := cst.Put(context.TODO(), cas) - if err != nil { - return nil, err - } - - return &types.Actor{ - Code: builtin.CronActorCodeID, - Head: stcid, - Nonce: 0, - Balance: types.NewInt(0), - }, nil -} diff --git a/chain/gen/genesis/t04_power.go b/chain/gen/genesis/t04_power.go deleted file mode 100644 index 136026977..000000000 --- a/chain/gen/genesis/t04_power.go +++ /dev/null @@ -1,47 +0,0 @@ -package genesis - -import ( - "context" - "github.com/filecoin-project/specs-actors/actors/builtin" - - "github.com/filecoin-project/specs-actors/actors/abi/big" - "github.com/filecoin-project/specs-actors/actors/builtin/power" - "github.com/ipfs/go-hamt-ipld" - bstore "github.com/ipfs/go-ipfs-blockstore" - cbor "github.com/ipfs/go-ipld-cbor" - - "github.com/filecoin-project/lotus/chain/types" -) - -func SetupStoragePowerActor(bs bstore.Blockstore) (*types.Actor, error) { - ctx := context.TODO() - cst := cbor.NewCborStore(bs) - nd := hamt.NewNode(cst, hamt.UseTreeBitWidth(5)) - emptyhamt, err := cst.Put(ctx, nd) - if err != nil { - return nil, err - } - - sms := &power.State{ - TotalRawBytePower: big.NewInt(0), - TotalQualityAdjPower: big.NewInt(1), // TODO: has to be 1 initially to avoid div by zero. Kinda annoying, should find a way to fix - TotalPledgeCollateral: big.NewInt(0), - MinerCount: 0, - CronEventQueue: emptyhamt, - LastEpochTick: 0, - Claims: emptyhamt, - NumMinersMeetingMinPower: 0, - } - - stcid, err := cst.Put(ctx, sms) - if err != nil { - return nil, err - } - - return &types.Actor{ - Code: builtin.StoragePowerActorCodeID, - Head: stcid, - Nonce: 0, - Balance: types.NewInt(0), - }, nil -} diff --git a/chain/gen/genesis/t05_market.go b/chain/gen/genesis/t05_market.go deleted file mode 100644 index 9c55dff19..000000000 --- a/chain/gen/genesis/t05_market.go +++ /dev/null @@ -1,42 +0,0 @@ -package genesis - -import ( - "context" - "github.com/ipfs/go-hamt-ipld" - - "github.com/filecoin-project/go-amt-ipld/v2" - "github.com/filecoin-project/specs-actors/actors/builtin" - "github.com/filecoin-project/specs-actors/actors/builtin/market" - bstore "github.com/ipfs/go-ipfs-blockstore" - cbor "github.com/ipfs/go-ipld-cbor" - - "github.com/filecoin-project/lotus/chain/types" -) - -func SetupStorageMarketActor(bs bstore.Blockstore) (*types.Actor, error) { - cst := cbor.NewCborStore(bs) - - a, err := amt.NewAMT(cst).Flush(context.TODO()) - if err != nil { - return nil, err - } - h, err := cst.Put(context.TODO(), hamt.NewNode(cst, hamt.UseTreeBitWidth(5))) - if err != nil { - return nil, err - } - - sms := market.ConstructState(a, h, h) - - stcid, err := cst.Put(context.TODO(), sms) - if err != nil { - return nil, err - } - - act := &types.Actor{ - Code: builtin.StorageMarketActorCodeID, - Head: stcid, - Balance: types.NewInt(0), - } - - return act, nil -} diff --git a/chain/gen/genesis/t06_vreg.go b/chain/gen/genesis/t06_vreg.go deleted file mode 100644 index 093925d3e..000000000 --- a/chain/gen/genesis/t06_vreg.go +++ /dev/null @@ -1,59 +0,0 @@ -package genesis - -import ( - "context" - - "github.com/filecoin-project/go-address" - "github.com/ipfs/go-hamt-ipld" - bstore "github.com/ipfs/go-ipfs-blockstore" - cbor "github.com/ipfs/go-ipld-cbor" - - "github.com/filecoin-project/specs-actors/actors/builtin" - "github.com/filecoin-project/specs-actors/actors/builtin/verifreg" - - "github.com/filecoin-project/lotus/chain/types" -) - -var RootVerifierAddr address.Address - -var RootVerifierID address.Address - -func init() { - k, err := address.NewFromString("t3qfoulel6fy6gn3hjmbhpdpf6fs5aqjb5fkurhtwvgssizq4jey5nw4ptq5up6h7jk7frdvvobv52qzmgjinq") - if err != nil { - panic(err) - } - - RootVerifierAddr = k - - idk, err := address.NewFromString("t080") - if err != nil { - panic(err) - } - - RootVerifierID = idk -} - -func SetupVerifiedRegistryActor(bs bstore.Blockstore) (*types.Actor, error) { - cst := cbor.NewCborStore(bs) - - h, err := cst.Put(context.TODO(), hamt.NewNode(cst, hamt.UseTreeBitWidth(5))) - if err != nil { - return nil, err - } - - sms := verifreg.ConstructState(h, RootVerifierID) - - stcid, err := cst.Put(context.TODO(), sms) - if err != nil { - return nil, err - } - - act := &types.Actor{ - Code: builtin.VerifiedRegistryActorCodeID, - Head: stcid, - Balance: types.NewInt(0), - } - - return act, nil -} diff --git a/chain/gen/genesis/util.go b/chain/gen/genesis/util.go index 0a5d7e16b..beca1183c 100644 --- a/chain/gen/genesis/util.go +++ b/chain/gen/genesis/util.go @@ -3,11 +3,12 @@ package genesis import ( "context" - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/specs-actors/actors/abi" cbg "github.com/whyrusleeping/cbor-gen" "golang.org/x/xerrors" + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/vm" @@ -21,25 +22,15 @@ func mustEnc(i cbg.CBORMarshaler) []byte { return enc } -func doExec(ctx context.Context, vm *vm.VM, to, from address.Address, method abi.MethodNum, params []byte) ([]byte, error) { - return doExecValue(ctx, vm, to, from, types.NewInt(0), method, params) -} - -func doExecValue(ctx context.Context, vm *vm.VM, to, from address.Address, value types.BigInt, method abi.MethodNum, params []byte) ([]byte, error) { - act, err := vm.StateTree().GetActor(from) - if err != nil { - return nil, xerrors.Errorf("doExec failed to get from actor: %w", err) - } - +func doExecValue(ctx context.Context, vm vm.Interface, to, from address.Address, value types.BigInt, method abi.MethodNum, params []byte) ([]byte, error) { ret, err := vm.ApplyImplicitMessage(ctx, &types.Message{ To: to, From: from, Method: method, Params: params, GasLimit: 1_000_000_000_000_000, - GasPrice: types.NewInt(0), Value: value, - Nonce: act.Nonce, + Nonce: 0, }) if err != nil { return nil, xerrors.Errorf("doExec apply message failed: %w", err) diff --git a/chain/gen/mining.go b/chain/gen/mining.go deleted file mode 100644 index ad8dfdf5b..000000000 --- a/chain/gen/mining.go +++ /dev/null @@ -1,166 +0,0 @@ -package gen - -import ( - "context" - - bls "github.com/filecoin-project/filecoin-ffi" - amt "github.com/filecoin-project/go-amt-ipld/v2" - "github.com/filecoin-project/specs-actors/actors/crypto" - cid "github.com/ipfs/go-cid" - cbor "github.com/ipfs/go-ipld-cbor" - cbg "github.com/whyrusleeping/cbor-gen" - "golang.org/x/xerrors" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/chain/state" - "github.com/filecoin-project/lotus/chain/stmgr" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/chain/vm" - "github.com/filecoin-project/lotus/chain/wallet" -) - -func MinerCreateBlock(ctx context.Context, sm *stmgr.StateManager, w *wallet.Wallet, bt *api.BlockTemplate) (*types.FullBlock, error) { - - pts, err := sm.ChainStore().LoadTipSet(bt.Parents) - if err != nil { - return nil, xerrors.Errorf("failed to load parent tipset: %w", err) - } - - st, recpts, err := sm.TipSetState(ctx, pts) - if err != nil { - return nil, xerrors.Errorf("failed to load tipset state: %w", err) - } - - worker, err := stmgr.GetMinerWorkerRaw(ctx, sm, st, bt.Miner) - if err != nil { - return nil, xerrors.Errorf("failed to get miner worker: %w", err) - } - - next := &types.BlockHeader{ - Miner: bt.Miner, - Parents: bt.Parents.Cids(), - Ticket: bt.Ticket, - ElectionProof: bt.Eproof, - - BeaconEntries: bt.BeaconValues, - Height: bt.Epoch, - Timestamp: bt.Timestamp, - WinPoStProof: bt.WinningPoStProof, - ParentStateRoot: st, - ParentMessageReceipts: recpts, - } - - var blsMessages []*types.Message - var secpkMessages []*types.SignedMessage - - var blsMsgCids, secpkMsgCids []cid.Cid - var blsSigs []crypto.Signature - for _, msg := range bt.Messages { - if msg.Signature.Type == crypto.SigTypeBLS { - blsSigs = append(blsSigs, msg.Signature) - blsMessages = append(blsMessages, &msg.Message) - - c, err := sm.ChainStore().PutMessage(&msg.Message) - if err != nil { - return nil, err - } - - blsMsgCids = append(blsMsgCids, c) - } else { - c, err := sm.ChainStore().PutMessage(msg) - if err != nil { - return nil, err - } - - secpkMsgCids = append(secpkMsgCids, c) - secpkMessages = append(secpkMessages, msg) - - } - } - - bs := cbor.NewCborStore(sm.ChainStore().Blockstore()) - blsmsgroot, err := amt.FromArray(ctx, bs, toIfArr(blsMsgCids)) - if err != nil { - return nil, xerrors.Errorf("building bls amt: %w", err) - } - secpkmsgroot, err := amt.FromArray(ctx, bs, toIfArr(secpkMsgCids)) - if err != nil { - return nil, xerrors.Errorf("building secpk amt: %w", err) - } - - mmcid, err := bs.Put(ctx, &types.MsgMeta{ - BlsMessages: blsmsgroot, - SecpkMessages: secpkmsgroot, - }) - if err != nil { - return nil, err - } - next.Messages = mmcid - - aggSig, err := aggregateSignatures(blsSigs) - if err != nil { - return nil, err - } - - next.BLSAggregate = aggSig - pweight, err := sm.ChainStore().Weight(ctx, pts) - if err != nil { - return nil, err - } - next.ParentWeight = pweight - - cst := cbor.NewCborStore(sm.ChainStore().Blockstore()) - tree, err := state.LoadStateTree(cst, st) - if err != nil { - return nil, xerrors.Errorf("failed to load state tree: %w", err) - } - - waddr, err := vm.ResolveToKeyAddr(tree, cst, worker) - if err != nil { - return nil, xerrors.Errorf("failed to resolve miner address to key address: %w", err) - } - - nosigbytes, err := next.SigningBytes() - if err != nil { - return nil, xerrors.Errorf("failed to get signing bytes for block: %w", err) - } - - sig, err := w.Sign(ctx, waddr, nosigbytes) - if err != nil { - return nil, xerrors.Errorf("failed to sign new block: %w", err) - } - - next.BlockSig = sig - - fullBlock := &types.FullBlock{ - Header: next, - BlsMessages: blsMessages, - SecpkMessages: secpkMessages, - } - - return fullBlock, nil -} - -func aggregateSignatures(sigs []crypto.Signature) (*crypto.Signature, error) { - var blsSigs []bls.Signature - for _, s := range sigs { - var bsig bls.Signature - copy(bsig[:], s.Data) - blsSigs = append(blsSigs, bsig) - } - - aggSig := bls.Aggregate(blsSigs) - return &crypto.Signature{ - Type: crypto.SigTypeBLS, - Data: aggSig[:], - }, nil -} - -func toIfArr(cids []cid.Cid) []cbg.CBORMarshaler { - out := make([]cbg.CBORMarshaler, 0, len(cids)) - for _, c := range cids { - oc := cbg.CborCid(c) - out = append(out, &oc) - } - return out -} diff --git a/chain/gen/slashfilter/slashfilter.go b/chain/gen/slashfilter/slashfilter.go new file mode 100644 index 000000000..986586267 --- /dev/null +++ b/chain/gen/slashfilter/slashfilter.go @@ -0,0 +1,122 @@ +package slashfilter + +import ( + "context" + "fmt" + + "github.com/ipfs/go-cid" + ds "github.com/ipfs/go-datastore" + "github.com/ipfs/go-datastore/namespace" + "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" +) + +type SlashFilter struct { + byEpoch ds.Datastore // double-fork mining faults, parent-grinding fault + byParents ds.Datastore // time-offset mining faults +} + +func New(dstore ds.Batching) *SlashFilter { + return &SlashFilter{ + byEpoch: namespace.Wrap(dstore, ds.NewKey("/slashfilter/epoch")), + byParents: namespace.Wrap(dstore, ds.NewKey("/slashfilter/parents")), + } +} + +func (f *SlashFilter) MinedBlock(ctx context.Context, bh *types.BlockHeader, parentEpoch abi.ChainEpoch) error { + if build.IsNearUpgrade(bh.Height, build.UpgradeOrangeHeight) { + return nil + } + + epochKey := ds.NewKey(fmt.Sprintf("/%s/%d", bh.Miner, bh.Height)) + { + // double-fork mining (2 blocks at one epoch) + if err := checkFault(ctx, f.byEpoch, epochKey, bh, "double-fork mining faults"); err != nil { + return err + } + } + + parentsKey := ds.NewKey(fmt.Sprintf("/%s/%x", bh.Miner, types.NewTipSetKey(bh.Parents...).Bytes())) + { + // time-offset mining faults (2 blocks with the same parents) + if err := checkFault(ctx, f.byParents, parentsKey, bh, "time-offset mining faults"); err != nil { + return err + } + } + + { + // parent-grinding fault (didn't mine on top of our own block) + + // First check if we have mined a block on the parent epoch + parentEpochKey := ds.NewKey(fmt.Sprintf("/%s/%d", bh.Miner, parentEpoch)) + have, err := f.byEpoch.Has(ctx, parentEpochKey) + if err != nil { + return err + } + + if have { + // If we had, make sure it's in our parent tipset + cidb, err := f.byEpoch.Get(ctx, parentEpochKey) + if err != nil { + return xerrors.Errorf("getting other block cid: %w", err) + } + + _, parent, err := cid.CidFromBytes(cidb) + if err != nil { + return err + } + + var found bool + for _, c := range bh.Parents { + if c.Equals(parent) { + found = true + } + } + + if !found { + return xerrors.Errorf("produced block would trigger 'parent-grinding fault' consensus fault; miner: %s; bh: %s, expected parent: %s", bh.Miner, bh.Cid(), parent) + } + } + } + + if err := f.byParents.Put(ctx, parentsKey, bh.Cid().Bytes()); err != nil { + return xerrors.Errorf("putting byEpoch entry: %w", err) + } + + if err := f.byEpoch.Put(ctx, epochKey, bh.Cid().Bytes()); err != nil { + return xerrors.Errorf("putting byEpoch entry: %w", err) + } + + return nil +} + +func checkFault(ctx context.Context, t ds.Datastore, key ds.Key, bh *types.BlockHeader, faultType string) error { + fault, err := t.Has(ctx, key) + if err != nil { + return err + } + + if fault { + cidb, err := t.Get(ctx, key) + if err != nil { + return xerrors.Errorf("getting other block cid: %w", err) + } + + _, other, err := cid.CidFromBytes(cidb) + if err != nil { + return err + } + + if other == bh.Cid() { + return nil + } + + return xerrors.Errorf("produced block would trigger '%s' consensus fault; miner: %s; bh: %s, other: %s", faultType, bh.Miner, bh.Cid(), other) + } + + return nil +} diff --git a/chain/index/interface.go b/chain/index/interface.go new file mode 100644 index 000000000..f875a94bf --- /dev/null +++ b/chain/index/interface.go @@ -0,0 +1,45 @@ +package index + +import ( + "context" + "errors" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-state-types/abi" +) + +var ErrNotFound = errors.New("message not found") +var ErrClosed = errors.New("index closed") + +// MsgInfo is the Message metadata the index tracks. +type MsgInfo struct { + // the message this record refers to + Message cid.Cid + // the tipset where this message was included + TipSet cid.Cid + // the epoch where this message was included + Epoch abi.ChainEpoch +} + +// MsgIndex is the interface to the message index +type MsgIndex interface { + // GetMsgInfo retrieves the message metadata through the index. + // The lookup is done using the onchain message Cid; that is the signed message Cid + // for SECP messages and unsigned message Cid for BLS messages. + GetMsgInfo(ctx context.Context, m cid.Cid) (MsgInfo, error) + // Close closes the index + Close() error +} + +type dummyMsgIndex struct{} + +func (dummyMsgIndex) GetMsgInfo(ctx context.Context, m cid.Cid) (MsgInfo, error) { + return MsgInfo{}, ErrNotFound +} + +func (dummyMsgIndex) Close() error { + return nil +} + +var DummyMsgIndex MsgIndex = dummyMsgIndex{} diff --git a/chain/index/msgindex.go b/chain/index/msgindex.go new file mode 100644 index 000000000..39ba487f2 --- /dev/null +++ b/chain/index/msgindex.go @@ -0,0 +1,553 @@ +package index + +import ( + "context" + "database/sql" + "errors" + "io/fs" + "os" + "path" + "sync" + "time" + + "github.com/ipfs/go-cid" + logging "github.com/ipfs/go-log/v2" + _ "github.com/mattn/go-sqlite3" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/chain/types" +) + +var log = logging.Logger("msgindex") + +var dbName = "msgindex.db" +var dbDefs = []string{ + `CREATE TABLE IF NOT EXISTS messages ( + cid VARCHAR(80) PRIMARY KEY ON CONFLICT REPLACE, + tipset_cid VARCHAR(80) NOT NULL, + epoch INTEGER NOT NULL + )`, + `CREATE INDEX IF NOT EXISTS tipset_cids ON messages (tipset_cid) + `, + `CREATE TABLE IF NOT EXISTS _meta ( + version UINT64 NOT NULL UNIQUE + )`, + `INSERT OR IGNORE INTO _meta (version) VALUES (1)`, +} +var dbPragmas = []string{} + +const ( + // prepared stmts + dbqGetMessageInfo = "SELECT tipset_cid, epoch FROM messages WHERE cid = ?" + dbqInsertMessage = "INSERT INTO messages VALUES (?, ?, ?)" + dbqDeleteTipsetMessages = "DELETE FROM messages WHERE tipset_cid = ?" + // reconciliation + dbqCountMessages = "SELECT COUNT(*) FROM messages" + dbqMinEpoch = "SELECT MIN(epoch) FROM messages" + dbqCountTipsetMessages = "SELECT COUNT(*) FROM messages WHERE tipset_cid = ?" + dbqDeleteMessagesByEpoch = "DELETE FROM messages WHERE epoch >= ?" +) + +// coalescer configuration (TODO: use observer instead) +// these are exposed to make tests snappy +var ( + CoalesceMinDelay = time.Second + CoalesceMaxDelay = 15 * time.Second + CoalesceMergeInterval = time.Second +) + +// chain store interface; we could use store.ChainStore directly, +// but this simplifies unit testing. +type ChainStore interface { + SubscribeHeadChanges(f store.ReorgNotifee) + MessagesForTipset(ctx context.Context, ts *types.TipSet) ([]types.ChainMsg, error) + GetHeaviestTipSet() *types.TipSet + GetTipSetFromKey(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error) +} + +var _ ChainStore = (*store.ChainStore)(nil) + +type msgIndex struct { + cs ChainStore + + db *sql.DB + selectMsgStmt *sql.Stmt + insertMsgStmt *sql.Stmt + deleteTipSetStmt *sql.Stmt + + sema chan struct{} + mx sync.Mutex + pend []headChange + + cancel func() + workers sync.WaitGroup + closeLk sync.RWMutex + closed bool +} + +var _ MsgIndex = (*msgIndex)(nil) + +type headChange struct { + rev []*types.TipSet + app []*types.TipSet +} + +func NewMsgIndex(lctx context.Context, basePath string, cs ChainStore) (MsgIndex, error) { + var ( + dbPath string + exists bool + err error + ) + + err = os.MkdirAll(basePath, 0755) + if err != nil { + return nil, xerrors.Errorf("error creating msgindex base directory: %w", err) + } + + dbPath = path.Join(basePath, dbName) + _, err = os.Stat(dbPath) + switch { + case err == nil: + exists = true + + case errors.Is(err, fs.ErrNotExist): + + case err != nil: + return nil, xerrors.Errorf("error stating msgindex database: %w", err) + } + + db, err := sql.Open("sqlite3", dbPath) + if err != nil { + // TODO [nice to have]: automaticaly delete corrupt databases + // but for now we can just error and let the operator delete. + return nil, xerrors.Errorf("error opening msgindex database: %w", err) + } + + if err := prepareDB(db); err != nil { + return nil, xerrors.Errorf("error creating msgindex database: %w", err) + } + + // TODO we may consider populating the index when first creating the db + if exists { + if err := reconcileIndex(db, cs); err != nil { + return nil, xerrors.Errorf("error reconciling msgindex database: %w", err) + } + } + + ctx, cancel := context.WithCancel(lctx) + + msgIndex := &msgIndex{ + db: db, + cs: cs, + sema: make(chan struct{}, 1), + cancel: cancel, + } + + err = msgIndex.prepareStatements() + if err != nil { + if err := db.Close(); err != nil { + log.Errorf("error closing msgindex database: %s", err) + } + + return nil, xerrors.Errorf("error preparing msgindex database statements: %w", err) + } + + rnf := store.WrapHeadChangeCoalescer( + msgIndex.onHeadChange, + CoalesceMinDelay, + CoalesceMaxDelay, + CoalesceMergeInterval, + ) + cs.SubscribeHeadChanges(rnf) + + msgIndex.workers.Add(1) + go msgIndex.background(ctx) + + return msgIndex, nil +} + +func PopulateAfterSnapshot(lctx context.Context, basePath string, cs ChainStore) error { + err := os.MkdirAll(basePath, 0755) + if err != nil { + return xerrors.Errorf("error creating msgindex base directory: %w", err) + } + + dbPath := path.Join(basePath, dbName) + + // if a database already exists, we try to delete it and create a new one + if _, err := os.Stat(dbPath); err == nil { + if err = os.Remove(dbPath); err != nil { + return xerrors.Errorf("msgindex already exists at %s and can't be deleted", dbPath) + } + } + + db, err := sql.Open("sqlite3", dbPath) + if err != nil { + return xerrors.Errorf("error opening msgindex database: %w", err) + } + defer func() { + if err := db.Close(); err != nil { + log.Errorf("error closing msgindex database: %s", err) + } + }() + + if err := prepareDB(db); err != nil { + return xerrors.Errorf("error creating msgindex database: %w", err) + } + + tx, err := db.Begin() + if err != nil { + return xerrors.Errorf("error when starting transaction: %w", err) + } + + rollback := func() { + if err := tx.Rollback(); err != nil { + log.Errorf("error in rollback: %s", err) + } + } + + insertStmt, err := tx.Prepare(dbqInsertMessage) + if err != nil { + rollback() + return xerrors.Errorf("error preparing insertStmt: %w", err) + } + + curTs := cs.GetHeaviestTipSet() + startHeight := curTs.Height() + for curTs != nil { + tscid, err := curTs.Key().Cid() + if err != nil { + rollback() + return xerrors.Errorf("error computing tipset cid: %w", err) + } + + tskey := tscid.String() + epoch := int64(curTs.Height()) + + msgs, err := cs.MessagesForTipset(lctx, curTs) + if err != nil { + log.Infof("stopping import after %d tipsets", startHeight-curTs.Height()) + break + } + + for _, msg := range msgs { + key := msg.Cid().String() + if _, err := insertStmt.Exec(key, tskey, epoch); err != nil { + rollback() + return xerrors.Errorf("error inserting message: %w", err) + } + } + + curTs, err = cs.GetTipSetFromKey(lctx, curTs.Parents()) + if err != nil { + rollback() + return xerrors.Errorf("error walking chain: %w", err) + } + } + + err = tx.Commit() + if err != nil { + return xerrors.Errorf("error committing transaction: %w", err) + } + + return nil +} + +// init utilities +func prepareDB(db *sql.DB) error { + for _, stmt := range dbDefs { + if _, err := db.Exec(stmt); err != nil { + return xerrors.Errorf("error executing sql statement '%s': %w", stmt, err) + } + } + + for _, stmt := range dbPragmas { + if _, err := db.Exec(stmt); err != nil { + return xerrors.Errorf("error executing sql statement '%s': %w", stmt, err) + } + } + + return nil +} + +func reconcileIndex(db *sql.DB, cs ChainStore) error { + // Invariant: after reconciliation, every tipset in the index is in the current chain; ie either + // the chain head or reachable by walking the chain. + // Algorithm: + // 1. Count messages in index; if none, trivially reconciled. + // TODO we may consider populating the index in that case + // 2. Find the minimum tipset in the index; this will mark the end of the reconciliation walk + // 3. Walk from current tipset until we find a tipset in the index. + // 4. Delete (revert!) all tipsets above the found tipset. + // 5. If the walk ends in the boundary epoch, then delete everything. + // + + row := db.QueryRow(dbqCountMessages) + + var result int64 + if err := row.Scan(&result); err != nil { + return xerrors.Errorf("error counting messages: %w", err) + } + + if result == 0 { + return nil + } + + row = db.QueryRow(dbqMinEpoch) + if err := row.Scan(&result); err != nil { + return xerrors.Errorf("error finding boundary epoch: %w", err) + } + + boundaryEpoch := abi.ChainEpoch(result) + + countMsgsStmt, err := db.Prepare(dbqCountTipsetMessages) + if err != nil { + return xerrors.Errorf("error preparing statement: %w", err) + } + + curTs := cs.GetHeaviestTipSet() + for curTs != nil && curTs.Height() >= boundaryEpoch { + tsCid, err := curTs.Key().Cid() + if err != nil { + return xerrors.Errorf("error computing tipset cid: %w", err) + } + + key := tsCid.String() + row = countMsgsStmt.QueryRow(key) + if err := row.Scan(&result); err != nil { + return xerrors.Errorf("error counting messages: %w", err) + } + + if result > 0 { + // found it! + boundaryEpoch = curTs.Height() + 1 + break + } + + // walk up + parents := curTs.Parents() + curTs, err = cs.GetTipSetFromKey(context.TODO(), parents) + if err != nil { + return xerrors.Errorf("error walking chain: %w", err) + } + } + + // delete everything above the minEpoch + if _, err = db.Exec(dbqDeleteMessagesByEpoch, int64(boundaryEpoch)); err != nil { + return xerrors.Errorf("error deleting stale reorged out message: %w", err) + } + + return nil +} + +func (x *msgIndex) prepareStatements() error { + stmt, err := x.db.Prepare(dbqGetMessageInfo) + if err != nil { + return xerrors.Errorf("prepare selectMsgStmt: %w", err) + } + x.selectMsgStmt = stmt + + stmt, err = x.db.Prepare(dbqInsertMessage) + if err != nil { + return xerrors.Errorf("prepare insertMsgStmt: %w", err) + } + x.insertMsgStmt = stmt + + stmt, err = x.db.Prepare(dbqDeleteTipsetMessages) + if err != nil { + return xerrors.Errorf("prepare deleteTipSetStmt: %w", err) + } + x.deleteTipSetStmt = stmt + + return nil +} + +// head change notifee +func (x *msgIndex) onHeadChange(rev, app []*types.TipSet) error { + x.closeLk.RLock() + defer x.closeLk.RUnlock() + + if x.closed { + return nil + } + + // do it in the background to avoid blocking head change processing + x.mx.Lock() + x.pend = append(x.pend, headChange{rev: rev, app: app}) + pendLen := len(x.pend) + x.mx.Unlock() + + // complain loudly if this is building backlog + if pendLen > 10 { + log.Warnf("message index head change processing is building backlog: %d pending head changes", pendLen) + } + + select { + case x.sema <- struct{}{}: + default: + } + + return nil +} + +func (x *msgIndex) background(ctx context.Context) { + defer x.workers.Done() + + for { + select { + case <-x.sema: + err := x.processHeadChanges(ctx) + if err != nil { + // we can't rely on an inconsistent index, so shut it down. + log.Errorf("error processing head change notifications: %s; shutting down message index", err) + if err2 := x.Close(); err2 != nil { + log.Errorf("error shutting down index: %s", err2) + } + } + + case <-ctx.Done(): + return + } + } +} + +func (x *msgIndex) processHeadChanges(ctx context.Context) error { + x.mx.Lock() + pend := x.pend + x.pend = nil + x.mx.Unlock() + + tx, err := x.db.Begin() + if err != nil { + return xerrors.Errorf("error creating transaction: %w", err) + } + + for _, hc := range pend { + for _, ts := range hc.rev { + if err := x.doRevert(ctx, tx, ts); err != nil { + if err2 := tx.Rollback(); err2 != nil { + log.Errorf("error rolling back transaction: %s", err2) + } + return xerrors.Errorf("error reverting %s: %w", ts, err) + } + } + + for _, ts := range hc.app { + if err := x.doApply(ctx, tx, ts); err != nil { + if err2 := tx.Rollback(); err2 != nil { + log.Errorf("error rolling back transaction: %s", err2) + } + return xerrors.Errorf("error applying %s: %w", ts, err) + } + } + } + + return tx.Commit() +} + +func (x *msgIndex) doRevert(ctx context.Context, tx *sql.Tx, ts *types.TipSet) error { + tskey, err := ts.Key().Cid() + if err != nil { + return xerrors.Errorf("error computing tipset cid: %w", err) + } + + key := tskey.String() + _, err = tx.Stmt(x.deleteTipSetStmt).Exec(key) + return err +} + +func (x *msgIndex) doApply(ctx context.Context, tx *sql.Tx, ts *types.TipSet) error { + tscid, err := ts.Key().Cid() + if err != nil { + return xerrors.Errorf("error computing tipset cid: %w", err) + } + + tskey := tscid.String() + epoch := int64(ts.Height()) + + msgs, err := x.cs.MessagesForTipset(ctx, ts) + if err != nil { + return xerrors.Errorf("error retrieving messages for tipset %s: %w", ts, err) + } + + insertStmt := tx.Stmt(x.insertMsgStmt) + for _, msg := range msgs { + key := msg.Cid().String() + if _, err := insertStmt.Exec(key, tskey, epoch); err != nil { + return xerrors.Errorf("error inserting message: %w", err) + } + } + + return nil +} + +// interface +func (x *msgIndex) GetMsgInfo(ctx context.Context, m cid.Cid) (MsgInfo, error) { + x.closeLk.RLock() + defer x.closeLk.RUnlock() + + if x.closed { + return MsgInfo{}, ErrClosed + } + + var ( + tipset string + epoch int64 + ) + + key := m.String() + row := x.selectMsgStmt.QueryRow(key) + err := row.Scan(&tipset, &epoch) + switch { + case err == sql.ErrNoRows: + return MsgInfo{}, ErrNotFound + + case err != nil: + return MsgInfo{}, xerrors.Errorf("error querying msgindex database: %w", err) + } + + tipsetCid, err := cid.Decode(tipset) + if err != nil { + return MsgInfo{}, xerrors.Errorf("error decoding tipset cid: %w", err) + } + + return MsgInfo{ + Message: m, + TipSet: tipsetCid, + Epoch: abi.ChainEpoch(epoch), + }, nil +} + +func (x *msgIndex) Close() error { + x.closeLk.Lock() + defer x.closeLk.Unlock() + + if x.closed { + return nil + } + + x.closed = true + + x.cancel() + x.workers.Wait() + + return x.db.Close() +} + +// informal apis for itests; not exposed in the main interface +func (x *msgIndex) CountMessages() (int64, error) { + x.closeLk.RLock() + defer x.closeLk.RUnlock() + + if x.closed { + return 0, ErrClosed + } + + var result int64 + row := x.db.QueryRow(dbqCountMessages) + err := row.Scan(&result) + return result, err +} diff --git a/chain/index/msgindex_test.go b/chain/index/msgindex_test.go new file mode 100644 index 000000000..bf4bc6190 --- /dev/null +++ b/chain/index/msgindex_test.go @@ -0,0 +1,307 @@ +package index + +import ( + "context" + "errors" + "math/rand" + "os" + "testing" + "time" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-address" + + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/mock" +) + +func TestBasicMsgIndex(t *testing.T) { + // the most basic of tests: + // 1. Create an index with mock chain store + // 2. Advance the chain for a few tipsets + // 3. Verify that the index contains all messages with the correct tipset/epoch + cs := newMockChainStore() + cs.genesis() + + tmp := t.TempDir() + t.Cleanup(func() { _ = os.RemoveAll(tmp) }) + + msgIndex, err := NewMsgIndex(context.Background(), tmp, cs) + require.NoError(t, err) + + defer msgIndex.Close() //nolint + + for i := 0; i < 10; i++ { + t.Logf("advance to epoch %d", i+1) + err := cs.advance() + require.NoError(t, err) + } + + waitForCoalescerAfterLastEvent() + + t.Log("verifying index") + verifyIndex(t, cs, msgIndex) +} + +func TestReorgMsgIndex(t *testing.T) { + // slightly more nuanced test that includes reorgs + // 1. Create an index with mock chain store + // 2. Advance/Reorg the chain for a few tipsets + // 3. Verify that the index contains all messages with the correct tipset/epoch + cs := newMockChainStore() + cs.genesis() + + tmp := t.TempDir() + t.Cleanup(func() { _ = os.RemoveAll(tmp) }) + + msgIndex, err := NewMsgIndex(context.Background(), tmp, cs) + require.NoError(t, err) + + defer msgIndex.Close() //nolint + + for i := 0; i < 10; i++ { + t.Logf("advance to epoch %d", i+1) + err := cs.advance() + require.NoError(t, err) + } + + waitForCoalescerAfterLastEvent() + + // a simple reorg + t.Log("doing reorg") + reorgme := cs.curTs + reorgmeParent, err := cs.GetTipSetFromKey(context.Background(), reorgme.Parents()) + require.NoError(t, err) + cs.setHead(reorgmeParent) + reorgmeChild := cs.makeBlk() + err = cs.reorg([]*types.TipSet{reorgme}, []*types.TipSet{reorgmeChild}) + require.NoError(t, err) + + waitForCoalescerAfterLastEvent() + + t.Log("verifying index") + verifyIndex(t, cs, msgIndex) + + t.Log("verifying that reorged messages are not present") + verifyMissing(t, cs, msgIndex, reorgme) +} + +func TestReconcileMsgIndex(t *testing.T) { + // test that exercises the reconciliation code paths + // 1. Create and populate a basic msgindex, similar to TestBasicMsgIndex. + // 2. Close it + // 3. Reorg the mock chain store + // 4. Reopen the index to trigger reconciliation + // 5. Enxure that only the stable messages remain. + cs := newMockChainStore() + cs.genesis() + + tmp := t.TempDir() + t.Cleanup(func() { _ = os.RemoveAll(tmp) }) + + msgIndex, err := NewMsgIndex(context.Background(), tmp, cs) + require.NoError(t, err) + + for i := 0; i < 10; i++ { + t.Logf("advance to epoch %d", i+1) + err := cs.advance() + require.NoError(t, err) + } + + waitForCoalescerAfterLastEvent() + + // Close it and reorg + err = msgIndex.Close() + require.NoError(t, err) + cs.notify = nil + + // a simple reorg + t.Log("doing reorg") + reorgme := cs.curTs + reorgmeParent, err := cs.GetTipSetFromKey(context.Background(), reorgme.Parents()) + require.NoError(t, err) + cs.setHead(reorgmeParent) + reorgmeChild := cs.makeBlk() + err = cs.reorg([]*types.TipSet{reorgme}, []*types.TipSet{reorgmeChild}) + require.NoError(t, err) + + // reopen to reconcile + msgIndex, err = NewMsgIndex(context.Background(), tmp, cs) + require.NoError(t, err) + + defer msgIndex.Close() //nolint + + t.Log("verifying index") + // need to step one up because the last tipset is not known by the index + cs.setHead(reorgmeParent) + verifyIndex(t, cs, msgIndex) + + t.Log("verifying that reorged and unknown messages are not present") + verifyMissing(t, cs, msgIndex, reorgme, reorgmeChild) +} + +func verifyIndex(t *testing.T, cs *mockChainStore, msgIndex MsgIndex) { + for ts := cs.curTs; ts.Height() > 0; { + t.Logf("verify at height %d", ts.Height()) + blks := ts.Blocks() + if len(blks) == 0 { + break + } + + tsCid, err := ts.Key().Cid() + require.NoError(t, err) + + msgs, err := cs.MessagesForTipset(context.Background(), ts) + require.NoError(t, err) + for _, m := range msgs { + minfo, err := msgIndex.GetMsgInfo(context.Background(), m.Cid()) + require.NoError(t, err) + require.Equal(t, tsCid, minfo.TipSet) + require.Equal(t, ts.Height(), minfo.Epoch) + } + + parents := ts.Parents() + ts, err = cs.GetTipSetFromKey(context.Background(), parents) + require.NoError(t, err) + } +} + +func verifyMissing(t *testing.T, cs *mockChainStore, msgIndex MsgIndex, missing ...*types.TipSet) { + for _, ts := range missing { + msgs, err := cs.MessagesForTipset(context.Background(), ts) + require.NoError(t, err) + for _, m := range msgs { + _, err := msgIndex.GetMsgInfo(context.Background(), m.Cid()) + require.Equal(t, ErrNotFound, err) + } + } +} + +type mockChainStore struct { + notify store.ReorgNotifee + + curTs *types.TipSet + tipsets map[types.TipSetKey]*types.TipSet + msgs map[types.TipSetKey][]types.ChainMsg + + nonce uint64 +} + +var _ ChainStore = (*mockChainStore)(nil) + +var systemAddr address.Address +var rng *rand.Rand + +func init() { + systemAddr, _ = address.NewIDAddress(0) + rng = rand.New(rand.NewSource(314159)) + + // adjust those to make tests snappy + CoalesceMinDelay = 100 * time.Millisecond + CoalesceMaxDelay = time.Second + CoalesceMergeInterval = 100 * time.Millisecond +} + +func newMockChainStore() *mockChainStore { + return &mockChainStore{ + tipsets: make(map[types.TipSetKey]*types.TipSet), + msgs: make(map[types.TipSetKey][]types.ChainMsg), + } +} + +func (cs *mockChainStore) genesis() { + genBlock := mock.MkBlock(nil, 0, 0) + genTs := mock.TipSet(genBlock) + cs.msgs[genTs.Key()] = nil + cs.setHead(genTs) +} + +func (cs *mockChainStore) setHead(ts *types.TipSet) { + cs.curTs = ts + cs.tipsets[ts.Key()] = ts +} + +func (cs *mockChainStore) advance() error { + ts := cs.makeBlk() + return cs.reorg(nil, []*types.TipSet{ts}) +} + +func (cs *mockChainStore) reorg(rev, app []*types.TipSet) error { + for _, ts := range rev { + parents := ts.Parents() + cs.curTs = cs.tipsets[parents] + } + + for _, ts := range app { + cs.tipsets[ts.Key()] = ts + cs.curTs = ts + } + + if cs.notify != nil { + return cs.notify(rev, app) + } + + return nil +} + +func (cs *mockChainStore) makeBlk() *types.TipSet { + height := cs.curTs.Height() + 1 + + blk := mock.MkBlock(cs.curTs, uint64(height), uint64(height)) + blk.Messages = cs.makeGarbageCid() + + ts := mock.TipSet(blk) + msg1 := cs.makeMsg() + msg2 := cs.makeMsg() + cs.msgs[ts.Key()] = []types.ChainMsg{msg1, msg2} + + return ts +} + +func (cs *mockChainStore) makeMsg() *types.Message { + nonce := cs.nonce + cs.nonce++ + return &types.Message{To: systemAddr, From: systemAddr, Nonce: nonce} +} + +func (cs *mockChainStore) makeGarbageCid() cid.Cid { + garbage := blocks.NewBlock([]byte{byte(rng.Intn(256)), byte(rng.Intn(256)), byte(rng.Intn(256))}) + return garbage.Cid() +} + +func (cs *mockChainStore) SubscribeHeadChanges(f store.ReorgNotifee) { + cs.notify = f +} + +func (cs *mockChainStore) MessagesForTipset(ctx context.Context, ts *types.TipSet) ([]types.ChainMsg, error) { + msgs, ok := cs.msgs[ts.Key()] + if !ok { + return nil, errors.New("unknown tipset") + } + + return msgs, nil +} + +func (cs *mockChainStore) GetHeaviestTipSet() *types.TipSet { + return cs.curTs +} + +func (cs *mockChainStore) GetTipSetFromKey(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error) { + ts, ok := cs.tipsets[tsk] + if !ok { + return nil, errors.New("unknown tipset") + } + return ts, nil +} + +func waitForCoalescerAfterLastEvent() { + // It can take up to CoalesceMinDelay for the coalescer timer to fire after the last event. + // When the timer fires, it can wait up to CoalesceMinDelay again for more events. + // Therefore the total wait is 2 * CoalesceMinDelay. + // Then we wait another second for the listener (the index) to actually process events. + time.Sleep(2*CoalesceMinDelay + time.Second) +} diff --git a/chain/market/cbor_gen.go b/chain/market/cbor_gen.go new file mode 100644 index 000000000..9c9ef1a94 --- /dev/null +++ b/chain/market/cbor_gen.go @@ -0,0 +1,124 @@ +// Code generated by github.com/whyrusleeping/cbor-gen. DO NOT EDIT. + +package market + +import ( + "fmt" + "io" + "math" + "sort" + + cid "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + xerrors "golang.org/x/xerrors" +) + +var _ = xerrors.Errorf +var _ = cid.Undef +var _ = math.E +var _ = sort.Sort + +var lengthBufFundedAddressState = []byte{131} + +func (t *FundedAddressState) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufFundedAddressState); err != nil { + return err + } + + // t.Addr (address.Address) (struct) + if err := t.Addr.MarshalCBOR(cw); err != nil { + return err + } + + // t.AmtReserved (big.Int) (struct) + if err := t.AmtReserved.MarshalCBOR(cw); err != nil { + return err + } + + // t.MsgCid (cid.Cid) (struct) + + if t.MsgCid == nil { + if _, err := cw.Write(cbg.CborNull); err != nil { + return err + } + } else { + if err := cbg.WriteCid(cw, *t.MsgCid); err != nil { + return xerrors.Errorf("failed to write cid field t.MsgCid: %w", err) + } + } + + return nil +} + +func (t *FundedAddressState) UnmarshalCBOR(r io.Reader) (err error) { + *t = FundedAddressState{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != 3 { + return fmt.Errorf("cbor input had wrong number of fields") + } + + // t.Addr (address.Address) (struct) + + { + + if err := t.Addr.UnmarshalCBOR(cr); err != nil { + return xerrors.Errorf("unmarshaling t.Addr: %w", err) + } + + } + // t.AmtReserved (big.Int) (struct) + + { + + if err := t.AmtReserved.UnmarshalCBOR(cr); err != nil { + return xerrors.Errorf("unmarshaling t.AmtReserved: %w", err) + } + + } + // t.MsgCid (cid.Cid) (struct) + + { + + b, err := cr.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { + return err + } + + c, err := cbg.ReadCid(cr) + if err != nil { + return xerrors.Errorf("failed to read cid field t.MsgCid: %w", err) + } + + t.MsgCid = &c + } + + } + return nil +} diff --git a/chain/market/fundmanager.go b/chain/market/fundmanager.go new file mode 100644 index 000000000..fab71dfef --- /dev/null +++ b/chain/market/fundmanager.go @@ -0,0 +1,729 @@ +package market + +import ( + "context" + "fmt" + "sync" + + "github.com/ipfs/go-cid" + "github.com/ipfs/go-datastore" + logging "github.com/ipfs/go-log/v2" + "go.uber.org/fx" + "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/builtin" + "github.com/filecoin-project/go-state-types/builtin/v9/market" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/node/impl/full" + "github.com/filecoin-project/lotus/node/modules/dtypes" +) + +var log = logging.Logger("market_adapter") + +// API is the fx dependencies need to run a fund manager +type FundManagerAPI struct { + fx.In + + full.StateAPI + full.MpoolAPI +} + +// fundManagerAPI is the specific methods called by the FundManager +// (used by the tests) +type fundManagerAPI interface { + MpoolPushMessage(context.Context, *types.Message, *api.MessageSendSpec) (*types.SignedMessage, error) + StateMarketBalance(context.Context, address.Address, types.TipSetKey) (api.MarketBalance, error) + StateWaitMsg(ctx context.Context, cid cid.Cid, confidence uint64, limit abi.ChainEpoch, allowReplaced bool) (*api.MsgLookup, error) +} + +// FundManager keeps track of funds in a set of addresses +type FundManager struct { + ctx context.Context + shutdown context.CancelFunc + api fundManagerAPI + str *Store + + lk sync.Mutex + fundedAddrs map[address.Address]*fundedAddress +} + +func NewFundManager(lc fx.Lifecycle, api FundManagerAPI, ds dtypes.MetadataDS) *FundManager { + fm := newFundManager(&api, ds) + lc.Append(fx.Hook{ + OnStart: func(ctx context.Context) error { + return fm.Start() + }, + OnStop: func(ctx context.Context) error { + fm.Stop() + return nil + }, + }) + return fm +} + +// newFundManager is used by the tests +func newFundManager(api fundManagerAPI, ds datastore.Batching) *FundManager { + ctx, cancel := context.WithCancel(context.Background()) + return &FundManager{ + ctx: ctx, + shutdown: cancel, + api: api, + str: newStore(ds), + fundedAddrs: make(map[address.Address]*fundedAddress), + } +} + +func (fm *FundManager) Stop() { + fm.shutdown() +} + +func (fm *FundManager) Start() error { + fm.lk.Lock() + defer fm.lk.Unlock() + + // TODO: + // To save memory: + // - in State() only load addresses with in-progress messages + // - load the others just-in-time from getFundedAddress + // - delete(fm.fundedAddrs, addr) when the queue has been processed + return fm.str.forEach(fm.ctx, func(state *FundedAddressState) { + fa := newFundedAddress(fm, state.Addr) + fa.state = state + fm.fundedAddrs[fa.state.Addr] = fa + fa.start() + }) +} + +// Creates a fundedAddress if it doesn't already exist, and returns it +func (fm *FundManager) getFundedAddress(addr address.Address) *fundedAddress { + fm.lk.Lock() + defer fm.lk.Unlock() + + fa, ok := fm.fundedAddrs[addr] + if !ok { + fa = newFundedAddress(fm, addr) + fm.fundedAddrs[addr] = fa + } + return fa +} + +// Reserve adds amt to `reserved`. If there are not enough available funds for +// the address, submits a message on chain to top up available funds. +// Returns the cid of the message that was submitted on chain, or cid.Undef if +// the required funds were already available. +func (fm *FundManager) Reserve(ctx context.Context, wallet, addr address.Address, amt abi.TokenAmount) (cid.Cid, error) { + return fm.getFundedAddress(addr).reserve(ctx, wallet, amt) +} + +// Subtract from `reserved`. +func (fm *FundManager) Release(addr address.Address, amt abi.TokenAmount) error { + return fm.getFundedAddress(addr).release(amt) +} + +// Withdraw unreserved funds. Only succeeds if there are enough unreserved +// funds for the address. +// Returns the cid of the message that was submitted on chain. +func (fm *FundManager) Withdraw(ctx context.Context, wallet, addr address.Address, amt abi.TokenAmount) (cid.Cid, error) { + return fm.getFundedAddress(addr).withdraw(ctx, wallet, amt) +} + +// GetReserved returns the amount that is currently reserved for the address +func (fm *FundManager) GetReserved(addr address.Address) abi.TokenAmount { + return fm.getFundedAddress(addr).getReserved() +} + +// FundedAddressState keeps track of the state of an address with funds in the +// datastore +type FundedAddressState struct { + Addr address.Address + // AmtReserved is the amount that must be kept in the address (cannot be + // withdrawn) + AmtReserved abi.TokenAmount + // MsgCid is the cid of an in-progress on-chain message + MsgCid *cid.Cid +} + +// fundedAddress keeps track of the state and request queues for a +// particular address +type fundedAddress struct { + ctx context.Context + env *fundManagerEnvironment + str *Store + + lk sync.RWMutex + state *FundedAddressState + + // Note: These request queues are ephemeral, they are not saved to store + reservations []*fundRequest + releases []*fundRequest + withdrawals []*fundRequest + + // Used by the tests + onProcessStartListener func() bool +} + +func newFundedAddress(fm *FundManager, addr address.Address) *fundedAddress { + return &fundedAddress{ + ctx: fm.ctx, + env: &fundManagerEnvironment{api: fm.api}, + str: fm.str, + state: &FundedAddressState{ + Addr: addr, + AmtReserved: abi.NewTokenAmount(0), + }, + } +} + +// If there is an in-progress on-chain message, don't submit any more messages +// on chain until it completes +func (a *fundedAddress) start() { + a.lk.Lock() + defer a.lk.Unlock() + + if a.state.MsgCid != nil { + a.debugf("restart: wait for %s", a.state.MsgCid) + a.startWaitForResults(*a.state.MsgCid) + } +} + +func (a *fundedAddress) getReserved() abi.TokenAmount { + a.lk.RLock() + defer a.lk.RUnlock() + + return a.state.AmtReserved +} + +func (a *fundedAddress) reserve(ctx context.Context, wallet address.Address, amt abi.TokenAmount) (cid.Cid, error) { + return a.requestAndWait(ctx, wallet, amt, &a.reservations) +} + +func (a *fundedAddress) release(amt abi.TokenAmount) error { + _, err := a.requestAndWait(context.Background(), address.Undef, amt, &a.releases) + return err +} + +func (a *fundedAddress) withdraw(ctx context.Context, wallet address.Address, amt abi.TokenAmount) (cid.Cid, error) { + return a.requestAndWait(ctx, wallet, amt, &a.withdrawals) +} + +func (a *fundedAddress) requestAndWait(ctx context.Context, wallet address.Address, amt abi.TokenAmount, reqs *[]*fundRequest) (cid.Cid, error) { + // Create a request and add it to the request queue + req := newFundRequest(ctx, wallet, amt) + + a.lk.Lock() + *reqs = append(*reqs, req) + a.lk.Unlock() + + // Process the queue + go a.process() + + // Wait for the results + select { + case <-ctx.Done(): + return cid.Undef, ctx.Err() + case r := <-req.Result: + return r.msgCid, r.err + } +} + +// Used by the tests +func (a *fundedAddress) onProcessStart(fn func() bool) { + a.lk.Lock() + defer a.lk.Unlock() + + a.onProcessStartListener = fn +} + +// Process queued requests +func (a *fundedAddress) process() { + a.lk.Lock() + defer a.lk.Unlock() + + // Used by the tests + if a.onProcessStartListener != nil { + done := a.onProcessStartListener() + if !done { + return + } + a.onProcessStartListener = nil + } + + // Check if we're still waiting for the response to a message + if a.state.MsgCid != nil { + return + } + + // Check if there's anything to do + haveReservations := len(a.reservations) > 0 || len(a.releases) > 0 + haveWithdrawals := len(a.withdrawals) > 0 + if !haveReservations && !haveWithdrawals { + return + } + + // Process reservations / releases + if haveReservations { + res, err := a.processReservations(a.reservations, a.releases) + if err == nil { + a.applyStateChange(res.msgCid, res.amtReserved) + } + a.reservations = filterOutProcessedReqs(a.reservations) + a.releases = filterOutProcessedReqs(a.releases) + } + + // If there was no message sent on chain by adding reservations, and all + // reservations have completed processing, process withdrawals + if haveWithdrawals && a.state.MsgCid == nil && len(a.reservations) == 0 { + withdrawalCid, err := a.processWithdrawals(a.withdrawals) + if err == nil && withdrawalCid != cid.Undef { + a.applyStateChange(&withdrawalCid, types.EmptyInt) + } + a.withdrawals = filterOutProcessedReqs(a.withdrawals) + } + + // If a message was sent on-chain + if a.state.MsgCid != nil { + // Start waiting for results of message (async) + a.startWaitForResults(*a.state.MsgCid) + } + + // Process any remaining queued requests + go a.process() +} + +// Filter out completed requests +func filterOutProcessedReqs(reqs []*fundRequest) []*fundRequest { + filtered := make([]*fundRequest, 0, len(reqs)) + for _, req := range reqs { + if !req.Completed() { + filtered = append(filtered, req) + } + } + return filtered +} + +// Apply the results of processing queues and save to the datastore +func (a *fundedAddress) applyStateChange(msgCid *cid.Cid, amtReserved abi.TokenAmount) { + a.state.MsgCid = msgCid + if !amtReserved.Nil() { + a.state.AmtReserved = amtReserved + } + a.saveState() +} + +// Clear the pending message cid so that a new message can be sent +func (a *fundedAddress) clearWaitState() { + a.state.MsgCid = nil + a.saveState() +} + +// Save state to datastore +func (a *fundedAddress) saveState() { + // Not much we can do if saving to the datastore fails, just log + err := a.str.save(a.ctx, a.state) + if err != nil { + log.Errorf("saving state to store for addr %s: %v", a.state.Addr, err) + } +} + +// The result of processing the reservation / release queues +type processResult struct { + // Requests that completed without adding funds + covered []*fundRequest + // Requests that added funds + added []*fundRequest + + // The new reserved amount + amtReserved abi.TokenAmount + // The message cid, if a message was submitted on-chain + msgCid *cid.Cid +} + +// process reservations and releases, and return the resulting changes to state +func (a *fundedAddress) processReservations(reservations []*fundRequest, releases []*fundRequest) (pr *processResult, prerr error) { + // When the function returns + defer func() { + // If there's an error, mark all requests as errored + if prerr != nil { + for _, req := range append(reservations, releases...) { + req.Complete(cid.Undef, prerr) + } + return + } + + // Complete all release requests + for _, req := range releases { + req.Complete(cid.Undef, nil) + } + + // Complete all requests that were covered by released amounts + for _, req := range pr.covered { + req.Complete(cid.Undef, nil) + } + + // If a message was sent + if pr.msgCid != nil { + // Complete all add funds requests + for _, req := range pr.added { + req.Complete(*pr.msgCid, nil) + } + } + }() + + // Split reservations into those that are covered by released amounts, + // and those to add to the reserved amount. + // Note that we process requests from the same wallet in batches. So some + // requests may not be included in covered if they don't match the first + // covered request's wallet. These will be processed on a subsequent + // invocation of processReservations. + toCancel, toAdd, reservedDelta := splitReservations(reservations, releases) + + // Apply the reserved delta to the reserved amount + reserved := types.BigAdd(a.state.AmtReserved, reservedDelta) + if reserved.LessThan(abi.NewTokenAmount(0)) { + reserved = abi.NewTokenAmount(0) + } + res := &processResult{ + amtReserved: reserved, + covered: toCancel, + } + + // Work out the amount to add to the balance + amtToAdd := abi.NewTokenAmount(0) + if len(toAdd) > 0 && reserved.GreaterThan(abi.NewTokenAmount(0)) { + // Get available funds for address + avail, err := a.env.AvailableFunds(a.ctx, a.state.Addr) + if err != nil { + return res, err + } + + // amount to add = new reserved amount - available + amtToAdd = types.BigSub(reserved, avail) + a.debugf("reserved %d - avail %d = to add %d", reserved, avail, amtToAdd) + } + + // If there's nothing to add to the balance, bail out + if amtToAdd.LessThanEqual(abi.NewTokenAmount(0)) { + res.covered = append(res.covered, toAdd...) + return res, nil + } + + // Add funds to address + a.debugf("add funds %d", amtToAdd) + addFundsCid, err := a.env.AddFunds(a.ctx, toAdd[0].Wallet, a.state.Addr, amtToAdd) + if err != nil { + return res, err + } + + // Mark reservation requests as complete + res.added = toAdd + + // Save the message CID to state + res.msgCid = &addFundsCid + return res, nil +} + +// Split reservations into those that are under the total release amount +// (covered) and those that exceed it (to add). +// Note that we process requests from the same wallet in batches. So some +// requests may not be included in covered if they don't match the first +// covered request's wallet. +func splitReservations(reservations []*fundRequest, releases []*fundRequest) ([]*fundRequest, []*fundRequest, abi.TokenAmount) { + toCancel := make([]*fundRequest, 0, len(reservations)) + toAdd := make([]*fundRequest, 0, len(reservations)) + toAddAmt := abi.NewTokenAmount(0) + + // Sum release amounts + releaseAmt := abi.NewTokenAmount(0) + for _, req := range releases { + releaseAmt = types.BigAdd(releaseAmt, req.Amount()) + } + + // We only want to combine requests that come from the same wallet + batchWallet := address.Undef + for _, req := range reservations { + amt := req.Amount() + + // If the amount to add to the reserve is cancelled out by a release + if amt.LessThanEqual(releaseAmt) { + // Cancel the request and update the release total + releaseAmt = types.BigSub(releaseAmt, amt) + toCancel = append(toCancel, req) + continue + } + + // The amount to add is greater that the release total so we want + // to send an add funds request + + // The first time the wallet will be undefined + if batchWallet == address.Undef { + batchWallet = req.Wallet + } + // If this request's wallet is the same as the batch wallet, + // the requests will be combined + if batchWallet == req.Wallet { + delta := types.BigSub(amt, releaseAmt) + toAddAmt = types.BigAdd(toAddAmt, delta) + releaseAmt = abi.NewTokenAmount(0) + toAdd = append(toAdd, req) + } + } + + // The change in the reserved amount is "amount to add" - "amount to release" + reservedDelta := types.BigSub(toAddAmt, releaseAmt) + + return toCancel, toAdd, reservedDelta +} + +// process withdrawal queue +func (a *fundedAddress) processWithdrawals(withdrawals []*fundRequest) (msgCid cid.Cid, prerr error) { + // If there's an error, mark all withdrawal requests as errored + defer func() { + if prerr != nil { + for _, req := range withdrawals { + req.Complete(cid.Undef, prerr) + } + } + }() + + // Get the net available balance + avail, err := a.env.AvailableFunds(a.ctx, a.state.Addr) + if err != nil { + return cid.Undef, err + } + + netAvail := types.BigSub(avail, a.state.AmtReserved) + + // Fit as many withdrawals as possible into the available balance, and fail + // the rest + withdrawalAmt := abi.NewTokenAmount(0) + allowedAmt := abi.NewTokenAmount(0) + allowed := make([]*fundRequest, 0, len(withdrawals)) + var batchWallet address.Address + for _, req := range withdrawals { + amt := req.Amount() + if amt.IsZero() { + // If the context for the request was cancelled, bail out + req.Complete(cid.Undef, err) + continue + } + + // If the amount would exceed the available amount, complete the + // request with an error + newWithdrawalAmt := types.BigAdd(withdrawalAmt, amt) + if newWithdrawalAmt.GreaterThan(netAvail) { + msg := fmt.Sprintf("insufficient funds for withdrawal of %s: ", types.FIL(amt)) + msg += fmt.Sprintf("net available (%s) = available (%s) - reserved (%s)", + types.FIL(types.BigSub(netAvail, withdrawalAmt)), types.FIL(avail), types.FIL(a.state.AmtReserved)) + if !withdrawalAmt.IsZero() { + msg += fmt.Sprintf(" - queued withdrawals (%s)", types.FIL(withdrawalAmt)) + } + err := xerrors.Errorf(msg) + a.debugf("%s", err) + req.Complete(cid.Undef, err) + continue + } + + // If this is the first allowed withdrawal request in this batch, save + // its wallet address + if batchWallet == address.Undef { + batchWallet = req.Wallet + } + // If the request wallet doesn't match the batch wallet, bail out + // (the withdrawal will be processed after the current batch has + // completed) + if req.Wallet != batchWallet { + continue + } + + // Include this withdrawal request in the batch + withdrawalAmt = newWithdrawalAmt + a.debugf("withdraw %d", amt) + allowed = append(allowed, req) + allowedAmt = types.BigAdd(allowedAmt, amt) + } + + // Check if there is anything to withdraw. + // Note that if the context for a request is cancelled, + // req.Amount() returns zero + if allowedAmt.Equals(abi.NewTokenAmount(0)) { + // Mark allowed requests as complete + for _, req := range allowed { + req.Complete(cid.Undef, nil) + } + return cid.Undef, nil + } + + // Withdraw funds + a.debugf("withdraw funds %d", allowedAmt) + withdrawFundsCid, err := a.env.WithdrawFunds(a.ctx, allowed[0].Wallet, a.state.Addr, allowedAmt) + if err != nil { + return cid.Undef, err + } + + // Mark allowed requests as complete + for _, req := range allowed { + req.Complete(withdrawFundsCid, nil) + } + + // Save the message CID to state + return withdrawFundsCid, nil +} + +// asynchonously wait for results of message +func (a *fundedAddress) startWaitForResults(msgCid cid.Cid) { + go func() { + err := a.env.WaitMsg(a.ctx, msgCid) + if err != nil { + // We don't really care about the results here, we're just waiting + // so as to only process one on-chain message at a time + log.Errorf("waiting for results of message %s for addr %s: %v", msgCid, a.state.Addr, err) + } + + a.lk.Lock() + a.debugf("complete wait") + a.clearWaitState() + a.lk.Unlock() + + a.process() + }() +} + +func (a *fundedAddress) debugf(args ...interface{}) { + fmtStr := args[0].(string) + args = args[1:] + log.Debugf(a.state.Addr.String()+": "+fmtStr, args...) +} + +// The result of a fund request +type reqResult struct { + msgCid cid.Cid + err error +} + +// A request to change funds +type fundRequest struct { + ctx context.Context + amt abi.TokenAmount + completed chan struct{} + Wallet address.Address + Result chan reqResult +} + +func newFundRequest(ctx context.Context, wallet address.Address, amt abi.TokenAmount) *fundRequest { + return &fundRequest{ + ctx: ctx, + amt: amt, + Wallet: wallet, + Result: make(chan reqResult), + completed: make(chan struct{}), + } +} + +// Amount returns zero if the context has expired +func (frp *fundRequest) Amount() abi.TokenAmount { + if frp.ctx.Err() != nil { + return abi.NewTokenAmount(0) + } + return frp.amt +} + +// Complete is called with the message CID when the funds request has been +// started or with the error if there was an error +func (frp *fundRequest) Complete(msgCid cid.Cid, err error) { + select { + case <-frp.completed: + case <-frp.ctx.Done(): + case frp.Result <- reqResult{msgCid: msgCid, err: err}: + } + close(frp.completed) +} + +// Completed indicates if Complete has already been called +func (frp *fundRequest) Completed() bool { + select { + case <-frp.completed: + return true + default: + return false + } +} + +// fundManagerEnvironment simplifies some API calls +type fundManagerEnvironment struct { + api fundManagerAPI +} + +func (env *fundManagerEnvironment) AvailableFunds(ctx context.Context, addr address.Address) (abi.TokenAmount, error) { + bal, err := env.api.StateMarketBalance(ctx, addr, types.EmptyTSK) + if err != nil { + return abi.NewTokenAmount(0), err + } + + return types.BigSub(bal.Escrow, bal.Locked), nil +} + +func (env *fundManagerEnvironment) AddFunds( + ctx context.Context, + wallet address.Address, + addr address.Address, + amt abi.TokenAmount, +) (cid.Cid, error) { + params, err := actors.SerializeParams(&addr) + if err != nil { + return cid.Undef, err + } + + smsg, aerr := env.api.MpoolPushMessage(ctx, &types.Message{ + To: builtin.StorageMarketActorAddr, + From: wallet, + Value: amt, + Method: builtin.MethodsMarket.AddBalance, + Params: params, + }, nil) + + if aerr != nil { + return cid.Undef, aerr + } + + return smsg.Cid(), nil +} + +func (env *fundManagerEnvironment) WithdrawFunds( + ctx context.Context, + wallet address.Address, + addr address.Address, + amt abi.TokenAmount, +) (cid.Cid, error) { + params, err := actors.SerializeParams(&market.WithdrawBalanceParams{ + ProviderOrClientAddress: addr, + Amount: amt, + }) + if err != nil { + return cid.Undef, xerrors.Errorf("serializing params: %w", err) + } + + smsg, aerr := env.api.MpoolPushMessage(ctx, &types.Message{ + To: builtin.StorageMarketActorAddr, + From: wallet, + Value: types.NewInt(0), + Method: builtin.MethodsMarket.WithdrawBalance, + Params: params, + }, nil) + + if aerr != nil { + return cid.Undef, aerr + } + + return smsg.Cid(), nil +} + +func (env *fundManagerEnvironment) WaitMsg(ctx context.Context, c cid.Cid) error { + _, err := env.api.StateWaitMsg(ctx, c, build.MessageConfidence, api.LookbackNoLimit, true) + return err +} diff --git a/chain/market/fundmanager_test.go b/chain/market/fundmanager_test.go new file mode 100644 index 000000000..d79afbc51 --- /dev/null +++ b/chain/market/fundmanager_test.go @@ -0,0 +1,831 @@ +// stm: #unit +package market + +import ( + "bytes" + "context" + "sync" + "testing" + "time" + + "github.com/ipfs/go-cid" + ds "github.com/ipfs/go-datastore" + ds_sync "github.com/ipfs/go-datastore/sync" + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + markettypes "github.com/filecoin-project/go-state-types/builtin/v9/market" + tutils "github.com/filecoin-project/specs-actors/v2/support/testing" + + "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/chain/wallet" +) + +// TestFundManagerBasic verifies that the basic fund manager operations work +func TestFundManagerBasic(t *testing.T) { + //stm: @MARKET_RESERVE_FUNDS_001, @MARKET_RELEASE_FUNDS_001, @MARKET_WITHDRAW_FUNDS_001 + s := setup(t) + defer s.fm.Stop() + + // Reserve 10 + // balance: 0 -> 10 + // reserved: 0 -> 10 + amt := abi.NewTokenAmount(10) + sentinel, err := s.fm.Reserve(s.ctx, s.walletAddr, s.acctAddr, amt) + require.NoError(t, err) + + msg := s.mockApi.getSentMessage(sentinel) + checkAddMessageFields(t, msg, s.walletAddr, s.acctAddr, amt) + + s.mockApi.completeMsg(sentinel) + + // Reserve 7 + // balance: 10 -> 17 + // reserved: 10 -> 17 + amt = abi.NewTokenAmount(7) + sentinel, err = s.fm.Reserve(s.ctx, s.walletAddr, s.acctAddr, amt) + require.NoError(t, err) + + msg = s.mockApi.getSentMessage(sentinel) + checkAddMessageFields(t, msg, s.walletAddr, s.acctAddr, amt) + + s.mockApi.completeMsg(sentinel) + + // Release 5 + // balance: 17 + // reserved: 17 -> 12 + amt = abi.NewTokenAmount(5) + err = s.fm.Release(s.acctAddr, amt) + require.NoError(t, err) + + // Withdraw 2 + // balance: 17 -> 15 + // reserved: 12 + amt = abi.NewTokenAmount(2) + sentinel, err = s.fm.Withdraw(s.ctx, s.walletAddr, s.acctAddr, amt) + require.NoError(t, err) + + msg = s.mockApi.getSentMessage(sentinel) + checkWithdrawMessageFields(t, msg, s.walletAddr, s.acctAddr, amt) + + s.mockApi.completeMsg(sentinel) + + // Reserve 3 + // balance: 15 + // reserved: 12 -> 15 + // Note: reserved (15) is <= balance (15) so should not send on-chain + // message + msgCount := s.mockApi.messageCount() + amt = abi.NewTokenAmount(3) + sentinel, err = s.fm.Reserve(s.ctx, s.walletAddr, s.acctAddr, amt) + require.NoError(t, err) + require.Equal(t, msgCount, s.mockApi.messageCount()) + require.Equal(t, sentinel, cid.Undef) + + // Reserve 1 + // balance: 15 -> 16 + // reserved: 15 -> 16 + // Note: reserved (16) is above balance (15) so *should* send on-chain + // message to top up balance + amt = abi.NewTokenAmount(1) + topUp := abi.NewTokenAmount(1) + sentinel, err = s.fm.Reserve(s.ctx, s.walletAddr, s.acctAddr, amt) + require.NoError(t, err) + + s.mockApi.completeMsg(sentinel) + msg = s.mockApi.getSentMessage(sentinel) + checkAddMessageFields(t, msg, s.walletAddr, s.acctAddr, topUp) + + // Withdraw 1 + // balance: 16 + // reserved: 16 + // Note: Expect failure because there is no available balance to withdraw: + // balance - reserved = 16 - 16 = 0 + amt = abi.NewTokenAmount(1) + _, err = s.fm.Withdraw(s.ctx, s.walletAddr, s.acctAddr, amt) + require.Error(t, err) +} + +// TestFundManagerParallel verifies that operations can be run in parallel +func TestFundManagerParallel(t *testing.T) { + //stm: @MARKET_RESERVE_FUNDS_001, @MARKET_RELEASE_FUNDS_001, @MARKET_WITHDRAW_FUNDS_001 + s := setup(t) + defer s.fm.Stop() + + // Reserve 10 + amt := abi.NewTokenAmount(10) + sentinelReserve10, err := s.fm.Reserve(s.ctx, s.walletAddr, s.acctAddr, amt) + require.NoError(t, err) + + // Wait until all the subsequent requests are queued up + queueReady := make(chan struct{}) + fa := s.fm.getFundedAddress(s.acctAddr) + fa.onProcessStart(func() bool { + if len(fa.withdrawals) == 1 && len(fa.reservations) == 2 && len(fa.releases) == 1 { + close(queueReady) + return true + } + return false + }) + + // Withdraw 5 (should not run until after reserves / releases) + withdrawReady := make(chan error) + go func() { + amt = abi.NewTokenAmount(5) + _, err := s.fm.Withdraw(s.ctx, s.walletAddr, s.acctAddr, amt) + withdrawReady <- err + }() + + reserveSentinels := make(chan cid.Cid) + + // Reserve 3 + go func() { + amt := abi.NewTokenAmount(3) + sentinelReserve3, err := s.fm.Reserve(s.ctx, s.walletAddr, s.acctAddr, amt) + require.NoError(t, err) + reserveSentinels <- sentinelReserve3 + }() + + // Reserve 5 + go func() { + amt := abi.NewTokenAmount(5) + sentinelReserve5, err := s.fm.Reserve(s.ctx, s.walletAddr, s.acctAddr, amt) + require.NoError(t, err) + reserveSentinels <- sentinelReserve5 + }() + + // Release 2 + go func() { + amt := abi.NewTokenAmount(2) + err = s.fm.Release(s.acctAddr, amt) + require.NoError(t, err) + }() + + // Everything is queued up + <-queueReady + + // Complete the "Reserve 10" message + s.mockApi.completeMsg(sentinelReserve10) + msg := s.mockApi.getSentMessage(sentinelReserve10) + checkAddMessageFields(t, msg, s.walletAddr, s.acctAddr, abi.NewTokenAmount(10)) + + // The other requests should now be combined and be submitted on-chain as + // a single message + rs1 := <-reserveSentinels + rs2 := <-reserveSentinels + require.Equal(t, rs1, rs2) + + // Withdraw should not have been called yet, because reserve / release + // requests run first + select { + case <-withdrawReady: + require.Fail(t, "Withdraw should run after reserve / release") + default: + } + + // Complete the message + s.mockApi.completeMsg(rs1) + msg = s.mockApi.getSentMessage(rs1) + + // "Reserve 3" +3 + // "Reserve 5" +5 + // "Release 2" -2 + // Result: 6 + checkAddMessageFields(t, msg, s.walletAddr, s.acctAddr, abi.NewTokenAmount(6)) + + // Expect withdraw to fail because not enough available funds + err = <-withdrawReady + require.Error(t, err) +} + +// TestFundManagerReserveByWallet verifies that reserve requests are grouped by wallet +func TestFundManagerReserveByWallet(t *testing.T) { + //stm: @MARKET_RESERVE_FUNDS_001 + s := setup(t) + defer s.fm.Stop() + + walletAddrA, err := s.wllt.WalletNew(context.Background(), types.KTSecp256k1) + require.NoError(t, err) + walletAddrB, err := s.wllt.WalletNew(context.Background(), types.KTSecp256k1) + require.NoError(t, err) + + // Wait until all the reservation requests are queued up + walletAQueuedUp := make(chan struct{}) + queueReady := make(chan struct{}) + fa := s.fm.getFundedAddress(s.acctAddr) + fa.onProcessStart(func() bool { + if len(fa.reservations) == 1 { + close(walletAQueuedUp) + } + if len(fa.reservations) == 3 { + close(queueReady) + return true + } + return false + }) + + type reserveResult struct { + ws cid.Cid + err error + } + results := make(chan *reserveResult) + + amtA1 := abi.NewTokenAmount(1) + go func() { + // Wallet A: Reserve 1 + sentinelA1, err := s.fm.Reserve(s.ctx, walletAddrA, s.acctAddr, amtA1) + results <- &reserveResult{ + ws: sentinelA1, + err: err, + } + }() + + amtB1 := abi.NewTokenAmount(2) + amtB2 := abi.NewTokenAmount(3) + go func() { + // Wait for reservation for wallet A to be queued up + <-walletAQueuedUp + + // Wallet B: Reserve 2 + go func() { + sentinelB1, err := s.fm.Reserve(s.ctx, walletAddrB, s.acctAddr, amtB1) + results <- &reserveResult{ + ws: sentinelB1, + err: err, + } + }() + + // Wallet B: Reserve 3 + sentinelB2, err := s.fm.Reserve(s.ctx, walletAddrB, s.acctAddr, amtB2) + results <- &reserveResult{ + ws: sentinelB2, + err: err, + } + }() + + // All reservation requests are queued up + <-queueReady + + resA := <-results + sentinelA1 := resA.ws + + // Should send to wallet A + msg := s.mockApi.getSentMessage(sentinelA1) + checkAddMessageFields(t, msg, walletAddrA, s.acctAddr, amtA1) + + // Complete wallet A message + s.mockApi.completeMsg(sentinelA1) + + resB1 := <-results + resB2 := <-results + require.NoError(t, resB1.err) + require.NoError(t, resB2.err) + sentinelB1 := resB1.ws + sentinelB2 := resB2.ws + + // Should send different message to wallet B + require.NotEqual(t, sentinelA1, sentinelB1) + // Should be single message combining amount 1 and 2 + require.Equal(t, sentinelB1, sentinelB2) + msg = s.mockApi.getSentMessage(sentinelB1) + checkAddMessageFields(t, msg, walletAddrB, s.acctAddr, types.BigAdd(amtB1, amtB2)) +} + +// TestFundManagerWithdrawal verifies that as many withdraw operations as +// possible are processed +func TestFundManagerWithdrawalLimit(t *testing.T) { + //stm: @MARKET_RESERVE_FUNDS_001, @MARKET_RELEASE_FUNDS_001, @MARKET_WITHDRAW_FUNDS_001 + s := setup(t) + defer s.fm.Stop() + + // Reserve 10 + amt := abi.NewTokenAmount(10) + sentinelReserve10, err := s.fm.Reserve(s.ctx, s.walletAddr, s.acctAddr, amt) + require.NoError(t, err) + + // Complete the "Reserve 10" message + s.mockApi.completeMsg(sentinelReserve10) + + // Release 10 + err = s.fm.Release(s.acctAddr, amt) + require.NoError(t, err) + + // Queue up withdraw requests + queueReady := make(chan struct{}) + fa := s.fm.getFundedAddress(s.acctAddr) + withdrawalReqTotal := 3 + withdrawalReqEnqueued := 0 + withdrawalReqQueue := make(chan func(), withdrawalReqTotal) + fa.onProcessStart(func() bool { + // If a new withdrawal request was enqueued + if len(fa.withdrawals) > withdrawalReqEnqueued { + withdrawalReqEnqueued++ + + // Pop the next request and run it + select { + case fn := <-withdrawalReqQueue: + go fn() + default: + } + } + // Once all the requests have arrived, we're ready to process the queue + if withdrawalReqEnqueued == withdrawalReqTotal { + close(queueReady) + return true + } + return false + }) + + type withdrawResult struct { + reqIndex int + ws cid.Cid + err error + } + withdrawRes := make(chan *withdrawResult) + + // Queue up three "Withdraw 5" requests + enqueuedCount := 0 + for i := 0; i < withdrawalReqTotal; i++ { + withdrawalReqQueue <- func() { + idx := enqueuedCount + enqueuedCount++ + + amt := abi.NewTokenAmount(5) + ws, err := s.fm.Withdraw(s.ctx, s.walletAddr, s.acctAddr, amt) + withdrawRes <- &withdrawResult{reqIndex: idx, ws: ws, err: err} + } + } + // Start the first request + fn := <-withdrawalReqQueue + go fn() + + // All withdrawal requests are queued up and ready to be processed + <-queueReady + + // Organize results in request order + results := make([]*withdrawResult, withdrawalReqTotal) + for i := 0; i < 3; i++ { + res := <-withdrawRes + results[res.reqIndex] = res + } + + // Available 10 + // Withdraw 5 + // Expect Success + require.NoError(t, results[0].err) + // Available 5 + // Withdraw 5 + // Expect Success + require.NoError(t, results[1].err) + // Available 0 + // Withdraw 5 + // Expect FAIL + require.Error(t, results[2].err) + + // Expect withdrawal requests that fit under reserved amount to be combined + // into a single message on-chain + require.Equal(t, results[0].ws, results[1].ws) +} + +// TestFundManagerWithdrawByWallet verifies that withdraw requests are grouped by wallet +func TestFundManagerWithdrawByWallet(t *testing.T) { + //stm: @MARKET_RESERVE_FUNDS_001, @MARKET_RELEASE_FUNDS_001, @MARKET_WITHDRAW_FUNDS_001 + s := setup(t) + defer s.fm.Stop() + + walletAddrA, err := s.wllt.WalletNew(context.Background(), types.KTSecp256k1) + require.NoError(t, err) + walletAddrB, err := s.wllt.WalletNew(context.Background(), types.KTSecp256k1) + require.NoError(t, err) + + // Reserve 10 + reserveAmt := abi.NewTokenAmount(10) + sentinelReserve, err := s.fm.Reserve(s.ctx, s.walletAddr, s.acctAddr, reserveAmt) + require.NoError(t, err) + s.mockApi.completeMsg(sentinelReserve) + + time.Sleep(10 * time.Millisecond) + + // Release 10 + err = s.fm.Release(s.acctAddr, reserveAmt) + require.NoError(t, err) + + type withdrawResult struct { + ws cid.Cid + err error + } + results := make(chan *withdrawResult) + + // Wait until withdrawals are queued up + walletAQueuedUp := make(chan struct{}) + queueReady := make(chan struct{}) + withdrawalCount := 0 + fa := s.fm.getFundedAddress(s.acctAddr) + fa.onProcessStart(func() bool { + if len(fa.withdrawals) == withdrawalCount { + return false + } + withdrawalCount = len(fa.withdrawals) + + if withdrawalCount == 1 { + close(walletAQueuedUp) + } else if withdrawalCount == 3 { + close(queueReady) + return true + } + return false + }) + + amtA1 := abi.NewTokenAmount(1) + go func() { + // Wallet A: Withdraw 1 + sentinelA1, err := s.fm.Withdraw(s.ctx, walletAddrA, s.acctAddr, amtA1) + results <- &withdrawResult{ + ws: sentinelA1, + err: err, + } + }() + + amtB1 := abi.NewTokenAmount(2) + amtB2 := abi.NewTokenAmount(3) + go func() { + // Wait until withdraw for wallet A is queued up + <-walletAQueuedUp + + // Wallet B: Withdraw 2 + go func() { + sentinelB1, err := s.fm.Withdraw(s.ctx, walletAddrB, s.acctAddr, amtB1) + results <- &withdrawResult{ + ws: sentinelB1, + err: err, + } + }() + + // Wallet B: Withdraw 3 + sentinelB2, err := s.fm.Withdraw(s.ctx, walletAddrB, s.acctAddr, amtB2) + results <- &withdrawResult{ + ws: sentinelB2, + err: err, + } + }() + + // Withdrawals are queued up + <-queueReady + + // Should withdraw from wallet A first + resA1 := <-results + sentinelA1 := resA1.ws + msg := s.mockApi.getSentMessage(sentinelA1) + checkWithdrawMessageFields(t, msg, walletAddrA, s.acctAddr, amtA1) + + // Complete wallet A message + s.mockApi.completeMsg(sentinelA1) + + resB1 := <-results + resB2 := <-results + require.NoError(t, resB1.err) + require.NoError(t, resB2.err) + sentinelB1 := resB1.ws + sentinelB2 := resB2.ws + + // Should send different message for wallet B from wallet A + require.NotEqual(t, sentinelA1, sentinelB1) + // Should be single message combining amount 1 and 2 + require.Equal(t, sentinelB1, sentinelB2) + msg = s.mockApi.getSentMessage(sentinelB1) + checkWithdrawMessageFields(t, msg, walletAddrB, s.acctAddr, types.BigAdd(amtB1, amtB2)) +} + +// TestFundManagerRestart verifies that waiting for incomplete requests resumes +// on restart +func TestFundManagerRestart(t *testing.T) { + //stm: @MARKET_RESERVE_FUNDS_001 + s := setup(t) + defer s.fm.Stop() + + acctAddr2 := tutils.NewActorAddr(t, "addr2") + + // Address 1: Reserve 10 + amt := abi.NewTokenAmount(10) + sentinelAddr1, err := s.fm.Reserve(s.ctx, s.walletAddr, s.acctAddr, amt) + require.NoError(t, err) + + msg := s.mockApi.getSentMessage(sentinelAddr1) + checkAddMessageFields(t, msg, s.walletAddr, s.acctAddr, amt) + + // Address 2: Reserve 7 + amt2 := abi.NewTokenAmount(7) + sentinelAddr2Res7, err := s.fm.Reserve(s.ctx, s.walletAddr, acctAddr2, amt2) + require.NoError(t, err) + + msg2 := s.mockApi.getSentMessage(sentinelAddr2Res7) + checkAddMessageFields(t, msg2, s.walletAddr, acctAddr2, amt2) + + // Complete "Address 1: Reserve 10" + s.mockApi.completeMsg(sentinelAddr1) + + // Give the completed state a moment to be stored before restart + time.Sleep(time.Millisecond * 10) + + // Restart + mockApiAfter := s.mockApi + fmAfter := newFundManager(mockApiAfter, s.ds) + err = fmAfter.Start() + require.NoError(t, err) + + amt3 := abi.NewTokenAmount(9) + reserveSentinel := make(chan cid.Cid) + go func() { + // Address 2: Reserve 9 + sentinel3, err := fmAfter.Reserve(s.ctx, s.walletAddr, acctAddr2, amt3) + require.NoError(t, err) + reserveSentinel <- sentinel3 + }() + + // Expect no message to be sent, because still waiting for previous + // message "Address 2: Reserve 7" to complete on-chain + select { + case <-reserveSentinel: + require.Fail(t, "Expected no message to be sent") + case <-time.After(10 * time.Millisecond): + } + + // Complete "Address 2: Reserve 7" + mockApiAfter.completeMsg(sentinelAddr2Res7) + + // Expect waiting message to now be sent + sentinel3 := <-reserveSentinel + msg3 := mockApiAfter.getSentMessage(sentinel3) + checkAddMessageFields(t, msg3, s.walletAddr, acctAddr2, amt3) +} + +// TestFundManagerReleaseAfterPublish verifies that release is successful in +// the following scenario: +// 1. Deal A adds 5 to addr1: reserved 0 -> 5 available 0 -> 5 +// 2. Deal B adds 7 to addr1: reserved 5 -> 12 available 5 -> 12 +// 3. Deal B completes, reducing addr1 by 7: reserved 12 available 12 -> 5 +// 4. Deal A releases 5 from addr1: reserved 12 -> 7 available 5 +func TestFundManagerReleaseAfterPublish(t *testing.T) { + //stm: @MARKET_RESERVE_FUNDS_001, @MARKET_RELEASE_FUNDS_001 + s := setup(t) + defer s.fm.Stop() + + // Deal A: Reserve 5 + // balance: 0 -> 5 + // reserved: 0 -> 5 + amt := abi.NewTokenAmount(5) + sentinel, err := s.fm.Reserve(s.ctx, s.walletAddr, s.acctAddr, amt) + require.NoError(t, err) + s.mockApi.completeMsg(sentinel) + + // Deal B: Reserve 7 + // balance: 5 -> 12 + // reserved: 5 -> 12 + amt = abi.NewTokenAmount(7) + sentinel, err = s.fm.Reserve(s.ctx, s.walletAddr, s.acctAddr, amt) + require.NoError(t, err) + s.mockApi.completeMsg(sentinel) + + // Deal B: Publish (removes Deal B amount from balance) + // balance: 12 -> 5 + // reserved: 12 + amt = abi.NewTokenAmount(7) + s.mockApi.publish(s.acctAddr, amt) + + // Deal A: Release 5 + // balance: 5 + // reserved: 12 -> 7 + amt = abi.NewTokenAmount(5) + err = s.fm.Release(s.acctAddr, amt) + require.NoError(t, err) + + // Deal B: Release 7 + // balance: 5 + // reserved: 12 -> 7 + amt = abi.NewTokenAmount(5) + err = s.fm.Release(s.acctAddr, amt) + require.NoError(t, err) +} + +type scaffold struct { + ctx context.Context + ds *ds_sync.MutexDatastore + wllt *wallet.LocalWallet + walletAddr address.Address + acctAddr address.Address + mockApi *mockFundManagerAPI + fm *FundManager +} + +func setup(t *testing.T) *scaffold { + ctx := context.Background() + + wllt, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + walletAddr, err := wllt.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + acctAddr := tutils.NewActorAddr(t, "addr") + + mockApi := newMockFundManagerAPI(walletAddr) + dstore := ds_sync.MutexWrap(ds.NewMapDatastore()) + fm := newFundManager(mockApi, dstore) + return &scaffold{ + ctx: ctx, + ds: dstore, + wllt: wllt, + walletAddr: walletAddr, + acctAddr: acctAddr, + mockApi: mockApi, + fm: fm, + } +} + +func checkAddMessageFields(t *testing.T, msg *types.Message, from address.Address, to address.Address, amt abi.TokenAmount) { + require.Equal(t, from, msg.From) + require.Equal(t, market.Address, msg.To) + require.Equal(t, amt, msg.Value) + + var paramsTo address.Address + err := paramsTo.UnmarshalCBOR(bytes.NewReader(msg.Params)) + require.NoError(t, err) + require.Equal(t, to, paramsTo) +} + +func checkWithdrawMessageFields(t *testing.T, msg *types.Message, from address.Address, addr address.Address, amt abi.TokenAmount) { + require.Equal(t, from, msg.From) + require.Equal(t, market.Address, msg.To) + require.Equal(t, abi.NewTokenAmount(0), msg.Value) + + var params markettypes.WithdrawBalanceParams + err := params.UnmarshalCBOR(bytes.NewReader(msg.Params)) + require.NoError(t, err) + require.Equal(t, addr, params.ProviderOrClientAddress) + require.Equal(t, amt, params.Amount) +} + +type sentMsg struct { + msg *types.SignedMessage + ready chan struct{} +} + +type mockFundManagerAPI struct { + wallet address.Address + + lk sync.Mutex + escrow map[address.Address]abi.TokenAmount + sentMsgs map[cid.Cid]*sentMsg + completedMsgs map[cid.Cid]struct{} + waitingFor map[cid.Cid]chan struct{} +} + +func newMockFundManagerAPI(wallet address.Address) *mockFundManagerAPI { + return &mockFundManagerAPI{ + wallet: wallet, + escrow: make(map[address.Address]abi.TokenAmount), + sentMsgs: make(map[cid.Cid]*sentMsg), + completedMsgs: make(map[cid.Cid]struct{}), + waitingFor: make(map[cid.Cid]chan struct{}), + } +} + +func (mapi *mockFundManagerAPI) MpoolPushMessage(ctx context.Context, message *types.Message, spec *api.MessageSendSpec) (*types.SignedMessage, error) { + mapi.lk.Lock() + defer mapi.lk.Unlock() + + smsg := &types.SignedMessage{Message: *message} + mapi.sentMsgs[smsg.Cid()] = &sentMsg{msg: smsg, ready: make(chan struct{})} + + return smsg, nil +} + +func (mapi *mockFundManagerAPI) getSentMessage(c cid.Cid) *types.Message { + mapi.lk.Lock() + defer mapi.lk.Unlock() + + for i := 0; i < 1000; i++ { + if pending, ok := mapi.sentMsgs[c]; ok { + return &pending.msg.Message + } + time.Sleep(time.Millisecond) + } + panic("expected message to be sent") +} + +func (mapi *mockFundManagerAPI) messageCount() int { + mapi.lk.Lock() + defer mapi.lk.Unlock() + + return len(mapi.sentMsgs) +} + +func (mapi *mockFundManagerAPI) completeMsg(msgCid cid.Cid) { + mapi.lk.Lock() + + pmsg, ok := mapi.sentMsgs[msgCid] + if ok { + if pmsg.msg.Message.Method == market.Methods.AddBalance { + var escrowAcct address.Address + err := escrowAcct.UnmarshalCBOR(bytes.NewReader(pmsg.msg.Message.Params)) + if err != nil { + panic(err) + } + + escrow := mapi.getEscrow(escrowAcct) + before := escrow + escrow = types.BigAdd(escrow, pmsg.msg.Message.Value) + mapi.escrow[escrowAcct] = escrow + log.Debugf("%s: escrow %d -> %d", escrowAcct, before, escrow) + } else { + var params markettypes.WithdrawBalanceParams + err := params.UnmarshalCBOR(bytes.NewReader(pmsg.msg.Message.Params)) + if err != nil { + panic(err) + } + escrowAcct := params.ProviderOrClientAddress + + escrow := mapi.getEscrow(escrowAcct) + before := escrow + escrow = types.BigSub(escrow, params.Amount) + mapi.escrow[escrowAcct] = escrow + log.Debugf("%s: escrow %d -> %d", escrowAcct, before, escrow) + } + } + + mapi.completedMsgs[msgCid] = struct{}{} + + ready, ok := mapi.waitingFor[msgCid] + + mapi.lk.Unlock() + + if ok { + close(ready) + } +} + +func (mapi *mockFundManagerAPI) StateMarketBalance(ctx context.Context, a address.Address, key types.TipSetKey) (api.MarketBalance, error) { + mapi.lk.Lock() + defer mapi.lk.Unlock() + + return api.MarketBalance{ + Locked: abi.NewTokenAmount(0), + Escrow: mapi.getEscrow(a), + }, nil +} + +func (mapi *mockFundManagerAPI) getEscrow(a address.Address) abi.TokenAmount { + escrow := mapi.escrow[a] + if escrow.Nil() { + return abi.NewTokenAmount(0) + } + return escrow +} + +func (mapi *mockFundManagerAPI) publish(addr address.Address, amt abi.TokenAmount) { + mapi.lk.Lock() + defer mapi.lk.Unlock() + + escrow := mapi.escrow[addr] + if escrow.Nil() { + return + } + escrow = types.BigSub(escrow, amt) + if escrow.LessThan(abi.NewTokenAmount(0)) { + escrow = abi.NewTokenAmount(0) + } + mapi.escrow[addr] = escrow +} + +func (mapi *mockFundManagerAPI) StateWaitMsg(ctx context.Context, c cid.Cid, confidence uint64, limit abi.ChainEpoch, allowReplaced bool) (*api.MsgLookup, error) { + res := &api.MsgLookup{ + Message: c, + Receipt: types.MessageReceipt{ + ExitCode: 0, + Return: nil, + }, + } + ready := make(chan struct{}) + + mapi.lk.Lock() + _, ok := mapi.completedMsgs[c] + if !ok { + mapi.waitingFor[c] = ready + } + mapi.lk.Unlock() + + if !ok { + select { + case <-ctx.Done(): + case <-ready: + } + } + return res, nil +} diff --git a/chain/market/fundmgr.go b/chain/market/fundmgr.go deleted file mode 100644 index 42ad50b2b..000000000 --- a/chain/market/fundmgr.go +++ /dev/null @@ -1,82 +0,0 @@ -package market - -import ( - "context" - "sync" - - "github.com/filecoin-project/specs-actors/actors/builtin" - "github.com/ipfs/go-cid" - logging "github.com/ipfs/go-log" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/lotus/chain/actors" - "github.com/filecoin-project/lotus/chain/stmgr" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/node/impl/full" -) - -var log = logging.Logger("market_adapter") - -type FundMgr struct { - sm *stmgr.StateManager - mpool full.MpoolAPI - - lk sync.Mutex - available map[address.Address]types.BigInt -} - -func NewFundMgr(sm *stmgr.StateManager, mpool full.MpoolAPI) *FundMgr { - return &FundMgr{ - sm: sm, - mpool: mpool, - - available: map[address.Address]types.BigInt{}, - } -} - -func (fm *FundMgr) EnsureAvailable(ctx context.Context, addr, wallet address.Address, amt types.BigInt) (cid.Cid, error) { - fm.lk.Lock() - avail, ok := fm.available[addr] - if !ok { - bal, err := fm.sm.MarketBalance(ctx, addr, nil) - if err != nil { - fm.lk.Unlock() - return cid.Undef, err - } - - avail = types.BigSub(bal.Escrow, bal.Locked) - } - - toAdd := types.NewInt(0) - avail = types.BigSub(avail, amt) - if avail.LessThan(types.NewInt(0)) { - // TODO: some rules around adding more to avoid doing stuff on-chain - // all the time - toAdd = types.BigSub(toAdd, avail) - avail = types.NewInt(0) - } - fm.available[addr] = avail - - fm.lk.Unlock() - - var err error - params, err := actors.SerializeParams(&addr) - if err != nil { - return cid.Undef, err - } - - smsg, err := fm.mpool.MpoolPushMessage(ctx, &types.Message{ - To: builtin.StorageMarketActorAddr, - From: wallet, - Value: toAdd, - GasPrice: types.NewInt(0), - GasLimit: 1000000, - Method: builtin.MethodsMarket.AddBalance, - Params: params, - }) - if err != nil { - return cid.Undef, err - } - - return smsg.Cid(), nil -} diff --git a/chain/market/store.go b/chain/market/store.go new file mode 100644 index 000000000..ece1248f6 --- /dev/null +++ b/chain/market/store.go @@ -0,0 +1,91 @@ +package market + +import ( + "bytes" + "context" + + "github.com/ipfs/go-datastore" + "github.com/ipfs/go-datastore/namespace" + dsq "github.com/ipfs/go-datastore/query" + + "github.com/filecoin-project/go-address" + cborrpc "github.com/filecoin-project/go-cbor-util" + + "github.com/filecoin-project/lotus/node/modules/dtypes" +) + +const dsKeyAddr = "Addr" + +type Store struct { + ds datastore.Batching +} + +func newStore(ds dtypes.MetadataDS) *Store { + ds = namespace.Wrap(ds, datastore.NewKey("/fundmgr/")) + return &Store{ + ds: ds, + } +} + +// save the state to the datastore +func (ps *Store) save(ctx context.Context, state *FundedAddressState) error { + k := dskeyForAddr(state.Addr) + + b, err := cborrpc.Dump(state) + if err != nil { + return err + } + + return ps.ds.Put(ctx, k, b) +} + +// get the state for the given address +func (ps *Store) get(ctx context.Context, addr address.Address) (*FundedAddressState, error) { + k := dskeyForAddr(addr) + + data, err := ps.ds.Get(ctx, k) + if err != nil { + return nil, err + } + + var state FundedAddressState + err = cborrpc.ReadCborRPC(bytes.NewReader(data), &state) + if err != nil { + return nil, err + } + return &state, nil +} + +// forEach calls iter with each address in the datastore +func (ps *Store) forEach(ctx context.Context, iter func(*FundedAddressState)) error { + res, err := ps.ds.Query(ctx, dsq.Query{Prefix: dsKeyAddr}) + if err != nil { + return err + } + defer res.Close() //nolint:errcheck + + for { + res, ok := res.NextSync() + if !ok { + break + } + + if res.Error != nil { + return err + } + + var stored FundedAddressState + if err := stored.UnmarshalCBOR(bytes.NewReader(res.Value)); err != nil { + return err + } + + iter(&stored) + } + + return nil +} + +// The datastore key used to identify the address state +func dskeyForAddr(addr address.Address) datastore.Key { + return datastore.KeyWithNamespaces([]string{dsKeyAddr, addr.String()}) +} diff --git a/chain/messagepool/block_proba.go b/chain/messagepool/block_proba.go new file mode 100644 index 000000000..61bb018d7 --- /dev/null +++ b/chain/messagepool/block_proba.go @@ -0,0 +1,102 @@ +package messagepool + +import ( + "math" + "sync" +) + +var noWinnersProbCache []float64 +var noWinnersProbOnce sync.Once + +func noWinnersProb() []float64 { + noWinnersProbOnce.Do(func() { + poissPdf := func(x float64) float64 { + const Mu = 5 + lg, _ := math.Lgamma(x + 1) + result := math.Exp((math.Log(Mu) * x) - lg - Mu) + return result + } + + out := make([]float64, 0, MaxBlocks) + for i := 0; i < MaxBlocks; i++ { + out = append(out, poissPdf(float64(i))) + } + noWinnersProbCache = out + }) + return noWinnersProbCache +} + +var noWinnersProbAssumingCache []float64 +var noWinnersProbAssumingOnce sync.Once + +func noWinnersProbAssumingMoreThanOne() []float64 { + noWinnersProbAssumingOnce.Do(func() { + cond := math.Log(-1 + math.Exp(5)) + poissPdf := func(x float64) float64 { + const Mu = 5 + lg, _ := math.Lgamma(x + 1) + result := math.Exp((math.Log(Mu) * x) - lg - cond) + return result + } + + out := make([]float64, 0, MaxBlocks) + for i := 0; i < MaxBlocks; i++ { + out = append(out, poissPdf(float64(i+1))) + } + noWinnersProbAssumingCache = out + }) + return noWinnersProbAssumingCache +} + +func binomialCoefficient(n, k float64) float64 { + if k > n { + return math.NaN() + } + r := 1.0 + for d := 1.0; d <= k; d++ { + r *= n + r /= d + n-- + } + return r +} + +func (mp *MessagePool) blockProbabilities(tq float64) []float64 { + noWinners := noWinnersProbAssumingMoreThanOne() + + p := 1 - tq + binoPdf := func(x, trials float64) float64 { + // based on https://github.com/atgjack/prob + if x > trials { + return 0 + } + if p == 0 { + if x == 0 { + return 1.0 + } + return 0.0 + } + if p == 1 { + if x == trials { + return 1.0 + } + return 0.0 + } + coef := binomialCoefficient(trials, x) + pow := math.Pow(p, x) * math.Pow(1-p, trials-x) + if math.IsInf(coef, 0) { + return 0 + } + return coef * pow + } + + out := make([]float64, 0, MaxBlocks) + for place := 0; place < MaxBlocks; place++ { + var pPlace float64 + for otherWinners, pCase := range noWinners { + pPlace += pCase * binoPdf(float64(place), float64(otherWinners)) + } + out = append(out, pPlace) + } + return out +} diff --git a/chain/messagepool/block_proba_test.go b/chain/messagepool/block_proba_test.go new file mode 100644 index 000000000..6d121d222 --- /dev/null +++ b/chain/messagepool/block_proba_test.go @@ -0,0 +1,46 @@ +// stm: #unit +package messagepool + +import ( + "math" + "math/rand" + "testing" + "time" +) + +func TestBlockProbability(t *testing.T) { + //stm: @OTHER_IMPLEMENTATION_BLOCK_PROB_001 + mp := &MessagePool{} + bp := mp.blockProbabilities(1 - 0.15) + t.Logf("%+v\n", bp) + for i := 0; i < len(bp)-1; i++ { + if bp[i] < bp[i+1] { + t.Fatalf("expected decreasing block probabilities for this quality: %d %f %f", + i, bp[i], bp[i+1]) + } + } +} + +func TestWinnerProba(t *testing.T) { + //stm: @OTHER_IMPLEMENTATION_BLOCK_PROB_002 + rand.Seed(time.Now().UnixNano()) + const N = 1000000 + winnerProba := noWinnersProb() + sum := 0 + for i := 0; i < N; i++ { + minersRand := rand.Float64() + j := 0 + for ; j < MaxBlocks; j++ { + minersRand -= winnerProba[j] + if minersRand < 0 { + break + } + } + sum += j + } + + if avg := float64(sum) / N; math.Abs(avg-5) > 0.01 { + t.Fatalf("avg too far off: %f", avg) + } + +} diff --git a/chain/messagepool/check.go b/chain/messagepool/check.go new file mode 100644 index 000000000..fdec910c4 --- /dev/null +++ b/chain/messagepool/check.go @@ -0,0 +1,450 @@ +package messagepool + +import ( + "context" + "fmt" + stdbig "math/big" + "sort" + + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/big" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/vm" +) + +var baseFeeUpperBoundFactor = types.NewInt(10) + +// CheckMessages performs a set of logic checks for a list of messages, prior to submitting it to the mpool +func (mp *MessagePool) CheckMessages(ctx context.Context, protos []*api.MessagePrototype) ([][]api.MessageCheckStatus, error) { + flex := make([]bool, len(protos)) + msgs := make([]*types.Message, len(protos)) + for i, p := range protos { + flex[i] = !p.ValidNonce + msgs[i] = &p.Message + } + return mp.checkMessages(ctx, msgs, false, flex) +} + +// CheckPendingMessages performs a set of logical sets for all messages pending from a given actor +func (mp *MessagePool) CheckPendingMessages(ctx context.Context, from address.Address) ([][]api.MessageCheckStatus, error) { + var msgs []*types.Message + mp.lk.RLock() + mset, ok, err := mp.getPendingMset(ctx, from) + if err != nil { + mp.lk.RUnlock() + return nil, xerrors.Errorf("errored while getting pending mset: %w", err) + } + if ok { + msgs = make([]*types.Message, 0, len(mset.msgs)) + for _, sm := range mset.msgs { + msgs = append(msgs, &sm.Message) + } + } + mp.lk.RUnlock() + + if len(msgs) == 0 { + return nil, nil + } + + sort.Slice(msgs, func(i, j int) bool { + return msgs[i].Nonce < msgs[j].Nonce + }) + + return mp.checkMessages(ctx, msgs, true, nil) +} + +// CheckReplaceMessages performs a set of logical checks for related messages while performing a +// replacement. +func (mp *MessagePool) CheckReplaceMessages(ctx context.Context, replace []*types.Message) ([][]api.MessageCheckStatus, error) { + msgMap := make(map[address.Address]map[uint64]*types.Message) + count := 0 + + mp.lk.RLock() + for _, m := range replace { + mmap, ok := msgMap[m.From] + if !ok { + mmap = make(map[uint64]*types.Message) + msgMap[m.From] = mmap + mset, ok, err := mp.getPendingMset(ctx, m.From) + if err != nil { + mp.lk.RUnlock() + return nil, xerrors.Errorf("errored while getting pending mset: %w", err) + } + if ok { + count += len(mset.msgs) + for _, sm := range mset.msgs { + mmap[sm.Message.Nonce] = &sm.Message + } + } else { + count++ + } + } + mmap[m.Nonce] = m + } + mp.lk.RUnlock() + + msgs := make([]*types.Message, 0, count) + start := 0 + for _, mmap := range msgMap { + end := start + len(mmap) + + for _, m := range mmap { + msgs = append(msgs, m) + } + + sort.Slice(msgs[start:end], func(i, j int) bool { + return msgs[start+i].Nonce < msgs[start+j].Nonce + }) + + start = end + } + + return mp.checkMessages(ctx, msgs, true, nil) +} + +// flexibleNonces should be either nil or of len(msgs), it signifies that message at given index +// has non-determied nonce at this point +func (mp *MessagePool) checkMessages(ctx context.Context, msgs []*types.Message, interned bool, flexibleNonces []bool) (result [][]api.MessageCheckStatus, err error) { + if mp.api.IsLite() { + return nil, nil + } + mp.curTsLk.RLock() + curTs := mp.curTs + mp.curTsLk.RUnlock() + + epoch := curTs.Height() + 1 + + var baseFee big.Int + if len(curTs.Blocks()) > 0 { + baseFee = curTs.Blocks()[0].ParentBaseFee + } else { + baseFee, err = mp.api.ChainComputeBaseFee(context.Background(), curTs) + if err != nil { + return nil, xerrors.Errorf("error computing basefee: %w", err) + } + } + + baseFeeLowerBound := getBaseFeeLowerBound(baseFee, baseFeeLowerBoundFactor) + baseFeeUpperBound := types.BigMul(baseFee, baseFeeUpperBoundFactor) + + type actorState struct { + nextNonce uint64 + requiredFunds *stdbig.Int + } + + state := make(map[address.Address]*actorState) + balances := make(map[address.Address]big.Int) + + result = make([][]api.MessageCheckStatus, len(msgs)) + + for i, m := range msgs { + // pre-check: actor nonce + check := api.MessageCheckStatus{ + Cid: m.Cid(), + CheckStatus: api.CheckStatus{ + Code: api.CheckStatusMessageGetStateNonce, + }, + } + + st, ok := state[m.From] + if !ok { + mp.lk.RLock() + mset, ok, err := mp.getPendingMset(ctx, m.From) + if err != nil { + mp.lk.RUnlock() + return nil, xerrors.Errorf("errored while getting pending mset: %w", err) + } + if ok && !interned { + st = &actorState{nextNonce: mset.nextNonce, requiredFunds: mset.requiredFunds} + for _, m := range mset.msgs { + st.requiredFunds = new(stdbig.Int).Add(st.requiredFunds, m.Message.Value.Int) + } + state[m.From] = st + mp.lk.RUnlock() + + check.OK = true + check.Hint = map[string]interface{}{ + "nonce": st.nextNonce, + } + } else { + mp.lk.RUnlock() + + stateNonce, err := mp.getStateNonce(ctx, m.From, curTs) + if err != nil { + check.OK = false + check.Err = fmt.Sprintf("error retrieving state nonce: %s", err.Error()) + } else { + check.OK = true + check.Hint = map[string]interface{}{ + "nonce": stateNonce, + } + } + + st = &actorState{nextNonce: stateNonce, requiredFunds: new(stdbig.Int)} + state[m.From] = st + } + } else { + check.OK = true + } + + result[i] = append(result[i], check) + if !check.OK { + continue + } + + // pre-check: actor balance + check = api.MessageCheckStatus{ + Cid: m.Cid(), + CheckStatus: api.CheckStatus{ + Code: api.CheckStatusMessageGetStateBalance, + }, + } + + balance, ok := balances[m.From] + if !ok { + balance, err = mp.getStateBalance(ctx, m.From, curTs) + if err != nil { + check.OK = false + check.Err = fmt.Sprintf("error retrieving state balance: %s", err) + } else { + check.OK = true + check.Hint = map[string]interface{}{ + "balance": balance, + } + } + + balances[m.From] = balance + } else { + check.OK = true + check.Hint = map[string]interface{}{ + "balance": balance, + } + } + + result[i] = append(result[i], check) + if !check.OK { + continue + } + + // 1. Serialization + check = api.MessageCheckStatus{ + Cid: m.Cid(), + CheckStatus: api.CheckStatus{ + Code: api.CheckStatusMessageSerialize, + }, + } + + bytes, err := m.Serialize() + if err != nil { + check.OK = false + check.Err = err.Error() + } else { + check.OK = true + } + + result[i] = append(result[i], check) + + // 2. Message size + check = api.MessageCheckStatus{ + Cid: m.Cid(), + CheckStatus: api.CheckStatus{ + Code: api.CheckStatusMessageSize, + }, + } + + if len(bytes) > MaxMessageSize-128 { // 128 bytes to account for signature size + check.OK = false + check.Err = "message too big" + } else { + check.OK = true + } + + result[i] = append(result[i], check) + + // 3. Syntactic validation + check = api.MessageCheckStatus{ + Cid: m.Cid(), + CheckStatus: api.CheckStatus{ + Code: api.CheckStatusMessageValidity, + }, + } + nv, err := mp.getNtwkVersion(epoch) + if err != nil { + check.OK = false + check.Err = fmt.Sprintf("error retrieving network version: %s", err.Error()) + } else { + check.OK = true + } + if err := m.ValidForBlockInclusion(0, nv); err != nil { + check.OK = false + check.Err = fmt.Sprintf("syntactically invalid message: %s", err.Error()) + } else { + check.OK = true + } + + result[i] = append(result[i], check) + if !check.OK { + // skip remaining checks if it is a syntatically invalid message + continue + } + + // gas checks + + // 4. Min Gas + minGas := vm.PricelistByEpoch(epoch).OnChainMessage(m.ChainLength()) + + check = api.MessageCheckStatus{ + Cid: m.Cid(), + CheckStatus: api.CheckStatus{ + Code: api.CheckStatusMessageMinGas, + Hint: map[string]interface{}{ + "minGas": minGas, + }, + }, + } + + if m.GasLimit < minGas.Total() { + check.OK = false + check.Err = "GasLimit less than epoch minimum gas" + } else { + check.OK = true + } + + result[i] = append(result[i], check) + + // 5. Min Base Fee + check = api.MessageCheckStatus{ + Cid: m.Cid(), + CheckStatus: api.CheckStatus{ + Code: api.CheckStatusMessageMinBaseFee, + }, + } + + if m.GasFeeCap.LessThan(minimumBaseFee) { + check.OK = false + check.Err = "GasFeeCap less than minimum base fee" + } else { + check.OK = true + } + + result[i] = append(result[i], check) + if !check.OK { + goto checkState + } + + // 6. Base Fee + check = api.MessageCheckStatus{ + Cid: m.Cid(), + CheckStatus: api.CheckStatus{ + Code: api.CheckStatusMessageBaseFee, + Hint: map[string]interface{}{ + "baseFee": baseFee, + }, + }, + } + + if m.GasFeeCap.LessThan(baseFee) { + check.OK = false + check.Err = "GasFeeCap less than current base fee" + } else { + check.OK = true + } + + result[i] = append(result[i], check) + + // 7. Base Fee lower bound + check = api.MessageCheckStatus{ + Cid: m.Cid(), + CheckStatus: api.CheckStatus{ + Code: api.CheckStatusMessageBaseFeeLowerBound, + Hint: map[string]interface{}{ + "baseFeeLowerBound": baseFeeLowerBound, + "baseFee": baseFee, + }, + }, + } + + if m.GasFeeCap.LessThan(baseFeeLowerBound) { + check.OK = false + check.Err = "GasFeeCap less than base fee lower bound for inclusion in next 20 epochs" + } else { + check.OK = true + } + + result[i] = append(result[i], check) + + // 8. Base Fee upper bound + check = api.MessageCheckStatus{ + Cid: m.Cid(), + CheckStatus: api.CheckStatus{ + Code: api.CheckStatusMessageBaseFeeUpperBound, + Hint: map[string]interface{}{ + "baseFeeUpperBound": baseFeeUpperBound, + "baseFee": baseFee, + }, + }, + } + + if m.GasFeeCap.LessThan(baseFeeUpperBound) { + check.OK = true // on purpose, the checks is more of a warning + check.Err = "GasFeeCap less than base fee upper bound for inclusion in next 20 epochs" + } else { + check.OK = true + } + + result[i] = append(result[i], check) + + // stateful checks + checkState: + // 9. Message Nonce + check = api.MessageCheckStatus{ + Cid: m.Cid(), + CheckStatus: api.CheckStatus{ + Code: api.CheckStatusMessageNonce, + Hint: map[string]interface{}{ + "nextNonce": st.nextNonce, + }, + }, + } + + if (flexibleNonces == nil || !flexibleNonces[i]) && st.nextNonce != m.Nonce { + check.OK = false + check.Err = fmt.Sprintf("message nonce doesn't match next nonce (%d)", st.nextNonce) + } else { + check.OK = true + st.nextNonce++ + } + + result[i] = append(result[i], check) + + // check required funds -vs- balance + st.requiredFunds = new(stdbig.Int).Add(st.requiredFunds, m.RequiredFunds().Int) + st.requiredFunds.Add(st.requiredFunds, m.Value.Int) + + // 10. Balance + check = api.MessageCheckStatus{ + Cid: m.Cid(), + CheckStatus: api.CheckStatus{ + Code: api.CheckStatusMessageBalance, + Hint: map[string]interface{}{ + "requiredFunds": big.Int{Int: stdbig.NewInt(0).Set(st.requiredFunds)}, + }, + }, + } + + if balance.Int.Cmp(st.requiredFunds) < 0 { + check.OK = false + check.Err = "insufficient balance" + } else { + check.OK = true + } + + result[i] = append(result[i], check) + } + + return result, nil +} diff --git a/chain/messagepool/check_test.go b/chain/messagepool/check_test.go new file mode 100644 index 000000000..8458bdb0f --- /dev/null +++ b/chain/messagepool/check_test.go @@ -0,0 +1,224 @@ +// stm: #unit +package messagepool + +import ( + "context" + "fmt" + "testing" + + "github.com/ipfs/go-datastore" + logging "github.com/ipfs/go-log/v2" + "github.com/stretchr/testify/assert" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/consensus/filcns" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/mock" + "github.com/filecoin-project/lotus/chain/wallet" + _ "github.com/filecoin-project/lotus/lib/sigs/bls" + _ "github.com/filecoin-project/lotus/lib/sigs/secp" +) + +func init() { + _ = logging.SetLogLevel("*", "INFO") +} + +func getCheckMessageStatus(statusCode api.CheckStatusCode, msgStatuses []api.MessageCheckStatus) (*api.MessageCheckStatus, error) { + for i := 0; i < len(msgStatuses); i++ { + iMsgStatuses := msgStatuses[i] + if iMsgStatuses.CheckStatus.Code == statusCode { + return &iMsgStatuses, nil + } + } + return nil, fmt.Errorf("Could not find CheckStatusCode %s", statusCode) +} + +func TestCheckMessages(t *testing.T) { + //stm: @CHAIN_MEMPOOL_CHECK_MESSAGES_001 + tma := newTestMpoolAPI() + + w, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + ds := datastore.NewMapDatastore() + + mp, err := New(context.Background(), tma, ds, filcns.DefaultUpgradeSchedule(), "mptest", nil) + if err != nil { + t.Fatal(err) + } + + sender, err := w.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + tma.setBalance(sender, 1000e15) + target := mock.Address(1001) + + var protos []*api.MessagePrototype + for i := 0; i < 5; i++ { + msg := &types.Message{ + To: target, + From: sender, + Value: types.NewInt(1), + Nonce: uint64(i), + GasLimit: 50000000, + GasFeeCap: types.NewInt(minimumBaseFee.Uint64()), + GasPremium: types.NewInt(1), + Params: make([]byte, 2<<10), + } + proto := &api.MessagePrototype{ + Message: *msg, + ValidNonce: true, + } + protos = append(protos, proto) + } + + messageStatuses, err := mp.CheckMessages(context.TODO(), protos) + assert.NoError(t, err) + for i := 0; i < len(messageStatuses); i++ { + iMsgStatuses := messageStatuses[i] + for j := 0; j < len(iMsgStatuses); j++ { + jStatus := iMsgStatuses[i] + assert.True(t, jStatus.OK) + } + } +} + +func TestCheckPendingMessages(t *testing.T) { + //stm: @CHAIN_MEMPOOL_CHECK_PENDING_MESSAGES_001 + tma := newTestMpoolAPI() + + w, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + ds := datastore.NewMapDatastore() + + mp, err := New(context.Background(), tma, ds, filcns.DefaultUpgradeSchedule(), "mptest", nil) + if err != nil { + t.Fatal(err) + } + + sender, err := w.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + tma.setBalance(sender, 1000e15) + target := mock.Address(1001) + + // add a valid message to the pool + msg := &types.Message{ + To: target, + From: sender, + Value: types.NewInt(1), + Nonce: 0, + GasLimit: 50000000, + GasFeeCap: types.NewInt(minimumBaseFee.Uint64()), + GasPremium: types.NewInt(1), + Params: make([]byte, 2<<10), + } + + sig, err := w.WalletSign(context.TODO(), sender, msg.Cid().Bytes(), api.MsgMeta{}) + if err != nil { + panic(err) + } + sm := &types.SignedMessage{ + Message: *msg, + Signature: *sig, + } + mustAdd(t, mp, sm) + + messageStatuses, err := mp.CheckPendingMessages(context.TODO(), sender) + assert.NoError(t, err) + for i := 0; i < len(messageStatuses); i++ { + iMsgStatuses := messageStatuses[i] + for j := 0; j < len(iMsgStatuses); j++ { + jStatus := iMsgStatuses[i] + assert.True(t, jStatus.OK) + } + } +} + +func TestCheckReplaceMessages(t *testing.T) { + //stm: @CHAIN_MEMPOOL_CHECK_REPLACE_MESSAGES_001 + tma := newTestMpoolAPI() + + w, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + ds := datastore.NewMapDatastore() + + mp, err := New(context.Background(), tma, ds, filcns.DefaultUpgradeSchedule(), "mptest", nil) + if err != nil { + t.Fatal(err) + } + + sender, err := w.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + tma.setBalance(sender, 1000e15) + target := mock.Address(1001) + + // add a valid message to the pool + msg := &types.Message{ + To: target, + From: sender, + Value: types.NewInt(1), + Nonce: 0, + GasLimit: 50000000, + GasFeeCap: types.NewInt(minimumBaseFee.Uint64()), + GasPremium: types.NewInt(1), + Params: make([]byte, 2<<10), + } + + sig, err := w.WalletSign(context.TODO(), sender, msg.Cid().Bytes(), api.MsgMeta{}) + if err != nil { + panic(err) + } + sm := &types.SignedMessage{ + Message: *msg, + Signature: *sig, + } + mustAdd(t, mp, sm) + + // create a new message with the same data, except that it is too big + var msgs []*types.Message + invalidmsg := &types.Message{ + To: target, + From: sender, + Value: types.NewInt(1), + Nonce: 0, + GasLimit: 50000000, + GasFeeCap: types.NewInt(minimumBaseFee.Uint64()), + GasPremium: types.NewInt(1), + Params: make([]byte, 128<<10), + } + msgs = append(msgs, invalidmsg) + + { + messageStatuses, err := mp.CheckReplaceMessages(context.TODO(), msgs) + if err != nil { + t.Fatal(err) + } + for i := 0; i < len(messageStatuses); i++ { + iMsgStatuses := messageStatuses[i] + + status, err := getCheckMessageStatus(api.CheckStatusMessageSize, iMsgStatuses) + if err != nil { + t.Fatal(err) + } + // the replacement message should cause a status error + assert.False(t, status.OK) + } + } + +} diff --git a/chain/messagepool/config.go b/chain/messagepool/config.go new file mode 100644 index 000000000..72b7d9567 --- /dev/null +++ b/chain/messagepool/config.go @@ -0,0 +1,102 @@ +package messagepool + +import ( + "context" + "encoding/json" + "fmt" + "time" + + "github.com/ipfs/go-datastore" + + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/node/modules/dtypes" +) + +var ( + ReplaceByFeePercentageMinimum types.Percent = 110 + ReplaceByFeePercentageDefault types.Percent = 125 +) + +var ( + MemPoolSizeLimitHiDefault = 30000 + MemPoolSizeLimitLoDefault = 20000 + PruneCooldownDefault = time.Minute + GasLimitOverestimation = 1.25 + + ConfigKey = datastore.NewKey("/mpool/config") +) + +func loadConfig(ctx context.Context, ds dtypes.MetadataDS) (*types.MpoolConfig, error) { + haveCfg, err := ds.Has(ctx, ConfigKey) + if err != nil { + return nil, err + } + + if !haveCfg { + return DefaultConfig(), nil + } + + cfgBytes, err := ds.Get(ctx, ConfigKey) + if err != nil { + return nil, err + } + cfg := new(types.MpoolConfig) + err = json.Unmarshal(cfgBytes, cfg) + return cfg, err +} + +func saveConfig(ctx context.Context, cfg *types.MpoolConfig, ds dtypes.MetadataDS) error { + cfgBytes, err := json.Marshal(cfg) + if err != nil { + return err + } + return ds.Put(ctx, ConfigKey, cfgBytes) +} + +func (mp *MessagePool) GetConfig() *types.MpoolConfig { + return mp.getConfig().Clone() +} + +func (mp *MessagePool) getConfig() *types.MpoolConfig { + mp.cfgLk.RLock() + defer mp.cfgLk.RUnlock() + return mp.cfg +} + +func validateConfg(cfg *types.MpoolConfig) error { + if cfg.ReplaceByFeeRatio < ReplaceByFeePercentageMinimum { + return fmt.Errorf("'ReplaceByFeeRatio' is less than required %s < %s", + cfg.ReplaceByFeeRatio, ReplaceByFeePercentageMinimum) + } + if cfg.GasLimitOverestimation < 1 { + return fmt.Errorf("'GasLimitOverestimation' cannot be less than 1") + } + return nil +} + +func (mp *MessagePool) SetConfig(ctx context.Context, cfg *types.MpoolConfig) error { + if err := validateConfg(cfg); err != nil { + return err + } + cfg = cfg.Clone() + + mp.cfgLk.Lock() + mp.cfg = cfg + err := saveConfig(ctx, cfg, mp.ds) + if err != nil { + log.Warnf("error persisting mpool config: %s", err) + } + mp.cfgLk.Unlock() + + return nil +} + +func DefaultConfig() *types.MpoolConfig { + return &types.MpoolConfig{ + SizeLimitHigh: MemPoolSizeLimitHiDefault, + SizeLimitLow: MemPoolSizeLimitLoDefault, + ReplaceByFeeRatio: ReplaceByFeePercentageDefault, + PruneCooldown: PruneCooldownDefault, + GasLimitOverestimation: GasLimitOverestimation, + } +} diff --git a/chain/messagepool/gasguess/guessgas.go b/chain/messagepool/gasguess/guessgas.go new file mode 100644 index 000000000..f502e84a6 --- /dev/null +++ b/chain/messagepool/gasguess/guessgas.go @@ -0,0 +1,96 @@ +package gasguess + +import ( + "context" + + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/types" +) + +type ActorLookup func(context.Context, address.Address, types.TipSetKey) (*types.Actor, error) + +const failedGasGuessRatio = 0.5 +const failedGasGuessMax = 25_000_000 + +const MinGas = 1298450 +const MaxGas = 1600271356 + +type CostKey struct { + Code cid.Cid + M abi.MethodNum +} + +var Costs = map[CostKey]int64{ + {builtin0.InitActorCodeID, 2}: 8916753, + {builtin0.StorageMarketActorCodeID, 2}: 6955002, + {builtin0.StorageMarketActorCodeID, 4}: 245436108, + {builtin0.StorageMinerActorCodeID, 4}: 2315133, + {builtin0.StorageMinerActorCodeID, 5}: 1600271356, + {builtin0.StorageMinerActorCodeID, 6}: 22864493, + {builtin0.StorageMinerActorCodeID, 7}: 142002419, + {builtin0.StorageMinerActorCodeID, 10}: 23008274, + {builtin0.StorageMinerActorCodeID, 11}: 19303178, + {builtin0.StorageMinerActorCodeID, 14}: 566356835, + {builtin0.StorageMinerActorCodeID, 16}: 5325185, + {builtin0.StorageMinerActorCodeID, 18}: 2328637, + {builtin0.StoragePowerActorCodeID, 2}: 23600956, + // TODO: Just reuse v0 values for now, this isn't actually used + {builtin2.InitActorCodeID, 2}: 8916753, + {builtin2.StorageMarketActorCodeID, 2}: 6955002, + {builtin2.StorageMarketActorCodeID, 4}: 245436108, + {builtin2.StorageMinerActorCodeID, 4}: 2315133, + {builtin2.StorageMinerActorCodeID, 5}: 1600271356, + {builtin2.StorageMinerActorCodeID, 6}: 22864493, + {builtin2.StorageMinerActorCodeID, 7}: 142002419, + {builtin2.StorageMinerActorCodeID, 10}: 23008274, + {builtin2.StorageMinerActorCodeID, 11}: 19303178, + {builtin2.StorageMinerActorCodeID, 14}: 566356835, + {builtin2.StorageMinerActorCodeID, 16}: 5325185, + {builtin2.StorageMinerActorCodeID, 18}: 2328637, + {builtin2.StoragePowerActorCodeID, 2}: 23600956, +} + +func failedGuess(msg *types.SignedMessage) int64 { + guess := int64(float64(msg.Message.GasLimit) * failedGasGuessRatio) + if guess > failedGasGuessMax { + guess = failedGasGuessMax + } + return guess +} + +func GuessGasUsed(ctx context.Context, tsk types.TipSetKey, msg *types.SignedMessage, al ActorLookup) (int64, error) { + // MethodSend is the same in all versions. + if msg.Message.Method == builtin.MethodSend { + switch msg.Message.From.Protocol() { + case address.BLS: + return 1298450, nil + case address.SECP256K1: + return 1385999, nil + default: + // who knows? + return 1298450, nil + } + } + + to, err := al(ctx, msg.Message.To, tsk) + if err != nil { + return failedGuess(msg), xerrors.Errorf("could not lookup actor: %w", err) + } + + guess, ok := Costs[CostKey{to.Code, msg.Message.Method}] + if !ok { + return failedGuess(msg), xerrors.Errorf("unknown code-method combo") + } + if guess > msg.Message.GasLimit { + guess = msg.Message.GasLimit + } + return guess, nil +} diff --git a/chain/messagepool/messagepool.go b/chain/messagepool/messagepool.go index 047f409c4..50f64f903 100644 --- a/chain/messagepool/messagepool.go +++ b/chain/messagepool/messagepool.go @@ -6,41 +6,62 @@ import ( "errors" "fmt" "math" + stdbig "math/big" "sort" "sync" "time" - "github.com/filecoin-project/specs-actors/actors/crypto" - lru "github.com/hashicorp/golang-lru" + "github.com/hashicorp/go-multierror" + lru "github.com/hashicorp/golang-lru/v2" "github.com/ipfs/go-cid" "github.com/ipfs/go-datastore" "github.com/ipfs/go-datastore/namespace" "github.com/ipfs/go-datastore/query" logging "github.com/ipfs/go-log/v2" pubsub "github.com/libp2p/go-libp2p-pubsub" - lps "github.com/whyrusleeping/pubsub" - "go.uber.org/multierr" + "github.com/minio/blake2b-simd" + "github.com/raulk/clock" "golang.org/x/xerrors" + ffi "github.com/filecoin-project/filecoin-ffi" "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/go-state-types/network" + lps "github.com/filecoin-project/pubsub" + "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/consensus" "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/lib/sigs" + "github.com/filecoin-project/lotus/chain/vm" + "github.com/filecoin-project/lotus/journal" + "github.com/filecoin-project/lotus/metrics" "github.com/filecoin-project/lotus/node/modules/dtypes" ) var log = logging.Logger("messagepool") -const futureDebug = false +var futureDebug = false -const ReplaceByFeeRatio = 1.25 +var rbfNumBig = types.NewInt(uint64(ReplaceByFeePercentageMinimum)) +var rbfDenomBig = types.NewInt(100) -var ( - rbfNum = types.NewInt(uint64((ReplaceByFeeRatio - 1) * 256)) - rbfDenom = types.NewInt(256) -) +var RepublishInterval = time.Duration(10*build.BlockDelaySecs+build.PropagationDelaySecs) * time.Second + +var minimumBaseFee = types.NewInt(uint64(build.MinimumBaseFee)) +var baseFeeLowerBoundFactor = types.NewInt(10) +var baseFeeLowerBoundFactorConservative = types.NewInt(100) + +var MaxActorPendingMessages = 1000 +var MaxUntrustedActorPendingMessages = 10 + +var MaxNonceGap = uint64(4) + +const MaxMessageSize = 64 << 10 // 64KiB var ( ErrMessageTooBig = errors.New("message too big") @@ -49,11 +70,17 @@ var ( ErrNonceTooLow = errors.New("message nonce too low") + ErrGasFeeCapTooLow = errors.New("gas fee cap too low") + ErrNotEnoughFunds = errors.New("not enough funds to execute transaction") ErrInvalidToAddr = errors.New("message had invalid to address") - ErrBroadcastAnyway = errors.New("broadcasting message despite validation fail") + ErrSoftValidationFailure = errors.New("validation failure") + ErrRBFTooLowPremium = errors.New("replace by fee has too low GasPremium") + ErrTooManyPendingMessages = errors.New("too many pending messages for actor") + ErrNonceGap = errors.New("unfulfilled nonce gap") + ErrExistingNonce = errors.New("message with nonce already exists") ) const ( @@ -62,26 +89,77 @@ const ( localUpdates = "update" ) +// Journal event types. +const ( + evtTypeMpoolAdd = iota + evtTypeMpoolRemove + evtTypeMpoolRepub +) + +// MessagePoolEvt is the journal entry for message pool events. +type MessagePoolEvt struct { + Action string + Messages []MessagePoolEvtMessage + Error error `json:",omitempty"` +} + +type MessagePoolEvtMessage struct { + types.Message + + CID cid.Cid +} + +func init() { + // if the republish interval is too short compared to the pubsub timecache, adjust it + minInterval := pubsub.TimeCacheDuration + time.Duration(build.PropagationDelaySecs)*time.Second + if RepublishInterval < minInterval { + RepublishInterval = minInterval + } +} + type MessagePool struct { - lk sync.Mutex + lk sync.RWMutex - closer chan struct{} - repubTk *time.Ticker + ds dtypes.MetadataDS + addSema chan struct{} + + closer chan struct{} + + repubTk *clock.Ticker + repubTrigger chan struct{} + + republished map[cid.Cid]struct{} + + // do NOT access this map directly, use isLocal, setLocal, and forEachLocal respectively localAddrs map[address.Address]struct{} + // do NOT access this map directly, use getPendingMset, setPendingMset, deletePendingMset, forEachPending, and clearPending respectively pending map[address.Address]*msgSet - curTsLk sync.Mutex // DO NOT LOCK INSIDE lk + keyCache *lru.Cache[address.Address, address.Address] + + curTsLk sync.RWMutex // DO NOT LOCK INSIDE lk curTs *types.TipSet + cfgLk sync.RWMutex + cfg *types.MpoolConfig + api Provider minGasPrice types.BigInt - maxTxPoolSize int + getNtwkVersion func(abi.ChainEpoch) (network.Version, error) - blsSigCache *lru.TwoQueueCache + currentSize int + + // pruneTrigger is a channel used to trigger a mempool pruning + pruneTrigger chan struct{} + + // pruneCooldown is a channel used to allow a cooldown time between prunes + pruneCooldown chan struct{} + + blsSigCache *lru.TwoQueueCache[cid.Cid, crypto.Signature] changes *lps.PubSub @@ -89,241 +167,571 @@ type MessagePool struct { netName dtypes.NetworkName - sigValCache *lru.TwoQueueCache + sigValCache *lru.TwoQueueCache[string, struct{}] + + stateNonceCache *lru.Cache[stateNonceCacheKey, uint64] + + evtTypes [3]journal.EventType + journal journal.Journal +} + +type stateNonceCacheKey struct { + tsk types.TipSetKey + addr address.Address } type msgSet struct { - msgs map[uint64]*types.SignedMessage - nextNonce uint64 + msgs map[uint64]*types.SignedMessage + nextNonce uint64 + requiredFunds *stdbig.Int } -func newMsgSet() *msgSet { +func newMsgSet(nonce uint64) *msgSet { return &msgSet{ - msgs: make(map[uint64]*types.SignedMessage), + msgs: make(map[uint64]*types.SignedMessage), + nextNonce: nonce, + requiredFunds: stdbig.NewInt(0), } } -func (ms *msgSet) add(m *types.SignedMessage) error { - if len(ms.msgs) == 0 || m.Message.Nonce >= ms.nextNonce { - ms.nextNonce = m.Message.Nonce + 1 +func ComputeMinRBF(curPrem abi.TokenAmount) abi.TokenAmount { + minPrice := types.BigDiv(types.BigMul(curPrem, rbfNumBig), rbfDenomBig) + return types.BigAdd(minPrice, types.NewInt(1)) +} + +func ComputeRBF(curPrem abi.TokenAmount, replaceByFeeRatio types.Percent) abi.TokenAmount { + rbfNumBig := types.NewInt(uint64(replaceByFeeRatio)) + minPrice := types.BigDiv(types.BigMul(curPrem, rbfNumBig), rbfDenomBig) + return types.BigAdd(minPrice, types.NewInt(1)) +} + +func CapGasFee(mff dtypes.DefaultMaxFeeFunc, msg *types.Message, sendSpec *api.MessageSendSpec) { + var maxFee abi.TokenAmount + if sendSpec != nil { + maxFee = sendSpec.MaxFee } + if maxFee.Int == nil || maxFee.Equals(big.Zero()) { + mf, err := mff() + if err != nil { + log.Errorf("failed to get default max gas fee: %+v", err) + mf = big.Zero() + } + maxFee = mf + } + + gl := types.NewInt(uint64(msg.GasLimit)) + totalFee := types.BigMul(msg.GasFeeCap, gl) + + if totalFee.LessThanEqual(maxFee) { + msg.GasPremium = big.Min(msg.GasFeeCap, msg.GasPremium) // cap premium at FeeCap + return + } + + msg.GasFeeCap = big.Div(maxFee, gl) + msg.GasPremium = big.Min(msg.GasFeeCap, msg.GasPremium) // cap premium at FeeCap +} + +func (ms *msgSet) add(m *types.SignedMessage, mp *MessagePool, strict, untrusted bool) (bool, error) { + nextNonce := ms.nextNonce + nonceGap := false + + maxNonceGap := MaxNonceGap + maxActorPendingMessages := MaxActorPendingMessages + if untrusted { + maxNonceGap = 0 + maxActorPendingMessages = MaxUntrustedActorPendingMessages + } + + switch { + case m.Message.Nonce == nextNonce: + nextNonce++ + // advance if we are filling a gap + for _, fillGap := ms.msgs[nextNonce]; fillGap; _, fillGap = ms.msgs[nextNonce] { + nextNonce++ + } + + case strict && m.Message.Nonce > nextNonce+maxNonceGap: + return false, xerrors.Errorf("message nonce has too big a gap from expected nonce (Nonce: %d, nextNonce: %d): %w", m.Message.Nonce, nextNonce, ErrNonceGap) + + case m.Message.Nonce > nextNonce: + nonceGap = true + } + exms, has := ms.msgs[m.Message.Nonce] if has { + // refuse RBF if we have a gap + if strict && nonceGap { + return false, xerrors.Errorf("rejecting replace by fee because of nonce gap (Nonce: %d, nextNonce: %d): %w", m.Message.Nonce, nextNonce, ErrNonceGap) + } + if m.Cid() != exms.Cid() { // check if RBF passes - minPrice := exms.Message.GasPrice - minPrice = types.BigAdd(minPrice, types.BigDiv(types.BigMul(minPrice, rbfNum), rbfDenom)) - minPrice = types.BigAdd(minPrice, types.NewInt(1)) - if types.BigCmp(m.Message.GasPrice, minPrice) > 0 { - log.Infow("add with RBF", "oldprice", exms.Message.GasPrice, - "newprice", m.Message.GasPrice, "addr", m.Message.From, "nonce", m.Message.Nonce) + minPrice := ComputeMinRBF(exms.Message.GasPremium) + if types.BigCmp(m.Message.GasPremium, minPrice) >= 0 { + log.Debugw("add with RBF", "oldpremium", exms.Message.GasPremium, + "newpremium", m.Message.GasPremium, "addr", m.Message.From, "nonce", m.Message.Nonce) } else { - log.Info("add with duplicate nonce") - return xerrors.Errorf("message to %s with nonce %d already in mpool", m.Message.To, m.Message.Nonce) + log.Debugf("add with duplicate nonce. message from %s with nonce %d already in mpool,"+ + " increase GasPremium to %s from %s to trigger replace by fee: %s", + m.Message.From, m.Message.Nonce, minPrice, m.Message.GasPremium, + ErrRBFTooLowPremium) + return false, xerrors.Errorf("message from %s with nonce %d already in mpool,"+ + " increase GasPremium to %s from %s to trigger replace by fee: %w", + m.Message.From, m.Message.Nonce, minPrice, m.Message.GasPremium, + ErrRBFTooLowPremium) + } + } else { + return false, xerrors.Errorf("message from %s with nonce %d already in mpool: %w", + m.Message.From, m.Message.Nonce, ErrExistingNonce) + } + + ms.requiredFunds.Sub(ms.requiredFunds, exms.Message.RequiredFunds().Int) + // ms.requiredFunds.Sub(ms.requiredFunds, exms.Message.Value.Int) + } + + if !has && strict && len(ms.msgs) >= maxActorPendingMessages { + log.Errorf("too many pending messages from actor %s", m.Message.From) + return false, ErrTooManyPendingMessages + } + + if strict && nonceGap { + log.Debugf("adding nonce-gapped message from %s (nonce: %d, nextNonce: %d)", + m.Message.From, m.Message.Nonce, nextNonce) + } + + ms.nextNonce = nextNonce + ms.msgs[m.Message.Nonce] = m + ms.requiredFunds.Add(ms.requiredFunds, m.Message.RequiredFunds().Int) + // ms.requiredFunds.Add(ms.requiredFunds, m.Message.Value.Int) + + return !has, nil +} + +func (ms *msgSet) rm(nonce uint64, applied bool) { + m, has := ms.msgs[nonce] + if !has { + if applied && nonce >= ms.nextNonce { + // we removed a message we did not know about because it was applied + // we need to adjust the nonce and check if we filled a gap + ms.nextNonce = nonce + 1 + for _, fillGap := ms.msgs[ms.nextNonce]; fillGap; _, fillGap = ms.msgs[ms.nextNonce] { + ms.nextNonce++ } } + return } - ms.msgs[m.Message.Nonce] = m - return nil + ms.requiredFunds.Sub(ms.requiredFunds, m.Message.RequiredFunds().Int) + // ms.requiredFunds.Sub(ms.requiredFunds, m.Message.Value.Int) + delete(ms.msgs, nonce) + + // adjust next nonce + if applied { + // we removed a (known) message because it was applied in a tipset + // we can't possibly have filled a gap in this case + if nonce >= ms.nextNonce { + ms.nextNonce = nonce + 1 + } + return + } + + // we removed a message because it was pruned + // we have to adjust the nonce if it creates a gap or rewinds state + if nonce < ms.nextNonce { + ms.nextNonce = nonce + } } -type Provider interface { - SubscribeHeadChanges(func(rev, app []*types.TipSet) error) *types.TipSet - PutMessage(m types.ChainMsg) (cid.Cid, error) - PubSubPublish(string, []byte) error - StateGetActor(address.Address, *types.TipSet) (*types.Actor, error) - StateAccountKey(context.Context, address.Address, *types.TipSet) (address.Address, error) - MessagesForBlock(*types.BlockHeader) ([]*types.Message, []*types.SignedMessage, error) - MessagesForTipset(*types.TipSet) ([]types.ChainMsg, error) - LoadTipSet(tsk types.TipSetKey) (*types.TipSet, error) +func (ms *msgSet) getRequiredFunds(nonce uint64) types.BigInt { + requiredFunds := new(stdbig.Int).Set(ms.requiredFunds) + + m, has := ms.msgs[nonce] + if has { + requiredFunds.Sub(requiredFunds, m.Message.RequiredFunds().Int) + // requiredFunds.Sub(requiredFunds, m.Message.Value.Int) + } + + return types.BigInt{Int: requiredFunds} } -type mpoolProvider struct { - sm *stmgr.StateManager - ps *pubsub.PubSub +func (ms *msgSet) toSlice() []*types.SignedMessage { + set := make([]*types.SignedMessage, 0, len(ms.msgs)) + + for _, m := range ms.msgs { + set = append(set, m) + } + + sort.Slice(set, func(i, j int) bool { + return set[i].Message.Nonce < set[j].Message.Nonce + }) + + return set } -func NewProvider(sm *stmgr.StateManager, ps *pubsub.PubSub) Provider { - return &mpoolProvider{sm, ps} -} +func New(ctx context.Context, api Provider, ds dtypes.MetadataDS, us stmgr.UpgradeSchedule, netName dtypes.NetworkName, j journal.Journal) (*MessagePool, error) { + cache, _ := lru.New2Q[cid.Cid, crypto.Signature](build.BlsSignatureCacheSize) + verifcache, _ := lru.New2Q[string, struct{}](build.VerifSigCacheSize) + stateNonceCache, _ := lru.New[stateNonceCacheKey, uint64](32768) // 32k * ~200 bytes = 6MB + keycache, _ := lru.New[address.Address, address.Address](1_000_000) -func (mpp *mpoolProvider) SubscribeHeadChanges(cb func(rev, app []*types.TipSet) error) *types.TipSet { - mpp.sm.ChainStore().SubscribeHeadChanges(cb) - return mpp.sm.ChainStore().GetHeaviestTipSet() -} + cfg, err := loadConfig(ctx, ds) + if err != nil { + return nil, xerrors.Errorf("error loading mpool config: %w", err) + } -func (mpp *mpoolProvider) PutMessage(m types.ChainMsg) (cid.Cid, error) { - return mpp.sm.ChainStore().PutMessage(m) -} - -func (mpp *mpoolProvider) PubSubPublish(k string, v []byte) error { - return mpp.ps.Publish(k, v) -} - -func (mpp *mpoolProvider) StateGetActor(addr address.Address, ts *types.TipSet) (*types.Actor, error) { - return mpp.sm.GetActor(addr, ts) -} - -func (mpp *mpoolProvider) StateAccountKey(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) { - return mpp.sm.ResolveToKeyAddress(ctx, addr, ts) -} - -func (mpp *mpoolProvider) MessagesForBlock(h *types.BlockHeader) ([]*types.Message, []*types.SignedMessage, error) { - return mpp.sm.ChainStore().MessagesForBlock(h) -} - -func (mpp *mpoolProvider) MessagesForTipset(ts *types.TipSet) ([]types.ChainMsg, error) { - return mpp.sm.ChainStore().MessagesForTipset(ts) -} - -func (mpp *mpoolProvider) LoadTipSet(tsk types.TipSetKey) (*types.TipSet, error) { - return mpp.sm.ChainStore().LoadTipSet(tsk) -} - -func New(api Provider, ds dtypes.MetadataDS, netName dtypes.NetworkName) (*MessagePool, error) { - cache, _ := lru.New2Q(build.BlsSignatureCacheSize) - verifcache, _ := lru.New2Q(build.VerifSigCacheSize) + if j == nil { + j = journal.NilJournal() + } mp := &MessagePool{ - closer: make(chan struct{}), - repubTk: time.NewTicker(build.BlockDelay * 10 * time.Second), - localAddrs: make(map[address.Address]struct{}), - pending: make(map[address.Address]*msgSet), - minGasPrice: types.NewInt(0), - maxTxPoolSize: 5000, - blsSigCache: cache, - sigValCache: verifcache, - changes: lps.New(50), - localMsgs: namespace.Wrap(ds, datastore.NewKey(localMsgsDs)), - api: api, - netName: netName, + ds: ds, + addSema: make(chan struct{}, 1), + closer: make(chan struct{}), + repubTk: build.Clock.Ticker(RepublishInterval), + repubTrigger: make(chan struct{}, 1), + localAddrs: make(map[address.Address]struct{}), + pending: make(map[address.Address]*msgSet), + keyCache: keycache, + minGasPrice: types.NewInt(0), + getNtwkVersion: us.GetNtwkVersion, + pruneTrigger: make(chan struct{}, 1), + pruneCooldown: make(chan struct{}, 1), + blsSigCache: cache, + sigValCache: verifcache, + stateNonceCache: stateNonceCache, + changes: lps.New(50), + localMsgs: namespace.Wrap(ds, datastore.NewKey(localMsgsDs)), + api: api, + netName: netName, + cfg: cfg, + evtTypes: [...]journal.EventType{ + evtTypeMpoolAdd: j.RegisterEventType("mpool", "add"), + evtTypeMpoolRemove: j.RegisterEventType("mpool", "remove"), + evtTypeMpoolRepub: j.RegisterEventType("mpool", "repub"), + }, + journal: j, } - if err := mp.loadLocal(); err != nil { - log.Errorf("loading local messages: %+v", err) - } + // enable initial prunes + mp.pruneCooldown <- struct{}{} - go mp.repubLocal() + ctx, cancel := context.WithCancel(context.TODO()) + // load the current tipset and subscribe to head changes _before_ loading local messages mp.curTs = api.SubscribeHeadChanges(func(rev, app []*types.TipSet) error { - err := mp.HeadChange(rev, app) + err := mp.HeadChange(ctx, rev, app) if err != nil { log.Errorf("mpool head notif handler error: %+v", err) } return err }) + mp.curTsLk.Lock() + mp.lk.Lock() + + go func() { + defer cancel() + err := mp.loadLocal(ctx) + + mp.lk.Unlock() + mp.curTsLk.Unlock() + + if err != nil { + log.Errorf("loading local messages: %+v", err) + } + + log.Info("mpool ready") + + mp.runLoop(ctx) + }() + return mp, nil } +func (mp *MessagePool) ForEachPendingMessage(f func(cid.Cid) error) error { + mp.lk.Lock() + defer mp.lk.Unlock() + + for _, mset := range mp.pending { + for _, m := range mset.msgs { + err := f(m.Cid()) + if err != nil { + return err + } + + err = f(m.Message.Cid()) + if err != nil { + return err + } + } + } + + return nil +} + +func (mp *MessagePool) resolveToKey(ctx context.Context, addr address.Address) (address.Address, error) { + //if addr is not an ID addr, then it is already resolved to a key + if addr.Protocol() != address.ID { + return addr, nil + } + return mp.resolveToKeyFromID(ctx, addr) +} + +func (mp *MessagePool) resolveToKeyFromID(ctx context.Context, addr address.Address) (address.Address, error) { + + // check the cache + a, ok := mp.keyCache.Get(addr) + if ok { + return a, nil + } + + // resolve the address + ka, err := mp.api.StateDeterministicAddressAtFinality(ctx, addr, mp.curTs) + if err != nil { + return address.Undef, err + } + + // place both entries in the cache (may both be key addresses, which is fine) + mp.keyCache.Add(addr, ka) + return ka, nil +} + +func (mp *MessagePool) getPendingMset(ctx context.Context, addr address.Address) (*msgSet, bool, error) { + ra, err := mp.resolveToKey(ctx, addr) + if err != nil { + return nil, false, err + } + + ms, f := mp.pending[ra] + + return ms, f, nil +} + +func (mp *MessagePool) setPendingMset(ctx context.Context, addr address.Address, ms *msgSet) error { + ra, err := mp.resolveToKey(ctx, addr) + if err != nil { + return err + } + + mp.pending[ra] = ms + + return nil +} + +// This method isn't strictly necessary, since it doesn't resolve any addresses, but it's safer to have +func (mp *MessagePool) forEachPending(f func(address.Address, *msgSet)) { + for la, ms := range mp.pending { + f(la, ms) + } +} + +func (mp *MessagePool) deletePendingMset(ctx context.Context, addr address.Address) error { + ra, err := mp.resolveToKey(ctx, addr) + if err != nil { + return err + } + + delete(mp.pending, ra) + + return nil +} + +// This method isn't strictly necessary, since it doesn't resolve any addresses, but it's safer to have +func (mp *MessagePool) clearPending() { + mp.pending = make(map[address.Address]*msgSet) +} + +func (mp *MessagePool) isLocal(ctx context.Context, addr address.Address) (bool, error) { + ra, err := mp.resolveToKey(ctx, addr) + if err != nil { + return false, err + } + + _, f := mp.localAddrs[ra] + + return f, nil +} + +func (mp *MessagePool) setLocal(ctx context.Context, addr address.Address) error { + ra, err := mp.resolveToKey(ctx, addr) + if err != nil { + return err + } + + mp.localAddrs[ra] = struct{}{} + + return nil +} + +// This method isn't strictly necessary, since it doesn't resolve any addresses, but it's safer to have +func (mp *MessagePool) forEachLocal(ctx context.Context, f func(context.Context, address.Address)) { + for la := range mp.localAddrs { + f(ctx, la) + } +} + func (mp *MessagePool) Close() error { close(mp.closer) return nil } -func (mp *MessagePool) repubLocal() { +func (mp *MessagePool) Prune() { + // this magic incantation of triggering prune thrice is here to make the Prune method + // synchronous: + // so, its a single slot buffered channel. The first send fills the channel, + // the second send goes through when the pruning starts, + // and the third send goes through (and noops) after the pruning finishes + // and goes through the loop again + mp.pruneTrigger <- struct{}{} + mp.pruneTrigger <- struct{}{} + mp.pruneTrigger <- struct{}{} +} + +func (mp *MessagePool) runLoop(ctx context.Context) { for { select { case <-mp.repubTk.C: - mp.lk.Lock() - - msgsForAddr := make(map[address.Address][]*types.SignedMessage) - for a := range mp.localAddrs { - msgsForAddr[a] = mp.pendingFor(a) + if err := mp.republishPendingMessages(ctx); err != nil { + log.Errorf("error while republishing messages: %s", err) + } + case <-mp.repubTrigger: + if err := mp.republishPendingMessages(ctx); err != nil { + log.Errorf("error while republishing messages: %s", err) } - mp.lk.Unlock() - - var errout error - outputMsgs := []*types.SignedMessage{} - - for a, msgs := range msgsForAddr { - a, err := mp.api.StateGetActor(a, nil) - if err != nil { - errout = multierr.Append(errout, xerrors.Errorf("could not get actor state: %w", err)) - continue - } - - curNonce := a.Nonce - for _, m := range msgs { - if m.Message.Nonce < curNonce { - continue - } - if m.Message.Nonce != curNonce { - break - } - outputMsgs = append(outputMsgs, m) - curNonce++ - } - + case <-mp.pruneTrigger: + if err := mp.pruneExcessMessages(); err != nil { + log.Errorf("failed to prune excess messages from mempool: %s", err) } - if len(outputMsgs) != 0 { - log.Infow("republishing local messages", "n", len(outputMsgs)) - } - - for _, msg := range outputMsgs { - msgb, err := msg.Serialize() - if err != nil { - errout = multierr.Append(errout, xerrors.Errorf("could not serialize: %w", err)) - continue - } - - err = mp.api.PubSubPublish(build.MessagesTopic(mp.netName), msgb) - if err != nil { - errout = multierr.Append(errout, xerrors.Errorf("could not publish: %w", err)) - continue - } - } - - if errout != nil { - log.Errorf("errors while republishing: %+v", errout) - } case <-mp.closer: mp.repubTk.Stop() return } } - } -func (mp *MessagePool) addLocal(m *types.SignedMessage, msgb []byte) error { - mp.localAddrs[m.Message.From] = struct{}{} +func (mp *MessagePool) addLocal(ctx context.Context, m *types.SignedMessage) error { + if err := mp.setLocal(ctx, m.Message.From); err != nil { + return err + } - if err := mp.localMsgs.Put(datastore.NewKey(string(m.Cid().Bytes())), msgb); err != nil { + msgb, err := m.Serialize() + if err != nil { + return xerrors.Errorf("error serializing message: %w", err) + } + + if err := mp.localMsgs.Put(ctx, datastore.NewKey(string(m.Cid().Bytes())), msgb); err != nil { return xerrors.Errorf("persisting local message: %w", err) } return nil } -func (mp *MessagePool) Push(m *types.SignedMessage) (cid.Cid, error) { - msgb, err := m.Serialize() +// verifyMsgBeforeAdd verifies that the message meets the minimum criteria for block inclusion +// and whether the message has enough funds to be included in the next 20 blocks. +// If the message is not valid for block inclusion, it returns an error. +// For local messages, if the message can be included in the next 20 blocks, it returns true to +// signal that it should be immediately published. If the message cannot be included in the next 20 +// blocks, it returns false so that the message doesn't immediately get published (and ignored by our +// peers); instead it will be published through the republish loop, once the base fee has fallen +// sufficiently. +// For non local messages, if the message cannot be included in the next 20 blocks it returns +// a (soft) validation error. +func (mp *MessagePool) verifyMsgBeforeAdd(ctx context.Context, m *types.SignedMessage, curTs *types.TipSet, local bool) (bool, error) { + epoch := curTs.Height() + 1 + minGas := vm.PricelistByEpoch(epoch).OnChainMessage(m.ChainLength()) + + if err := m.VMMessage().ValidForBlockInclusion(minGas.Total(), mp.api.StateNetworkVersion(ctx, epoch)); err != nil { + return false, xerrors.Errorf("message will not be included in a block: %w", err) + } + + // this checks if the GasFeeCap is sufficiently high for inclusion in the next 20 blocks + // if the GasFeeCap is too low, we soft reject the message (Ignore in pubsub) and rely + // on republish to push it through later, if the baseFee has fallen. + // this is a defensive check that stops minimum baseFee spam attacks from overloading validation + // queues. + // Note that for local messages, we always add them so that they can be accepted and republished + // automatically. + publish := local + + var baseFee big.Int + if len(curTs.Blocks()) > 0 { + baseFee = curTs.Blocks()[0].ParentBaseFee + } else { + var err error + baseFee, err = mp.api.ChainComputeBaseFee(context.TODO(), curTs) + if err != nil { + return false, xerrors.Errorf("computing basefee: %w", err) + } + } + + baseFeeLowerBound := getBaseFeeLowerBound(baseFee, baseFeeLowerBoundFactorConservative) + if m.Message.GasFeeCap.LessThan(baseFeeLowerBound) { + if local { + log.Warnf("local message will not be immediately published because GasFeeCap doesn't meet the lower bound for inclusion in the next 20 blocks (GasFeeCap: %s, baseFeeLowerBound: %s)", + m.Message.GasFeeCap, baseFeeLowerBound) + publish = false + } else { + return false, xerrors.Errorf("GasFeeCap doesn't meet base fee lower bound for inclusion in the next 20 blocks (GasFeeCap: %s, baseFeeLowerBound: %s): %w", + m.Message.GasFeeCap, baseFeeLowerBound, ErrSoftValidationFailure) + } + } + + return publish, nil +} + +// Push checks the signed message for any violations, adds the message to the message pool and +// publishes the message if the publish flag is set +func (mp *MessagePool) Push(ctx context.Context, m *types.SignedMessage, publish bool) (cid.Cid, error) { + done := metrics.Timer(ctx, metrics.MpoolPushDuration) + defer done() + + err := mp.checkMessage(ctx, m) if err != nil { return cid.Undef, err } - if err := mp.Add(m); err != nil { + // serialize push access to reduce lock contention + mp.addSema <- struct{}{} + defer func() { + <-mp.addSema + }() + + mp.curTsLk.Lock() + ok, err := mp.addTs(ctx, m, mp.curTs, true, false) + if err != nil { + mp.curTsLk.Unlock() return cid.Undef, err } + mp.curTsLk.Unlock() - mp.lk.Lock() - if err := mp.addLocal(m, msgb); err != nil { - mp.lk.Unlock() - return cid.Undef, err + if ok && publish { + msgb, err := m.Serialize() + if err != nil { + return cid.Undef, xerrors.Errorf("error serializing message: %w", err) + } + + err = mp.api.PubSubPublish(build.MessagesTopic(mp.netName), msgb) + if err != nil { + return cid.Undef, xerrors.Errorf("error publishing message: %w", err) + } } - mp.lk.Unlock() - return m.Cid(), mp.api.PubSubPublish(build.MessagesTopic(mp.netName), msgb) + return m.Cid(), nil } -func (mp *MessagePool) Add(m *types.SignedMessage) error { +func (mp *MessagePool) checkMessage(ctx context.Context, m *types.SignedMessage) error { // big messages are bad, anti DOS - if m.Size() > 32*1024 { + if m.Size() > MaxMessageSize { return xerrors.Errorf("mpool message too large (%dB): %w", m.Size(), ErrMessageTooBig) } + // Perform syntactic validation, minGas=0 as we check the actual mingas before we add it + if err := m.Message.ValidForBlockInclusion(0, mp.api.StateNetworkVersion(ctx, mp.curTs.Height())); err != nil { + return xerrors.Errorf("message not valid for block inclusion: %w", err) + } + if m.Message.To == address.Undef { return ErrInvalidToAddr } @@ -332,25 +740,69 @@ func (mp *MessagePool) Add(m *types.SignedMessage) error { return ErrMessageValueTooHigh } + if m.Message.GasFeeCap.LessThan(minimumBaseFee) { + return ErrGasFeeCapTooLow + } + if err := mp.VerifyMsgSig(m); err != nil { - log.Warnf("mpooladd signature verification failed: %s", err) + return xerrors.Errorf("signature verification failed: %s", err) + } + + return nil +} + +func (mp *MessagePool) Add(ctx context.Context, m *types.SignedMessage) error { + done := metrics.Timer(ctx, metrics.MpoolAddDuration) + defer done() + + err := mp.checkMessage(ctx, m) + if err != nil { return err } + // serialize push access to reduce lock contention + mp.addSema <- struct{}{} + defer func() { + <-mp.addSema + }() + + mp.curTsLk.RLock() + tmpCurTs := mp.curTs + mp.curTsLk.RUnlock() + + //ensures computations are cached without holding lock + _, _ = mp.api.GetActorAfter(m.Message.From, tmpCurTs) + _, _ = mp.getStateNonce(ctx, m.Message.From, tmpCurTs) + mp.curTsLk.Lock() + if tmpCurTs == mp.curTs { + //with the lock enabled, mp.curTs is the same Ts as we just had, so we know that our computations are cached + } else { + //curTs has been updated so we want to cache the new one: + tmpCurTs = mp.curTs + //we want to release the lock, cache the computations then grab it again + mp.curTsLk.Unlock() + _, _ = mp.api.GetActorAfter(m.Message.From, tmpCurTs) + _, _ = mp.getStateNonce(ctx, m.Message.From, tmpCurTs) + mp.curTsLk.Lock() + //now that we have the lock, we continue, we could do this as a loop forever, but that's bad to loop forever, and this was added as an optimization and it seems once is enough because the computation < block time + } + defer mp.curTsLk.Unlock() - return mp.addTs(m, mp.curTs) + + _, err = mp.addTs(ctx, m, mp.curTs, false, false) + return err } func sigCacheKey(m *types.SignedMessage) (string, error) { switch m.Signature.Type { case crypto.SigTypeBLS: - if len(m.Signature.Data) < 90 { - return "", fmt.Errorf("bls signature too short") + if len(m.Signature.Data) != ffi.SignatureBytes { + return "", fmt.Errorf("bls signature incorrectly sized") } - - return string(m.Cid().Bytes()) + string(m.Signature.Data[64:]), nil - case crypto.SigTypeSecp256k1: + hashCache := blake2b.Sum256(append(m.Cid().Bytes(), m.Signature.Data...)) + return string(hashCache[:]), nil + case crypto.SigTypeSecp256k1, crypto.SigTypeDelegated: return string(m.Cid().Bytes()), nil default: return "", xerrors.Errorf("unrecognized signature type: %d", m.Signature.Type) @@ -369,8 +821,8 @@ func (mp *MessagePool) VerifyMsgSig(m *types.SignedMessage) error { return nil } - if err := sigs.Verify(&m.Signature, m.Message.From, m.Message.Cid().Bytes()); err != nil { - return err + if err := consensus.AuthenticateMessage(m, m.Message.From); err != nil { + return xerrors.Errorf("failed to validate signature: %w", err) } mp.sigValCache.Add(sck, struct{}{}) @@ -378,92 +830,227 @@ func (mp *MessagePool) VerifyMsgSig(m *types.SignedMessage) error { return nil } -func (mp *MessagePool) addTs(m *types.SignedMessage, curTs *types.TipSet) error { - snonce, err := mp.getStateNonce(m.Message.From, curTs) +func (mp *MessagePool) checkBalance(ctx context.Context, m *types.SignedMessage, curTs *types.TipSet) error { + balance, err := mp.getStateBalance(ctx, m.Message.From, curTs) if err != nil { - return xerrors.Errorf("failed to look up actor state nonce: %s: %w", err, ErrBroadcastAnyway) + return xerrors.Errorf("failed to check sender balance: %s: %w", err, ErrSoftValidationFailure) + } + + requiredFunds := m.Message.RequiredFunds() + if balance.LessThan(requiredFunds) { + return xerrors.Errorf("not enough funds (required: %s, balance: %s): %w", types.FIL(requiredFunds), types.FIL(balance), ErrNotEnoughFunds) + } + + // add Value for soft failure check + // requiredFunds = types.BigAdd(requiredFunds, m.Message.Value) + + mset, ok, err := mp.getPendingMset(ctx, m.Message.From) + if err != nil { + log.Debugf("mpoolcheckbalance failed to get pending mset: %s", err) + return err + } + + if ok { + requiredFunds = types.BigAdd(requiredFunds, mset.getRequiredFunds(m.Message.Nonce)) + } + + if balance.LessThan(requiredFunds) { + // Note: we fail here for ErrSoftValidationFailure to signal a soft failure because we might + // be out of sync. + return xerrors.Errorf("not enough funds including pending messages (required: %s, balance: %s): %w", types.FIL(requiredFunds), types.FIL(balance), ErrSoftValidationFailure) + } + + return nil +} + +func (mp *MessagePool) addTs(ctx context.Context, m *types.SignedMessage, curTs *types.TipSet, local, untrusted bool) (bool, error) { + done := metrics.Timer(ctx, metrics.MpoolAddTsDuration) + defer done() + + snonce, err := mp.getStateNonce(ctx, m.Message.From, curTs) + if err != nil { + return false, xerrors.Errorf("failed to look up actor state nonce: %s: %w", err, ErrSoftValidationFailure) + } + + if snonce > m.Message.Nonce { + return false, xerrors.Errorf("minimum expected nonce is %d: %w", snonce, ErrNonceTooLow) + } + + senderAct, err := mp.api.GetActorAfter(m.Message.From, curTs) + if err != nil { + return false, xerrors.Errorf("failed to get sender actor: %w", err) + } + + // This message can only be included in the _next_ epoch and beyond, hence the +1. + epoch := curTs.Height() + 1 + nv := mp.api.StateNetworkVersion(ctx, epoch) + + // TODO: I'm not thrilled about depending on filcns here, but I prefer this to duplicating logic + if !consensus.IsValidForSending(nv, senderAct) { + return false, xerrors.Errorf("sender actor %s is not a valid top-level sender", m.Message.From) + } + + mp.lk.Lock() + defer mp.lk.Unlock() + + publish, err := mp.verifyMsgBeforeAdd(ctx, m, curTs, local) + if err != nil { + return false, xerrors.Errorf("verify msg failed: %w", err) + } + + if err := mp.checkBalance(ctx, m, curTs); err != nil { + return false, xerrors.Errorf("failed to check balance: %w", err) + } + + err = mp.addLocked(ctx, m, !local, untrusted) + if err != nil { + return false, xerrors.Errorf("failed to add locked: %w", err) + } + + if local { + err = mp.addLocal(ctx, m) + if err != nil { + return false, xerrors.Errorf("error persisting local message: %w", err) + } + } + + return publish, nil +} + +func (mp *MessagePool) addLoaded(ctx context.Context, m *types.SignedMessage) error { + err := mp.checkMessage(ctx, m) + if err != nil { + return err + } + + curTs := mp.curTs + + if curTs == nil { + return xerrors.Errorf("current tipset not loaded") + } + + snonce, err := mp.getStateNonce(ctx, m.Message.From, curTs) + if err != nil { + return xerrors.Errorf("failed to look up actor state nonce: %s: %w", err, ErrSoftValidationFailure) } if snonce > m.Message.Nonce { return xerrors.Errorf("minimum expected nonce is %d: %w", snonce, ErrNonceTooLow) } - balance, err := mp.getStateBalance(m.Message.From, curTs) + _, err = mp.verifyMsgBeforeAdd(ctx, m, curTs, true) if err != nil { - return xerrors.Errorf("failed to check sender balance: %s: %w", err, ErrBroadcastAnyway) + return err } - if balance.LessThan(m.Message.RequiredFunds()) { - return xerrors.Errorf("not enough funds (required: %s, balance: %s): %w", types.FIL(m.Message.RequiredFunds()), types.FIL(balance), ErrNotEnoughFunds) + if err := mp.checkBalance(ctx, m, curTs); err != nil { + return err } + return mp.addLocked(ctx, m, false, false) +} + +func (mp *MessagePool) addSkipChecks(ctx context.Context, m *types.SignedMessage) error { mp.lk.Lock() defer mp.lk.Unlock() - return mp.addLocked(m) + return mp.addLocked(ctx, m, false, false) } -func (mp *MessagePool) addSkipChecks(m *types.SignedMessage) error { - mp.lk.Lock() - defer mp.lk.Unlock() - - return mp.addLocked(m) -} - -func (mp *MessagePool) addLocked(m *types.SignedMessage) error { +func (mp *MessagePool) addLocked(ctx context.Context, m *types.SignedMessage, strict, untrusted bool) error { log.Debugf("mpooladd: %s %d", m.Message.From, m.Message.Nonce) if m.Signature.Type == crypto.SigTypeBLS { mp.blsSigCache.Add(m.Cid(), m.Signature) } - if m.Message.GasLimit > build.BlockGasLimit { - return xerrors.Errorf("given message has too high of a gas limit") + if _, err := mp.api.PutMessage(ctx, m); err != nil { + return xerrors.Errorf("mpooladd cs.PutMessage failed: %s", err) } - if _, err := mp.api.PutMessage(m); err != nil { - log.Warnf("mpooladd cs.PutMessage failed: %s", err) + if _, err := mp.api.PutMessage(ctx, &m.Message); err != nil { + return xerrors.Errorf("mpooladd cs.PutMessage failed: %s", err) + } + + // Note: If performance becomes an issue, making this getOrCreatePendingMset will save some work + mset, ok, err := mp.getPendingMset(ctx, m.Message.From) + if err != nil { + log.Debug(err) return err } - if _, err := mp.api.PutMessage(&m.Message); err != nil { - log.Warnf("mpooladd cs.PutMessage failed: %s", err) - return err - } - - mset, ok := mp.pending[m.Message.From] if !ok { - mset = newMsgSet() - mp.pending[m.Message.From] = mset + nonce, err := mp.getStateNonce(ctx, m.Message.From, mp.curTs) + if err != nil { + return xerrors.Errorf("failed to get initial actor nonce: %w", err) + } + + mset = newMsgSet(nonce) + if err = mp.setPendingMset(ctx, m.Message.From, mset); err != nil { + return xerrors.Errorf("failed to set pending mset: %w", err) + } } - if err := mset.add(m); err != nil { - log.Info(err) + incr, err := mset.add(m, mp, strict, untrusted) + if err != nil { + log.Debug(err) + return err + } + + if incr { + mp.currentSize++ + if mp.currentSize > mp.getConfig().SizeLimitHigh { + // send signal to prune messages if it hasnt already been sent + select { + case mp.pruneTrigger <- struct{}{}: + default: + } + } } mp.changes.Pub(api.MpoolUpdate{ Type: api.MpoolAdd, Message: m, }, localUpdates) + + mp.journal.RecordEvent(mp.evtTypes[evtTypeMpoolAdd], func() interface{} { + return MessagePoolEvt{ + Action: "add", + Messages: []MessagePoolEvtMessage{{Message: m.Message, CID: m.Cid()}}, + } + }) + return nil } -func (mp *MessagePool) GetNonce(addr address.Address) (uint64, error) { - mp.curTsLk.Lock() - defer mp.curTsLk.Unlock() +func (mp *MessagePool) GetNonce(ctx context.Context, addr address.Address, _ types.TipSetKey) (uint64, error) { + mp.curTsLk.RLock() + defer mp.curTsLk.RUnlock() - mp.lk.Lock() - defer mp.lk.Unlock() + mp.lk.RLock() + defer mp.lk.RUnlock() - return mp.getNonceLocked(addr, mp.curTs) + return mp.getNonceLocked(ctx, addr, mp.curTs) } -func (mp *MessagePool) getNonceLocked(addr address.Address, curTs *types.TipSet) (uint64, error) { - stateNonce, err := mp.getStateNonce(addr, curTs) // sanity check +// GetActor should not be used. It is only here to satisfy interface mess caused by lite node handling +func (mp *MessagePool) GetActor(_ context.Context, addr address.Address, _ types.TipSetKey) (*types.Actor, error) { + mp.curTsLk.RLock() + defer mp.curTsLk.RUnlock() + return mp.api.GetActorAfter(addr, mp.curTs) +} + +func (mp *MessagePool) getNonceLocked(ctx context.Context, addr address.Address, curTs *types.TipSet) (uint64, error) { + stateNonce, err := mp.getStateNonce(ctx, addr, curTs) // sanity check if err != nil { return 0, err } - mset, ok := mp.pending[addr] + mset, ok, err := mp.getPendingMset(ctx, addr) + if err != nil { + log.Debugf("mpoolgetnonce failed to get mset: %s", err) + return 0, err + } + if ok { if stateNonce > mset.nextNonce { log.Errorf("state nonce was larger than mset.nextNonce (%d > %d)", stateNonce, mset.nextNonce) @@ -477,43 +1064,63 @@ func (mp *MessagePool) getNonceLocked(addr address.Address, curTs *types.TipSet) return stateNonce, nil } -func (mp *MessagePool) getStateNonce(addr address.Address, curTs *types.TipSet) (uint64, error) { - // TODO: this method probably should be cached +func (mp *MessagePool) getStateNonce(ctx context.Context, addr address.Address, ts *types.TipSet) (uint64, error) { + done := metrics.Timer(ctx, metrics.MpoolGetNonceDuration) + defer done() - act, err := mp.api.StateGetActor(addr, curTs) + nk := stateNonceCacheKey{ + tsk: ts.Key(), + addr: addr, + } + + n, ok := mp.stateNonceCache.Get(nk) + if ok { + return n, nil + } + + // get the nonce from the actor before ts + actor, err := mp.api.GetActorBefore(addr, ts) + if err != nil { + return 0, err + } + nextNonce := actor.Nonce + + raddr, err := mp.resolveToKey(ctx, addr) if err != nil { return 0, err } - baseNonce := act.Nonce - - // TODO: the correct thing to do here is probably to set curTs to chain.head - // but since we have an accurate view of the world until a head change occurs, - // this should be fine - if curTs == nil { - return baseNonce, nil - } - - msgs, err := mp.api.MessagesForTipset(curTs) + // loop over all messages sent by 'addr' and find the highest nonce + messages, err := mp.api.MessagesForTipset(ctx, ts) if err != nil { - return 0, xerrors.Errorf("failed to check messages for tipset: %w", err) + return 0, err } + for _, message := range messages { + msg := message.VMMessage() - for _, m := range msgs { - msg := m.VMMessage() - if msg.From == addr { - if msg.Nonce != baseNonce { - return 0, xerrors.Errorf("tipset %s has bad nonce ordering (%d != %d)", curTs.Cids(), msg.Nonce, baseNonce) + maddr, err := mp.resolveToKey(ctx, msg.From) + if err != nil { + log.Warnf("failed to resolve message from address: %s", err) + continue + } + + if maddr == raddr { + if n := msg.Nonce + 1; n > nextNonce { + nextNonce = n } - baseNonce++ } } - return baseNonce, nil + mp.stateNonceCache.Add(nk, nextNonce) + + return nextNonce, nil } -func (mp *MessagePool) getStateBalance(addr address.Address, ts *types.TipSet) (types.BigInt, error) { - act, err := mp.api.StateGetActor(addr, ts) +func (mp *MessagePool) getStateBalance(ctx context.Context, addr address.Address, ts *types.TipSet) (types.BigInt, error) { + done := metrics.Timer(ctx, metrics.MpoolGetBalanceDuration) + defer done() + + act, err := mp.api.GetActorAfter(addr, ts) if err != nil { return types.EmptyInt, err } @@ -521,52 +1128,60 @@ func (mp *MessagePool) getStateBalance(addr address.Address, ts *types.TipSet) ( return act.Balance, nil } -func (mp *MessagePool) PushWithNonce(ctx context.Context, addr address.Address, cb func(address.Address, uint64) (*types.SignedMessage, error)) (*types.SignedMessage, error) { +// this method is provided for the gateway to push messages. +// differences from Push: +// - strict checks are enabled +// - extra strict add checks are used when adding the messages to the msgSet +// that means: no nonce gaps, at most 10 pending messages for the actor +func (mp *MessagePool) PushUntrusted(ctx context.Context, m *types.SignedMessage) (cid.Cid, error) { + err := mp.checkMessage(ctx, m) + if err != nil { + return cid.Undef, err + } + + // serialize push access to reduce lock contention + mp.addSema <- struct{}{} + defer func() { + <-mp.addSema + }() + mp.curTsLk.Lock() - defer mp.curTsLk.Unlock() + publish, err := mp.addTs(ctx, m, mp.curTs, true, true) + if err != nil { + mp.curTsLk.Unlock() + return cid.Undef, err + } + mp.curTsLk.Unlock() - mp.lk.Lock() - defer mp.lk.Unlock() - - fromKey := addr - if fromKey.Protocol() == address.ID { - var err error - fromKey, err = mp.api.StateAccountKey(ctx, fromKey, mp.curTs) + if publish { + msgb, err := m.Serialize() if err != nil { - return nil, xerrors.Errorf("resolving sender key: %w", err) + return cid.Undef, xerrors.Errorf("error serializing message: %w", err) + } + + err = mp.api.PubSubPublish(build.MessagesTopic(mp.netName), msgb) + if err != nil { + return cid.Undef, xerrors.Errorf("error publishing message: %w", err) } } - nonce, err := mp.getNonceLocked(fromKey, mp.curTs) - if err != nil { - return nil, xerrors.Errorf("get nonce locked failed: %w", err) - } - - msg, err := cb(fromKey, nonce) - if err != nil { - return nil, err - } - - msgb, err := msg.Serialize() - if err != nil { - return nil, err - } - - if err := mp.addLocked(msg); err != nil { - return nil, xerrors.Errorf("add locked failed: %w", err) - } - if err := mp.addLocal(msg, msgb); err != nil { - log.Errorf("addLocal failed: %+v", err) - } - - return msg, mp.api.PubSubPublish(build.MessagesTopic(mp.netName), msgb) + return m.Cid(), nil } -func (mp *MessagePool) Remove(from address.Address, nonce uint64) { +func (mp *MessagePool) Remove(ctx context.Context, from address.Address, nonce uint64, applied bool) { mp.lk.Lock() defer mp.lk.Unlock() - mset, ok := mp.pending[from] + mp.remove(ctx, from, nonce, applied) +} + +func (mp *MessagePool) remove(ctx context.Context, from address.Address, nonce uint64, applied bool) { + mset, ok, err := mp.getPendingMset(ctx, from) + if err != nil { + log.Debugf("mpoolremove failed to get mset: %s", err) + return + } + if !ok { return } @@ -576,67 +1191,76 @@ func (mp *MessagePool) Remove(from address.Address, nonce uint64) { Type: api.MpoolRemove, Message: m, }, localUpdates) + + mp.journal.RecordEvent(mp.evtTypes[evtTypeMpoolRemove], func() interface{} { + return MessagePoolEvt{ + Action: "remove", + Messages: []MessagePoolEvtMessage{{Message: m.Message, CID: m.Cid()}}} + }) + + mp.currentSize-- } // NB: This deletes any message with the given nonce. This makes sense // as two messages with the same sender cannot have the same nonce - delete(mset.msgs, nonce) + mset.rm(nonce, applied) if len(mset.msgs) == 0 { - delete(mp.pending, from) - } else { - var max uint64 - for nonce := range mset.msgs { - if max < nonce { - max = nonce - } + if err = mp.deletePendingMset(ctx, from); err != nil { + log.Debugf("mpoolremove failed to delete mset: %s", err) + return } - if max < nonce { - max = nonce // we could have not seen the removed message before - } - - mset.nextNonce = max + 1 } } -func (mp *MessagePool) Pending() ([]*types.SignedMessage, *types.TipSet) { - mp.curTsLk.Lock() - defer mp.curTsLk.Unlock() +func (mp *MessagePool) Pending(ctx context.Context) ([]*types.SignedMessage, *types.TipSet) { + mp.curTsLk.RLock() + defer mp.curTsLk.RUnlock() - mp.lk.Lock() - defer mp.lk.Unlock() + mp.lk.RLock() + defer mp.lk.RUnlock() + return mp.allPending(ctx) +} + +func (mp *MessagePool) allPending(ctx context.Context) ([]*types.SignedMessage, *types.TipSet) { out := make([]*types.SignedMessage, 0) - for a := range mp.pending { - out = append(out, mp.pendingFor(a)...) - } + + mp.forEachPending(func(a address.Address, mset *msgSet) { + out = append(out, mset.toSlice()...) + }) return out, mp.curTs } -func (mp *MessagePool) pendingFor(a address.Address) []*types.SignedMessage { - mset := mp.pending[a] - if mset == nil || len(mset.msgs) == 0 { +func (mp *MessagePool) PendingFor(ctx context.Context, a address.Address) ([]*types.SignedMessage, *types.TipSet) { + mp.curTsLk.RLock() + defer mp.curTsLk.RUnlock() + + mp.lk.RLock() + defer mp.lk.RUnlock() + return mp.pendingFor(ctx, a), mp.curTs +} + +func (mp *MessagePool) pendingFor(ctx context.Context, a address.Address) []*types.SignedMessage { + mset, ok, err := mp.getPendingMset(ctx, a) + if err != nil { + log.Debugf("mpoolpendingfor failed to get mset: %s", err) return nil } - set := make([]*types.SignedMessage, 0, len(mset.msgs)) - - for _, m := range mset.msgs { - set = append(set, m) + if mset == nil || !ok || len(mset.msgs) == 0 { + return nil } - sort.Slice(set, func(i, j int) bool { - return set[i].Message.Nonce < set[j].Message.Nonce - }) - - return set + return mset.toSlice() } -func (mp *MessagePool) HeadChange(revert []*types.TipSet, apply []*types.TipSet) error { +func (mp *MessagePool) HeadChange(ctx context.Context, revert []*types.TipSet, apply []*types.TipSet) error { mp.curTsLk.Lock() defer mp.curTsLk.Unlock() + repubTrigger := false rmsgs := make(map[address.Address]map[uint64]*types.SignedMessage) add := func(m *types.SignedMessage) { s, ok := rmsgs[m.Message.From] @@ -649,7 +1273,7 @@ func (mp *MessagePool) HeadChange(revert []*types.TipSet, apply []*types.TipSet) rm := func(from address.Address, nonce uint64) { s, ok := rmsgs[from] if !ok { - mp.Remove(from, nonce) + mp.Remove(ctx, from, nonce, true) return } @@ -658,55 +1282,87 @@ func (mp *MessagePool) HeadChange(revert []*types.TipSet, apply []*types.TipSet) return } - mp.Remove(from, nonce) + mp.Remove(ctx, from, nonce, true) } - for _, ts := range revert { - pts, err := mp.api.LoadTipSet(ts.Parents()) - if err != nil { - return err + maybeRepub := func(cid cid.Cid) { + if !repubTrigger { + mp.lk.RLock() + _, republished := mp.republished[cid] + mp.lk.RUnlock() + if republished { + repubTrigger = true + } } + } - msgs, err := mp.MessagesForBlocks(ts.Blocks()) + var merr error + + for _, ts := range revert { + pts, err := mp.api.LoadTipSet(ctx, ts.Parents()) if err != nil { - return err + log.Errorf("error loading reverted tipset parent: %s", err) + merr = multierror.Append(merr, err) + continue } mp.curTs = pts + msgs, err := mp.MessagesForBlocks(ctx, ts.Blocks()) + if err != nil { + log.Errorf("error retrieving messages for reverted block: %s", err) + merr = multierror.Append(merr, err) + continue + } + for _, msg := range msgs { add(msg) } } for _, ts := range apply { + mp.curTs = ts + for _, b := range ts.Blocks() { - bmsgs, smsgs, err := mp.api.MessagesForBlock(b) + bmsgs, smsgs, err := mp.api.MessagesForBlock(ctx, b) if err != nil { - return xerrors.Errorf("failed to get messages for apply block %s(height %d) (msgroot = %s): %w", b.Cid(), b.Height, b.Messages, err) + xerr := xerrors.Errorf("failed to get messages for apply block %s(height %d) (msgroot = %s): %w", b.Cid(), b.Height, b.Messages, err) + log.Errorf("error retrieving messages for block: %s", xerr) + merr = multierror.Append(merr, xerr) + continue } + for _, msg := range smsgs { rm(msg.Message.From, msg.Message.Nonce) + maybeRepub(msg.Cid()) } for _, msg := range bmsgs { rm(msg.From, msg.Nonce) + maybeRepub(msg.Cid()) } } + } - mp.curTs = ts + if repubTrigger { + select { + case mp.repubTrigger <- struct{}{}: + default: + } } for _, s := range rmsgs { for _, msg := range s { - if err := mp.addSkipChecks(msg); err != nil { + if err := mp.addSkipChecks(ctx, msg); err != nil { log.Errorf("Failed to readd message from reorg to mpool: %s", err) } } } if len(revert) > 0 && futureDebug { - msgs, ts := mp.Pending() + mp.lk.RLock() + msgs, ts := mp.allPending(ctx) + mp.lk.RUnlock() buckets := map[address.Address]*statBucket{} @@ -723,7 +1379,8 @@ func (mp *MessagePool) HeadChange(revert []*types.TipSet, apply []*types.TipSet) } for a, bkt := range buckets { - act, err := mp.api.StateGetActor(a, ts) + // TODO that might not be correct with GatActorAfter but it is only debug code + act, err := mp.api.GetActorAfter(a, ts) if err != nil { log.Debugf("%s, err: %s\n", a, err) continue @@ -770,18 +1427,83 @@ func (mp *MessagePool) HeadChange(revert []*types.TipSet, apply []*types.TipSet) } } - return nil + return merr +} + +func (mp *MessagePool) runHeadChange(ctx context.Context, from *types.TipSet, to *types.TipSet, rmsgs map[address.Address]map[uint64]*types.SignedMessage) error { + add := func(m *types.SignedMessage) { + s, ok := rmsgs[m.Message.From] + if !ok { + s = make(map[uint64]*types.SignedMessage) + rmsgs[m.Message.From] = s + } + s[m.Message.Nonce] = m + } + rm := func(from address.Address, nonce uint64) { + s, ok := rmsgs[from] + if !ok { + return + } + + if _, ok := s[nonce]; ok { + delete(s, nonce) + return + } + + } + + revert, apply, err := store.ReorgOps(ctx, mp.api.LoadTipSet, from, to) + if err != nil { + return xerrors.Errorf("failed to compute reorg ops for mpool pending messages: %w", err) + } + + var merr error + + for _, ts := range revert { + msgs, err := mp.MessagesForBlocks(ctx, ts.Blocks()) + if err != nil { + log.Errorf("error retrieving messages for reverted block: %s", err) + merr = multierror.Append(merr, err) + continue + } + + for _, msg := range msgs { + add(msg) + } + } + + for _, ts := range apply { + for _, b := range ts.Blocks() { + bmsgs, smsgs, err := mp.api.MessagesForBlock(ctx, b) + if err != nil { + xerr := xerrors.Errorf("failed to get messages for apply block %s(height %d) (msgroot = %s): %w", b.Cid(), b.Height, b.Messages, err) + log.Errorf("error retrieving messages for block: %s", xerr) + merr = multierror.Append(merr, xerr) + continue + } + + for _, msg := range smsgs { + rm(msg.Message.From, msg.Message.Nonce) + } + + for _, msg := range bmsgs { + rm(msg.From, msg.Nonce) + } + } + } + + return merr } type statBucket struct { msgs map[uint64]*types.SignedMessage } -func (mp *MessagePool) MessagesForBlocks(blks []*types.BlockHeader) ([]*types.SignedMessage, error) { +func (mp *MessagePool) MessagesForBlocks(ctx context.Context, blks []*types.BlockHeader) ([]*types.SignedMessage, error) { out := make([]*types.SignedMessage, 0) for _, b := range blks { - bmsgs, smsgs, err := mp.api.MessagesForBlock(b) + bmsgs, smsgs, err := mp.api.MessagesForBlock(ctx, b) if err != nil { return nil, xerrors.Errorf("failed to get messages for apply block %s(height %d) (msgroot = %s): %w", b.Cid(), b.Height, b.Messages, err) } @@ -792,7 +1514,7 @@ func (mp *MessagePool) MessagesForBlocks(blks []*types.BlockHeader) ([]*types.Si if smsg != nil { out = append(out, smsg) } else { - log.Warnf("could not recover signature for bls message %s", msg.Cid()) + log.Debugf("could not recover signature for bls message %s", msg.Cid()) } } } @@ -801,15 +1523,10 @@ func (mp *MessagePool) MessagesForBlocks(blks []*types.BlockHeader) ([]*types.Si } func (mp *MessagePool) RecoverSig(msg *types.Message) *types.SignedMessage { - val, ok := mp.blsSigCache.Get(msg.Cid()) + sig, ok := mp.blsSigCache.Get(msg.Cid()) if !ok { return nil } - sig, ok := val.(crypto.Signature) - if !ok { - log.Errorf("value in signature cache was not a signature (got %T)", val) - return nil - } return &types.SignedMessage{ Message: *msg, @@ -822,7 +1539,8 @@ func (mp *MessagePool) Updates(ctx context.Context) (<-chan api.MpoolUpdate, err sub := mp.changes.Sub(localUpdates) go func() { - defer mp.changes.Unsub(sub, localUpdates) + defer mp.changes.Unsub(sub) + defer close(out) for { select { @@ -831,9 +1549,13 @@ func (mp *MessagePool) Updates(ctx context.Context) (<-chan api.MpoolUpdate, err case out <- u.(api.MpoolUpdate): case <-ctx.Done(): return + case <-mp.closer: + return } case <-ctx.Done(): return + case <-mp.closer: + return } } }() @@ -841,8 +1563,8 @@ func (mp *MessagePool) Updates(ctx context.Context) (<-chan api.MpoolUpdate, err return out, nil } -func (mp *MessagePool) loadLocal() error { - res, err := mp.localMsgs.Query(query.Query{}) +func (mp *MessagePool) loadLocal(ctx context.Context) error { + res, err := mp.localMsgs.Query(ctx, query.Query{}) if err != nil { return xerrors.Errorf("query local messages: %w", err) } @@ -857,28 +1579,81 @@ func (mp *MessagePool) loadLocal() error { return xerrors.Errorf("unmarshaling local message: %w", err) } - if err := mp.Add(&sm); err != nil { + if err := mp.addLoaded(ctx, &sm); err != nil { if xerrors.Is(err, ErrNonceTooLow) { continue // todo: drop the message from local cache (if above certain confidence threshold) } log.Errorf("adding local message: %+v", err) } + + if err = mp.setLocal(ctx, sm.Message.From); err != nil { + log.Debugf("mpoolloadLocal errored: %s", err) + return err + } } return nil } -const MinGasPrice = 0 +func (mp *MessagePool) Clear(ctx context.Context, local bool) { + mp.lk.Lock() + defer mp.lk.Unlock() -func (mp *MessagePool) EstimateGasPrice(ctx context.Context, nblocksincl uint64, sender address.Address, gaslimit int64, tsk types.TipSetKey) (types.BigInt, error) { - // TODO: something smarter obviously - switch nblocksincl { - case 0: - return types.NewInt(MinGasPrice + 2), nil - case 1: - return types.NewInt(MinGasPrice + 1), nil - default: - return types.NewInt(MinGasPrice), nil + // remove everything if local is true, including removing local messages from + // the datastore + if local { + mp.forEachLocal(ctx, func(ctx context.Context, la address.Address) { + mset, ok, err := mp.getPendingMset(ctx, la) + if err != nil { + log.Warnf("errored while getting pending mset: %w", err) + return + } + + if ok { + for _, m := range mset.msgs { + err := mp.localMsgs.Delete(ctx, datastore.NewKey(string(m.Cid().Bytes()))) + if err != nil { + log.Warnf("error deleting local message: %s", err) + } + } + } + }) + + mp.clearPending() + mp.republished = nil + + return } + + mp.forEachPending(func(a address.Address, ms *msgSet) { + isLocal, err := mp.isLocal(ctx, a) + if err != nil { + log.Warnf("errored while determining isLocal: %w", err) + return + } + + if isLocal { + return + } + + if err = mp.deletePendingMset(ctx, a); err != nil { + log.Warnf("errored while deleting mset: %w", err) + return + } + }) +} + +func getBaseFeeLowerBound(baseFee, factor types.BigInt) types.BigInt { + baseFeeLowerBound := types.BigDiv(baseFee, factor) + if baseFeeLowerBound.LessThan(minimumBaseFee) { + baseFeeLowerBound = minimumBaseFee + } + + return baseFeeLowerBound +} + +type MpoolNonceAPI interface { + GetNonce(context.Context, address.Address, types.TipSetKey) (uint64, error) + GetActor(context.Context, address.Address, types.TipSetKey) (*types.Actor, error) } diff --git a/chain/messagepool/messagepool_test.go b/chain/messagepool/messagepool_test.go index 26d35a361..a781b5074 100644 --- a/chain/messagepool/messagepool_test.go +++ b/chain/messagepool/messagepool_test.go @@ -1,97 +1,203 @@ +// stm: #unit package messagepool import ( "context" "fmt" + "sort" "testing" + "github.com/ipfs/go-cid" + "github.com/ipfs/go-datastore" + logging "github.com/ipfs/go-log/v2" + "github.com/stretchr/testify/assert" + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/go-state-types/network" + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/consensus/filcns" + "github.com/filecoin-project/lotus/chain/messagepool/gasguess" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types/mock" "github.com/filecoin-project/lotus/chain/wallet" _ "github.com/filecoin-project/lotus/lib/sigs/bls" _ "github.com/filecoin-project/lotus/lib/sigs/secp" - "github.com/filecoin-project/specs-actors/actors/crypto" - "github.com/ipfs/go-cid" - "github.com/ipfs/go-datastore" ) -type testMpoolApi struct { +func init() { + _ = logging.SetLogLevel("*", "INFO") +} + +type testMpoolAPI struct { cb func(rev, app []*types.TipSet) error bmsgs map[cid.Cid][]*types.SignedMessage statenonce map[address.Address]uint64 + balance map[address.Address]types.BigInt tipsets []*types.TipSet + + published int + + baseFee types.BigInt } -func newTestMpoolApi() *testMpoolApi { - return &testMpoolApi{ +func newTestMpoolAPI() *testMpoolAPI { + tma := &testMpoolAPI{ bmsgs: make(map[cid.Cid][]*types.SignedMessage), statenonce: make(map[address.Address]uint64), + balance: make(map[address.Address]types.BigInt), + baseFee: types.NewInt(100), } + genesis := mock.MkBlock(nil, 1, 1) + tma.tipsets = append(tma.tipsets, mock.TipSet(genesis)) + return tma } -func (tma *testMpoolApi) applyBlock(t *testing.T, b *types.BlockHeader) { +func (tma *testMpoolAPI) nextBlock() *types.BlockHeader { + newBlk := mock.MkBlock(tma.tipsets[len(tma.tipsets)-1], 1, 1) + tma.tipsets = append(tma.tipsets, mock.TipSet(newBlk)) + return newBlk +} + +func (tma *testMpoolAPI) nextBlockWithHeight(height uint64) *types.BlockHeader { + newBlk := mock.MkBlock(tma.tipsets[len(tma.tipsets)-1], 1, 1) + newBlk.Height = abi.ChainEpoch(height) + tma.tipsets = append(tma.tipsets, mock.TipSet(newBlk)) + return newBlk +} + +func (tma *testMpoolAPI) applyBlock(t *testing.T, b *types.BlockHeader) { t.Helper() if err := tma.cb(nil, []*types.TipSet{mock.TipSet(b)}); err != nil { t.Fatal(err) } } -func (tma *testMpoolApi) revertBlock(t *testing.T, b *types.BlockHeader) { +func (tma *testMpoolAPI) revertBlock(t *testing.T, b *types.BlockHeader) { t.Helper() if err := tma.cb([]*types.TipSet{mock.TipSet(b)}, nil); err != nil { t.Fatal(err) } } -func (tma *testMpoolApi) setStateNonce(addr address.Address, v uint64) { +func (tma *testMpoolAPI) setStateNonce(addr address.Address, v uint64) { tma.statenonce[addr] = v } -func (tma *testMpoolApi) setBlockMessages(h *types.BlockHeader, msgs ...*types.SignedMessage) { +func (tma *testMpoolAPI) setBalance(addr address.Address, v uint64) { + tma.balance[addr] = types.FromFil(v) +} + +func (tma *testMpoolAPI) setBalanceRaw(addr address.Address, v types.BigInt) { + tma.balance[addr] = v +} + +func (tma *testMpoolAPI) setBlockMessages(h *types.BlockHeader, msgs ...*types.SignedMessage) { tma.bmsgs[h.Cid()] = msgs - tma.tipsets = append(tma.tipsets, mock.TipSet(h)) } -func (tma *testMpoolApi) SubscribeHeadChanges(cb func(rev, app []*types.TipSet) error) *types.TipSet { +func (tma *testMpoolAPI) SubscribeHeadChanges(cb func(rev, app []*types.TipSet) error) *types.TipSet { tma.cb = cb - return nil + return tma.tipsets[0] } -func (tma *testMpoolApi) PutMessage(m types.ChainMsg) (cid.Cid, error) { +func (tma *testMpoolAPI) PutMessage(ctx context.Context, m types.ChainMsg) (cid.Cid, error) { return cid.Undef, nil } -func (tma *testMpoolApi) PubSubPublish(string, []byte) error { +func (tma *testMpoolAPI) IsLite() bool { + return false +} + +func (tma *testMpoolAPI) PubSubPublish(string, []byte) error { + tma.published++ return nil } -func (tma *testMpoolApi) StateGetActor(addr address.Address, ts *types.TipSet) (*types.Actor, error) { +func (tma *testMpoolAPI) GetActorBefore(addr address.Address, ts *types.TipSet) (*types.Actor, error) { + balance, ok := tma.balance[addr] + if !ok { + balance = types.NewInt(1000e6) + tma.balance[addr] = balance + } + + nonce := tma.statenonce[addr] + return &types.Actor{ - Nonce: tma.statenonce[addr], - Balance: types.NewInt(90000000), + Code: builtin2.AccountActorCodeID, + Nonce: nonce, + Balance: balance, }, nil } -func (tma *testMpoolApi) StateAccountKey(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) { - if addr.Protocol() != address.BLS && addr.Protocol() != address.SECP256K1 { +func (tma *testMpoolAPI) GetActorAfter(addr address.Address, ts *types.TipSet) (*types.Actor, error) { + // regression check for load bug + if ts == nil { + panic("GetActorAfter called with nil tipset") + } + + balance, ok := tma.balance[addr] + if !ok { + balance = types.NewInt(1000e6) + tma.balance[addr] = balance + } + + msgs := make([]*types.SignedMessage, 0) + for _, b := range ts.Blocks() { + for _, m := range tma.bmsgs[b.Cid()] { + if m.Message.From == addr { + msgs = append(msgs, m) + } + } + } + + sort.Slice(msgs, func(i, j int) bool { + return msgs[i].Message.Nonce < msgs[j].Message.Nonce + }) + + nonce := tma.statenonce[addr] + + for _, m := range msgs { + if m.Message.Nonce != nonce { + break + } + nonce++ + } + + return &types.Actor{ + Code: builtin2.AccountActorCodeID, + Nonce: nonce, + Balance: balance, + }, nil +} + +func (tma *testMpoolAPI) StateDeterministicAddressAtFinality(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) { + if addr.Protocol() != address.BLS && addr.Protocol() != address.SECP256K1 && addr.Protocol() != address.Delegated { return address.Undef, fmt.Errorf("given address was not a key addr") } return addr, nil } -func (tma *testMpoolApi) MessagesForBlock(h *types.BlockHeader) ([]*types.Message, []*types.SignedMessage, error) { +func (tma *testMpoolAPI) StateNetworkVersion(ctx context.Context, h abi.ChainEpoch) network.Version { + return build.TestNetworkVersion +} + +func (tma *testMpoolAPI) MessagesForBlock(ctx context.Context, h *types.BlockHeader) ([]*types.Message, []*types.SignedMessage, error) { return nil, tma.bmsgs[h.Cid()], nil } -func (tma *testMpoolApi) MessagesForTipset(ts *types.TipSet) ([]types.ChainMsg, error) { +func (tma *testMpoolAPI) MessagesForTipset(ctx context.Context, ts *types.TipSet) ([]types.ChainMsg, error) { if len(ts.Blocks()) != 1 { panic("cant deal with multiblock tipsets in this test") } - bm, sm, err := tma.MessagesForBlock(ts.Blocks()[0]) + bm, sm, err := tma.MessagesForBlock(ctx, ts.Blocks()[0]) if err != nil { return nil, err } @@ -108,7 +214,7 @@ func (tma *testMpoolApi) MessagesForTipset(ts *types.TipSet) ([]types.ChainMsg, return out, nil } -func (tma *testMpoolApi) LoadTipSet(tsk types.TipSetKey) (*types.TipSet, error) { +func (tma *testMpoolAPI) LoadTipSet(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error) { for _, ts := range tma.tipsets { if types.CidArrsEqual(tsk.Cids(), ts.Cids()) { return ts, nil @@ -118,9 +224,14 @@ func (tma *testMpoolApi) LoadTipSet(tsk types.TipSetKey) (*types.TipSet, error) return nil, fmt.Errorf("tipset not found") } +func (tma *testMpoolAPI) ChainComputeBaseFee(ctx context.Context, ts *types.TipSet) (types.BigInt, error) { + return tma.baseFee, nil +} + func assertNonce(t *testing.T, mp *MessagePool, addr address.Address, val uint64) { t.Helper() - n, err := mp.GetNonce(addr) + // stm: @CHAIN_MEMPOOL_GET_NONCE_001 + n, err := mp.GetNonce(context.TODO(), addr, types.EmptyTSK) if err != nil { t.Fatal(err) } @@ -132,13 +243,15 @@ func assertNonce(t *testing.T, mp *MessagePool, addr address.Address, val uint64 func mustAdd(t *testing.T, mp *MessagePool, msg *types.SignedMessage) { t.Helper() - if err := mp.Add(msg); err != nil { + if err := mp.Add(context.TODO(), msg); err != nil { t.Fatal(err) } } func TestMessagePool(t *testing.T) { - tma := newTestMpoolApi() + // stm: @CHAIN_MEMPOOL_GET_NONCE_001 + + tma := newTestMpoolAPI() w, err := wallet.NewWallet(wallet.NewMemKeyStore()) if err != nil { @@ -147,14 +260,14 @@ func TestMessagePool(t *testing.T) { ds := datastore.NewMapDatastore() - mp, err := New(tma, ds, "mptest") + mp, err := New(context.Background(), tma, ds, filcns.DefaultUpgradeSchedule(), "mptest", nil) if err != nil { t.Fatal(err) } - a := mock.MkBlock(nil, 1, 1) + a := tma.nextBlock() - sender, err := w.GenerateKey(crypto.SigTypeBLS) + sender, err := w.WalletNew(context.Background(), types.KTSecp256k1) if err != nil { t.Fatal(err) } @@ -178,8 +291,75 @@ func TestMessagePool(t *testing.T) { assertNonce(t, mp, sender, 2) } -func TestRevertMessages(t *testing.T) { - tma := newTestMpoolApi() +func TestCheckMessageBig(t *testing.T) { + tma := newTestMpoolAPI() + + w, err := wallet.NewWallet(wallet.NewMemKeyStore()) + assert.NoError(t, err) + + from, err := w.WalletNew(context.Background(), types.KTBLS) + assert.NoError(t, err) + + tma.setBalance(from, 1000e9) + + ds := datastore.NewMapDatastore() + + mp, err := New(context.Background(), tma, ds, filcns.DefaultUpgradeSchedule(), "mptest", nil) + assert.NoError(t, err) + + to := mock.Address(1001) + + { + msg := &types.Message{ + To: to, + From: from, + Value: types.NewInt(1), + Nonce: 0, + GasLimit: 60000000, + GasFeeCap: types.NewInt(100), + GasPremium: types.NewInt(1), + Params: make([]byte, 41<<10), // 41KiB payload + } + + sig, err := w.WalletSign(context.TODO(), from, msg.Cid().Bytes(), api.MsgMeta{}) + if err != nil { + panic(err) + } + sm := &types.SignedMessage{ + Message: *msg, + Signature: *sig, + } + mustAdd(t, mp, sm) + } + + { + msg := &types.Message{ + To: to, + From: from, + Value: types.NewInt(1), + Nonce: 0, + GasLimit: 50000000, + GasFeeCap: types.NewInt(100), + GasPremium: types.NewInt(1), + Params: make([]byte, 64<<10), // 64KiB payload + } + + sig, err := w.WalletSign(context.TODO(), from, msg.Cid().Bytes(), api.MsgMeta{}) + if err != nil { + panic(err) + } + sm := &types.SignedMessage{ + Message: *msg, + Signature: *sig, + } + // stm: @CHAIN_MEMPOOL_PUSH_001 + err = mp.Add(context.TODO(), sm) + assert.ErrorIs(t, err, ErrMessageTooBig) + } +} + +func TestMessagePoolMessagesInEachBlock(t *testing.T) { + tma := newTestMpoolAPI() w, err := wallet.NewWallet(wallet.NewMemKeyStore()) if err != nil { @@ -188,15 +368,66 @@ func TestRevertMessages(t *testing.T) { ds := datastore.NewMapDatastore() - mp, err := New(tma, ds, "mptest") + mp, err := New(context.Background(), tma, ds, filcns.DefaultUpgradeSchedule(), "mptest", nil) if err != nil { t.Fatal(err) } - a := mock.MkBlock(nil, 1, 1) - b := mock.MkBlock(mock.TipSet(a), 1, 1) + a := tma.nextBlock() - sender, err := w.GenerateKey(crypto.SigTypeBLS) + sender, err := w.WalletNew(context.Background(), types.KTBLS) + if err != nil { + t.Fatal(err) + } + target := mock.Address(1001) + + var msgs []*types.SignedMessage + for i := 0; i < 5; i++ { + m := mock.MkMessage(sender, target, uint64(i), w) + msgs = append(msgs, m) + mustAdd(t, mp, m) + } + + tma.setStateNonce(sender, 0) + + tma.setBlockMessages(a, msgs[0], msgs[1]) + tma.applyBlock(t, a) + tsa := mock.TipSet(a) + + // stm: @CHAIN_MEMPOOL_PENDING_001 + _, _ = mp.Pending(context.TODO()) + + // stm: @CHAIN_MEMPOOL_SELECT_001 + selm, _ := mp.SelectMessages(context.Background(), tsa, 1) + if len(selm) == 0 { + t.Fatal("should have returned the rest of the messages") + } +} + +func TestRevertMessages(t *testing.T) { + futureDebug = true + defer func() { + futureDebug = false + }() + + tma := newTestMpoolAPI() + + w, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + ds := datastore.NewMapDatastore() + + mp, err := New(context.Background(), tma, ds, filcns.DefaultUpgradeSchedule(), "mptest", nil) + if err != nil { + t.Fatal(err) + } + + a := tma.nextBlock() + b := tma.nextBlock() + + sender, err := w.WalletNew(context.Background(), types.KTBLS) if err != nil { t.Fatal(err) } @@ -227,9 +458,679 @@ func TestRevertMessages(t *testing.T) { assertNonce(t, mp, sender, 4) - p, _ := mp.Pending() + // stm: @CHAIN_MEMPOOL_PENDING_001 + p, _ := mp.Pending(context.TODO()) + fmt.Printf("%+v\n", p) if len(p) != 3 { t.Fatal("expected three messages in mempool") } } + +func TestPruningSimple(t *testing.T) { + oldMaxNonceGap := MaxNonceGap + MaxNonceGap = 1000 + defer func() { + MaxNonceGap = oldMaxNonceGap + }() + + tma := newTestMpoolAPI() + + w, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + ds := datastore.NewMapDatastore() + + mp, err := New(context.Background(), tma, ds, filcns.DefaultUpgradeSchedule(), "mptest", nil) + if err != nil { + t.Fatal(err) + } + + a := tma.nextBlock() + tma.applyBlock(t, a) + + sender, err := w.WalletNew(context.Background(), types.KTBLS) + if err != nil { + t.Fatal(err) + } + tma.setBalance(sender, 1) // in FIL + target := mock.Address(1001) + + for i := 0; i < 5; i++ { + smsg := mock.MkMessage(sender, target, uint64(i), w) + if err := mp.Add(context.TODO(), smsg); err != nil { + t.Fatal(err) + } + } + + for i := 10; i < 50; i++ { + smsg := mock.MkMessage(sender, target, uint64(i), w) + if err := mp.Add(context.TODO(), smsg); err != nil { + t.Fatal(err) + } + } + + mp.cfg.SizeLimitHigh = 40 + mp.cfg.SizeLimitLow = 10 + + mp.Prune() + + // stm: @CHAIN_MEMPOOL_PENDING_001 + msgs, _ := mp.Pending(context.TODO()) + if len(msgs) != 5 { + t.Fatal("expected only 5 messages in pool, got: ", len(msgs)) + } +} + +func TestLoadLocal(t *testing.T) { + tma := newTestMpoolAPI() + ds := datastore.NewMapDatastore() + + mp, err := New(context.Background(), tma, ds, filcns.DefaultUpgradeSchedule(), "mptest", nil) + if err != nil { + t.Fatal(err) + } + + // the actors + w1, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a1, err := w1.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + w2, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a2, err := w2.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + tma.setBalance(a1, 1) // in FIL + tma.setBalance(a2, 1) // in FIL + gasLimit := gasguess.Costs[gasguess.CostKey{Code: builtin2.StorageMarketActorCodeID, M: 2}] + msgs := make(map[cid.Cid]struct{}) + for i := 0; i < 10; i++ { + m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(i+1)) + // stm: @CHAIN_MEMPOOL_PUSH_001 + cid, err := mp.Push(context.TODO(), m, true) + if err != nil { + t.Fatal(err) + } + msgs[cid] = struct{}{} + } + err = mp.Close() + if err != nil { + t.Fatal(err) + } + + mp, err = New(context.Background(), tma, ds, filcns.DefaultUpgradeSchedule(), "mptest", nil) + if err != nil { + t.Fatal(err) + } + + // stm: @CHAIN_MEMPOOL_PENDING_001 + pmsgs, _ := mp.Pending(context.TODO()) + if len(msgs) != len(pmsgs) { + t.Fatalf("expected %d messages, but got %d", len(msgs), len(pmsgs)) + } + + for _, m := range pmsgs { + cid := m.Cid() + _, ok := msgs[cid] + if !ok { + t.Fatal("unknown message") + } + + delete(msgs, cid) + } + + if len(msgs) > 0 { + t.Fatalf("not all messages were laoded; missing %d messages", len(msgs)) + } +} + +func TestClearAll(t *testing.T) { + tma := newTestMpoolAPI() + ds := datastore.NewMapDatastore() + + mp, err := New(context.Background(), tma, ds, filcns.DefaultUpgradeSchedule(), "mptest", nil) + if err != nil { + t.Fatal(err) + } + + // the actors + w1, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a1, err := w1.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + w2, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a2, err := w2.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + tma.setBalance(a1, 1) // in FIL + tma.setBalance(a2, 1) // in FIL + gasLimit := gasguess.Costs[gasguess.CostKey{Code: builtin2.StorageMarketActorCodeID, M: 2}] + for i := 0; i < 10; i++ { + m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(i+1)) + // stm: @CHAIN_MEMPOOL_PUSH_001 + _, err := mp.Push(context.TODO(), m, true) + if err != nil { + t.Fatal(err) + } + } + + for i := 0; i < 10; i++ { + m := makeTestMessage(w2, a2, a1, uint64(i), gasLimit, uint64(i+1)) + mustAdd(t, mp, m) + } + + // stm: @CHAIN_MEMPOOL_CLEAR_001 + mp.Clear(context.Background(), true) + + // stm: @CHAIN_MEMPOOL_PENDING_001 + pending, _ := mp.Pending(context.TODO()) + if len(pending) > 0 { + t.Fatalf("cleared the mpool, but got %d pending messages", len(pending)) + } +} + +func TestClearNonLocal(t *testing.T) { + tma := newTestMpoolAPI() + ds := datastore.NewMapDatastore() + + mp, err := New(context.Background(), tma, ds, filcns.DefaultUpgradeSchedule(), "mptest", nil) + if err != nil { + t.Fatal(err) + } + + // the actors + w1, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a1, err := w1.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + w2, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a2, err := w2.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + tma.setBalance(a1, 1) // in FIL + tma.setBalance(a2, 1) // in FIL + + gasLimit := gasguess.Costs[gasguess.CostKey{Code: builtin2.StorageMarketActorCodeID, M: 2}] + for i := 0; i < 10; i++ { + m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(i+1)) + // stm: @CHAIN_MEMPOOL_PUSH_001 + _, err := mp.Push(context.TODO(), m, true) + if err != nil { + t.Fatal(err) + } + } + + for i := 0; i < 10; i++ { + m := makeTestMessage(w2, a2, a1, uint64(i), gasLimit, uint64(i+1)) + mustAdd(t, mp, m) + } + + // stm: @CHAIN_MEMPOOL_CLEAR_001 + mp.Clear(context.Background(), false) + + // stm: @CHAIN_MEMPOOL_PENDING_001 + pending, _ := mp.Pending(context.TODO()) + if len(pending) != 10 { + t.Fatalf("expected 10 pending messages, but got %d instead", len(pending)) + } + + for _, m := range pending { + if m.Message.From != a1 { + t.Fatalf("expected message from %s but got one from %s instead", a1, m.Message.From) + } + } +} + +func TestUpdates(t *testing.T) { + tma := newTestMpoolAPI() + ds := datastore.NewMapDatastore() + + mp, err := New(context.Background(), tma, ds, filcns.DefaultUpgradeSchedule(), "mptest", nil) + if err != nil { + t.Fatal(err) + } + + // the actors + w1, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a1, err := w1.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + w2, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a2, err := w2.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + ctx, cancel := context.WithCancel(context.TODO()) + defer cancel() + + ch, err := mp.Updates(ctx) + if err != nil { + t.Fatal(err) + } + + gasLimit := gasguess.Costs[gasguess.CostKey{Code: builtin2.StorageMarketActorCodeID, M: 2}] + + tma.setBalance(a1, 1) // in FIL + tma.setBalance(a2, 1) // in FIL + + for i := 0; i < 10; i++ { + m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(i+1)) + // stm: @CHAIN_MEMPOOL_PUSH_001 + _, err := mp.Push(context.TODO(), m, true) + if err != nil { + t.Fatal(err) + } + + _, ok := <-ch + if !ok { + t.Fatal("expected update, but got a closed channel instead") + } + } + + err = mp.Close() + if err != nil { + t.Fatal(err) + } + + _, ok := <-ch + if ok { + t.Fatal("expected closed channel, but got an update instead") + } +} + +func TestMessageBelowMinGasFee(t *testing.T) { + // stm: @CHAIN_MEMPOOL_PUSH_001 + tma := newTestMpoolAPI() + + w, err := wallet.NewWallet(wallet.NewMemKeyStore()) + assert.NoError(t, err) + + from, err := w.WalletNew(context.Background(), types.KTBLS) + assert.NoError(t, err) + + tma.setBalance(from, 1000e9) + + ds := datastore.NewMapDatastore() + + mp, err := New(context.Background(), tma, ds, filcns.DefaultUpgradeSchedule(), "mptest", nil) + assert.NoError(t, err) + + to := mock.Address(1001) + + // fee is just below minimum gas fee + fee := minimumBaseFee.Uint64() - 1 + { + msg := &types.Message{ + To: to, + From: from, + Value: types.NewInt(1), + Nonce: 0, + GasLimit: 50000000, + GasFeeCap: types.NewInt(fee), + GasPremium: types.NewInt(1), + Params: make([]byte, 32<<10), + } + + sig, err := w.WalletSign(context.TODO(), from, msg.Cid().Bytes(), api.MsgMeta{}) + if err != nil { + panic(err) + } + sm := &types.SignedMessage{ + Message: *msg, + Signature: *sig, + } + err = mp.Add(context.TODO(), sm) + assert.ErrorIs(t, err, ErrGasFeeCapTooLow) + } +} + +func TestMessageValueTooHigh(t *testing.T) { + // stm: @CHAIN_MEMPOOL_PUSH_001 + tma := newTestMpoolAPI() + + w, err := wallet.NewWallet(wallet.NewMemKeyStore()) + assert.NoError(t, err) + + from, err := w.WalletNew(context.Background(), types.KTBLS) + assert.NoError(t, err) + + tma.setBalance(from, 1000e9) + + ds := datastore.NewMapDatastore() + + mp, err := New(context.Background(), tma, ds, filcns.DefaultUpgradeSchedule(), "mptest", nil) + assert.NoError(t, err) + + to := mock.Address(1001) + + totalFil := types.TotalFilecoinInt + extra := types.NewInt(1) + + value := types.BigAdd(totalFil, extra) + { + msg := &types.Message{ + To: to, + From: from, + Value: value, + Nonce: 0, + GasLimit: 50000000, + GasFeeCap: types.NewInt(minimumBaseFee.Uint64()), + GasPremium: types.NewInt(1), + Params: make([]byte, 32<<10), + } + + sig, err := w.WalletSign(context.TODO(), from, msg.Cid().Bytes(), api.MsgMeta{}) + if err != nil { + panic(err) + } + sm := &types.SignedMessage{ + Message: *msg, + Signature: *sig, + } + err = mp.Add(context.TODO(), sm) + assert.Error(t, err) + } +} + +func TestMessageSignatureInvalid(t *testing.T) { + // stm: @CHAIN_MEMPOOL_PUSH_001 + tma := newTestMpoolAPI() + + w, err := wallet.NewWallet(wallet.NewMemKeyStore()) + assert.NoError(t, err) + + from, err := w.WalletNew(context.Background(), types.KTBLS) + assert.NoError(t, err) + + tma.setBalance(from, 1000e9) + + ds := datastore.NewMapDatastore() + + mp, err := New(context.Background(), tma, ds, filcns.DefaultUpgradeSchedule(), "mptest", nil) + assert.NoError(t, err) + + to := mock.Address(1001) + + { + msg := &types.Message{ + To: to, + From: from, + Value: types.NewInt(1), + Nonce: 0, + GasLimit: 50000000, + GasFeeCap: types.NewInt(minimumBaseFee.Uint64()), + GasPremium: types.NewInt(1), + Params: make([]byte, 32<<10), + } + + badSig := &crypto.Signature{ + Type: crypto.SigTypeSecp256k1, + Data: make([]byte, 0), + } + sm := &types.SignedMessage{ + Message: *msg, + Signature: *badSig, + } + err = mp.Add(context.TODO(), sm) + assert.Error(t, err) + assert.Contains(t, err.Error(), "invalid signature length") + } +} + +func TestAddMessageTwice(t *testing.T) { + // stm: @CHAIN_MEMPOOL_PUSH_001 + tma := newTestMpoolAPI() + + w, err := wallet.NewWallet(wallet.NewMemKeyStore()) + assert.NoError(t, err) + + from, err := w.WalletNew(context.Background(), types.KTBLS) + assert.NoError(t, err) + + tma.setBalance(from, 1000e9) + + ds := datastore.NewMapDatastore() + + mp, err := New(context.Background(), tma, ds, filcns.DefaultUpgradeSchedule(), "mptest", nil) + assert.NoError(t, err) + + to := mock.Address(1001) + + { + msg := &types.Message{ + To: to, + From: from, + Value: types.NewInt(1), + Nonce: 0, + GasLimit: 50000000, + GasFeeCap: types.NewInt(minimumBaseFee.Uint64()), + GasPremium: types.NewInt(1), + Params: make([]byte, 32<<10), + } + + sig, err := w.WalletSign(context.TODO(), from, msg.Cid().Bytes(), api.MsgMeta{}) + if err != nil { + panic(err) + } + sm := &types.SignedMessage{ + Message: *msg, + Signature: *sig, + } + mustAdd(t, mp, sm) + + err = mp.Add(context.TODO(), sm) + assert.Contains(t, err.Error(), "with nonce 0 already in mpool") + } +} + +func TestAddMessageTwiceNonceGap(t *testing.T) { + // stm: @CHAIN_MEMPOOL_PUSH_001 + tma := newTestMpoolAPI() + + w, err := wallet.NewWallet(wallet.NewMemKeyStore()) + assert.NoError(t, err) + + from, err := w.WalletNew(context.Background(), types.KTBLS) + assert.NoError(t, err) + + tma.setBalance(from, 1000e9) + + ds := datastore.NewMapDatastore() + + mp, err := New(context.Background(), tma, ds, filcns.DefaultUpgradeSchedule(), "mptest", nil) + assert.NoError(t, err) + + to := mock.Address(1001) + + { + // create message with invalid nonce (1) + sm := makeTestMessage(w, from, to, 1, 50_000_000, minimumBaseFee.Uint64()) + mustAdd(t, mp, sm) + + // then try to add message again + err = mp.Add(context.TODO(), sm) + assert.Contains(t, err.Error(), "unfulfilled nonce gap") + } +} + +func TestAddMessageTwiceCidDiff(t *testing.T) { + tma := newTestMpoolAPI() + + w, err := wallet.NewWallet(wallet.NewMemKeyStore()) + assert.NoError(t, err) + + from, err := w.WalletNew(context.Background(), types.KTBLS) + assert.NoError(t, err) + + tma.setBalance(from, 1000e9) + + ds := datastore.NewMapDatastore() + + mp, err := New(context.Background(), tma, ds, filcns.DefaultUpgradeSchedule(), "mptest", nil) + assert.NoError(t, err) + + to := mock.Address(1001) + + { + sm := makeTestMessage(w, from, to, 0, 50_000_000, minimumBaseFee.Uint64()) + mustAdd(t, mp, sm) + + // Create message with different data, so CID is different + sm2 := makeTestMessage(w, from, to, 0, 50_000_001, minimumBaseFee.Uint64()) + + // stm: @CHAIN_MEMPOOL_PUSH_001 + // then try to add message again + err = mp.Add(context.TODO(), sm2) + // assert.Contains(t, err.Error(), "replace by fee has too low GasPremium") + assert.Error(t, err) + } +} + +func TestAddMessageTwiceCidDiffReplaced(t *testing.T) { + // stm: @CHAIN_MEMPOOL_PUSH_001 + tma := newTestMpoolAPI() + + w, err := wallet.NewWallet(wallet.NewMemKeyStore()) + assert.NoError(t, err) + + from, err := w.WalletNew(context.Background(), types.KTBLS) + assert.NoError(t, err) + + tma.setBalance(from, 1000e9) + + ds := datastore.NewMapDatastore() + + mp, err := New(context.Background(), tma, ds, filcns.DefaultUpgradeSchedule(), "mptest", nil) + assert.NoError(t, err) + + to := mock.Address(1001) + + { + sm := makeTestMessage(w, from, to, 0, 50_000_000, minimumBaseFee.Uint64()) + mustAdd(t, mp, sm) + + // Create message with different data, so CID is different + sm2 := makeTestMessage(w, from, to, 0, 50_000_000, minimumBaseFee.Uint64()*2) + mustAdd(t, mp, sm2) + } +} + +func TestRemoveMessage(t *testing.T) { + // stm: @CHAIN_MEMPOOL_PUSH_001 + tma := newTestMpoolAPI() + + w, err := wallet.NewWallet(wallet.NewMemKeyStore()) + assert.NoError(t, err) + + from, err := w.WalletNew(context.Background(), types.KTBLS) + assert.NoError(t, err) + + tma.setBalance(from, 1000e9) + + ds := datastore.NewMapDatastore() + + mp, err := New(context.Background(), tma, ds, filcns.DefaultUpgradeSchedule(), "mptest", nil) + assert.NoError(t, err) + + to := mock.Address(1001) + + { + sm := makeTestMessage(w, from, to, 0, 50_000_000, minimumBaseFee.Uint64()) + mustAdd(t, mp, sm) + + // stm: @CHAIN_MEMPOOL_REMOVE_001 + // remove message for sender + mp.Remove(context.TODO(), from, sm.Message.Nonce, true) + + // stm: @CHAIN_MEMPOOL_PENDING_FOR_001 + // check messages in pool: should be none present + msgs := mp.pendingFor(context.TODO(), from) + assert.Len(t, msgs, 0) + } +} + +func TestCapGasFee(t *testing.T) { + t.Run("use default maxfee", func(t *testing.T) { + msg := &types.Message{ + GasLimit: 100_000_000, + GasFeeCap: abi.NewTokenAmount(100_000_000), + GasPremium: abi.NewTokenAmount(100_000), + } + CapGasFee(func() (abi.TokenAmount, error) { + return abi.NewTokenAmount(100_000_000_000), nil + }, msg, nil) + assert.Equal(t, msg.GasFeeCap.Int64(), int64(1000)) + assert.Equal(t, msg.GasPremium.Int.Int64(), int64(1000)) + }) + + t.Run("use spec maxfee", func(t *testing.T) { + msg := &types.Message{ + GasLimit: 100_000_000, + GasFeeCap: abi.NewTokenAmount(100_000_000), + GasPremium: abi.NewTokenAmount(100_000), + } + CapGasFee(nil, msg, &api.MessageSendSpec{MaxFee: abi.NewTokenAmount(100_000_000_000)}) + assert.Equal(t, msg.GasFeeCap.Int64(), int64(1000)) + assert.Equal(t, msg.GasPremium.Int.Int64(), int64(1000)) + }) + + t.Run("use smaller feecap value when fee is enough", func(t *testing.T) { + msg := &types.Message{ + GasLimit: 100_000_000, + GasFeeCap: abi.NewTokenAmount(100_000), + GasPremium: abi.NewTokenAmount(100_000_000), + } + CapGasFee(nil, msg, &api.MessageSendSpec{MaxFee: abi.NewTokenAmount(100_000_000_000_000)}) + assert.Equal(t, msg.GasFeeCap.Int64(), int64(100_000)) + assert.Equal(t, msg.GasPremium.Int.Int64(), int64(100_000)) + }) +} diff --git a/chain/messagepool/provider.go b/chain/messagepool/provider.go new file mode 100644 index 000000000..764e6c13a --- /dev/null +++ b/chain/messagepool/provider.go @@ -0,0 +1,149 @@ +package messagepool + +import ( + "context" + "errors" + "time" + + "github.com/ipfs/go-cid" + pubsub "github.com/libp2p/go-libp2p-pubsub" + "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/chain/stmgr" + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/chain/types" +) + +var ( + HeadChangeCoalesceMinDelay = 2 * time.Second + HeadChangeCoalesceMaxDelay = 6 * time.Second + HeadChangeCoalesceMergeInterval = time.Second +) + +type Provider interface { + SubscribeHeadChanges(func(rev, app []*types.TipSet) error) *types.TipSet + PutMessage(ctx context.Context, m types.ChainMsg) (cid.Cid, error) + PubSubPublish(string, []byte) error + GetActorBefore(address.Address, *types.TipSet) (*types.Actor, error) + GetActorAfter(address.Address, *types.TipSet) (*types.Actor, error) + StateDeterministicAddressAtFinality(context.Context, address.Address, *types.TipSet) (address.Address, error) + StateNetworkVersion(context.Context, abi.ChainEpoch) network.Version + MessagesForBlock(context.Context, *types.BlockHeader) ([]*types.Message, []*types.SignedMessage, error) + MessagesForTipset(context.Context, *types.TipSet) ([]types.ChainMsg, error) + LoadTipSet(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error) + ChainComputeBaseFee(ctx context.Context, ts *types.TipSet) (types.BigInt, error) + IsLite() bool +} + +type mpoolProvider struct { + sm *stmgr.StateManager + ps *pubsub.PubSub + + lite MpoolNonceAPI +} + +var _ Provider = (*mpoolProvider)(nil) + +func NewProvider(sm *stmgr.StateManager, ps *pubsub.PubSub) Provider { + return &mpoolProvider{sm: sm, ps: ps} +} + +func NewProviderLite(sm *stmgr.StateManager, ps *pubsub.PubSub, noncer MpoolNonceAPI) Provider { + return &mpoolProvider{sm: sm, ps: ps, lite: noncer} +} + +func (mpp *mpoolProvider) IsLite() bool { + return mpp.lite != nil +} + +func (mpp *mpoolProvider) getActorLite(addr address.Address, ts *types.TipSet) (*types.Actor, error) { + if !mpp.IsLite() { + return nil, errors.New("should not use getActorLite on non lite Provider") + } + + n, err := mpp.lite.GetNonce(context.TODO(), addr, ts.Key()) + if err != nil { + return nil, xerrors.Errorf("getting nonce over lite: %w", err) + } + a, err := mpp.lite.GetActor(context.TODO(), addr, ts.Key()) + if err != nil { + return nil, xerrors.Errorf("getting actor over lite: %w", err) + } + a.Nonce = n + return a, nil +} + +func (mpp *mpoolProvider) SubscribeHeadChanges(cb func(rev, app []*types.TipSet) error) *types.TipSet { + mpp.sm.ChainStore().SubscribeHeadChanges( + store.WrapHeadChangeCoalescer( + cb, + HeadChangeCoalesceMinDelay, + HeadChangeCoalesceMaxDelay, + HeadChangeCoalesceMergeInterval, + )) + return mpp.sm.ChainStore().GetHeaviestTipSet() +} + +func (mpp *mpoolProvider) PutMessage(ctx context.Context, m types.ChainMsg) (cid.Cid, error) { + return mpp.sm.ChainStore().PutMessage(ctx, m) +} + +func (mpp *mpoolProvider) PubSubPublish(k string, v []byte) error { + return mpp.ps.Publish(k, v) // nolint +} + +func (mpp *mpoolProvider) GetActorBefore(addr address.Address, ts *types.TipSet) (*types.Actor, error) { + if mpp.IsLite() { + return mpp.getActorLite(addr, ts) + } + + return mpp.sm.LoadActor(context.TODO(), addr, ts) +} + +func (mpp *mpoolProvider) GetActorAfter(addr address.Address, ts *types.TipSet) (*types.Actor, error) { + if mpp.IsLite() { + return mpp.getActorLite(addr, ts) + } + + stcid, _, err := mpp.sm.TipSetState(context.TODO(), ts) + if err != nil { + return nil, xerrors.Errorf("computing tipset state for GetActor: %w", err) + } + st, err := mpp.sm.StateTree(stcid) + if err != nil { + return nil, xerrors.Errorf("failed to load state tree: %w", err) + } + return st.GetActor(addr) +} + +func (mpp *mpoolProvider) StateDeterministicAddressAtFinality(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) { + return mpp.sm.ResolveToDeterministicAddressAtFinality(ctx, addr, ts) +} + +func (mpp *mpoolProvider) StateNetworkVersion(ctx context.Context, height abi.ChainEpoch) network.Version { + return mpp.sm.GetNetworkVersion(ctx, height) +} + +func (mpp *mpoolProvider) MessagesForBlock(ctx context.Context, h *types.BlockHeader) ([]*types.Message, []*types.SignedMessage, error) { + return mpp.sm.ChainStore().MessagesForBlock(ctx, h) +} + +func (mpp *mpoolProvider) MessagesForTipset(ctx context.Context, ts *types.TipSet) ([]types.ChainMsg, error) { + return mpp.sm.ChainStore().MessagesForTipset(ctx, ts) +} + +func (mpp *mpoolProvider) LoadTipSet(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error) { + return mpp.sm.ChainStore().LoadTipSet(ctx, tsk) +} + +func (mpp *mpoolProvider) ChainComputeBaseFee(ctx context.Context, ts *types.TipSet) (types.BigInt, error) { + baseFee, err := mpp.sm.ChainStore().ComputeBaseFee(ctx, ts) + if err != nil { + return types.NewInt(0), xerrors.Errorf("computing base fee at %s: %w", ts, err) + } + return baseFee, nil +} diff --git a/chain/messagepool/pruning.go b/chain/messagepool/pruning.go new file mode 100644 index 000000000..24d7fee56 --- /dev/null +++ b/chain/messagepool/pruning.go @@ -0,0 +1,122 @@ +package messagepool + +import ( + "context" + "sort" + "time" + + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + + "github.com/filecoin-project/lotus/chain/types" +) + +func (mp *MessagePool) pruneExcessMessages() error { + mp.curTsLk.Lock() + ts := mp.curTs + mp.curTsLk.Unlock() + + mp.lk.Lock() + defer mp.lk.Unlock() + + mpCfg := mp.getConfig() + if mp.currentSize < mpCfg.SizeLimitHigh { + return nil + } + + select { + case <-mp.pruneCooldown: + err := mp.pruneMessages(context.TODO(), ts) + go func() { + time.Sleep(mpCfg.PruneCooldown) + mp.pruneCooldown <- struct{}{} + }() + return err + default: + return xerrors.New("cannot prune before cooldown") + } +} + +func (mp *MessagePool) pruneMessages(ctx context.Context, ts *types.TipSet) error { + start := time.Now() + defer func() { + log.Infof("message pruning took %s", time.Since(start)) + }() + + baseFee, err := mp.api.ChainComputeBaseFee(ctx, ts) + if err != nil { + return xerrors.Errorf("computing basefee: %w", err) + } + baseFeeLowerBound := getBaseFeeLowerBound(baseFee, baseFeeLowerBoundFactor) + + pending, _ := mp.getPendingMessages(ctx, ts, ts) + + // protected actors -- not pruned + protected := make(map[address.Address]struct{}) + + mpCfg := mp.getConfig() + // we never prune priority addresses + for _, actor := range mpCfg.PriorityAddrs { + pk, err := mp.resolveToKey(ctx, actor) + if err != nil { + log.Debugf("pruneMessages failed to resolve priority address: %s", err) + } + + protected[pk] = struct{}{} + } + + // we also never prune locally published messages + mp.forEachLocal(ctx, func(ctx context.Context, actor address.Address) { + protected[actor] = struct{}{} + }) + + // Collect all messages to track which ones to remove and create chains for block inclusion + pruneMsgs := make(map[cid.Cid]*types.SignedMessage, mp.currentSize) + keepCount := 0 + + var chains []*msgChain + for actor, mset := range pending { + // we never prune protected actors + _, keep := protected[actor] + if keep { + keepCount += len(mset) + continue + } + + // not a protected actor, track the messages and create chains + for _, m := range mset { + pruneMsgs[m.Message.Cid()] = m + } + actorChains := mp.createMessageChains(actor, mset, baseFeeLowerBound, ts) + chains = append(chains, actorChains...) + } + + // Sort the chains + sort.Slice(chains, func(i, j int) bool { + return chains[i].Before(chains[j]) + }) + + // Keep messages (remove them from pruneMsgs) from chains while we are under the low water mark + loWaterMark := mpCfg.SizeLimitLow +keepLoop: + for _, chain := range chains { + for _, m := range chain.msgs { + if keepCount < loWaterMark { + delete(pruneMsgs, m.Message.Cid()) + keepCount++ + } else { + break keepLoop + } + } + } + + // and remove all messages that are still in pruneMsgs after processing the chains + log.Infof("Pruning %d messages", len(pruneMsgs)) + for _, m := range pruneMsgs { + mp.remove(ctx, m.Message.From, m.Message.Nonce, false) + } + + return nil +} diff --git a/chain/messagepool/repub.go b/chain/messagepool/repub.go new file mode 100644 index 000000000..a87d5e08a --- /dev/null +++ b/chain/messagepool/repub.go @@ -0,0 +1,187 @@ +package messagepool + +import ( + "context" + "sort" + "time" + + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/messagepool/gasguess" + "github.com/filecoin-project/lotus/chain/types" +) + +const repubMsgLimit = 30 + +var RepublishBatchDelay = 100 * time.Millisecond + +func (mp *MessagePool) republishPendingMessages(ctx context.Context) error { + mp.curTsLk.RLock() + ts := mp.curTs + mp.curTsLk.RUnlock() + + baseFee, err := mp.api.ChainComputeBaseFee(context.TODO(), ts) + if err != nil { + return xerrors.Errorf("computing basefee: %w", err) + } + baseFeeLowerBound := getBaseFeeLowerBound(baseFee, baseFeeLowerBoundFactor) + + pending := make(map[address.Address]map[uint64]*types.SignedMessage) + + mp.lk.Lock() + mp.republished = nil // clear this to avoid races triggering an early republish + mp.lk.Unlock() + + mp.lk.RLock() + mp.forEachLocal(ctx, func(ctx context.Context, actor address.Address) { + mset, ok, err := mp.getPendingMset(ctx, actor) + if err != nil { + log.Debugf("failed to get mset: %w", err) + return + } + + if !ok { + return + } + if len(mset.msgs) == 0 { + return + } + // we need to copy this while holding the lock to avoid races with concurrent modification + pend := make(map[uint64]*types.SignedMessage, len(mset.msgs)) + for nonce, m := range mset.msgs { + pend[nonce] = m + } + pending[actor] = pend + }) + mp.lk.RUnlock() + + if len(pending) == 0 { + return nil + } + + var chains []*msgChain + for actor, mset := range pending { + // We use the baseFee lower bound for createChange so that we optimistically include + // chains that might become profitable in the next 20 blocks. + // We still check the lowerBound condition for individual messages so that we don't send + // messages that will be rejected by the mpool spam protector, so this is safe to do. + next := mp.createMessageChains(actor, mset, baseFeeLowerBound, ts) + chains = append(chains, next...) + } + + if len(chains) == 0 { + return nil + } + + sort.Slice(chains, func(i, j int) bool { + return chains[i].Before(chains[j]) + }) + + gasLimit := build.BlockGasLimit + minGas := int64(gasguess.MinGas) + var msgs []*types.SignedMessage +loop: + for i := 0; i < len(chains); { + chain := chains[i] + + // we can exceed this if we have picked (some) longer chain already + if len(msgs) > repubMsgLimit { + break + } + + // there is not enough gas for any message + if gasLimit <= minGas { + break + } + + // has the chain been invalidated? + if !chain.valid { + i++ + continue + } + + // does it fit in a block? + if chain.gasLimit <= gasLimit { + // check the baseFee lower bound -- only republish messages that can be included in the chain + // within the next 20 blocks. + for _, m := range chain.msgs { + if m.Message.GasFeeCap.LessThan(baseFeeLowerBound) { + chain.Invalidate() + continue loop + } + gasLimit -= m.Message.GasLimit + msgs = append(msgs, m) + } + + // we processed the whole chain, advance + i++ + continue + } + + // we can't fit the current chain but there is gas to spare + // trim it and push it down + chain.Trim(gasLimit, repubMsgLimit, mp, baseFee) + for j := i; j < len(chains)-1; j++ { + if chains[j].Before(chains[j+1]) { + break + } + chains[j], chains[j+1] = chains[j+1], chains[j] + } + } + + count := 0 + if len(msgs) > repubMsgLimit { + msgs = msgs[:repubMsgLimit] + } + + log.Infof("republishing %d messages", len(msgs)) + for _, m := range msgs { + mb, err := m.Serialize() + if err != nil { + return xerrors.Errorf("cannot serialize message: %w", err) + } + + err = mp.api.PubSubPublish(build.MessagesTopic(mp.netName), mb) + if err != nil { + return xerrors.Errorf("cannot publish: %w", err) + } + + count++ + + if count < len(msgs) { + // this delay is here to encourage the pubsub subsystem to process the messages serially + // and avoid creating nonce gaps because of concurrent validation. + time.Sleep(RepublishBatchDelay) + } + } + + if len(msgs) > 0 { + mp.journal.RecordEvent(mp.evtTypes[evtTypeMpoolRepub], func() interface{} { + msgsEv := make([]MessagePoolEvtMessage, 0, len(msgs)) + for _, m := range msgs { + msgsEv = append(msgsEv, MessagePoolEvtMessage{Message: m.Message, CID: m.Cid()}) + } + return MessagePoolEvt{ + Action: "repub", + Messages: msgsEv, + } + }) + } + + // track most recently republished messages + republished := make(map[cid.Cid]struct{}) + for _, m := range msgs[:count] { + republished[m.Cid()] = struct{}{} + } + + // update the republished set so that we can trigger early republish from head changes + mp.lk.Lock() + mp.republished = republished + mp.lk.Unlock() + + return nil +} diff --git a/chain/messagepool/repub_test.go b/chain/messagepool/repub_test.go new file mode 100644 index 000000000..9cdabc02f --- /dev/null +++ b/chain/messagepool/repub_test.go @@ -0,0 +1,79 @@ +// stm: #unit +package messagepool + +import ( + "context" + "testing" + "time" + + "github.com/ipfs/go-datastore" + + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + + "github.com/filecoin-project/lotus/chain/consensus/filcns" + "github.com/filecoin-project/lotus/chain/messagepool/gasguess" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/wallet" +) + +func TestRepubMessages(t *testing.T) { + //stm: @TOKEN_WALLET_NEW_001 + oldRepublishBatchDelay := RepublishBatchDelay + RepublishBatchDelay = time.Microsecond + defer func() { + RepublishBatchDelay = oldRepublishBatchDelay + }() + + tma := newTestMpoolAPI() + ds := datastore.NewMapDatastore() + + mp, err := New(context.Background(), tma, ds, filcns.DefaultUpgradeSchedule(), "mptest", nil) + if err != nil { + t.Fatal(err) + } + + // the actors + w1, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a1, err := w1.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + w2, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a2, err := w2.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + gasLimit := gasguess.Costs[gasguess.CostKey{Code: builtin2.StorageMarketActorCodeID, M: 2}] + + tma.setBalance(a1, 1) // in FIL + + for i := 0; i < 10; i++ { + m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(i+1)) + //stm: @CHAIN_MEMPOOL_PUSH_001 + _, err := mp.Push(context.TODO(), m, true) + if err != nil { + t.Fatal(err) + } + } + + if tma.published != 10 { + t.Fatalf("expected to have published 10 messages, but got %d instead", tma.published) + } + + mp.repubTrigger <- struct{}{} + time.Sleep(100 * time.Millisecond) + + if tma.published != 20 { + t.Fatalf("expected to have published 20 messages, but got %d instead", tma.published) + } +} diff --git a/chain/messagepool/selection.go b/chain/messagepool/selection.go new file mode 100644 index 000000000..163bd76f9 --- /dev/null +++ b/chain/messagepool/selection.go @@ -0,0 +1,1025 @@ +package messagepool + +import ( + "context" + "math/big" + "math/rand" + "sort" + "time" + + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + tbig "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/crypto" + + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/messagepool/gasguess" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/vm" +) + +var bigBlockGasLimit = big.NewInt(build.BlockGasLimit) + +const MaxBlocks = 15 + +type msgChain struct { + msgs []*types.SignedMessage + gasReward *big.Int + gasLimit int64 + gasPerf float64 + effPerf float64 + bp float64 + parentOffset float64 + valid bool + merged bool + next *msgChain + prev *msgChain + sigType crypto.SigType +} + +func (mp *MessagePool) SelectMessages(ctx context.Context, ts *types.TipSet, tq float64) ([]*types.SignedMessage, error) { + mp.curTsLk.RLock() + defer mp.curTsLk.RUnlock() + + mp.lk.RLock() + defer mp.lk.RUnlock() + + // See if we need to prune before selection; excessive buildup can lead to slow selection, + // so prune if we have too many messages (ignoring the cooldown). + mpCfg := mp.getConfig() + if mp.currentSize > mpCfg.SizeLimitHigh { + log.Infof("too many messages; pruning before selection") + if err := mp.pruneMessages(ctx, ts); err != nil { + log.Warnf("error pruning excess messages: %s", err) + } + } + + // if the ticket quality is high enough that the first block has higher probability + // than any other block, then we don't bother with optimal selection because the + // first block will always have higher effective performance + var sm *selectedMessages + var err error + if tq > 0.84 { + sm, err = mp.selectMessagesGreedy(ctx, mp.curTs, ts) + } else { + sm, err = mp.selectMessagesOptimal(ctx, mp.curTs, ts, tq) + } + + if err != nil { + return nil, err + } + + if sm == nil { + return nil, nil + } + + // one last sanity check + if len(sm.msgs) > build.BlockMessageLimit { + log.Errorf("message selection chose too many messages %d > %d", len(sm.msgs), build.BlockMessageLimit) + sm.msgs = sm.msgs[:build.BlockMessageLimit] + } + + return sm.msgs, nil +} + +type selectedMessages struct { + msgs []*types.SignedMessage + gasLimit int64 + secpLimit int + blsLimit int +} + +// returns false if chain can't be added due to block constraints +func (sm *selectedMessages) tryToAdd(mc *msgChain) bool { + l := len(mc.msgs) + + if build.BlockMessageLimit < l+len(sm.msgs) || sm.gasLimit < mc.gasLimit { + return false + } + + if mc.sigType == crypto.SigTypeBLS { + if sm.blsLimit < l { + return false + } + + sm.msgs = append(sm.msgs, mc.msgs...) + sm.blsLimit -= l + sm.gasLimit -= mc.gasLimit + } else if mc.sigType == crypto.SigTypeSecp256k1 || mc.sigType == crypto.SigTypeDelegated { + if sm.secpLimit < l { + return false + } + + sm.msgs = append(sm.msgs, mc.msgs...) + sm.secpLimit -= l + sm.gasLimit -= mc.gasLimit + } + + // don't add the weird sigType msg, but otherwise proceed + return true +} + +// returns false if messages can't be added due to block constraints +// will trim / invalidate chain as appropriate +func (sm *selectedMessages) tryToAddWithDeps(mc *msgChain, mp *MessagePool, baseFee types.BigInt) bool { + // compute the dependencies that must be merged and the gas limit including deps + chainGasLimit := mc.gasLimit + chainMsgLimit := len(mc.msgs) + depGasLimit := int64(0) + depMsgLimit := 0 + smMsgLimit := 0 + + if mc.sigType == crypto.SigTypeBLS { + smMsgLimit = sm.blsLimit + } else if mc.sigType == crypto.SigTypeSecp256k1 || mc.sigType == crypto.SigTypeDelegated { + smMsgLimit = sm.secpLimit + } else { + return false + } + + if smMsgLimit > build.BlockMessageLimit-len(sm.msgs) { + smMsgLimit = build.BlockMessageLimit - len(sm.msgs) + } + + var chainDeps []*msgChain + for curChain := mc.prev; curChain != nil && !curChain.merged; curChain = curChain.prev { + chainDeps = append(chainDeps, curChain) + chainGasLimit += curChain.gasLimit + chainMsgLimit += len(curChain.msgs) + depGasLimit += curChain.gasLimit + depMsgLimit += len(curChain.msgs) + } + + // the chain doesn't fit as-is, so trim / invalidate it and return false + if chainGasLimit > sm.gasLimit || chainMsgLimit > smMsgLimit { + + // it doesn't all fit; now we have to take into account the dependent chains before + // making a decision about trimming or invalidating. + // if the dependencies exceed the block limits, then we must invalidate the chain + // as it can never be included. + // Otherwise we can just trim and continue + if depGasLimit > sm.gasLimit || depMsgLimit >= smMsgLimit { + mc.Invalidate() + } else { + // dependencies fit, just trim it + mc.Trim(sm.gasLimit-depGasLimit, smMsgLimit-depMsgLimit, mp, baseFee) + } + + return false + } + + // the chain fits! include it together with all dependencies + for i := len(chainDeps) - 1; i >= 0; i-- { + curChain := chainDeps[i] + curChain.merged = true + sm.msgs = append(sm.msgs, curChain.msgs...) + } + + mc.merged = true + + sm.msgs = append(sm.msgs, mc.msgs...) + sm.gasLimit -= chainGasLimit + + if mc.sigType == crypto.SigTypeBLS { + sm.blsLimit -= chainMsgLimit + } else if mc.sigType == crypto.SigTypeSecp256k1 || mc.sigType == crypto.SigTypeDelegated { + sm.secpLimit -= chainMsgLimit + } + + return true +} + +func (sm *selectedMessages) trimChain(mc *msgChain, mp *MessagePool, baseFee types.BigInt) { + msgLimit := build.BlockMessageLimit - len(sm.msgs) + if mc.sigType == crypto.SigTypeBLS { + if msgLimit > sm.blsLimit { + msgLimit = sm.blsLimit + } + } else if mc.sigType == crypto.SigTypeSecp256k1 || mc.sigType == crypto.SigTypeDelegated { + if msgLimit > sm.secpLimit { + msgLimit = sm.secpLimit + } + } + + if mc.gasLimit > sm.gasLimit || len(mc.msgs) > msgLimit { + mc.Trim(sm.gasLimit, msgLimit, mp, baseFee) + } +} + +func (mp *MessagePool) selectMessagesOptimal(ctx context.Context, curTs, ts *types.TipSet, tq float64) (*selectedMessages, error) { + start := time.Now() + + baseFee, err := mp.api.ChainComputeBaseFee(context.TODO(), ts) + if err != nil { + return nil, xerrors.Errorf("computing basefee: %w", err) + } + + // 0. Load messages from the target tipset; if it is the same as the current tipset in + // the mpool, then this is just the pending messages + pending, err := mp.getPendingMessages(ctx, curTs, ts) + if err != nil { + return nil, err + } + + if len(pending) == 0 { + return nil, nil + } + + // defer only here so if we have no pending messages we don't spam + defer func() { + log.Infow("message selection done", "took", time.Since(start)) + }() + + // 0b. Select all priority messages that fit in the block + minGas := int64(gasguess.MinGas) + result := mp.selectPriorityMessages(ctx, pending, baseFee, ts) + + // have we filled the block? + if result.gasLimit < minGas || len(result.msgs) >= build.BlockMessageLimit { + return result, nil + } + + // 1. Create a list of dependent message chains with maximal gas reward per limit consumed + startChains := time.Now() + var chains []*msgChain + for actor, mset := range pending { + next := mp.createMessageChains(actor, mset, baseFee, ts) + chains = append(chains, next...) + } + if dt := time.Since(startChains); dt > time.Millisecond { + log.Infow("create message chains done", "took", dt) + } + + // 2. Sort the chains + sort.Slice(chains, func(i, j int) bool { + return chains[i].Before(chains[j]) + }) + + if len(chains) != 0 && chains[0].gasPerf < 0 { + log.Warnw("all messages in mpool have non-positive gas performance", "bestGasPerf", chains[0].gasPerf) + return result, nil + } + + // 3. Partition chains into blocks (without trimming) + // we use the full blockGasLimit (as opposed to the residual gas limit from the + // priority message selection) as we have to account for what other block providers are doing + nextChain := 0 + partitions := make([][]*msgChain, MaxBlocks) + for i := 0; i < MaxBlocks && nextChain < len(chains); i++ { + gasLimit := build.BlockGasLimit + msgLimit := build.BlockMessageLimit + for nextChain < len(chains) { + chain := chains[nextChain] + nextChain++ + partitions[i] = append(partitions[i], chain) + gasLimit -= chain.gasLimit + msgLimit -= len(chain.msgs) + if gasLimit < minGas || msgLimit <= 0 { + break + } + } + + } + + // 4. Compute effective performance for each chain, based on the partition they fall into + // The effective performance is the gasPerf of the chain * block probability + blockProb := mp.blockProbabilities(tq) + effChains := 0 + for i := 0; i < MaxBlocks; i++ { + for _, chain := range partitions[i] { + chain.SetEffectivePerf(blockProb[i]) + } + effChains += len(partitions[i]) + } + + // nullify the effective performance of chains that don't fit in any partition + for _, chain := range chains[effChains:] { + chain.SetNullEffectivePerf() + } + + // 5. Resort the chains based on effective performance + sort.Slice(chains, func(i, j int) bool { + return chains[i].BeforeEffective(chains[j]) + }) + + // 6. Merge the head chains to produce the list of messages selected for inclusion + // subject to the residual block limits + // When a chain is merged in, all its previous dependent chains *must* also be + // merged in or we'll have a broken block + startMerge := time.Now() + last := len(chains) + for i, chain := range chains { + // did we run out of performing chains? + if chain.gasPerf < 0 { + break + } + + // has it already been merged? + if chain.merged { + continue + } + + if result.tryToAddWithDeps(chain, mp, baseFee) { + // adjust the effective performance for all subsequent chains + if next := chain.next; next != nil && next.effPerf > 0 { + next.effPerf += next.parentOffset + for next = next.next; next != nil && next.effPerf > 0; next = next.next { + next.setEffPerf() + } + } + + // re-sort to account for already merged chains and effective performance adjustments + // the sort *must* be stable or we end up getting negative gasPerfs pushed up. + sort.SliceStable(chains[i+1:], func(i, j int) bool { + return chains[i].BeforeEffective(chains[j]) + }) + + continue + } + + // we can't fit this chain and its dependencies because of block limits -- we are + // at the edge + last = i + break + } + if dt := time.Since(startMerge); dt > time.Millisecond { + log.Infow("merge message chains done", "took", dt) + } + + // 7. We have reached the edge of what can fit wholesale; if we still hae available + // gasLimit to pack some more chains, then trim the last chain and push it down. + // Trimming invalidates subsequent dependent chains so that they can't be selected + // as their dependency cannot be (fully) included. + // We do this in a loop because the blocker might have been inordinately large and + // we might have to do it multiple times to satisfy tail packing + startTail := time.Now() +tailLoop: + for result.gasLimit >= minGas && last < len(chains) { + + if !chains[last].valid { + last++ + continue tailLoop + } + + // trim if necessary + result.trimChain(chains[last], mp, baseFee) + + // push down if it hasn't been invalidated + if chains[last].valid { + for i := last; i < len(chains)-1; i++ { + if chains[i].BeforeEffective(chains[i+1]) { + break + } + chains[i], chains[i+1] = chains[i+1], chains[i] + } + } + + // select the next (valid and fitting) chain and its dependencies for inclusion + for _, chain := range chains[last:] { + // has the chain been invalidated? + if !chain.valid { + continue + } + + // has it already been merged? + if chain.merged { + continue + } + + // if gasPerf < 0 we have no more profitable chains + if chain.gasPerf < 0 { + break tailLoop + } + + if result.tryToAddWithDeps(chain, mp, baseFee) { + continue + } + + continue tailLoop + } + + // the merge loop ended after processing all the chains and we we probably have still + // gas to spare; end the loop. + break + } + if dt := time.Since(startTail); dt > time.Millisecond { + log.Infow("pack tail chains done", "took", dt) + } + + // if we have room to spare, pick some random (non-negative) chains to fill the block + // we pick randomly so that we minimize the probability of duplication among all block producers + if result.gasLimit >= minGas && len(result.msgs) <= build.BlockMessageLimit { + preRandomLength := len(result.msgs) + + startRandom := time.Now() + shuffleChains(chains) + + for _, chain := range chains { + // have we filled the block + if result.gasLimit < minGas || len(result.msgs) >= build.BlockMessageLimit { + break + } + + // has it been merged or invalidated? + if chain.merged || !chain.valid { + continue + } + + // is it negative? + if chain.gasPerf < 0 { + continue + } + + if result.tryToAddWithDeps(chain, mp, baseFee) { + continue + } + + if chain.valid { + // chain got trimmed on the previous call to tryToAddWithDeps, can now be included + result.tryToAddWithDeps(chain, mp, baseFee) + continue + } + } + + if dt := time.Since(startRandom); dt > time.Millisecond { + log.Infow("pack random tail chains done", "took", dt) + } + + if len(result.msgs) != preRandomLength { + log.Warnf("optimal selection failed to pack a block; picked %d messages with random selection", + len(result.msgs)-preRandomLength) + } + } + + return result, nil +} + +func (mp *MessagePool) selectMessagesGreedy(ctx context.Context, curTs, ts *types.TipSet) (*selectedMessages, error) { + start := time.Now() + + baseFee, err := mp.api.ChainComputeBaseFee(context.TODO(), ts) + if err != nil { + return nil, xerrors.Errorf("computing basefee: %w", err) + } + + // 0. Load messages for the target tipset; if it is the same as the current tipset in the mpool + // then this is just the pending messages + pending, err := mp.getPendingMessages(ctx, curTs, ts) + if err != nil { + return nil, err + } + + if len(pending) == 0 { + return nil, nil + } + + // defer only here so if we have no pending messages we don't spam + defer func() { + log.Infow("message selection done", "took", time.Since(start)) + }() + + // 0b. Select all priority messages that fit in the block + minGas := int64(gasguess.MinGas) + result := mp.selectPriorityMessages(ctx, pending, baseFee, ts) + + // have we filled the block? + if result.gasLimit < minGas || len(result.msgs) > build.BlockMessageLimit { + return result, nil + } + + // 1. Create a list of dependent message chains with maximal gas reward per limit consumed + startChains := time.Now() + var chains []*msgChain + for actor, mset := range pending { + next := mp.createMessageChains(actor, mset, baseFee, ts) + chains = append(chains, next...) + } + if dt := time.Since(startChains); dt > time.Millisecond { + log.Infow("create message chains done", "took", dt) + } + + // 2. Sort the chains + sort.Slice(chains, func(i, j int) bool { + return chains[i].Before(chains[j]) + }) + + if len(chains) != 0 && chains[0].gasPerf < 0 { + log.Warnw("all messages in mpool have non-positive gas performance", "bestGasPerf", chains[0].gasPerf) + return result, nil + } + + // 3. Merge the head chains to produce the list of messages selected for inclusion, subject to + // the block gas and message limits. + startMerge := time.Now() + last := len(chains) + for i, chain := range chains { + // did we run out of performing chains? + if chain.gasPerf < 0 { + break + } + + // does it fit in the block? + if result.tryToAdd(chain) { + // there was room, we added the chain, keep going + continue + } + + // we can't fit this chain because of block limits -- we are at the edge + last = i + break + } + if dt := time.Since(startMerge); dt > time.Millisecond { + log.Infow("merge message chains done", "took", dt) + } + + // 4. We have reached the edge of what we can fit wholesale; if we still have available gasLimit + // to pack some more chains, then trim the last chain and push it down. + // Trimming invalidates subsequent dependent chains so that they can't be selected as their + // dependency cannot be (fully) included. + // We do this in a loop because the blocker might have been inordinately large and we might + // have to do it multiple times to satisfy tail packing. + startTail := time.Now() +tailLoop: + for result.gasLimit >= minGas && last < len(chains) { + // trim + result.trimChain(chains[last], mp, baseFee) + + // push down if it hasn't been invalidated + if chains[last].valid { + for i := last; i < len(chains)-1; i++ { + if chains[i].Before(chains[i+1]) { + break + } + chains[i], chains[i+1] = chains[i+1], chains[i] + } + } + + // select the next (valid and fitting) chain for inclusion + for i, chain := range chains[last:] { + // has the chain been invalidated? + if !chain.valid { + continue + } + + // if gasPerf < 0 we have no more profitable chains + if chain.gasPerf < 0 { + break tailLoop + } + + // does it fit in the bock? + if result.tryToAdd(chain) { + // there was room, we added the chain, keep going + continue + } + + // this chain needs to be trimmed + last += i + continue tailLoop + } + + // the merge loop ended after processing all the chains and we probably still have + // gas to spare; end the loop + break + } + if dt := time.Since(startTail); dt > time.Millisecond { + log.Infow("pack tail chains done", "took", dt) + } + + return result, nil +} + +func (mp *MessagePool) selectPriorityMessages(ctx context.Context, pending map[address.Address]map[uint64]*types.SignedMessage, baseFee types.BigInt, ts *types.TipSet) *selectedMessages { + start := time.Now() + defer func() { + if dt := time.Since(start); dt > time.Millisecond { + log.Infow("select priority messages done", "took", dt) + } + }() + mpCfg := mp.getConfig() + result := &selectedMessages{ + msgs: make([]*types.SignedMessage, 0, mpCfg.SizeLimitLow), + gasLimit: build.BlockGasLimit, + blsLimit: cbg.MaxLength, + secpLimit: cbg.MaxLength, + } + minGas := int64(gasguess.MinGas) + + // 1. Get priority actor chains + var chains []*msgChain + priority := mpCfg.PriorityAddrs + for _, actor := range priority { + pk, err := mp.resolveToKey(ctx, actor) + if err != nil { + log.Debugf("mpooladdlocal failed to resolve sender: %s", err) + return result + } + + mset, ok := pending[pk] + if ok { + // remove actor from pending set as we are already processed these messages + delete(pending, pk) + // create chains for the priority actor + next := mp.createMessageChains(actor, mset, baseFee, ts) + chains = append(chains, next...) + } + } + if len(chains) == 0 { + return result + } + + // 2. Sort the chains + sort.Slice(chains, func(i, j int) bool { + return chains[i].Before(chains[j]) + }) + + if len(chains) != 0 && chains[0].gasPerf < 0 { + log.Warnw("all priority messages in mpool have negative gas performance", "bestGasPerf", chains[0].gasPerf) + return result + } + + // 3. Merge chains until the block limit, as long as they have non-negative gas performance + last := len(chains) + for i, chain := range chains { + if chain.gasPerf < 0 { + break + } + + if result.tryToAdd(chain) { + // there was room, we added the chain, keep going + continue + } + + // we can't fit this chain because of block gasLimit -- we are at the edge + last = i + break + } + +tailLoop: + for result.gasLimit >= minGas && last < len(chains) { + // trim, discarding negative performing messages + + result.trimChain(chains[last], mp, baseFee) + + // push down if it hasn't been invalidated + if chains[last].valid { + for i := last; i < len(chains)-1; i++ { + if chains[i].Before(chains[i+1]) { + break + } + chains[i], chains[i+1] = chains[i+1], chains[i] + } + } + + // select the next (valid and fitting) chain for inclusion + for i, chain := range chains[last:] { + // has the chain been invalidated + if !chain.valid { + continue + } + + // if gasPerf < 0 we have no more profitable chains + if chain.gasPerf < 0 { + break tailLoop + } + + // does it fit in the bock? + if result.tryToAdd(chain) { + // there was room, we added the chain, keep going + continue + } + + // this chain needs to be trimmed + last += i + continue tailLoop + } + + // the merge loop ended after processing all the chains and we probably still have gas to spare; + // end the loop + break + } + + return result +} + +func (mp *MessagePool) getPendingMessages(ctx context.Context, curTs, ts *types.TipSet) (map[address.Address]map[uint64]*types.SignedMessage, error) { + start := time.Now() + + result := make(map[address.Address]map[uint64]*types.SignedMessage) + defer func() { + if dt := time.Since(start); dt > time.Millisecond { + log.Infow("get pending messages done", "took", dt) + } + }() + + // are we in sync? + inSync := false + if curTs.Height() == ts.Height() && curTs.Equals(ts) { + inSync = true + } + + mp.forEachPending(func(a address.Address, mset *msgSet) { + if inSync { + // no need to copy the map + result[a] = mset.msgs + } else { + // we need to copy the map to avoid clobbering it as we load more messages + msetCopy := make(map[uint64]*types.SignedMessage, len(mset.msgs)) + for nonce, m := range mset.msgs { + msetCopy[nonce] = m + } + result[a] = msetCopy + + } + }) + + // we are in sync, that's the happy path + if inSync { + return result, nil + } + + if err := mp.runHeadChange(ctx, curTs, ts, result); err != nil { + return nil, xerrors.Errorf("failed to process difference between mpool head and given head: %w", err) + } + + return result, nil +} + +func (*MessagePool) getGasReward(msg *types.SignedMessage, baseFee types.BigInt) *big.Int { + maxPremium := types.BigSub(msg.Message.GasFeeCap, baseFee) + + if types.BigCmp(maxPremium, msg.Message.GasPremium) > 0 { + maxPremium = msg.Message.GasPremium + } + + gasReward := tbig.Mul(maxPremium, types.NewInt(uint64(msg.Message.GasLimit))) + if gasReward.Sign() == -1 { + // penalty multiplier + gasReward = tbig.Mul(gasReward, types.NewInt(3)) + } + return gasReward.Int +} + +func (*MessagePool) getGasPerf(gasReward *big.Int, gasLimit int64) float64 { + // gasPerf = gasReward * build.BlockGasLimit / gasLimit + a := new(big.Rat).SetInt(new(big.Int).Mul(gasReward, bigBlockGasLimit)) + b := big.NewRat(1, gasLimit) + c := new(big.Rat).Mul(a, b) + r, _ := c.Float64() + return r +} + +func (mp *MessagePool) createMessageChains(actor address.Address, mset map[uint64]*types.SignedMessage, baseFee types.BigInt, ts *types.TipSet) []*msgChain { + // collect all messages + msgs := make([]*types.SignedMessage, 0, len(mset)) + for _, m := range mset { + msgs = append(msgs, m) + } + + // sort by nonce + sort.Slice(msgs, func(i, j int) bool { + return msgs[i].Message.Nonce < msgs[j].Message.Nonce + }) + + // sanity checks: + // - there can be no gaps in nonces, starting from the current actor nonce + // if there is a gap, drop messages after the gap, we can't include them + // - all messages must have minimum gas and the total gas for the candidate messages + // cannot exceed the block limit; drop all messages that exceed the limit + // - the total gasReward cannot exceed the actor's balance; drop all messages that exceed + // the balance + a, err := mp.api.GetActorAfter(actor, ts) + if err != nil { + log.Errorf("failed to load actor state, not building chain for %s: %v", actor, err) + return nil + } + + curNonce := a.Nonce + balance := a.Balance.Int + gasLimit := int64(0) + skip := 0 + i := 0 + rewards := make([]*big.Int, 0, len(msgs)) + for i = 0; i < len(msgs); i++ { + m := msgs[i] + + if m.Message.Nonce < curNonce { + log.Warnf("encountered message from actor %s with nonce (%d) less than the current nonce (%d)", + actor, m.Message.Nonce, curNonce) + skip++ + continue + } + + if m.Message.Nonce != curNonce { + break + } + curNonce++ + + minGas := vm.PricelistByEpoch(ts.Height()).OnChainMessage(m.ChainLength()).Total() + if m.Message.GasLimit < minGas { + break + } + + gasLimit += m.Message.GasLimit + if gasLimit > build.BlockGasLimit { + break + } + + required := m.Message.RequiredFunds().Int + if balance.Cmp(required) < 0 { + break + } + + balance = new(big.Int).Sub(balance, required) + + value := m.Message.Value.Int + balance = new(big.Int).Sub(balance, value) + + gasReward := mp.getGasReward(m, baseFee) + rewards = append(rewards, gasReward) + } + + // check we have a sane set of messages to construct the chains + if i > skip { + msgs = msgs[skip:i] + } else { + return nil + } + + // if we have more messages from this sender than can fit in a block, drop the extra ones + if len(msgs) > build.BlockMessageLimit { + msgs = msgs[:build.BlockMessageLimit] + } + + // ok, now we can construct the chains using the messages we have + // invariant: each chain has a bigger gasPerf than the next -- otherwise they can be merged + // and increase the gasPerf of the first chain + // We do this in two passes: + // - in the first pass we create chains that aggregate messages with non-decreasing gasPerf + // - in the second pass we merge chains to maintain the invariant. + var chains []*msgChain + var curChain *msgChain + + newChain := func(m *types.SignedMessage, i int) *msgChain { + chain := new(msgChain) + chain.msgs = []*types.SignedMessage{m} + chain.gasReward = rewards[i] + chain.gasLimit = m.Message.GasLimit + chain.gasPerf = mp.getGasPerf(chain.gasReward, chain.gasLimit) + chain.valid = true + chain.sigType = m.Signature.Type + return chain + } + + // create the individual chains + for i, m := range msgs { + if curChain == nil { + curChain = newChain(m, i) + continue + } + + gasReward := new(big.Int).Add(curChain.gasReward, rewards[i]) + gasLimit := curChain.gasLimit + m.Message.GasLimit + gasPerf := mp.getGasPerf(gasReward, gasLimit) + + // try to add the message to the current chain -- if it decreases the gasPerf, or then make a + // new chain + if gasPerf < curChain.gasPerf { + chains = append(chains, curChain) + curChain = newChain(m, i) + } else { + curChain.msgs = append(curChain.msgs, m) + curChain.gasReward = gasReward + curChain.gasLimit = gasLimit + curChain.gasPerf = gasPerf + } + } + chains = append(chains, curChain) + + // merge chains to maintain the invariant + for { + merged := 0 + + for i := len(chains) - 1; i > 0; i-- { + if chains[i].gasPerf >= chains[i-1].gasPerf { + chains[i-1].msgs = append(chains[i-1].msgs, chains[i].msgs...) + chains[i-1].gasReward = new(big.Int).Add(chains[i-1].gasReward, chains[i].gasReward) + chains[i-1].gasLimit += chains[i].gasLimit + chains[i-1].gasPerf = mp.getGasPerf(chains[i-1].gasReward, chains[i-1].gasLimit) + chains[i].valid = false + merged++ + } + } + + if merged == 0 { + break + } + + // drop invalidated chains + newChains := make([]*msgChain, 0, len(chains)-merged) + for _, c := range chains { + if c.valid { + newChains = append(newChains, c) + } + } + chains = newChains + } + + // link dependent chains + for i := 0; i < len(chains)-1; i++ { + chains[i].next = chains[i+1] + } + + for i := len(chains) - 1; i > 0; i-- { + chains[i].prev = chains[i-1] + } + + return chains +} + +func (mc *msgChain) Before(other *msgChain) bool { + return mc.gasPerf > other.gasPerf || + (mc.gasPerf == other.gasPerf && mc.gasReward.Cmp(other.gasReward) > 0) +} + +func (mc *msgChain) Trim(gasLimit int64, msgLimit int, mp *MessagePool, baseFee types.BigInt) { + i := len(mc.msgs) - 1 + for i >= 0 && (mc.gasLimit > gasLimit || mc.gasPerf < 0 || i >= msgLimit) { + gasReward := mp.getGasReward(mc.msgs[i], baseFee) + mc.gasReward = new(big.Int).Sub(mc.gasReward, gasReward) + mc.gasLimit -= mc.msgs[i].Message.GasLimit + if mc.gasLimit > 0 { + mc.gasPerf = mp.getGasPerf(mc.gasReward, mc.gasLimit) + if mc.bp != 0 { + mc.setEffPerf() + } + } else { + mc.gasPerf = 0 + mc.effPerf = 0 + } + i-- + } + + if i < 0 { + mc.msgs = nil + mc.valid = false + } else { + mc.msgs = mc.msgs[:i+1] + } + + // TODO: if the trim above is a no-op, this (may) needlessly invalidates the next chain + if mc.next != nil { + mc.next.Invalidate() + mc.next = nil + } +} + +func (mc *msgChain) Invalidate() { + mc.valid = false + mc.msgs = nil + if mc.next != nil { + mc.next.Invalidate() + mc.next = nil + } +} + +func (mc *msgChain) SetEffectivePerf(bp float64) { + mc.bp = bp + mc.setEffPerf() +} + +func (mc *msgChain) setEffPerf() { + effPerf := mc.gasPerf * mc.bp + if effPerf > 0 && mc.prev != nil { + effPerfWithParent := (effPerf*float64(mc.gasLimit) + mc.prev.effPerf*float64(mc.prev.gasLimit)) / float64(mc.gasLimit+mc.prev.gasLimit) + mc.parentOffset = effPerf - effPerfWithParent + effPerf = effPerfWithParent + } + mc.effPerf = effPerf + +} + +func (mc *msgChain) SetNullEffectivePerf() { + if mc.gasPerf < 0 { + mc.effPerf = mc.gasPerf + } else { + mc.effPerf = 0 + } +} + +func (mc *msgChain) BeforeEffective(other *msgChain) bool { + // move merged chains to the front so we can discard them earlier + return (mc.merged && !other.merged) || + (mc.gasPerf >= 0 && other.gasPerf < 0) || + mc.effPerf > other.effPerf || + (mc.effPerf == other.effPerf && mc.gasPerf > other.gasPerf) || + (mc.effPerf == other.effPerf && mc.gasPerf == other.gasPerf && mc.gasReward.Cmp(other.gasReward) > 0) +} + +func shuffleChains(lst []*msgChain) { + for i := range lst { + j := rand.Intn(i + 1) + lst[i], lst[j] = lst[j], lst[i] + } +} diff --git a/chain/messagepool/selection_test.go b/chain/messagepool/selection_test.go new file mode 100644 index 000000000..c3a5c6d6f --- /dev/null +++ b/chain/messagepool/selection_test.go @@ -0,0 +1,1692 @@ +// stm: #unit +package messagepool + +import ( + "compress/gzip" + "context" + "encoding/json" + "fmt" + "io" + "math" + "math/big" + "math/rand" + "os" + "sort" + "testing" + + "github.com/ipfs/go-cid" + "github.com/ipfs/go-datastore" + logging "github.com/ipfs/go-log/v2" + cbg "github.com/whyrusleeping/cbor-gen" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/crypto" + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/consensus/filcns" + "github.com/filecoin-project/lotus/chain/messagepool/gasguess" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/mock" + "github.com/filecoin-project/lotus/chain/wallet" + _ "github.com/filecoin-project/lotus/lib/sigs/bls" + _ "github.com/filecoin-project/lotus/lib/sigs/delegated" + _ "github.com/filecoin-project/lotus/lib/sigs/secp" +) + +func init() { + // bump this for the selection tests + MaxActorPendingMessages = 1000000 +} + +func makeTestMessage(w *wallet.LocalWallet, from, to address.Address, nonce uint64, gasLimit int64, gasPrice uint64) *types.SignedMessage { + msg := &types.Message{ + From: from, + To: to, + Method: 2, + Value: types.FromFil(0), + Nonce: nonce, + GasLimit: gasLimit, + GasFeeCap: types.NewInt(100 + gasPrice), + GasPremium: types.NewInt(gasPrice), + } + sig, err := w.WalletSign(context.TODO(), from, msg.Cid().Bytes(), api.MsgMeta{}) + if err != nil { + panic(err) + } + return &types.SignedMessage{ + Message: *msg, + Signature: *sig, + } +} + +func makeTestMpool() (*MessagePool, *testMpoolAPI) { + tma := newTestMpoolAPI() + ds := datastore.NewMapDatastore() + mp, err := New(context.Background(), tma, ds, filcns.DefaultUpgradeSchedule(), "test", nil) + if err != nil { + panic(err) + } + + return mp, tma +} + +func TestMessageChains(t *testing.T) { + //stm: @TOKEN_WALLET_NEW_001 + //stm: @CHAIN_MEMPOOL_CREATE_MSG_CHAINS_001 + mp, tma := makeTestMpool() + + // the actors + w1, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a1, err := w1.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + w2, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a2, err := w2.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + block := tma.nextBlock() + ts := mock.TipSet(block) + + gasLimit := gasguess.Costs[gasguess.CostKey{Code: builtin2.StorageMarketActorCodeID, M: 2}] + + tma.setBalance(a1, 1) // in FIL + + // test chain aggregations + + // test1: 10 messages from a1 to a2, with increasing gasPerf; it should + // make a single chain with 10 messages given enough balance + mset := make(map[uint64]*types.SignedMessage) + for i := 0; i < 10; i++ { + m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(i+1)) + mset[uint64(i)] = m + } + baseFee := types.NewInt(0) + + chains := mp.createMessageChains(a1, mset, baseFee, ts) + if len(chains) != 1 { + t.Fatal("expected a single chain") + } + if len(chains[0].msgs) != 10 { + t.Fatalf("expected 10 messages in the chain but got %d", len(chains[0].msgs)) + } + for i, m := range chains[0].msgs { + if m.Message.Nonce != uint64(i) { + t.Fatalf("expected nonce %d but got %d", i, m.Message.Nonce) + } + } + + // test2 : 10 messages from a1 to a2, with decreasing gasPerf; it should + // make 10 chains with 1 message each + mset = make(map[uint64]*types.SignedMessage) + for i := 0; i < 10; i++ { + m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(10-i)) + mset[uint64(i)] = m + } + + chains = mp.createMessageChains(a1, mset, baseFee, ts) + if len(chains) != 10 { + t.Fatal("expected 10 chains") + } + for i, chain := range chains { + if len(chain.msgs) != 1 { + t.Fatalf("expected 1 message in chain %d but got %d", i, len(chain.msgs)) + } + } + for i, chain := range chains { + m := chain.msgs[0] + if m.Message.Nonce != uint64(i) { + t.Fatalf("expected nonce %d but got %d", i, m.Message.Nonce) + } + } + + // test3a: 10 messages from a1 to a2, with gasPerf increasing in groups of 3; it should + // merge them in two chains, one with 9 messages and one with the last message + mset = make(map[uint64]*types.SignedMessage) + for i := 0; i < 10; i++ { + m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(1+i%3)) + mset[uint64(i)] = m + } + + chains = mp.createMessageChains(a1, mset, baseFee, ts) + if len(chains) != 2 { + t.Fatal("expected 1 chain") + } + + if len(chains[0].msgs) != 9 { + t.Fatalf("expected 9 messages in the chain but got %d", len(chains[0].msgs)) + } + if len(chains[1].msgs) != 1 { + t.Fatalf("expected 1 messages in the chain but got %d", len(chains[1].msgs)) + } + nextNonce := 0 + for _, chain := range chains { + for _, m := range chain.msgs { + if m.Message.Nonce != uint64(nextNonce) { + t.Fatalf("expected nonce %d but got %d", nextNonce, m.Message.Nonce) + } + nextNonce++ + } + } + + // test3b: 10 messages from a1 to a2, with gasPerf decreasing in groups of 3 with a bias for the + // earlier chains; it should make 4 chains, the first 3 with 3 messages and the last with + // a single message + mset = make(map[uint64]*types.SignedMessage) + for i := 0; i < 10; i++ { + bias := (12 - i) / 3 + m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(1+i%3+bias)) + mset[uint64(i)] = m + } + + chains = mp.createMessageChains(a1, mset, baseFee, ts) + if len(chains) != 4 { + t.Fatal("expected 4 chains") + } + for i, chain := range chains { + expectedLen := 3 + if i > 2 { + expectedLen = 1 + } + if len(chain.msgs) != expectedLen { + t.Fatalf("expected %d message in chain %d but got %d", expectedLen, i, len(chain.msgs)) + } + } + nextNonce = 0 + for _, chain := range chains { + for _, m := range chain.msgs { + if m.Message.Nonce != uint64(nextNonce) { + t.Fatalf("expected nonce %d but got %d", nextNonce, m.Message.Nonce) + } + nextNonce++ + } + } + + // test chain breaks + + // test4: 10 messages with non-consecutive nonces; it should make a single chain with just + // the first message + mset = make(map[uint64]*types.SignedMessage) + for i := 0; i < 10; i++ { + m := makeTestMessage(w1, a1, a2, uint64(i*2), gasLimit, uint64(i+1)) + mset[uint64(i)] = m + } + + chains = mp.createMessageChains(a1, mset, baseFee, ts) + if len(chains) != 1 { + t.Fatal("expected a single chain") + } + if len(chains[0].msgs) != 1 { + t.Fatalf("expected 1 message in the chain but got %d", len(chains[0].msgs)) + } + for i, m := range chains[0].msgs { + if m.Message.Nonce != uint64(i) { + t.Fatalf("expected nonce %d but got %d", i, m.Message.Nonce) + } + } + + // test5: 10 messages with increasing gasLimit, except for the 6th message which has less than + // the epoch gasLimit; it should create a single chain with the first 5 messages + mset = make(map[uint64]*types.SignedMessage) + for i := 0; i < 10; i++ { + var m *types.SignedMessage + if i != 5 { + m = makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(i+1)) + } else { + m = makeTestMessage(w1, a1, a2, uint64(i), 1, uint64(i+1)) + } + mset[uint64(i)] = m + } + + chains = mp.createMessageChains(a1, mset, baseFee, ts) + if len(chains) != 1 { + t.Fatal("expected a single chain") + } + if len(chains[0].msgs) != 5 { + t.Fatalf("expected 5 message in the chain but got %d", len(chains[0].msgs)) + } + for i, m := range chains[0].msgs { + if m.Message.Nonce != uint64(i) { + t.Fatalf("expected nonce %d but got %d", i, m.Message.Nonce) + } + } + + // test6: one more message than what can fit in a block according to gas limit, with increasing + // gasPerf; it should create a single chain with the max messages + maxMessages := int(build.BlockGasLimit / gasLimit) + nMessages := maxMessages + 1 + + mset = make(map[uint64]*types.SignedMessage) + for i := 0; i < nMessages; i++ { + mset[uint64(i)] = makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(i+1)) + } + + chains = mp.createMessageChains(a1, mset, baseFee, ts) + if len(chains) != 1 { + t.Fatal("expected a single chain") + } + if len(chains[0].msgs) != maxMessages { + t.Fatalf("expected %d message in the chain but got %d", maxMessages, len(chains[0].msgs)) + } + for i, m := range chains[0].msgs { + if m.Message.Nonce != uint64(i) { + t.Fatalf("expected nonce %d but got %d", i, m.Message.Nonce) + } + } + + // test5: insufficient balance for all messages + tma.setBalanceRaw(a1, types.NewInt(uint64((300)*gasLimit+1))) + + mset = make(map[uint64]*types.SignedMessage) + for i := 0; i < 10; i++ { + mset[uint64(i)] = makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(i+1)) + } + + chains = mp.createMessageChains(a1, mset, baseFee, ts) + if len(chains) != 1 { + t.Fatalf("expected a single chain: got %d", len(chains)) + } + if len(chains[0].msgs) != 2 { + t.Fatalf("expected %d message in the chain but got %d", 2, len(chains[0].msgs)) + } + for i, m := range chains[0].msgs { + if m.Message.Nonce != uint64(i) { + t.Fatalf("expected nonce %d but got %d", i, m.Message.Nonce) + } + } + +} + +func TestMessageChainSkipping(t *testing.T) { + //stm: @TOKEN_WALLET_NEW_001, @CHAIN_MEMPOOL_CREATE_MSG_CHAINS_001 + + // regression test for chain skip bug + + mp, tma := makeTestMpool() + + // the actors + w1, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a1, err := w1.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + w2, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a2, err := w2.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + block := tma.nextBlock() + ts := mock.TipSet(block) + + gasLimit := gasguess.Costs[gasguess.CostKey{Code: builtin2.StorageMarketActorCodeID, M: 2}] + baseFee := types.NewInt(0) + + tma.setBalance(a1, 1) // in FIL + tma.setStateNonce(a1, 10) + + mset := make(map[uint64]*types.SignedMessage) + for i := 0; i < 20; i++ { + bias := (20 - i) / 3 + m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(1+i%3+bias)) + mset[uint64(i)] = m + } + + chains := mp.createMessageChains(a1, mset, baseFee, ts) + if len(chains) != 4 { + t.Fatalf("expected 4 chains, got %d", len(chains)) + } + for i, chain := range chains { + var expectedLen int + switch { + case i == 0: + expectedLen = 2 + case i > 2: + expectedLen = 2 + default: + expectedLen = 3 + } + if len(chain.msgs) != expectedLen { + t.Fatalf("expected %d message in chain %d but got %d", expectedLen, i, len(chain.msgs)) + } + } + nextNonce := 10 + for _, chain := range chains { + for _, m := range chain.msgs { + if m.Message.Nonce != uint64(nextNonce) { + t.Fatalf("expected nonce %d but got %d", nextNonce, m.Message.Nonce) + } + nextNonce++ + } + } + +} + +func TestBasicMessageSelection(t *testing.T) { + //stm: @TOKEN_WALLET_NEW_001, @CHAIN_MEMPOOL_SELECT_001 + oldMaxNonceGap := MaxNonceGap + MaxNonceGap = 1000 + defer func() { + MaxNonceGap = oldMaxNonceGap + }() + + mp, tma := makeTestMpool() + + // the actors + w1, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a1, err := w1.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + w2, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a2, err := w2.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + block := tma.nextBlock() + ts := mock.TipSet(block) + tma.applyBlock(t, block) + + gasLimit := gasguess.Costs[gasguess.CostKey{Code: builtin2.StorageMarketActorCodeID, M: 2}] + + tma.setBalance(a1, 1) // in FIL + tma.setBalance(a2, 1) // in FIL + + // we create 10 messages from each actor to another, with the first actor paying higher + // gas prices than the second; we expect message selection to order his messages first + for i := 0; i < 10; i++ { + m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(2*i+1)) + mustAdd(t, mp, m) + } + + for i := 0; i < 10; i++ { + m := makeTestMessage(w2, a2, a1, uint64(i), gasLimit, uint64(i+1)) + mustAdd(t, mp, m) + } + + msgs, err := mp.SelectMessages(context.Background(), ts, 1.0) + if err != nil { + t.Fatal(err) + } + + if len(msgs) != 20 { + t.Fatalf("exptected 20 messages, got %d", len(msgs)) + } + + nextNonce := 0 + for i := 0; i < 10; i++ { + if msgs[i].Message.From != a1 { + t.Fatalf("expected message from actor a1") + } + if msgs[i].Message.Nonce != uint64(nextNonce) { + t.Fatalf("expected nonce %d, got %d", msgs[i].Message.Nonce, nextNonce) + } + nextNonce++ + } + + nextNonce = 0 + for i := 10; i < 20; i++ { + if msgs[i].Message.From != a2 { + t.Fatalf("expected message from actor a2") + } + if msgs[i].Message.Nonce != uint64(nextNonce) { + t.Fatalf("expected nonce %d, got %d", msgs[i].Message.Nonce, nextNonce) + } + nextNonce++ + } + + // now we make a block with all the messages and advance the chain + block2 := tma.nextBlock() + tma.setBlockMessages(block2, msgs...) + tma.applyBlock(t, block2) + + // we should have no pending messages in the mpool + pend, _ := mp.Pending(context.TODO()) + if len(pend) != 0 { + t.Fatalf("expected no pending messages, but got %d", len(pend)) + } + + // create a block and advance the chain without applying to the mpool + msgs = nil + for i := 10; i < 20; i++ { + m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(2*i+1)) + msgs = append(msgs, m) + m = makeTestMessage(w2, a2, a1, uint64(i), gasLimit, uint64(i+1)) + msgs = append(msgs, m) + } + block3 := tma.nextBlock() + tma.setBlockMessages(block3, msgs...) + ts3 := mock.TipSet(block3) + + // now create another set of messages and add them to the mpool + for i := 20; i < 30; i++ { + m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(2*i+200)) + mustAdd(t, mp, m) + m = makeTestMessage(w2, a2, a1, uint64(i), gasLimit, uint64(i+1)) + mustAdd(t, mp, m) + } + + // select messages in the last tipset; this should include the missed messages as well as + // the last messages we added, with the first actor's messages first + // first we need to update the nonce on the tma + tma.setStateNonce(a1, 10) + tma.setStateNonce(a2, 10) + + msgs, err = mp.SelectMessages(context.Background(), ts3, 1.0) + if err != nil { + t.Fatal(err) + } + if len(msgs) != 20 { + t.Fatalf("expected 20 messages, got %d", len(msgs)) + } + + nextNonce = 20 + for i := 0; i < 10; i++ { + if msgs[i].Message.From != a1 { + t.Fatalf("expected message from actor a1") + } + if msgs[i].Message.Nonce != uint64(nextNonce) { + t.Fatalf("expected nonce %d, got %d", msgs[i].Message.Nonce, nextNonce) + } + nextNonce++ + } + + nextNonce = 20 + for i := 10; i < 20; i++ { + if msgs[i].Message.From != a2 { + t.Fatalf("expected message from actor a2") + } + if msgs[i].Message.Nonce != uint64(nextNonce) { + t.Fatalf("expected nonce %d, got %d", msgs[i].Message.Nonce, nextNonce) + } + nextNonce++ + } +} + +func TestMessageSelectionTrimmingGas(t *testing.T) { + //stm: @TOKEN_WALLET_NEW_001, @CHAIN_MEMPOOL_SELECT_001 + mp, tma := makeTestMpool() + + // the actors + w1, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a1, err := w1.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + w2, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a2, err := w2.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + block := tma.nextBlock() + ts := mock.TipSet(block) + tma.applyBlock(t, block) + + gasLimit := gasguess.Costs[gasguess.CostKey{Code: builtin2.StorageMarketActorCodeID, M: 2}] + + tma.setBalance(a1, 1) // in FIL + tma.setBalance(a2, 1) // in FIL + + // make many small chains for the two actors + nMessages := int((build.BlockGasLimit / gasLimit) + 1) + for i := 0; i < nMessages; i++ { + bias := (nMessages - i) / 3 + m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(1+i%3+bias)) + mustAdd(t, mp, m) + m = makeTestMessage(w2, a2, a1, uint64(i), gasLimit, uint64(1+i%3+bias)) + mustAdd(t, mp, m) + } + + msgs, err := mp.SelectMessages(context.Background(), ts, 1.0) + if err != nil { + t.Fatal(err) + } + + expected := int(build.BlockGasLimit / gasLimit) + if len(msgs) != expected { + t.Fatalf("expected %d messages, but got %d", expected, len(msgs)) + } + + mGasLimit := int64(0) + for _, m := range msgs { + mGasLimit += m.Message.GasLimit + } + if mGasLimit > build.BlockGasLimit { + t.Fatal("selected messages gas limit exceeds block gas limit!") + } + +} + +func TestMessageSelectionTrimmingMsgsBasic(t *testing.T) { + //stm: @TOKEN_WALLET_NEW_001, @CHAIN_MEMPOOL_SELECT_001 + mp, tma := makeTestMpool() + + // the actors + w1, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a1, err := w1.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + block := tma.nextBlock() + ts := mock.TipSet(block) + tma.applyBlock(t, block) + + tma.setBalance(a1, 1) // in FIL + + // create a larger than selectable chain + for i := 0; i < build.BlockMessageLimit; i++ { + m := makeTestMessage(w1, a1, a1, uint64(i), 300000, 100) + mustAdd(t, mp, m) + } + + msgs, err := mp.SelectMessages(context.Background(), ts, 1.0) + if err != nil { + t.Fatal(err) + } + + expected := cbg.MaxLength + if len(msgs) != expected { + t.Fatalf("expected %d messages, but got %d", expected, len(msgs)) + } + + mGasLimit := int64(0) + for _, m := range msgs { + mGasLimit += m.Message.GasLimit + } + if mGasLimit > build.BlockGasLimit { + t.Fatal("selected messages gas limit exceeds block gas limit!") + } + +} + +func TestMessageSelectionTrimmingMsgsTwoSendersBasic(t *testing.T) { + //stm: @TOKEN_WALLET_NEW_001, @CHAIN_MEMPOOL_SELECT_001 + mp, tma := makeTestMpool() + + // the actors + w1, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a1, err := w1.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + w2, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a2, err := w2.WalletNew(context.Background(), types.KTBLS) + if err != nil { + t.Fatal(err) + } + + block := tma.nextBlock() + ts := mock.TipSet(block) + tma.applyBlock(t, block) + + tma.setBalance(a1, 1) // in FIL + tma.setBalance(a2, 1) // in FIL + + // create 2 larger than selectable chains + for i := 0; i < build.BlockMessageLimit; i++ { + m := makeTestMessage(w1, a1, a2, uint64(i), 300000, 100) + mustAdd(t, mp, m) + // a2's messages are preferred + m = makeTestMessage(w2, a2, a1, uint64(i), 300000, 1000) + mustAdd(t, mp, m) + } + + msgs, err := mp.SelectMessages(context.Background(), ts, 1.0) + if err != nil { + t.Fatal(err) + } + + mGasLimit := int64(0) + counts := make(map[crypto.SigType]uint) + for _, m := range msgs { + mGasLimit += m.Message.GasLimit + counts[m.Signature.Type]++ + } + + if mGasLimit > build.BlockGasLimit { + t.Fatal("selected messages gas limit exceeds block gas limit!") + } + + expected := build.BlockMessageLimit + if len(msgs) != expected { + t.Fatalf("expected %d messages, but got %d", expected, len(msgs)) + } + + if counts[crypto.SigTypeBLS] != cbg.MaxLength { + t.Fatalf("expected %d bls messages, but got %d", cbg.MaxLength, len(msgs)) + } +} + +func TestMessageSelectionTrimmingMsgsTwoSendersAdvanced(t *testing.T) { + //stm: @TOKEN_WALLET_NEW_001, @CHAIN_MEMPOOL_SELECT_001 + mp, tma := makeTestMpool() + + // the actors + w1, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a1, err := w1.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + w2, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a2, err := w2.WalletNew(context.Background(), types.KTBLS) + if err != nil { + t.Fatal(err) + } + + block := tma.nextBlock() + ts := mock.TipSet(block) + tma.applyBlock(t, block) + + tma.setBalance(a1, 1) // in FIL + tma.setBalance(a2, 1) // in FIL + + // create 2 almost max-length chains of equal value + i := 0 + for i = 0; i < cbg.MaxLength-1; i++ { + m := makeTestMessage(w1, a1, a2, uint64(i), 300000, 100) + mustAdd(t, mp, m) + // a2's messages are preferred + m = makeTestMessage(w2, a2, a1, uint64(i), 300000, 100) + mustAdd(t, mp, m) + } + + // a1's 8192th message is worth more than a2's + m := makeTestMessage(w1, a1, a2, uint64(i), 300000, 1000) + mustAdd(t, mp, m) + + m = makeTestMessage(w2, a2, a1, uint64(i), 300000, 100) + mustAdd(t, mp, m) + + i++ + + // a2's (unselectable) 8193rd message is worth SO MUCH + m = makeTestMessage(w2, a2, a1, uint64(i), 300000, 1000000) + mustAdd(t, mp, m) + + msgs, err := mp.SelectMessages(context.Background(), ts, 1.0) + if err != nil { + t.Fatal(err) + } + + mGasLimit := int64(0) + counts := make(map[crypto.SigType]uint) + for _, m := range msgs { + mGasLimit += m.Message.GasLimit + counts[m.Signature.Type]++ + } + + if mGasLimit > build.BlockGasLimit { + t.Fatal("selected messages gas limit exceeds block gas limit!") + } + + expected := build.BlockMessageLimit + if len(msgs) != expected { + t.Fatalf("expected %d messages, but got %d", expected, len(msgs)) + } + + // we should have taken the secp chain + if counts[crypto.SigTypeSecp256k1] != cbg.MaxLength { + t.Fatalf("expected %d bls messages, but got %d", cbg.MaxLength, len(msgs)) + } +} + +func TestPriorityMessageSelection(t *testing.T) { + //stm: @TOKEN_WALLET_NEW_001, @CHAIN_MEMPOOL_SELECT_001 + mp, tma := makeTestMpool() + + // the actors + w1, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a1, err := w1.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + w2, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a2, err := w2.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + block := tma.nextBlock() + ts := mock.TipSet(block) + tma.applyBlock(t, block) + + gasLimit := gasguess.Costs[gasguess.CostKey{Code: builtin2.StorageMarketActorCodeID, M: 2}] + + tma.setBalance(a1, 1) // in FIL + tma.setBalance(a2, 1) // in FIL + + mp.cfg.PriorityAddrs = []address.Address{a1} + + nMessages := 10 + for i := 0; i < nMessages; i++ { + bias := (nMessages - i) / 3 + m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(1+i%3+bias)) + mustAdd(t, mp, m) + m = makeTestMessage(w2, a2, a1, uint64(i), gasLimit, uint64(1+i%3+bias)) + mustAdd(t, mp, m) + } + + msgs, err := mp.SelectMessages(context.Background(), ts, 1.0) + if err != nil { + t.Fatal(err) + } + + if len(msgs) != 20 { + t.Fatalf("expected 20 messages but got %d", len(msgs)) + } + + // messages from a1 must be first + nextNonce := uint64(0) + for i := 0; i < 10; i++ { + m := msgs[i] + if m.Message.From != a1 { + t.Fatal("expected messages from a1 before messages from a2") + } + if m.Message.Nonce != nextNonce { + t.Fatalf("expected nonce %d but got %d", nextNonce, m.Message.Nonce) + } + nextNonce++ + } + + nextNonce = 0 + for i := 10; i < 20; i++ { + m := msgs[i] + if m.Message.From != a2 { + t.Fatal("expected messages from a2 after messages from a1") + } + if m.Message.Nonce != nextNonce { + t.Fatalf("expected nonce %d but got %d", nextNonce, m.Message.Nonce) + } + nextNonce++ + } +} + +func TestPriorityMessageSelection2(t *testing.T) { + //stm: @TOKEN_WALLET_NEW_001, @CHAIN_MEMPOOL_SELECT_001 + mp, tma := makeTestMpool() + + // the actors + w1, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a1, err := w1.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + w2, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a2, err := w2.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + block := tma.nextBlock() + ts := mock.TipSet(block) + tma.applyBlock(t, block) + + gasLimit := gasguess.Costs[gasguess.CostKey{Code: builtin2.StorageMarketActorCodeID, M: 2}] + + tma.setBalance(a1, 1) // in FIL + tma.setBalance(a2, 1) // in FIL + + mp.cfg.PriorityAddrs = []address.Address{a1} + + nMessages := int(2 * build.BlockGasLimit / gasLimit) + for i := 0; i < nMessages; i++ { + bias := (nMessages - i) / 3 + m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(1+i%3+bias)) + mustAdd(t, mp, m) + m = makeTestMessage(w2, a2, a1, uint64(i), gasLimit, uint64(1+i%3+bias)) + mustAdd(t, mp, m) + } + + msgs, err := mp.SelectMessages(context.Background(), ts, 1.0) + if err != nil { + t.Fatal(err) + } + + expectedMsgs := int(build.BlockGasLimit / gasLimit) + if len(msgs) != expectedMsgs { + t.Fatalf("expected %d messages but got %d", expectedMsgs, len(msgs)) + } + + // all messages must be from a1 + nextNonce := uint64(0) + for _, m := range msgs { + if m.Message.From != a1 { + t.Fatal("expected messages from a1 before messages from a2") + } + if m.Message.Nonce != nextNonce { + t.Fatalf("expected nonce %d but got %d", nextNonce, m.Message.Nonce) + } + nextNonce++ + } +} + +func TestPriorityMessageSelection3(t *testing.T) { + //stm: @TOKEN_WALLET_NEW_001, @CHAIN_MEMPOOL_SELECT_001 + mp, tma := makeTestMpool() + + // the actors + w1, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a1, err := w1.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + w2, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a2, err := w2.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + block := tma.nextBlock() + ts := mock.TipSet(block) + tma.applyBlock(t, block) + + gasLimit := gasguess.Costs[gasguess.CostKey{Code: builtin2.StorageMarketActorCodeID, M: 2}] + + tma.setBalance(a1, 1) // in FIL + tma.setBalance(a2, 1) // in FIL + + mp.cfg.PriorityAddrs = []address.Address{a1} + + tma.baseFee = types.NewInt(1000) + nMessages := 10 + for i := 0; i < nMessages; i++ { + bias := (nMessages - i) / 3 + m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(1000+i%3+bias)) + mustAdd(t, mp, m) + // messages from a2 have negative performance + m = makeTestMessage(w2, a2, a1, uint64(i), gasLimit, 100) + mustAdd(t, mp, m) + } + + // test greedy selection + msgs, err := mp.SelectMessages(context.Background(), ts, 1.0) + if err != nil { + t.Fatal(err) + } + + expectedMsgs := 10 + if len(msgs) != expectedMsgs { + t.Fatalf("expected %d messages but got %d", expectedMsgs, len(msgs)) + } + + // all messages must be from a1 + nextNonce := uint64(0) + for _, m := range msgs { + if m.Message.From != a1 { + t.Fatal("expected messages from a1 before messages from a2") + } + if m.Message.Nonce != nextNonce { + t.Fatalf("expected nonce %d but got %d", nextNonce, m.Message.Nonce) + } + nextNonce++ + } + + // test optimal selection + msgs, err = mp.SelectMessages(context.Background(), ts, 0.1) + if err != nil { + t.Fatal(err) + } + + expectedMsgs = 10 + if len(msgs) != expectedMsgs { + t.Fatalf("expected %d messages but got %d", expectedMsgs, len(msgs)) + } + + // all messages must be from a1 + nextNonce = uint64(0) + for _, m := range msgs { + if m.Message.From != a1 { + t.Fatal("expected messages from a1 before messages from a2") + } + if m.Message.Nonce != nextNonce { + t.Fatalf("expected nonce %d but got %d", nextNonce, m.Message.Nonce) + } + nextNonce++ + } + +} + +func TestOptimalMessageSelection1(t *testing.T) { + //stm: @TOKEN_WALLET_NEW_001, @CHAIN_MEMPOOL_SELECT_001 + + // this test uses just a single actor sending messages with a low tq + // the chain depenent merging algorithm should pick messages from the actor + // from the start + mp, tma := makeTestMpool() + + // the actors + w1, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a1, err := w1.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + w2, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a2, err := w2.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + block := tma.nextBlock() + ts := mock.TipSet(block) + tma.applyBlock(t, block) + + gasLimit := gasguess.Costs[gasguess.CostKey{Code: builtin2.StorageMarketActorCodeID, M: 2}] + + tma.setBalance(a1, 1) // in FIL + tma.setBalance(a2, 1) // in FIL + + nMessages := int(10 * build.BlockGasLimit / gasLimit) + for i := 0; i < nMessages; i++ { + bias := (nMessages - i) / 3 + m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(1+i%3+bias)) + mustAdd(t, mp, m) + } + + msgs, err := mp.SelectMessages(context.Background(), ts, 0.25) + if err != nil { + t.Fatal(err) + } + + expectedMsgs := int(build.BlockGasLimit / gasLimit) + if len(msgs) != expectedMsgs { + t.Fatalf("expected %d messages, but got %d", expectedMsgs, len(msgs)) + } + + nextNonce := uint64(0) + for _, m := range msgs { + if m.Message.From != a1 { + t.Fatal("expected message from a1") + } + + if m.Message.Nonce != nextNonce { + t.Fatalf("expected nonce %d but got %d", nextNonce, m.Message.Nonce) + } + nextNonce++ + } +} + +func TestOptimalMessageSelection2(t *testing.T) { + //stm: @TOKEN_WALLET_NEW_001, @CHAIN_MEMPOOL_SELECT_001 + + // this test uses two actors sending messages to each other, with the first + // actor paying (much) higher gas premium than the second. + // We select with a low ticket quality; the chain depenent merging algorithm should pick + // messages from the second actor from the start + mp, tma := makeTestMpool() + + // the actors + w1, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a1, err := w1.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + w2, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a2, err := w2.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + block := tma.nextBlock() + ts := mock.TipSet(block) + tma.applyBlock(t, block) + + gasLimit := gasguess.Costs[gasguess.CostKey{Code: builtin2.StorageMarketActorCodeID, M: 2}] + + tma.setBalance(a1, 1) // in FIL + tma.setBalance(a2, 1) // in FIL + + nMessages := int(5 * build.BlockGasLimit / gasLimit) + for i := 0; i < nMessages; i++ { + bias := (nMessages - i) / 3 + m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(200000+i%3+bias)) + mustAdd(t, mp, m) + m = makeTestMessage(w2, a2, a1, uint64(i), gasLimit, uint64(190000+i%3+bias)) + mustAdd(t, mp, m) + } + + msgs, err := mp.SelectMessages(context.Background(), ts, 0.1) + if err != nil { + t.Fatal(err) + } + + expectedMsgs := int(build.BlockGasLimit / gasLimit) + if len(msgs) != expectedMsgs { + t.Fatalf("expected %d messages, but got %d", expectedMsgs, len(msgs)) + } + + var nFrom1, nFrom2 int + var nextNonce1, nextNonce2 uint64 + for _, m := range msgs { + if m.Message.From == a1 { + if m.Message.Nonce != nextNonce1 { + t.Fatalf("expected nonce %d but got %d", nextNonce1, m.Message.Nonce) + } + nextNonce1++ + nFrom1++ + } else { + if m.Message.Nonce != nextNonce2 { + t.Fatalf("expected nonce %d but got %d", nextNonce2, m.Message.Nonce) + } + nextNonce2++ + nFrom2++ + } + } + + if nFrom1 > nFrom2 { + t.Fatalf("expected more messages from a2 than a1; nFrom1=%d nFrom2=%d", nFrom1, nFrom2) + } +} + +func TestOptimalMessageSelection3(t *testing.T) { + //stm: @TOKEN_WALLET_NEW_001, @CHAIN_MEMPOOL_SELECT_001 + + // this test uses 10 actors sending a block of messages to each other, with the the first + // actors paying higher gas premium than the subsequent actors. + // We select with a low ticket quality; the chain dependent merging algorithm should pick + // messages from the median actor from the start + mp, tma := makeTestMpool() + + nActors := 10 + // the actors + var actors []address.Address + var wallets []*wallet.LocalWallet + + for i := 0; i < nActors; i++ { + w, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a, err := w.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + actors = append(actors, a) + wallets = append(wallets, w) + } + + block := tma.nextBlock() + ts := mock.TipSet(block) + tma.applyBlock(t, block) + + gasLimit := gasguess.Costs[gasguess.CostKey{Code: builtin2.StorageMarketActorCodeID, M: 2}] + + for _, a := range actors { + tma.setBalance(a, 1) // in FIL + } + + nMessages := int(build.BlockGasLimit/gasLimit) + 1 + for i := 0; i < nMessages; i++ { + for j := 0; j < nActors; j++ { + premium := 500000 + 10000*(nActors-j) + (nMessages+2-i)/(30*nActors) + i%3 + m := makeTestMessage(wallets[j], actors[j], actors[j%nActors], uint64(i), gasLimit, uint64(premium)) + mustAdd(t, mp, m) + } + } + + msgs, err := mp.SelectMessages(context.Background(), ts, 0.1) + if err != nil { + t.Fatal(err) + } + + expectedMsgs := int(build.BlockGasLimit / gasLimit) + if len(msgs) != expectedMsgs { + t.Fatalf("expected %d messages, but got %d", expectedMsgs, len(msgs)) + } + + whoIs := func(a address.Address) int { + for i, aa := range actors { + if a == aa { + return i + } + } + return -1 + } + + nonces := make([]uint64, nActors) + for _, m := range msgs { + who := whoIs(m.Message.From) + if who < 3 { + t.Fatalf("got message from %dth actor", who) + } + + nextNonce := nonces[who] + if m.Message.Nonce != nextNonce { + t.Fatalf("expected nonce %d but got %d", nextNonce, m.Message.Nonce) + } + nonces[who]++ + } +} + +func testCompetitiveMessageSelection(t *testing.T, rng *rand.Rand, getPremium func() uint64) (float64, float64, float64) { + // in this test we use 300 actors and send 10 blocks of messages. + // actors send with an randomly distributed premium dictated by the getPremium function. + // a number of miners select with varying ticket quality and we compare the + // capacity and rewards of greedy selection -vs- optimal selection + mp, tma := makeTestMpool() + + nActors := 300 + // the actors + var actors []address.Address + var wallets []*wallet.LocalWallet + + for i := 0; i < nActors; i++ { + w, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a, err := w.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + actors = append(actors, a) + wallets = append(wallets, w) + } + + block := tma.nextBlock() + ts := mock.TipSet(block) + tma.applyBlock(t, block) + + gasLimit := gasguess.Costs[gasguess.CostKey{Code: builtin2.StorageMarketActorCodeID, M: 2}] + baseFee := types.NewInt(0) + + for _, a := range actors { + tma.setBalance(a, 1) // in FIL + } + + nMessages := 10 * int(build.BlockGasLimit/gasLimit) + t.Log("nMessages", nMessages) + nonces := make([]uint64, nActors) + for i := 0; i < nMessages; i++ { + from := rng.Intn(nActors) + to := rng.Intn(nActors) + nonce := nonces[from] + nonces[from]++ + premium := getPremium() + m := makeTestMessage(wallets[from], actors[from], actors[to], nonce, gasLimit, premium) + mustAdd(t, mp, m) + } + + logging.SetLogLevel("messagepool", "error") + + // 1. greedy selection + gm, err := mp.selectMessagesGreedy(context.Background(), ts, ts) + if err != nil { + t.Fatal(err) + } + + greedyMsgs := gm.msgs + + totalGreedyCapacity := 0.0 + totalGreedyReward := 0.0 + totalOptimalCapacity := 0.0 + totalOptimalReward := 0.0 + totalBestTQReward := 0.0 + const runs = 1 + for i := 0; i < runs; i++ { + // 2. optimal selection + minersRand := rng.Float64() + winerProba := noWinnersProb() + i := 0 + for ; i < MaxBlocks && minersRand > 0; i++ { + minersRand -= winerProba[i] + } + nMiners := i - 1 + if nMiners < 1 { + nMiners = 1 + } + + optMsgs := make(map[cid.Cid]*types.SignedMessage) + bestTq := 0.0 + var bestMsgs []*types.SignedMessage + for j := 0; j < nMiners; j++ { + tq := rng.Float64() + msgs, err := mp.SelectMessages(context.Background(), ts, tq) + if err != nil { + t.Fatal(err) + } + if tq > bestTq { + bestMsgs = msgs + } + + for _, m := range msgs { + optMsgs[m.Cid()] = m + } + } + + totalGreedyCapacity += float64(len(greedyMsgs)) + totalOptimalCapacity += float64(len(optMsgs)) + boost := float64(len(optMsgs)) / float64(len(greedyMsgs)) + + t.Logf("nMiners: %d", nMiners) + t.Logf("greedy capacity %d, optimal capacity %d (x %.1f )", len(greedyMsgs), + len(optMsgs), boost) + if len(greedyMsgs) > len(optMsgs) { + t.Errorf("greedy capacity higher than optimal capacity; wtf") + } + + greedyReward := big.NewInt(0) + for _, m := range greedyMsgs { + greedyReward.Add(greedyReward, mp.getGasReward(m, baseFee)) + } + + optReward := big.NewInt(0) + for _, m := range optMsgs { + optReward.Add(optReward, mp.getGasReward(m, baseFee)) + } + + bestTqReward := big.NewInt(0) + for _, m := range bestMsgs { + bestTqReward.Add(bestTqReward, mp.getGasReward(m, baseFee)) + } + + totalBestTQReward += float64(bestTqReward.Uint64()) + + nMinersBig := big.NewInt(int64(nMiners)) + greedyAvgReward, _ := new(big.Rat).SetFrac(greedyReward, nMinersBig).Float64() + totalGreedyReward += greedyAvgReward + optimalAvgReward, _ := new(big.Rat).SetFrac(optReward, nMinersBig).Float64() + totalOptimalReward += optimalAvgReward + + boost = optimalAvgReward / greedyAvgReward + t.Logf("greedy reward: %.0f, optimal reward: %.0f (x %.1f )", greedyAvgReward, + optimalAvgReward, boost) + + } + + capacityBoost := totalOptimalCapacity / totalGreedyCapacity + rewardBoost := totalOptimalReward / totalGreedyReward + t.Logf("Average capacity boost: %f", capacityBoost) + t.Logf("Average reward boost: %f", rewardBoost) + t.Logf("Average best tq reward: %f", totalBestTQReward/runs/1e12) + + logging.SetLogLevel("messagepool", "info") + + return capacityBoost, rewardBoost, totalBestTQReward / runs / 1e12 +} + +func makeExpPremiumDistribution(rng *rand.Rand) func() uint64 { + return func() uint64 { + premium := 20000*math.Exp(-3.*rng.Float64()) + 5000 + return uint64(premium) + } +} + +func makeZipfPremiumDistribution(rng *rand.Rand) func() uint64 { + zipf := rand.NewZipf(rng, 1.001, 1, 40000) + return func() uint64 { + return zipf.Uint64() + 10000 + } +} + +func TestCompetitiveMessageSelectionExp(t *testing.T) { + //stm: @TOKEN_WALLET_NEW_001, @CHAIN_MEMPOOL_SELECT_001 + + if testing.Short() { + t.Skip("skipping in short mode") + } + var capacityBoost, rewardBoost, tqReward float64 + seeds := []int64{1947, 1976, 2020, 2100, 10000, 143324, 432432, 131, 32, 45} + for _, seed := range seeds { + t.Log("running competitive message selection with Exponential premium distribution and seed", seed) + rng := rand.New(rand.NewSource(seed)) + cb, rb, tqR := testCompetitiveMessageSelection(t, rng, makeExpPremiumDistribution(rng)) + capacityBoost += cb + rewardBoost += rb + tqReward += tqR + } + + capacityBoost /= float64(len(seeds)) + rewardBoost /= float64(len(seeds)) + tqReward /= float64(len(seeds)) + t.Logf("Average capacity boost across all seeds: %f", capacityBoost) + t.Logf("Average reward boost across all seeds: %f", rewardBoost) + t.Logf("Average reward of best ticket across all seeds: %f", tqReward) +} + +func TestCompetitiveMessageSelectionZipf(t *testing.T) { + //stm: @TOKEN_WALLET_NEW_001, @CHAIN_MEMPOOL_SELECT_001 + + if testing.Short() { + t.Skip("skipping in short mode") + } + var capacityBoost, rewardBoost, tqReward float64 + seeds := []int64{1947, 1976, 2020, 2100, 10000, 143324, 432432, 131, 32, 45} + for _, seed := range seeds { + t.Log("running competitive message selection with Zipf premium distribution and seed", seed) + rng := rand.New(rand.NewSource(seed)) + cb, rb, tqR := testCompetitiveMessageSelection(t, rng, makeZipfPremiumDistribution(rng)) + capacityBoost += cb + rewardBoost += rb + tqReward += tqR + } + + tqReward /= float64(len(seeds)) + capacityBoost /= float64(len(seeds)) + rewardBoost /= float64(len(seeds)) + t.Logf("Average capacity boost across all seeds: %f", capacityBoost) + t.Logf("Average reward boost across all seeds: %f", rewardBoost) + t.Logf("Average reward of best ticket across all seeds: %f", tqReward) +} + +func TestGasReward(t *testing.T) { + //stm: @CHAIN_MEMPOOL_GET_GAS_REWARD_001 + tests := []struct { + Premium uint64 + FeeCap uint64 + BaseFee uint64 + GasReward int64 + }{ + {Premium: 100, FeeCap: 200, BaseFee: 100, GasReward: 100}, + {Premium: 100, FeeCap: 200, BaseFee: 210, GasReward: -10 * 3}, + {Premium: 200, FeeCap: 250, BaseFee: 210, GasReward: 40}, + {Premium: 200, FeeCap: 250, BaseFee: 2000, GasReward: -1750 * 3}, + } + + mp := new(MessagePool) + for _, test := range tests { + test := test + t.Run(fmt.Sprintf("%v", test), func(t *testing.T) { + msg := &types.SignedMessage{ + Message: types.Message{ + GasLimit: 10, + GasFeeCap: types.NewInt(test.FeeCap), + GasPremium: types.NewInt(test.Premium), + }, + } + rew := mp.getGasReward(msg, types.NewInt(test.BaseFee)) + if rew.Cmp(big.NewInt(test.GasReward*10)) != 0 { + t.Errorf("bad reward: expected %d, got %s", test.GasReward*10, rew) + } + }) + } +} + +func TestRealWorldSelection(t *testing.T) { + //stm: @TOKEN_WALLET_NEW_001, @TOKEN_WALLET_SIGN_001, @CHAIN_MEMPOOL_SELECT_001 + + // load test-messages.json.gz and rewrite the messages so that + // 1) we map each real actor to a test actor so that we can sign the messages + // 2) adjust the nonces so that they start from 0 + file, err := os.Open("test-messages.json.gz") + if err != nil { + t.Fatal(err) + } + + gzr, err := gzip.NewReader(file) + if err != nil { + t.Fatal(err) + } + + dec := json.NewDecoder(gzr) + + var msgs []*types.SignedMessage + baseNonces := make(map[address.Address]uint64) + +readLoop: + for { + m := new(types.SignedMessage) + err := dec.Decode(m) + switch err { + case nil: + msgs = append(msgs, m) + nonce, ok := baseNonces[m.Message.From] + if !ok || m.Message.Nonce < nonce { + baseNonces[m.Message.From] = m.Message.Nonce + } + + case io.EOF: + break readLoop + + default: + t.Fatal(err) + } + } + + actorMap := make(map[address.Address]address.Address) + actorWallets := make(map[address.Address]api.Wallet) + + for _, m := range msgs { + baseNonce := baseNonces[m.Message.From] + + localActor, ok := actorMap[m.Message.From] + if !ok { + w, err := wallet.NewWallet(wallet.NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a, err := w.WalletNew(context.Background(), types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + actorMap[m.Message.From] = a + actorWallets[a] = w + localActor = a + } + + w, ok := actorWallets[localActor] + if !ok { + t.Fatalf("failed to lookup wallet for actor %s", localActor) + } + + m.Message.From = localActor + m.Message.Nonce -= baseNonce + + sig, err := w.WalletSign(context.TODO(), localActor, m.Message.Cid().Bytes(), api.MsgMeta{}) + if err != nil { + t.Fatal(err) + } + + m.Signature = *sig + } + + mp, tma := makeTestMpool() + + block := tma.nextBlockWithHeight(build.UpgradeBreezeHeight + 10) + ts := mock.TipSet(block) + tma.applyBlock(t, block) + + for _, a := range actorMap { + tma.setBalance(a, 1000000) + } + + tma.baseFee = types.NewInt(800_000_000) + + sort.Slice(msgs, func(i, j int) bool { + return msgs[i].Message.Nonce < msgs[j].Message.Nonce + }) + + // add the messages + for _, m := range msgs { + mustAdd(t, mp, m) + } + + // do message selection and check block packing + minGasLimit := int64(0.9 * float64(build.BlockGasLimit)) + + // greedy first + selected, err := mp.SelectMessages(context.Background(), ts, 1.0) + if err != nil { + t.Fatal(err) + } + + gasLimit := int64(0) + for _, m := range selected { + gasLimit += m.Message.GasLimit + } + if gasLimit < minGasLimit { + t.Fatalf("failed to pack with tq=1.0; packed %d, minimum packing: %d", gasLimit, minGasLimit) + } + + // high quality ticket + selected, err = mp.SelectMessages(context.Background(), ts, .8) + if err != nil { + t.Fatal(err) + } + + gasLimit = int64(0) + for _, m := range selected { + gasLimit += m.Message.GasLimit + } + if gasLimit < minGasLimit { + t.Fatalf("failed to pack with tq=0.8; packed %d, minimum packing: %d", gasLimit, minGasLimit) + } + + // mid quality ticket + selected, err = mp.SelectMessages(context.Background(), ts, .4) + if err != nil { + t.Fatal(err) + } + + gasLimit = int64(0) + for _, m := range selected { + gasLimit += m.Message.GasLimit + } + if gasLimit < minGasLimit { + t.Fatalf("failed to pack with tq=0.4; packed %d, minimum packing: %d", gasLimit, minGasLimit) + } + + // low quality ticket + selected, err = mp.SelectMessages(context.Background(), ts, .1) + if err != nil { + t.Fatal(err) + } + + gasLimit = int64(0) + for _, m := range selected { + gasLimit += m.Message.GasLimit + } + if gasLimit < minGasLimit { + t.Fatalf("failed to pack with tq=0.1; packed %d, minimum packing: %d", gasLimit, minGasLimit) + } + + // very low quality ticket + selected, err = mp.SelectMessages(context.Background(), ts, .01) + if err != nil { + t.Fatal(err) + } + + gasLimit = int64(0) + for _, m := range selected { + gasLimit += m.Message.GasLimit + } + if gasLimit < minGasLimit { + t.Fatalf("failed to pack with tq=0.01; packed %d, minimum packing: %d", gasLimit, minGasLimit) + } + +} diff --git a/chain/messagepool/test-messages.json.gz b/chain/messagepool/test-messages.json.gz new file mode 100644 index 000000000..09481e1f8 Binary files /dev/null and b/chain/messagepool/test-messages.json.gz differ diff --git a/chain/messagesigner/messagesigner.go b/chain/messagesigner/messagesigner.go new file mode 100644 index 000000000..cd31a3b73 --- /dev/null +++ b/chain/messagesigner/messagesigner.go @@ -0,0 +1,211 @@ +package messagesigner + +import ( + "bytes" + "context" + "sync" + + "github.com/google/uuid" + "github.com/ipfs/go-datastore" + "github.com/ipfs/go-datastore/namespace" + logging "github.com/ipfs/go-log/v2" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/messagepool" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/ethtypes" + "github.com/filecoin-project/lotus/node/modules/dtypes" +) + +const dsKeyActorNonce = "ActorNextNonce" +const dsKeyMsgUUIDSet = "MsgUuidSet" + +var log = logging.Logger("messagesigner") + +type MsgSigner interface { + SignMessage(ctx context.Context, msg *types.Message, spec *api.MessageSendSpec, cb func(*types.SignedMessage) error) (*types.SignedMessage, error) + GetSignedMessage(ctx context.Context, uuid uuid.UUID) (*types.SignedMessage, error) + StoreSignedMessage(ctx context.Context, uuid uuid.UUID, message *types.SignedMessage) error + NextNonce(ctx context.Context, addr address.Address) (uint64, error) + SaveNonce(ctx context.Context, addr address.Address, nonce uint64) error +} + +// MessageSigner keeps track of nonces per address, and increments the nonce +// when signing a message +type MessageSigner struct { + wallet api.Wallet + lk sync.Mutex + mpool messagepool.MpoolNonceAPI + ds datastore.Batching +} + +func NewMessageSigner(wallet api.Wallet, mpool messagepool.MpoolNonceAPI, ds dtypes.MetadataDS) *MessageSigner { + ds = namespace.Wrap(ds, datastore.NewKey("/message-signer/")) + return &MessageSigner{ + wallet: wallet, + mpool: mpool, + ds: ds, + } +} + +// SignMessage increments the nonce for the message From address, and signs +// the message +func (ms *MessageSigner) SignMessage(ctx context.Context, msg *types.Message, spec *api.MessageSendSpec, cb func(*types.SignedMessage) error) (*types.SignedMessage, error) { + ms.lk.Lock() + defer ms.lk.Unlock() + + // Get the next message nonce + nonce, err := ms.NextNonce(ctx, msg.From) + if err != nil { + return nil, xerrors.Errorf("failed to create nonce: %w", err) + } + + // Sign the message with the nonce + msg.Nonce = nonce + + sb, err := SigningBytes(msg, msg.From.Protocol()) + if err != nil { + return nil, err + } + mb, err := msg.ToStorageBlock() + if err != nil { + return nil, xerrors.Errorf("serializing message: %w", err) + } + + sig, err := ms.wallet.WalletSign(ctx, msg.From, sb, api.MsgMeta{ + Type: api.MTChainMsg, + Extra: mb.RawData(), + }) + + if err != nil { + return nil, xerrors.Errorf("failed to sign message: %w, addr=%s", err, msg.From) + } + + // Callback with the signed message + smsg := &types.SignedMessage{ + Message: *msg, + Signature: *sig, + } + + err = cb(smsg) + if err != nil { + return nil, err + } + + // If the callback executed successfully, write the nonce to the datastore + if err := ms.SaveNonce(ctx, msg.From, nonce); err != nil { + return nil, xerrors.Errorf("failed to save nonce: %w", err) + } + + return smsg, nil +} + +func (ms *MessageSigner) GetSignedMessage(ctx context.Context, uuid uuid.UUID) (*types.SignedMessage, error) { + + key := datastore.KeyWithNamespaces([]string{dsKeyMsgUUIDSet, uuid.String()}) + bytes, err := ms.ds.Get(ctx, key) + if err != nil { + return nil, err + } + return types.DecodeSignedMessage(bytes) +} + +func (ms *MessageSigner) StoreSignedMessage(ctx context.Context, uuid uuid.UUID, message *types.SignedMessage) error { + + key := datastore.KeyWithNamespaces([]string{dsKeyMsgUUIDSet, uuid.String()}) + serializedMsg, err := message.Serialize() + if err != nil { + return err + } + return ms.ds.Put(ctx, key, serializedMsg) +} + +// NextNonce gets the next nonce for the given address. +// If there is no nonce in the datastore, gets the nonce from the message pool. +func (ms *MessageSigner) NextNonce(ctx context.Context, addr address.Address) (uint64, error) { + // Nonces used to be created by the mempool and we need to support nodes + // that have mempool nonces, so first check the mempool for a nonce for + // this address. Note that the mempool returns the actor state's nonce + // by default. + nonce, err := ms.mpool.GetNonce(ctx, addr, types.EmptyTSK) + if err != nil { + return 0, xerrors.Errorf("failed to get nonce from mempool: %w", err) + } + + // Get the next nonce for this address from the datastore + addrNonceKey := ms.dstoreKey(addr) + dsNonceBytes, err := ms.ds.Get(ctx, addrNonceKey) + + switch { + case xerrors.Is(err, datastore.ErrNotFound): + // If a nonce for this address hasn't yet been created in the + // datastore, just use the nonce from the mempool + return nonce, nil + + case err != nil: + return 0, xerrors.Errorf("failed to get nonce from datastore: %w", err) + + default: + // There is a nonce in the datastore, so unmarshall it + maj, dsNonce, err := cbg.CborReadHeader(bytes.NewReader(dsNonceBytes)) + if err != nil { + return 0, xerrors.Errorf("failed to parse nonce from datastore: %w", err) + } + if maj != cbg.MajUnsignedInt { + return 0, xerrors.Errorf("bad cbor type parsing nonce from datastore") + } + + // The message pool nonce should be <= than the datastore nonce + if nonce <= dsNonce { + nonce = dsNonce + } else { + log.Warnf("mempool nonce was larger than datastore nonce (%d > %d)", nonce, dsNonce) + } + + return nonce, nil + } +} + +// SaveNonce increments the nonce for this address and writes it to the +// datastore +func (ms *MessageSigner) SaveNonce(ctx context.Context, addr address.Address, nonce uint64) error { + // Increment the nonce + nonce++ + + // Write the nonce to the datastore + addrNonceKey := ms.dstoreKey(addr) + buf := bytes.Buffer{} + _, err := buf.Write(cbg.CborEncodeMajorType(cbg.MajUnsignedInt, nonce)) + if err != nil { + return xerrors.Errorf("failed to marshall nonce: %w", err) + } + err = ms.ds.Put(ctx, addrNonceKey, buf.Bytes()) + if err != nil { + return xerrors.Errorf("failed to write nonce to datastore: %w", err) + } + return nil +} + +func (ms *MessageSigner) dstoreKey(addr address.Address) datastore.Key { + return datastore.KeyWithNamespaces([]string{dsKeyActorNonce, addr.String()}) +} + +func SigningBytes(msg *types.Message, sigType address.Protocol) ([]byte, error) { + if sigType == address.Delegated { + txArgs, err := ethtypes.EthTxArgsFromUnsignedEthMessage(msg) + if err != nil { + return nil, xerrors.Errorf("failed to reconstruct eth transaction: %w", err) + } + rlpEncodedMsg, err := txArgs.ToRlpUnsignedMsg() + if err != nil { + return nil, xerrors.Errorf("failed to repack eth rlp message: %w", err) + } + return rlpEncodedMsg, nil + } + + return msg.Cid().Bytes(), nil +} diff --git a/chain/messagesigner/messagesigner_consensus.go b/chain/messagesigner/messagesigner_consensus.go new file mode 100644 index 000000000..905bb7199 --- /dev/null +++ b/chain/messagesigner/messagesigner_consensus.go @@ -0,0 +1,98 @@ +package messagesigner + +import ( + "context" + + "github.com/google/uuid" + "github.com/ipfs/go-datastore" + "github.com/ipfs/go-datastore/namespace" + "github.com/libp2p/go-libp2p/core/peer" + "golang.org/x/xerrors" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/messagepool" + "github.com/filecoin-project/lotus/chain/types" + consensus "github.com/filecoin-project/lotus/lib/consensus/raft" + "github.com/filecoin-project/lotus/node/modules/dtypes" +) + +type MessageSignerConsensus struct { + MsgSigner + Consensus *consensus.Consensus +} + +func NewMessageSignerConsensus( + wallet api.Wallet, + mpool messagepool.MpoolNonceAPI, + ds dtypes.MetadataDS, + consensus *consensus.Consensus) *MessageSignerConsensus { + + ds = namespace.Wrap(ds, datastore.NewKey("/message-signer-consensus/")) + return &MessageSignerConsensus{ + MsgSigner: &MessageSigner{ + wallet: wallet, + mpool: mpool, + ds: ds, + }, + Consensus: consensus, + } +} + +func (ms *MessageSignerConsensus) IsLeader(ctx context.Context) bool { + return ms.Consensus.IsLeader(ctx) +} + +func (ms *MessageSignerConsensus) RedirectToLeader(ctx context.Context, method string, arg interface{}, ret interface{}) (bool, error) { + ok, err := ms.Consensus.RedirectToLeader(method, arg, ret.(*types.SignedMessage)) + if err != nil { + return ok, err + } + return ok, nil +} + +func (ms *MessageSignerConsensus) SignMessage( + ctx context.Context, + msg *types.Message, + spec *api.MessageSendSpec, + cb func(*types.SignedMessage) error) (*types.SignedMessage, error) { + + signedMsg, err := ms.MsgSigner.SignMessage(ctx, msg, spec, cb) + if err != nil { + return nil, err + } + + op := &consensus.ConsensusOp{ + Nonce: signedMsg.Message.Nonce, + Uuid: spec.MsgUuid, + Addr: signedMsg.Message.From, + SignedMsg: signedMsg, + } + err = ms.Consensus.Commit(ctx, op) + if err != nil { + return nil, err + } + + return signedMsg, nil +} + +func (ms *MessageSignerConsensus) GetSignedMessage(ctx context.Context, uuid uuid.UUID) (*types.SignedMessage, error) { + cstate, err := ms.Consensus.State(ctx) + if err != nil { + return nil, err + } + + //cstate := state.(Consensus.RaftState) + msg, ok := cstate.MsgUuids[uuid] + if !ok { + return nil, xerrors.Errorf("Msg with Uuid %s not available", uuid) + } + return msg, nil +} + +func (ms *MessageSignerConsensus) GetRaftState(ctx context.Context) (*consensus.RaftState, error) { + return ms.Consensus.State(ctx) +} + +func (ms *MessageSignerConsensus) Leader(ctx context.Context) (peer.ID, error) { + return ms.Consensus.Leader(ctx) +} diff --git a/chain/messagesigner/messagesigner_test.go b/chain/messagesigner/messagesigner_test.go new file mode 100644 index 000000000..637f17b46 --- /dev/null +++ b/chain/messagesigner/messagesigner_test.go @@ -0,0 +1,205 @@ +// stm: #unit +package messagesigner + +import ( + "context" + "sync" + "testing" + + "github.com/ipfs/go-datastore" + ds_sync "github.com/ipfs/go-datastore/sync" + "github.com/stretchr/testify/require" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + + "github.com/filecoin-project/lotus/chain/messagepool" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/wallet" +) + +type mockMpool struct { + lk sync.RWMutex + nonces map[address.Address]uint64 +} + +var _ messagepool.MpoolNonceAPI = (*mockMpool)(nil) + +func newMockMpool() *mockMpool { + return &mockMpool{nonces: make(map[address.Address]uint64)} +} + +func (mp *mockMpool) setNonce(addr address.Address, nonce uint64) { + mp.lk.Lock() + defer mp.lk.Unlock() + + mp.nonces[addr] = nonce +} + +func (mp *mockMpool) GetNonce(_ context.Context, addr address.Address, _ types.TipSetKey) (uint64, error) { + mp.lk.RLock() + defer mp.lk.RUnlock() + + return mp.nonces[addr], nil +} +func (mp *mockMpool) GetActor(_ context.Context, addr address.Address, _ types.TipSetKey) (*types.Actor, error) { + panic("don't use it") +} + +func TestMessageSignerSignMessage(t *testing.T) { + ctx := context.Background() + + w, _ := wallet.NewWallet(wallet.NewMemKeyStore()) + from1, err := w.WalletNew(ctx, types.KTSecp256k1) + require.NoError(t, err) + from2, err := w.WalletNew(ctx, types.KTSecp256k1) + require.NoError(t, err) + to1, err := w.WalletNew(ctx, types.KTSecp256k1) + require.NoError(t, err) + to2, err := w.WalletNew(ctx, types.KTSecp256k1) + require.NoError(t, err) + + //stm: @CHAIN_MESSAGE_SIGNER_NEW_SIGNER_001, @CHAIN_MESSAGE_SIGNER_SIGN_MESSAGE_001, @CHAIN_MESSAGE_SIGNER_SIGN_MESSAGE_005 + type msgSpec struct { + msg *types.Message + mpoolNonce [1]uint64 + expNonce uint64 + cbErr error + } + tests := []struct { + name string + msgs []msgSpec + }{{ + // No nonce yet in datastore + name: "no nonce yet", + msgs: []msgSpec{{ + msg: &types.Message{ + To: to1, + From: from1, + }, + expNonce: 0, + }}, + }, { + // Get nonce value of zero from mpool + name: "mpool nonce zero", + msgs: []msgSpec{{ + msg: &types.Message{ + To: to1, + From: from1, + }, + mpoolNonce: [1]uint64{0}, + expNonce: 0, + }}, + }, { + // Get non-zero nonce value from mpool + name: "mpool nonce set", + msgs: []msgSpec{{ + msg: &types.Message{ + To: to1, + From: from1, + }, + mpoolNonce: [1]uint64{5}, + expNonce: 5, + }, { + msg: &types.Message{ + To: to1, + From: from1, + }, + // Should adjust datastore nonce because mpool nonce is higher + mpoolNonce: [1]uint64{10}, + expNonce: 10, + }}, + }, { + // Nonce should increment independently for each address + name: "nonce increments per address", + msgs: []msgSpec{{ + msg: &types.Message{ + To: to1, + From: from1, + }, + expNonce: 0, + }, { + msg: &types.Message{ + To: to1, + From: from1, + }, + expNonce: 1, + }, { + msg: &types.Message{ + To: to2, + From: from2, + }, + mpoolNonce: [1]uint64{5}, + expNonce: 5, + }, { + msg: &types.Message{ + To: to2, + From: from2, + }, + expNonce: 6, + }, { + msg: &types.Message{ + To: to1, + From: from1, + }, + expNonce: 2, + }}, + }, { + name: "recover from callback error", + msgs: []msgSpec{{ + // No nonce yet in datastore + msg: &types.Message{ + To: to1, + From: from1, + }, + expNonce: 0, + }, { + // Increment nonce + msg: &types.Message{ + To: to1, + From: from1, + }, + expNonce: 1, + }, { + // Callback returns error + msg: &types.Message{ + To: to1, + From: from1, + }, + cbErr: xerrors.Errorf("err"), + }, { + // Callback successful, should increment nonce in datastore + msg: &types.Message{ + To: to1, + From: from1, + }, + expNonce: 2, + }}, + }} + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + mpool := newMockMpool() + ds := ds_sync.MutexWrap(datastore.NewMapDatastore()) + ms := NewMessageSigner(w, mpool, ds) + + for _, m := range tt.msgs { + if len(m.mpoolNonce) == 1 { + mpool.setNonce(m.msg.From, m.mpoolNonce[0]) + } + merr := m.cbErr + smsg, err := ms.SignMessage(ctx, m.msg, nil, func(message *types.SignedMessage) error { + return merr + }) + + if m.cbErr != nil { + require.Error(t, err) + require.Nil(t, smsg) + } else { + require.NoError(t, err) + require.Equal(t, m.expNonce, smsg.Message.Nonce) + } + } + }) + } +} diff --git a/chain/metrics/consensus.go b/chain/metrics/consensus.go deleted file mode 100644 index bc7e019d2..000000000 --- a/chain/metrics/consensus.go +++ /dev/null @@ -1,128 +0,0 @@ -package metrics - -import ( - "context" - "encoding/json" - "time" - - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/ipfs/go-cid" - logging "github.com/ipfs/go-log/v2" - pubsub "github.com/libp2p/go-libp2p-pubsub" - "go.uber.org/fx" - - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/node/impl/full" - "github.com/filecoin-project/lotus/node/modules/helpers" -) - -var log = logging.Logger("metrics") - -const baseTopic = "/fil/headnotifs/" - -type Update struct { - Type string -} - -func SendHeadNotifs(nickname string) func(mctx helpers.MetricsCtx, lc fx.Lifecycle, ps *pubsub.PubSub, chain full.ChainAPI) error { - return func(mctx helpers.MetricsCtx, lc fx.Lifecycle, ps *pubsub.PubSub, chain full.ChainAPI) error { - ctx := helpers.LifecycleCtx(mctx, lc) - - lc.Append(fx.Hook{ - OnStart: func(_ context.Context) error { - gen, err := chain.Chain.GetGenesis() - if err != nil { - return err - } - - topic := baseTopic + gen.Cid().String() - - go func() { - if err := sendHeadNotifs(ctx, ps, topic, chain, nickname); err != nil { - log.Error("consensus metrics error", err) - return - } - }() - go func() { - sub, err := ps.Subscribe(topic) - if err != nil { - return - } - defer sub.Cancel() - - for { - if _, err := sub.Next(ctx); err != nil { - return - } - } - - }() - return nil - }, - }) - - return nil - } -} - -type message struct { - // TipSet - Cids []cid.Cid - Blocks []*types.BlockHeader - Height abi.ChainEpoch - Weight types.BigInt - Time uint64 - Nonce uint64 - - // Meta - - NodeName string -} - -func sendHeadNotifs(ctx context.Context, ps *pubsub.PubSub, topic string, chain full.ChainAPI, nickname string) error { - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - notifs, err := chain.ChainNotify(ctx) - if err != nil { - return err - } - - // using unix nano time makes very sure we pick a nonce higher than previous restart - nonce := uint64(time.Now().UnixNano()) - - for { - select { - case notif := <-notifs: - n := notif[len(notif)-1] - - w, err := chain.ChainTipSetWeight(ctx, n.Val.Key()) - if err != nil { - return err - } - - m := message{ - Cids: n.Val.Cids(), - Blocks: n.Val.Blocks(), - Height: n.Val.Height(), - Weight: w, - NodeName: nickname, - Time: uint64(time.Now().UnixNano() / 1000_000), - Nonce: nonce, - } - - b, err := json.Marshal(m) - if err != nil { - return err - } - - if err := ps.Publish(topic, b); err != nil { - return err - } - case <-ctx.Done(): - return nil - } - - nonce++ - } -} diff --git a/chain/rand/rand.go b/chain/rand/rand.go new file mode 100644 index 000000000..c35280ab5 --- /dev/null +++ b/chain/rand/rand.go @@ -0,0 +1,220 @@ +package rand + +import ( + "context" + "encoding/binary" + + "github.com/ipfs/go-cid" + logging "github.com/ipfs/go-log/v2" + "github.com/minio/blake2b-simd" + "go.opencensus.io/trace" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/go-state-types/network" + + "github.com/filecoin-project/lotus/chain/beacon" + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/vm" +) + +var log = logging.Logger("rand") + +func DrawRandomness(rbase []byte, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { + h := blake2b.New256() + if err := binary.Write(h, binary.BigEndian, int64(pers)); err != nil { + return nil, xerrors.Errorf("deriving randomness: %w", err) + } + VRFDigest := blake2b.Sum256(rbase) + _, err := h.Write(VRFDigest[:]) + if err != nil { + return nil, xerrors.Errorf("hashing VRFDigest: %w", err) + } + if err := binary.Write(h, binary.BigEndian, round); err != nil { + return nil, xerrors.Errorf("deriving randomness: %w", err) + } + _, err = h.Write(entropy) + if err != nil { + return nil, xerrors.Errorf("hashing entropy: %w", err) + } + + return h.Sum(nil), nil +} + +func (sr *stateRand) GetBeaconRandomnessTipset(ctx context.Context, round abi.ChainEpoch, lookback bool) (*types.TipSet, error) { + _, span := trace.StartSpan(ctx, "store.GetBeaconRandomness") + defer span.End() + span.AddAttributes(trace.Int64Attribute("round", int64(round))) + + ts, err := sr.cs.LoadTipSet(ctx, types.NewTipSetKey(sr.blks...)) + if err != nil { + return nil, err + } + + if round > ts.Height() { + return nil, xerrors.Errorf("cannot draw randomness from the future") + } + + searchHeight := round + if searchHeight < 0 { + searchHeight = 0 + } + + randTs, err := sr.cs.GetTipsetByHeight(ctx, searchHeight, ts, lookback) + if err != nil { + return nil, err + } + + return randTs, nil +} + +func (sr *stateRand) getChainRandomness(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte, lookback bool) ([]byte, error) { + _, span := trace.StartSpan(ctx, "store.GetChainRandomness") + defer span.End() + span.AddAttributes(trace.Int64Attribute("round", int64(round))) + + ts, err := sr.cs.LoadTipSet(ctx, types.NewTipSetKey(sr.blks...)) + if err != nil { + return nil, err + } + + if round > ts.Height() { + return nil, xerrors.Errorf("cannot draw randomness from the future") + } + + searchHeight := round + if searchHeight < 0 { + searchHeight = 0 + } + + randTs, err := sr.cs.GetTipsetByHeight(ctx, searchHeight, ts, lookback) + if err != nil { + return nil, err + } + + mtb := randTs.MinTicketBlock() + + // if at (or just past -- for null epochs) appropriate epoch + // or at genesis (works for negative epochs) + return DrawRandomness(mtb.Ticket.VRFProof, pers, round, entropy) +} + +type NetworkVersionGetter func(context.Context, abi.ChainEpoch) network.Version + +type stateRand struct { + cs *store.ChainStore + blks []cid.Cid + beacon beacon.Schedule + networkVersionGetter NetworkVersionGetter +} + +func NewStateRand(cs *store.ChainStore, blks []cid.Cid, b beacon.Schedule, networkVersionGetter NetworkVersionGetter) vm.Rand { + return &stateRand{ + cs: cs, + blks: blks, + beacon: b, + networkVersionGetter: networkVersionGetter, + } +} + +// network v0-12 +func (sr *stateRand) getBeaconRandomnessV1(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { + randTs, err := sr.GetBeaconRandomnessTipset(ctx, round, true) + if err != nil { + return nil, err + } + + be, err := sr.cs.GetLatestBeaconEntry(ctx, randTs) + if err != nil { + return nil, err + } + + // if at (or just past -- for null epochs) appropriate epoch + // or at genesis (works for negative epochs) + return DrawRandomness(be.Data, pers, round, entropy) +} + +// network v13 +func (sr *stateRand) getBeaconRandomnessV2(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { + randTs, err := sr.GetBeaconRandomnessTipset(ctx, round, false) + if err != nil { + return nil, err + } + + be, err := sr.cs.GetLatestBeaconEntry(ctx, randTs) + if err != nil { + return nil, err + } + + // if at (or just past -- for null epochs) appropriate epoch + // or at genesis (works for negative epochs) + return DrawRandomness(be.Data, pers, round, entropy) +} + +// network v14 and on +func (sr *stateRand) getBeaconRandomnessV3(ctx context.Context, pers crypto.DomainSeparationTag, filecoinEpoch abi.ChainEpoch, entropy []byte) ([]byte, error) { + if filecoinEpoch < 0 { + return sr.getBeaconRandomnessV2(ctx, pers, filecoinEpoch, entropy) + } + + be, err := sr.extractBeaconEntryForEpoch(ctx, filecoinEpoch) + if err != nil { + log.Errorf("failed to get beacon entry as expected: %s", err) + return nil, err + } + + return DrawRandomness(be.Data, pers, filecoinEpoch, entropy) +} + +func (sr *stateRand) GetChainRandomness(ctx context.Context, pers crypto.DomainSeparationTag, filecoinEpoch abi.ChainEpoch, entropy []byte) ([]byte, error) { + nv := sr.networkVersionGetter(ctx, filecoinEpoch) + + if nv >= network.Version13 { + return sr.getChainRandomness(ctx, pers, filecoinEpoch, entropy, false) + } + + return sr.getChainRandomness(ctx, pers, filecoinEpoch, entropy, true) +} + +func (sr *stateRand) GetBeaconRandomness(ctx context.Context, pers crypto.DomainSeparationTag, filecoinEpoch abi.ChainEpoch, entropy []byte) ([]byte, error) { + nv := sr.networkVersionGetter(ctx, filecoinEpoch) + + if nv >= network.Version14 { + return sr.getBeaconRandomnessV3(ctx, pers, filecoinEpoch, entropy) + } else if nv == network.Version13 { + return sr.getBeaconRandomnessV2(ctx, pers, filecoinEpoch, entropy) + } else { + return sr.getBeaconRandomnessV1(ctx, pers, filecoinEpoch, entropy) + } +} + +func (sr *stateRand) extractBeaconEntryForEpoch(ctx context.Context, filecoinEpoch abi.ChainEpoch) (*types.BeaconEntry, error) { + randTs, err := sr.GetBeaconRandomnessTipset(ctx, filecoinEpoch, false) + if err != nil { + return nil, err + } + + nv := sr.networkVersionGetter(ctx, filecoinEpoch) + + round := sr.beacon.BeaconForEpoch(filecoinEpoch).MaxBeaconRoundForEpoch(nv, filecoinEpoch) + + for i := 0; i < 20; i++ { + cbe := randTs.Blocks()[0].BeaconEntries + for _, v := range cbe { + if v.Round == round { + return &v, nil + } + } + + next, err := sr.cs.LoadTipSet(ctx, randTs.Parents()) + if err != nil { + return nil, xerrors.Errorf("failed to load parents when searching back for beacon entry: %w", err) + } + + randTs = next + } + + return nil, xerrors.Errorf("didn't find beacon for round %d (epoch %d)", round, filecoinEpoch) +} diff --git a/chain/rand/rand_test.go b/chain/rand/rand_test.go new file mode 100644 index 000000000..acd928854 --- /dev/null +++ b/chain/rand/rand_test.go @@ -0,0 +1,245 @@ +// stm:#unit +package rand_test + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/go-state-types/network" + + "github.com/filecoin-project/lotus/chain/actors/policy" + "github.com/filecoin-project/lotus/chain/consensus/filcns" + "github.com/filecoin-project/lotus/chain/gen" + "github.com/filecoin-project/lotus/chain/rand" + "github.com/filecoin-project/lotus/chain/stmgr" +) + +func init() { + policy.SetSupportedProofTypes(abi.RegisteredSealProof_StackedDrg2KiBV1) + policy.SetConsensusMinerMinPower(abi.NewStoragePower(2048)) + policy.SetMinVerifiedDealSize(abi.NewStoragePower(256)) +} + +// in v12 and before, if the tipset corresponding to round X is null, we fetch the latest beacon entry BEFORE X that's in a non-null ts +func TestNullRandomnessV1(t *testing.T) { + ctx := context.Background() + cg, err := gen.NewGenerator() + if err != nil { + t.Fatal(err) + } + + for i := 0; i < 10; i++ { + _, err := cg.NextTipSet() + if err != nil { + t.Fatal(err) + } + } + + offset := cg.CurTipset.Blocks[0].Header.BeaconEntries[len(cg.CurTipset.Blocks[0].Header.BeaconEntries)-1].Round - uint64(cg.CurTipset.TipSet().Height()) + beforeNullHeight := cg.CurTipset.TipSet().Height() + + ts, err := cg.NextTipSetWithNulls(5) + if err != nil { + t.Fatal(err) + } + + entropy := []byte{0, 2, 3, 4} + // arbitrarily chosen + pers := crypto.DomainSeparationTag_WinningPoStChallengeSeed + + randEpoch := ts.TipSet.TipSet().Height() - 2 + + //stm: @BLOCKCHAIN_RAND_GET_BEACON_RANDOMNESS_V1_01, @BLOCKCHAIN_RAND_EXTRACT_BEACON_ENTRY_FOR_EPOCH_01, @BLOCKCHAIN_RAND_GET_BEACON_RANDOMNESS_TIPSET_02 + rand1, err := cg.StateManager().GetRandomnessFromBeacon(ctx, pers, randEpoch, entropy, ts.TipSet.TipSet().Key()) + if err != nil { + t.Fatal(err) + } + + //stm: @BLOCKCHAIN_BEACON_GET_BEACON_FOR_EPOCH_01 + bch := cg.BeaconSchedule().BeaconForEpoch(randEpoch).Entry(ctx, uint64(beforeNullHeight)+offset) + + select { + case resp := <-bch: + if resp.Err != nil { + t.Fatal(resp.Err) + } + + //stm: @BLOCKCHAIN_RAND_DRAW_RANDOMNESS_01 + rand2, err := rand.DrawRandomness(resp.Entry.Data, pers, randEpoch, entropy) + if err != nil { + t.Fatal(err) + } + + require.Equal(t, rand1, abi.Randomness(rand2)) + + case <-ctx.Done(): + t.Fatal("timed out") + } +} + +// at v13, if the tipset corresponding to round X is null, we fetch the latest beacon in the first non-null ts after X +func TestNullRandomnessV2(t *testing.T) { + ctx := context.Background() + + sched := stmgr.UpgradeSchedule{ + { + // prepare for upgrade. + Network: network.Version9, + Height: 1, + Migration: filcns.UpgradeActorsV2, + }, { + Network: network.Version10, + Height: 2, + Migration: filcns.UpgradeActorsV3, + }, { + Network: network.Version12, + Height: 3, + Migration: filcns.UpgradeActorsV4, + }, { + Network: network.Version13, + Height: 4, + Migration: filcns.UpgradeActorsV5, + }, + } + + cg, err := gen.NewGeneratorWithUpgradeSchedule(sched) + if err != nil { + t.Fatal(err) + } + + for i := 0; i < 10; i++ { + _, err := cg.NextTipSet() + if err != nil { + t.Fatal(err) + } + + } + + offset := cg.CurTipset.Blocks[0].Header.BeaconEntries[len(cg.CurTipset.Blocks[0].Header.BeaconEntries)-1].Round - uint64(cg.CurTipset.TipSet().Height()) + + ts, err := cg.NextTipSetWithNulls(5) + if err != nil { + t.Fatal(err) + } + + entropy := []byte{0, 2, 3, 4} + // arbitrarily chosen + pers := crypto.DomainSeparationTag_WinningPoStChallengeSeed + + randEpoch := ts.TipSet.TipSet().Height() - 2 + + //stm: @BLOCKCHAIN_RAND_GET_BEACON_RANDOMNESS_V2_01 + rand1, err := cg.StateManager().GetRandomnessFromBeacon(ctx, pers, randEpoch, entropy, ts.TipSet.TipSet().Key()) + if err != nil { + t.Fatal(err) + } + + //stm: @BLOCKCHAIN_BEACON_GET_BEACON_FOR_EPOCH_01 + bch := cg.BeaconSchedule().BeaconForEpoch(randEpoch).Entry(ctx, uint64(ts.TipSet.TipSet().Height())+offset) + + select { + case resp := <-bch: + if resp.Err != nil { + t.Fatal(resp.Err) + } + + //stm: @BLOCKCHAIN_RAND_DRAW_RANDOMNESS_01, @BLOCKCHAIN_RAND_EXTRACT_BEACON_ENTRY_FOR_EPOCH_01, @BLOCKCHAIN_RAND_GET_BEACON_RANDOMNESS_TIPSET_03 + // note that the randEpoch passed to DrawRandomness is still randEpoch (not the latest ts height) + rand2, err := rand.DrawRandomness(resp.Entry.Data, pers, randEpoch, entropy) + if err != nil { + t.Fatal(err) + } + + require.Equal(t, rand1, abi.Randomness(rand2)) + + case <-ctx.Done(): + t.Fatal("timed out") + } +} + +// after v14, if the tipset corresponding to round X is null, we still fetch the randomness for X (from the next non-null tipset) +func TestNullRandomnessV3(t *testing.T) { + ctx := context.Background() + sched := stmgr.UpgradeSchedule{ + { + // prepare for upgrade. + Network: network.Version9, + Height: 1, + Migration: filcns.UpgradeActorsV2, + }, { + Network: network.Version10, + Height: 2, + Migration: filcns.UpgradeActorsV3, + }, { + Network: network.Version12, + Height: 3, + Migration: filcns.UpgradeActorsV4, + }, { + Network: network.Version13, + Height: 4, + Migration: filcns.UpgradeActorsV5, + }, { + Network: network.Version14, + Height: 5, + Migration: filcns.UpgradeActorsV6, + }, + } + + cg, err := gen.NewGeneratorWithUpgradeSchedule(sched) + + if err != nil { + t.Fatal(err) + } + + for i := 0; i < 10; i++ { + _, err := cg.NextTipSet() + if err != nil { + t.Fatal(err) + } + + } + + ts, err := cg.NextTipSetWithNulls(5) + if err != nil { + t.Fatal(err) + } + + offset := cg.CurTipset.Blocks[0].Header.BeaconEntries[len(cg.CurTipset.Blocks[0].Header.BeaconEntries)-1].Round - uint64(cg.CurTipset.TipSet().Height()) + + entropy := []byte{0, 2, 3, 4} + // arbitrarily chosen + pers := crypto.DomainSeparationTag_WinningPoStChallengeSeed + + randEpoch := ts.TipSet.TipSet().Height() - 2 + + //stm: @BLOCKCHAIN_RAND_GET_BEACON_RANDOMNESS_V3_01, @BLOCKCHAIN_RAND_EXTRACT_BEACON_ENTRY_FOR_EPOCH_01 + rand1, err := cg.StateManager().GetRandomnessFromBeacon(ctx, pers, randEpoch, entropy, ts.TipSet.TipSet().Key()) + if err != nil { + t.Fatal(err) + } + + //stm: @BLOCKCHAIN_BEACON_GET_BEACON_FOR_EPOCH_01 + bch := cg.BeaconSchedule().BeaconForEpoch(randEpoch).Entry(ctx, uint64(randEpoch)+offset) + + select { + case resp := <-bch: + if resp.Err != nil { + t.Fatal(resp.Err) + } + + //stm: @BLOCKCHAIN_RAND_DRAW_RANDOMNESS_01 + rand2, err := rand.DrawRandomness(resp.Entry.Data, pers, randEpoch, entropy) + if err != nil { + t.Fatal(err) + } + + require.Equal(t, rand1, abi.Randomness(rand2)) + + case <-ctx.Done(): + t.Fatal("timed out") + } +} diff --git a/chain/state/statetree.go b/chain/state/statetree.go index c93b1c83c..3142a07d8 100644 --- a/chain/state/statetree.go +++ b/chain/state/statetree.go @@ -1,36 +1,48 @@ package state import ( + "bytes" "context" "fmt" - "github.com/filecoin-project/specs-actors/actors/builtin" - init_ "github.com/filecoin-project/specs-actors/actors/builtin/init" - - "github.com/filecoin-project/specs-actors/actors/util/adt" "github.com/ipfs/go-cid" - hamt "github.com/ipfs/go-hamt-ipld" cbor "github.com/ipfs/go-ipld-cbor" logging "github.com/ipfs/go-log/v2" + cbg "github.com/whyrusleeping/cbor-gen" "go.opencensus.io/trace" "golang.org/x/xerrors" "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + builtin_types "github.com/filecoin-project/go-state-types/builtin" + "github.com/filecoin-project/go-state-types/network" + states0 "github.com/filecoin-project/specs-actors/actors/states" + states2 "github.com/filecoin-project/specs-actors/v2/actors/states" + states3 "github.com/filecoin-project/specs-actors/v3/actors/states" + states4 "github.com/filecoin-project/specs-actors/v4/actors/states" + states5 "github.com/filecoin-project/specs-actors/v5/actors/states" + + "github.com/filecoin-project/lotus/chain/actors/adt" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" "github.com/filecoin-project/lotus/chain/types" ) var log = logging.Logger("statetree") -// Stores actors state by their ID. +// StateTree stores actors state by their ID. type StateTree struct { - root *hamt.Node - Store cbor.IpldStore + root adt.Map + version types.StateTreeVersion + info cid.Cid + Store cbor.IpldStore + lookupIDFun func(address.Address) (address.Address, error) snaps *stateSnaps } type stateSnaps struct { - layers []*stateSnapLayer + layers []*stateSnapLayer + lastMaybeNonEmptyResolveCache int } type stateSnapLayer struct { @@ -62,7 +74,12 @@ func (ss *stateSnaps) addLayer() { func (ss *stateSnaps) dropLayer() { ss.layers[len(ss.layers)-1] = nil // allow it to be GCed + ss.layers = ss.layers[:len(ss.layers)-1] + + if ss.lastMaybeNonEmptyResolveCache == len(ss.layers) { + ss.lastMaybeNonEmptyResolveCache = len(ss.layers) - 1 + } } func (ss *stateSnaps) mergeLastLayer() { @@ -81,7 +98,13 @@ func (ss *stateSnaps) mergeLastLayer() { } func (ss *stateSnaps) resolveAddress(addr address.Address) (address.Address, bool) { - for i := len(ss.layers) - 1; i >= 0; i-- { + for i := ss.lastMaybeNonEmptyResolveCache; i >= 0; i-- { + if len(ss.layers[i].resolveCache) == 0 { + if ss.lastMaybeNonEmptyResolveCache == i { + ss.lastMaybeNonEmptyResolveCache = i - 1 + } + continue + } resa, ok := ss.layers[i].resolveCache[addr] if ok { return resa, true @@ -92,6 +115,7 @@ func (ss *stateSnaps) resolveAddress(addr address.Address) (address.Address, boo func (ss *stateSnaps) cacheResolveAddress(addr, resa address.Address) { ss.layers[len(ss.layers)-1].resolveCache[addr] = resa + ss.lastMaybeNonEmptyResolveCache = len(ss.layers) - 1 } func (ss *stateSnaps) getActor(addr address.Address) (*types.Actor, error) { @@ -116,26 +140,171 @@ func (ss *stateSnaps) deleteActor(addr address.Address) { ss.layers[len(ss.layers)-1].actors[addr] = streeOp{Delete: true} } -func NewStateTree(cst cbor.IpldStore) (*StateTree, error) { - return &StateTree{ - root: hamt.NewNode(cst, hamt.UseTreeBitWidth(5)), - Store: cst, - snaps: newStateSnaps(), - }, nil +// VersionForNetwork returns the state tree version for the given network +// version. +func VersionForNetwork(ver network.Version) (types.StateTreeVersion, error) { + switch ver { + case network.Version0, network.Version1, network.Version2, network.Version3: + return types.StateTreeVersion0, nil + case network.Version4, network.Version5, network.Version6, network.Version7, network.Version8, network.Version9: + return types.StateTreeVersion1, nil + case network.Version10, network.Version11: + return types.StateTreeVersion2, nil + case network.Version12: + return types.StateTreeVersion3, nil + + case network.Version13, network.Version14, network.Version15, network.Version16, network.Version17: + return types.StateTreeVersion4, nil + + case network.Version18, network.Version19, network.Version20: + return types.StateTreeVersion5, nil + + default: + panic(fmt.Sprintf("unsupported network version %d", ver)) + } +} + +func NewStateTree(cst cbor.IpldStore, ver types.StateTreeVersion) (*StateTree, error) { + var info cid.Cid + switch ver { + case types.StateTreeVersion0: + // info is undefined + case types.StateTreeVersion1, types.StateTreeVersion2, types.StateTreeVersion3, types.StateTreeVersion4, types.StateTreeVersion5: + var err error + info, err = cst.Put(context.TODO(), new(types.StateInfo0)) + if err != nil { + return nil, err + } + default: + return nil, xerrors.Errorf("unsupported state tree version: %d", ver) + } + + store := adt.WrapStore(context.TODO(), cst) + var hamt adt.Map + switch ver { + case types.StateTreeVersion0: + tree, err := states0.NewTree(store) + if err != nil { + return nil, xerrors.Errorf("failed to create state tree: %w", err) + } + hamt = tree.Map + case types.StateTreeVersion1: + tree, err := states2.NewTree(store) + if err != nil { + return nil, xerrors.Errorf("failed to create state tree: %w", err) + } + hamt = tree.Map + case types.StateTreeVersion2: + tree, err := states3.NewTree(store) + if err != nil { + return nil, xerrors.Errorf("failed to create state tree: %w", err) + } + hamt = tree.Map + case types.StateTreeVersion3: + tree, err := states4.NewTree(store) + if err != nil { + return nil, xerrors.Errorf("failed to create state tree: %w", err) + } + hamt = tree.Map + case types.StateTreeVersion4: + tree, err := states5.NewTree(store) + if err != nil { + return nil, xerrors.Errorf("failed to create state tree: %w", err) + } + hamt = tree.Map + case types.StateTreeVersion5: + tree, err := builtin_types.NewTree(store) + if err != nil { + return nil, xerrors.Errorf("failed to create state tree: %w", err) + } + hamt = tree.Map + + default: + return nil, xerrors.Errorf("unsupported state tree version: %d", ver) + } + + s := &StateTree{ + root: hamt, + info: info, + version: ver, + Store: cst, + snaps: newStateSnaps(), + } + s.lookupIDFun = s.lookupIDinternal + return s, nil } func LoadStateTree(cst cbor.IpldStore, c cid.Cid) (*StateTree, error) { - nd, err := hamt.LoadNode(context.Background(), cst, c, hamt.UseTreeBitWidth(5)) - if err != nil { - log.Errorf("loading hamt node %s failed: %s", c, err) - return nil, err + var root types.StateRoot + // Try loading as a new-style state-tree (version/actors tuple). + if err := cst.Get(context.TODO(), c, &root); err != nil { + // We failed to decode as the new version, must be an old version. + root.Actors = c + root.Version = types.StateTreeVersion0 } - return &StateTree{ - root: nd, - Store: cst, - snaps: newStateSnaps(), - }, nil + store := adt.WrapStore(context.TODO(), cst) + + var ( + hamt adt.Map + err error + ) + switch root.Version { + case types.StateTreeVersion0: + var tree *states0.Tree + tree, err = states0.LoadTree(store, root.Actors) + if tree != nil { + hamt = tree.Map + } + case types.StateTreeVersion1: + var tree *states2.Tree + tree, err = states2.LoadTree(store, root.Actors) + if tree != nil { + hamt = tree.Map + } + case types.StateTreeVersion2: + var tree *states3.Tree + tree, err = states3.LoadTree(store, root.Actors) + if tree != nil { + hamt = tree.Map + } + case types.StateTreeVersion3: + var tree *states4.Tree + tree, err = states4.LoadTree(store, root.Actors) + if tree != nil { + hamt = tree.Map + } + case types.StateTreeVersion4: + var tree *states5.Tree + tree, err = states5.LoadTree(store, root.Actors) + if tree != nil { + hamt = tree.Map + } + case types.StateTreeVersion5: + var tree *builtin_types.ActorTree + tree, err = builtin_types.LoadTree(store, root.Actors) + if tree != nil { + hamt = tree.Map + } + + default: + return nil, xerrors.Errorf("unsupported state tree version: %d", root.Version) + } + if err != nil { + log.Errorf("failed to load state tree: %s", err) + return nil, xerrors.Errorf("failed to load state tree %s: %w", c, err) + } + + s := &StateTree{ + root: hamt, + info: root.Info, + version: root.Version, + Store: cst, + snaps: newStateSnaps(), + } + s.lookupIDFun = s.lookupIDinternal + + return s, nil } func (st *StateTree) SetActor(addr address.Address, act *types.Actor) error { @@ -149,7 +318,28 @@ func (st *StateTree) SetActor(addr address.Address, act *types.Actor) error { return nil } -// `LookupID` gets the ID address of this actor's `addr` stored in the `InitActor`. +func (st *StateTree) lookupIDinternal(addr address.Address) (address.Address, error) { + act, err := st.GetActor(init_.Address) + if err != nil { + return address.Undef, xerrors.Errorf("getting init actor: %w", err) + } + + ias, err := init_.Load(&AdtStore{st.Store}, act) + if err != nil { + return address.Undef, xerrors.Errorf("loading init actor state: %w", err) + } + + a, found, err := ias.ResolveAddress(addr) + if err == nil && !found { + err = types.ErrActorNotFound + } + if err != nil { + return address.Undef, xerrors.Errorf("resolve address %s: %w", addr, err) + } + return a, err +} + +// LookupID gets the ID address of this actor's `addr` stored in the `InitActor`. func (st *StateTree) LookupID(addr address.Address) (address.Address, error) { if addr.Protocol() == address.ID { return addr, nil @@ -159,20 +349,9 @@ func (st *StateTree) LookupID(addr address.Address) (address.Address, error) { if ok { return resa, nil } - - act, err := st.GetActor(builtin.InitActorAddr) + a, err := st.lookupIDFun(addr) if err != nil { - return address.Undef, xerrors.Errorf("getting init actor: %w", err) - } - - var ias init_.State - if err := st.Store.Get(context.TODO(), act.Head, &ias); err != nil { - return address.Undef, xerrors.Errorf("loading init actor state: %w", err) - } - - a, err := ias.ResolveAddress(&AdtStore{st.Store}, addr) - if err != nil { - return address.Undef, xerrors.Errorf("resolve address %s: %w", addr, err) + return a, err } st.snaps.cacheResolveAddress(addr, a) @@ -189,7 +368,7 @@ func (st *StateTree) GetActor(addr address.Address) (*types.Actor, error) { // Transform `addr` to its ID format. iaddr, err := st.LookupID(addr) if err != nil { - if xerrors.Is(err, init_.ErrAddressNotFound) { + if xerrors.Is(err, types.ErrActorNotFound) { return nil, xerrors.Errorf("resolution lookup failed (%s): %w", addr, err) } return nil, xerrors.Errorf("address resolution: %w", err) @@ -206,12 +385,20 @@ func (st *StateTree) GetActor(addr address.Address) (*types.Actor, error) { } var act types.Actor - err = st.root.Find(context.TODO(), string(addr.Bytes()), &act) - if err != nil { - if err == hamt.ErrNotFound { - return nil, types.ErrActorNotFound + var found bool + if st.version <= types.StateTreeVersion4 { + var act4 types.ActorV4 + found, err = st.root.Get(abi.AddrKey(addr), &act4) + if found { + act = *types.AsActorV5(&act4) } + } else { + found, err = st.root.Get(abi.AddrKey(addr), &act) + } + if err != nil { return nil, xerrors.Errorf("hamt find failed: %w", err) + } else if !found { + return nil, types.ErrActorNotFound } st.snaps.setActor(addr, &act) @@ -226,7 +413,7 @@ func (st *StateTree) DeleteActor(addr address.Address) error { iaddr, err := st.LookupID(addr) if err != nil { - if xerrors.Is(err, init_.ErrAddressNotFound) { + if xerrors.Is(err, types.ErrActorNotFound) { return xerrors.Errorf("resolution lookup failed (%s): %w", addr, err) } return xerrors.Errorf("address resolution: %w", err) @@ -245,7 +432,7 @@ func (st *StateTree) DeleteActor(addr address.Address) error { } func (st *StateTree) Flush(ctx context.Context) (cid.Cid, error) { - ctx, span := trace.StartSpan(ctx, "stateTree.Flush") + ctx, span := trace.StartSpan(ctx, "stateTree.Flush") //nolint:staticcheck defer span.End() if len(st.snaps.layers) != 1 { return cid.Undef, xerrors.Errorf("tried to flush state tree with snapshots on the stack") @@ -253,25 +440,37 @@ func (st *StateTree) Flush(ctx context.Context) (cid.Cid, error) { for addr, sto := range st.snaps.layers[0].actors { if sto.Delete { - if err := st.root.Delete(ctx, string(addr.Bytes())); err != nil { + if err := st.root.Delete(abi.AddrKey(addr)); err != nil { return cid.Undef, err } } else { - if err := st.root.Set(ctx, string(addr.Bytes()), &sto.Act); err != nil { - return cid.Undef, err + if st.version <= types.StateTreeVersion4 { + act4 := types.AsActorV4(&sto.Act) + if err := st.root.Put(abi.AddrKey(addr), act4); err != nil { + return cid.Undef, err + } + } else { + if err := st.root.Put(abi.AddrKey(addr), &sto.Act); err != nil { + return cid.Undef, err + } } } } - if err := st.root.Flush(ctx); err != nil { - return cid.Undef, err + root, err := st.root.Root() + if err != nil { + return cid.Undef, xerrors.Errorf("failed to flush state-tree hamt: %w", err) } - - return st.Store.Put(ctx, st.root) + // If we're version 0, return a raw tree. + if st.version == types.StateTreeVersion0 { + return root, nil + } + // Otherwise, return a versioned tree. + return st.Store.Put(ctx, &types.StateRoot{Version: st.version, Actors: root, Info: st.info}) } func (st *StateTree) Snapshot(ctx context.Context) error { - ctx, span := trace.StartSpan(ctx, "stateTree.SnapShot") + ctx, span := trace.StartSpan(ctx, "stateTree.SnapShot") //nolint:staticcheck defer span.End() st.snaps.addLayer() @@ -285,19 +484,19 @@ func (st *StateTree) ClearSnapshot() { func (st *StateTree) RegisterNewAddress(addr address.Address) (address.Address, error) { var out address.Address - err := st.MutateActor(builtin.InitActorAddr, func(initact *types.Actor) error { - var ias init_.State - if err := st.Store.Get(context.TODO(), initact.Head, &ias); err != nil { + err := st.MutateActor(init_.Address, func(initact *types.Actor) error { + ias, err := init_.Load(&AdtStore{st.Store}, initact) + if err != nil { return err } - oaddr, err := ias.MapAddressToNewID(&AdtStore{st.Store}, addr) + oaddr, err := ias.MapAddressToNewID(addr) if err != nil { return err } out = oaddr - ncid, err := st.Store.Put(context.TODO(), &ias) + ncid, err := st.Store.Put(context.TODO(), ias) if err != nil { return err } @@ -339,3 +538,127 @@ func (st *StateTree) MutateActor(addr address.Address, f func(*types.Actor) erro return st.SetActor(addr, act) } + +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. + if st.version <= types.StateTreeVersion4 { + var act types.ActorV4 + return st.root.ForEach(&act, func(k string) error { + act := act // copy + addr, err := address.NewFromBytes([]byte(k)) + if err != nil { + 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, types.AsActorV5(&act)) + }) + } + + var act types.Actor + return st.root.ForEach(&act, func(k string) error { + act := act // copy + addr, err := address.NewFromBytes([]byte(k)) + if err != nil { + 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) + }) +} + +// Version returns the version of the StateTree data structure in use. +func (st *StateTree) Version() types.StateTreeVersion { + return st.version +} + +func Diff(ctx context.Context, oldTree, newTree *StateTree) (map[string]types.Actor, error) { + out := map[string]types.Actor{} + + var ( + ncval, ocval cbg.Deferred + buf = bytes.NewReader(nil) + ) + if err := newTree.root.ForEach(&ncval, func(k string) error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + addr, err := address.NewFromBytes([]byte(k)) + if err != nil { + return xerrors.Errorf("address in state tree was not valid: %w", err) + } + + found, err := oldTree.root.Get(abi.AddrKey(addr), &ocval) + if err != nil { + return err + } + + if found && bytes.Equal(ocval.Raw, ncval.Raw) { + return nil // not changed + } + + if newTree.version <= types.StateTreeVersion4 { + var act types.ActorV4 + + buf.Reset(ncval.Raw) + err = act.UnmarshalCBOR(buf) + buf.Reset(nil) + + if err != nil { + return err + } + + out[addr.String()] = *types.AsActorV5(&act) + + } else { + var act types.Actor + + buf.Reset(ncval.Raw) + err = act.UnmarshalCBOR(buf) + buf.Reset(nil) + + if err != nil { + return err + } + + out[addr.String()] = act + } + + return nil + } + }); err != nil { + return nil, err + } + return out, nil +} diff --git a/chain/state/statetree_test.go b/chain/state/statetree_test.go index 4cdc87d19..9a221751a 100644 --- a/chain/state/statetree_test.go +++ b/chain/state/statetree_test.go @@ -1,3 +1,4 @@ +// stm: #unit package state import ( @@ -5,17 +6,21 @@ import ( "fmt" "testing" - "github.com/filecoin-project/specs-actors/actors/builtin" - - address "github.com/filecoin-project/go-address" - "github.com/filecoin-project/lotus/chain/types" "github.com/ipfs/go-cid" cbor "github.com/ipfs/go-ipld-cbor" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/network" + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/types" ) func BenchmarkStateTreeSet(b *testing.B) { + //stm: @CHAIN_STATETREE_SET_ACTOR_001 cst := cbor.NewMemCborStore() - st, err := NewStateTree(cst) + st, err := NewStateTree(cst, types.StateTreeVersion1) if err != nil { b.Fatal(err) } @@ -30,8 +35,8 @@ func BenchmarkStateTreeSet(b *testing.B) { } err = st.SetActor(a, &types.Actor{ Balance: types.NewInt(1258812523), - Code: builtin.StorageMinerActorCodeID, - Head: builtin.AccountActorCodeID, + Code: builtin2.StorageMinerActorCodeID, + Head: builtin2.AccountActorCodeID, Nonce: uint64(i), }) if err != nil { @@ -41,8 +46,14 @@ func BenchmarkStateTreeSet(b *testing.B) { } func BenchmarkStateTreeSetFlush(b *testing.B) { + //stm: @CHAIN_STATETREE_SET_ACTOR_001 cst := cbor.NewMemCborStore() - st, err := NewStateTree(cst) + sv, err := VersionForNetwork(build.TestNetworkVersion) + if err != nil { + b.Fatal(err) + } + + st, err := NewStateTree(cst, sv) if err != nil { b.Fatal(err) } @@ -57,8 +68,8 @@ func BenchmarkStateTreeSetFlush(b *testing.B) { } err = st.SetActor(a, &types.Actor{ Balance: types.NewInt(1258812523), - Code: builtin.StorageMinerActorCodeID, - Head: builtin.AccountActorCodeID, + Code: builtin2.StorageMinerActorCodeID, + Head: builtin2.AccountActorCodeID, Nonce: uint64(i), }) if err != nil { @@ -70,9 +81,120 @@ func BenchmarkStateTreeSetFlush(b *testing.B) { } } -func BenchmarkStateTree10kGetActor(b *testing.B) { +func TestResolveCache(t *testing.T) { + //stm: @CHAIN_STATETREE_SET_ACTOR_001, @CHAIN_STATETREE_GET_ACTOR_001, @CHAIN_STATETREE_VERSION_FOR_NETWORK_001 + //stm: @CHAIN_STATETREE_SNAPSHOT_001, @CHAIN_STATETREE_SNAPSHOT_CLEAR_001 cst := cbor.NewMemCborStore() - st, err := NewStateTree(cst) + sv, err := VersionForNetwork(build.TestNetworkVersion) + if err != nil { + t.Fatal(err) + } + + st, err := NewStateTree(cst, sv) + if err != nil { + t.Fatal(err) + } + nonId := address.NewForTestGetter()() + id, _ := address.NewIDAddress(1000) + + st.lookupIDFun = func(a address.Address) (address.Address, error) { + if a == nonId { + return id, nil + } + return address.Undef, types.ErrActorNotFound + } + + err = st.SetActor(nonId, &types.Actor{Nonce: 1}) + if err != nil { + t.Fatal(err) + } + + { + err = st.Snapshot(context.TODO()) + if err != nil { + t.Fatal(err) + } + act, err := st.GetActor(nonId) + if err != nil { + t.Fatal(err) + } + if act.Nonce != 1 { + t.Fatalf("expected nonce 1, got %d", act.Nonce) + } + err = st.SetActor(nonId, &types.Actor{Nonce: 2}) + if err != nil { + t.Fatal(err) + } + + act, err = st.GetActor(nonId) + if err != nil { + t.Fatal(err) + } + if act.Nonce != 2 { + t.Fatalf("expected nonce 2, got %d", act.Nonce) + } + + if err := st.Revert(); err != nil { + t.Fatal(err) + } + st.ClearSnapshot() + } + + act, err := st.GetActor(nonId) + if err != nil { + t.Fatal(err) + } + if act.Nonce != 1 { + t.Fatalf("expected nonce 1, got %d", act.Nonce) + } + + { + err = st.Snapshot(context.TODO()) + if err != nil { + t.Fatal(err) + } + act, err := st.GetActor(nonId) + if err != nil { + t.Fatal(err) + } + if act.Nonce != 1 { + t.Fatalf("expected nonce 1, got %d", act.Nonce) + } + err = st.SetActor(nonId, &types.Actor{Nonce: 2}) + if err != nil { + t.Fatal(err) + } + + act, err = st.GetActor(nonId) + if err != nil { + t.Fatal(err) + } + if act.Nonce != 2 { + t.Fatalf("expected nonce 2, got %d", act.Nonce) + } + st.ClearSnapshot() + } + + act, err = st.GetActor(nonId) + if err != nil { + t.Fatal(err) + } + if act.Nonce != 2 { + t.Fatalf("expected nonce 2, got %d", act.Nonce) + } + +} + +func BenchmarkStateTree10kGetActor(b *testing.B) { + //stm: @CHAIN_STATETREE_SET_ACTOR_001, @CHAIN_STATETREE_GET_ACTOR_001, @CHAIN_STATETREE_VERSION_FOR_NETWORK_001 + //stm: @CHAIN_STATETREE_FLUSH_001 + cst := cbor.NewMemCborStore() + sv, err := VersionForNetwork(build.TestNetworkVersion) + if err != nil { + b.Fatal(err) + } + + st, err := NewStateTree(cst, sv) if err != nil { b.Fatal(err) } @@ -83,8 +205,8 @@ func BenchmarkStateTree10kGetActor(b *testing.B) { } err = st.SetActor(a, &types.Actor{ Balance: types.NewInt(1258812523 + uint64(i)), - Code: builtin.StorageMinerActorCodeID, - Head: builtin.AccountActorCodeID, + Code: builtin2.StorageMinerActorCodeID, + Head: builtin2.AccountActorCodeID, Nonce: uint64(i), }) if err != nil { @@ -113,8 +235,14 @@ func BenchmarkStateTree10kGetActor(b *testing.B) { } func TestSetCache(t *testing.T) { + //stm: @CHAIN_STATETREE_SET_ACTOR_001, @CHAIN_STATETREE_GET_ACTOR_001, @CHAIN_STATETREE_VERSION_FOR_NETWORK_001 cst := cbor.NewMemCborStore() - st, err := NewStateTree(cst) + sv, err := VersionForNetwork(build.TestNetworkVersion) + if err != nil { + t.Fatal(err) + } + + st, err := NewStateTree(cst, sv) if err != nil { t.Fatal(err) } @@ -126,8 +254,8 @@ func TestSetCache(t *testing.T) { act := &types.Actor{ Balance: types.NewInt(0), - Code: builtin.StorageMinerActorCodeID, - Head: builtin.AccountActorCodeID, + Code: builtin2.StorageMinerActorCodeID, + Head: builtin2.AccountActorCodeID, Nonce: 0, } @@ -149,9 +277,17 @@ func TestSetCache(t *testing.T) { } func TestSnapshots(t *testing.T) { + //stm: @CHAIN_STATETREE_SET_ACTOR_001, @CHAIN_STATETREE_GET_ACTOR_001, @CHAIN_STATETREE_VERSION_FOR_NETWORK_001 + //stm: @CHAIN_STATETREE_FLUSH_001, @CHAIN_STATETREE_SNAPSHOT_REVERT_001, CHAIN_STATETREE_SNAPSHOT_CLEAR_001 ctx := context.Background() cst := cbor.NewMemCborStore() - st, err := NewStateTree(cst) + + sv, err := VersionForNetwork(build.TestNetworkVersion) + if err != nil { + t.Fatal(err) + } + + st, err := NewStateTree(cst, sv) if err != nil { t.Fatal(err) } @@ -170,7 +306,7 @@ func TestSnapshots(t *testing.T) { t.Fatal(err) } - if err := st.SetActor(addrs[0], &types.Actor{Code: builtin.AccountActorCodeID, Head: builtin.AccountActorCodeID, Balance: types.NewInt(55)}); err != nil { + if err := st.SetActor(addrs[0], &types.Actor{Code: builtin2.AccountActorCodeID, Head: builtin2.AccountActorCodeID, Balance: types.NewInt(55)}); err != nil { t.Fatal(err) } @@ -179,7 +315,7 @@ func TestSnapshots(t *testing.T) { t.Fatal(err) } - if err := st.SetActor(addrs[1], &types.Actor{Code: builtin.AccountActorCodeID, Head: builtin.AccountActorCodeID, Balance: types.NewInt(77)}); err != nil { + if err := st.SetActor(addrs[1], &types.Actor{Code: builtin2.AccountActorCodeID, Head: builtin2.AccountActorCodeID, Balance: types.NewInt(77)}); err != nil { t.Fatal(err) } @@ -190,7 +326,7 @@ func TestSnapshots(t *testing.T) { } // more operations in top level call... - if err := st.SetActor(addrs[2], &types.Actor{Code: builtin.AccountActorCodeID, Head: builtin.AccountActorCodeID, Balance: types.NewInt(123)}); err != nil { + if err := st.SetActor(addrs[2], &types.Actor{Code: builtin2.AccountActorCodeID, Head: builtin2.AccountActorCodeID, Balance: types.NewInt(123)}); err != nil { t.Fatal(err) } @@ -199,7 +335,7 @@ func TestSnapshots(t *testing.T) { t.Fatal(err) } - if err := st.SetActor(addrs[3], &types.Actor{Code: builtin.AccountActorCodeID, Head: builtin.AccountActorCodeID, Balance: types.NewInt(5)}); err != nil { + if err := st.SetActor(addrs[3], &types.Actor{Code: builtin2.AccountActorCodeID, Head: builtin2.AccountActorCodeID, Balance: types.NewInt(5)}); err != nil { t.Fatal(err) } @@ -233,8 +369,17 @@ func assertNotHas(t *testing.T, st *StateTree, addr address.Address) { } func TestStateTreeConsistency(t *testing.T) { + //stm: @CHAIN_STATETREE_SET_ACTOR_001, @CHAIN_STATETREE_VERSION_FOR_NETWORK_001, @CHAIN_STATETREE_FLUSH_001 cst := cbor.NewMemCborStore() - st, err := NewStateTree(cst) + + // TODO: ActorUpgrade: this test tests pre actors v2 + + sv, err := VersionForNetwork(network.Version3) + if err != nil { + t.Fatal(err) + } + + st, err := NewStateTree(cst, sv) if err != nil { t.Fatal(err) } @@ -255,12 +400,15 @@ func TestStateTreeConsistency(t *testing.T) { } for i, a := range addrs { - st.SetActor(a, &types.Actor{ + err := st.SetActor(a, &types.Actor{ Code: randomCid, Head: randomCid, Balance: types.NewInt(uint64(10000 + i)), Nonce: uint64(1000 - i), }) + if err != nil { + t.Fatalf("while setting actor: %+v", err) + } } root, err := st.Flush(context.TODO()) @@ -269,7 +417,7 @@ func TestStateTreeConsistency(t *testing.T) { } fmt.Println("root is: ", root) - if root.String() != "bafy2bzaceadyjnrv3sbjvowfl3jr4pdn5p2bf3exjjie2f3shg4oy5sub7h34" { + if root.String() != "bafy2bzaceb2bhqw75pqp44efoxvlnm73lnctq6djair56bfn5x3gw56epcxbi" { t.Fatal("MISMATCH!") } } diff --git a/chain/stmgr/actors.go b/chain/stmgr/actors.go new file mode 100644 index 000000000..4de39c7f1 --- /dev/null +++ b/chain/stmgr/actors.go @@ -0,0 +1,573 @@ +package stmgr + +import ( + "bytes" + "context" + "os" + + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + "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/big" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/go-state-types/network" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/actors/builtin/market" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/builtin/paych" + "github.com/filecoin-project/lotus/chain/actors/builtin/power" + "github.com/filecoin-project/lotus/chain/actors/builtin/verifreg" + "github.com/filecoin-project/lotus/chain/beacon" + "github.com/filecoin-project/lotus/chain/rand" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/vm" + "github.com/filecoin-project/lotus/storage/sealer/storiface" +) + +func GetMinerWorkerRaw(ctx context.Context, sm *StateManager, st cid.Cid, maddr address.Address) (address.Address, error) { + state, err := sm.StateTree(st) + if err != nil { + return address.Undef, xerrors.Errorf("(get sset) failed to load state tree: %w", err) + } + act, err := state.GetActor(maddr) + if err != nil { + return address.Undef, xerrors.Errorf("(get sset) failed to load miner actor: %w", err) + } + mas, err := miner.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return address.Undef, xerrors.Errorf("(get sset) failed to load miner actor state: %w", err) + } + + info, err := mas.Info() + if err != nil { + return address.Undef, xerrors.Errorf("failed to load actor info: %w", err) + } + + return vm.ResolveToDeterministicAddr(state, sm.cs.ActorStore(ctx), info.Worker) +} + +func GetPower(ctx context.Context, sm *StateManager, ts *types.TipSet, maddr address.Address) (power.Claim, power.Claim, bool, error) { + return GetPowerRaw(ctx, sm, ts.ParentState(), maddr) +} + +func GetPowerRaw(ctx context.Context, sm *StateManager, st cid.Cid, maddr address.Address) (power.Claim, power.Claim, bool, error) { + act, err := sm.LoadActorRaw(ctx, power.Address, st) + if err != nil { + return power.Claim{}, power.Claim{}, false, xerrors.Errorf("(get sset) failed to load power actor state: %w", err) + } + + pas, err := power.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return power.Claim{}, power.Claim{}, false, err + } + + tpow, err := pas.TotalPower() + if err != nil { + return power.Claim{}, power.Claim{}, false, err + } + + var mpow power.Claim + var minpow bool + if maddr != address.Undef { + var found bool + mpow, found, err = pas.MinerPower(maddr) + if err != nil || !found { + return power.Claim{}, tpow, false, err + } + + minpow, err = pas.MinerNominalPowerMeetsConsensusMinimum(maddr) + if err != nil { + return power.Claim{}, power.Claim{}, false, err + } + } + + return mpow, tpow, minpow, nil +} + +func PreCommitInfo(ctx context.Context, sm *StateManager, maddr address.Address, sid abi.SectorNumber, ts *types.TipSet) (*miner.SectorPreCommitOnChainInfo, error) { + act, err := sm.LoadActor(ctx, maddr, ts) + if err != nil { + return nil, xerrors.Errorf("(get sset) failed to load miner actor: %w", err) + } + + mas, err := miner.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return nil, xerrors.Errorf("(get sset) failed to load miner actor state: %w", err) + } + + return mas.GetPrecommittedSector(sid) +} + +// Returns nil, nil if sector is not found +func MinerSectorInfo(ctx context.Context, sm *StateManager, maddr address.Address, sid abi.SectorNumber, ts *types.TipSet) (*miner.SectorOnChainInfo, error) { + act, err := sm.LoadActor(ctx, maddr, ts) + if err != nil { + return nil, xerrors.Errorf("(get sset) failed to load miner actor: %w", err) + } + + mas, err := miner.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return nil, xerrors.Errorf("(get sset) failed to load miner actor state: %w", err) + } + + return mas.GetSector(sid) +} + +func GetSectorsForWinningPoSt(ctx context.Context, nv network.Version, pv storiface.Verifier, sm *StateManager, st cid.Cid, maddr address.Address, rand abi.PoStRandomness) ([]builtin.ExtendedSectorInfo, error) { + act, err := sm.LoadActorRaw(ctx, maddr, st) + if err != nil { + return nil, xerrors.Errorf("failed to load miner actor: %w", err) + } + + mas, err := miner.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return nil, xerrors.Errorf("failed to load miner actor state: %w", err) + } + + var provingSectors bitfield.BitField + if nv < network.Version7 { + allSectors, err := miner.AllPartSectors(mas, miner.Partition.AllSectors) + if err != nil { + return nil, xerrors.Errorf("get all sectors: %w", err) + } + + faultySectors, err := miner.AllPartSectors(mas, miner.Partition.FaultySectors) + if err != nil { + return nil, xerrors.Errorf("get faulty sectors: %w", err) + } + + provingSectors, err = bitfield.SubtractBitField(allSectors, faultySectors) + if err != nil { + return nil, xerrors.Errorf("calc proving sectors: %w", err) + } + } else { + provingSectors, err = miner.AllPartSectors(mas, miner.Partition.ActiveSectors) + if err != nil { + return nil, xerrors.Errorf("get active sectors sectors: %w", err) + } + } + + numProvSect, err := provingSectors.Count() + if err != nil { + return nil, xerrors.Errorf("failed to count bits: %w", err) + } + + // TODO(review): is this right? feels fishy to me + if numProvSect == 0 { + return nil, nil + } + + info, err := mas.Info() + if err != nil { + return nil, xerrors.Errorf("getting miner info: %w", err) + } + + mid, err := address.IDFromAddress(maddr) + if err != nil { + return nil, xerrors.Errorf("getting miner ID: %w", err) + } + + proofType, err := miner.WinningPoStProofTypeFromWindowPoStProofType(nv, info.WindowPoStProofType) + if err != nil { + return nil, xerrors.Errorf("determining winning post proof type: %w", err) + } + + ids, err := pv.GenerateWinningPoStSectorChallenge(ctx, proofType, abi.ActorID(mid), rand, numProvSect) + if err != nil { + return nil, xerrors.Errorf("generating winning post challenges: %w", err) + } + + iter, err := provingSectors.BitIterator() + if err != nil { + return nil, xerrors.Errorf("iterating over proving sectors: %w", err) + } + + // Select winning sectors by _index_ in the all-sectors bitfield. + selectedSectors := bitfield.New() + prev := uint64(0) + for _, n := range ids { + sno, err := iter.Nth(n - prev) + if err != nil { + return nil, xerrors.Errorf("iterating over proving sectors: %w", err) + } + selectedSectors.Set(sno) + prev = n + } + + sectors, err := mas.LoadSectors(&selectedSectors) + if err != nil { + return nil, xerrors.Errorf("loading proving sectors: %w", err) + } + + out := make([]builtin.ExtendedSectorInfo, len(sectors)) + for i, sinfo := range sectors { + out[i] = builtin.ExtendedSectorInfo{ + SealProof: sinfo.SealProof, + SectorNumber: sinfo.SectorNumber, + SealedCID: sinfo.SealedCID, + SectorKey: sinfo.SectorKeyCID, + } + } + + return out, nil +} + +func GetMinerSlashed(ctx context.Context, sm *StateManager, ts *types.TipSet, maddr address.Address) (bool, error) { + act, err := sm.LoadActor(ctx, power.Address, ts) + if err != nil { + return false, xerrors.Errorf("failed to load power actor: %w", err) + } + + spas, err := power.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return false, xerrors.Errorf("failed to load power actor state: %w", err) + } + + _, ok, err := spas.MinerPower(maddr) + if err != nil { + return false, xerrors.Errorf("getting miner power: %w", err) + } + + if !ok { + return true, nil + } + + return false, nil +} + +func GetStorageDeal(ctx context.Context, sm *StateManager, dealID abi.DealID, ts *types.TipSet) (*api.MarketDeal, error) { + act, err := sm.LoadActor(ctx, market.Address, ts) + if err != nil { + return nil, xerrors.Errorf("failed to load market actor: %w", err) + } + + state, err := market.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return nil, xerrors.Errorf("failed to load market actor state: %w", err) + } + + proposals, err := state.Proposals() + if err != nil { + return nil, xerrors.Errorf("failed to get proposals from state : %w", err) + } + + proposal, found, err := proposals.Get(dealID) + + if err != nil { + return nil, xerrors.Errorf("failed to get proposal : %w", err) + } else if !found { + return nil, xerrors.Errorf( + "deal %d not found "+ + "- deal may not have completed sealing before deal proposal "+ + "start epoch, or deal may have been slashed", + dealID) + } + + states, err := state.States() + if err != nil { + return nil, xerrors.Errorf("failed to get states : %w", err) + } + + st, found, err := states.Get(dealID) + if err != nil { + return nil, xerrors.Errorf("failed to get state : %w", err) + } + + if !found { + st = market.EmptyDealState() + } + + return &api.MarketDeal{ + Proposal: *proposal, + State: *st, + }, nil +} + +func ListMinerActors(ctx context.Context, sm *StateManager, ts *types.TipSet) ([]address.Address, error) { + act, err := sm.LoadActor(ctx, power.Address, ts) + if err != nil { + return nil, xerrors.Errorf("failed to load power actor: %w", err) + } + + powState, err := power.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return nil, xerrors.Errorf("failed to load power actor state: %w", err) + } + + return powState.ListAllMiners() +} + +func MinerGetBaseInfo(ctx context.Context, sm *StateManager, bcs beacon.Schedule, tsk types.TipSetKey, round abi.ChainEpoch, maddr address.Address, pv storiface.Verifier) (*api.MiningBaseInfo, error) { + ts, err := sm.ChainStore().LoadTipSet(ctx, tsk) + if err != nil { + return nil, xerrors.Errorf("failed to load tipset for mining base: %w", err) + } + + prev, err := sm.ChainStore().GetLatestBeaconEntry(ctx, ts) + if err != nil { + if os.Getenv("LOTUS_IGNORE_DRAND") != "_yes_" { + return nil, xerrors.Errorf("failed to get latest beacon entry: %w", err) + } + + prev = &types.BeaconEntry{} + } + + entries, err := beacon.BeaconEntriesForBlock(ctx, bcs, sm.GetNetworkVersion(ctx, round), round, ts.Height(), *prev) + if err != nil { + return nil, err + } + + rbase := *prev + if len(entries) > 0 { + rbase = entries[len(entries)-1] + } + + lbts, lbst, err := GetLookbackTipSetForRound(ctx, sm, ts, round) + if err != nil { + return nil, xerrors.Errorf("getting lookback miner actor state: %w", err) + } + + act, err := sm.LoadActorRaw(ctx, maddr, lbst) + if xerrors.Is(err, types.ErrActorNotFound) { + _, err := sm.LoadActor(ctx, maddr, ts) + if err != nil { + return nil, xerrors.Errorf("loading miner in current state: %w", err) + } + + return nil, nil + } + if err != nil { + return nil, xerrors.Errorf("failed to load miner actor: %w", err) + } + + mas, err := miner.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return nil, xerrors.Errorf("failed to load miner actor state: %w", err) + } + + buf := new(bytes.Buffer) + if err := maddr.MarshalCBOR(buf); err != nil { + return nil, xerrors.Errorf("failed to marshal miner address: %w", err) + } + + prand, err := rand.DrawRandomness(rbase.Data, crypto.DomainSeparationTag_WinningPoStChallengeSeed, round, buf.Bytes()) + if err != nil { + return nil, xerrors.Errorf("failed to get randomness for winning post: %w", err) + } + + nv := sm.GetNetworkVersion(ctx, ts.Height()) + + sectors, err := GetSectorsForWinningPoSt(ctx, nv, pv, sm, lbst, maddr, prand) + if err != nil { + return nil, xerrors.Errorf("getting winning post proving set: %w", err) + } + + if len(sectors) == 0 { + return nil, nil + } + + mpow, tpow, _, err := GetPowerRaw(ctx, sm, lbst, maddr) + if err != nil { + return nil, xerrors.Errorf("failed to get power: %w", err) + } + + info, err := mas.Info() + if err != nil { + return nil, err + } + + worker, err := sm.ResolveToDeterministicAddress(ctx, info.Worker, ts) + if err != nil { + return nil, xerrors.Errorf("resolving worker address: %w", err) + } + + // TODO: Not ideal performance...This method reloads miner and power state (already looked up here and in GetPowerRaw) + eligible, err := MinerEligibleToMine(ctx, sm, maddr, ts, lbts) + if err != nil { + return nil, xerrors.Errorf("determining miner eligibility: %w", err) + } + + return &api.MiningBaseInfo{ + MinerPower: mpow.QualityAdjPower, + NetworkPower: tpow.QualityAdjPower, + Sectors: sectors, + WorkerKey: worker, + SectorSize: info.SectorSize, + PrevBeaconEntry: *prev, + BeaconEntries: entries, + EligibleForMining: eligible, + }, nil +} + +func minerHasMinPower(ctx context.Context, sm *StateManager, addr address.Address, ts *types.TipSet) (bool, error) { + pact, err := sm.LoadActor(ctx, power.Address, ts) + if err != nil { + return false, xerrors.Errorf("loading power actor state: %w", err) + } + + ps, err := power.Load(sm.cs.ActorStore(ctx), pact) + if err != nil { + return false, err + } + + return ps.MinerNominalPowerMeetsConsensusMinimum(addr) +} + +func MinerEligibleToMine(ctx context.Context, sm *StateManager, addr address.Address, baseTs *types.TipSet, lookbackTs *types.TipSet) (bool, error) { + hmp, err := minerHasMinPower(ctx, sm, addr, lookbackTs) + + // TODO: We're blurring the lines between a "runtime network version" and a "Lotus upgrade epoch", is that unavoidable? + if sm.GetNetworkVersion(ctx, baseTs.Height()) <= network.Version3 { + return hmp, err + } + + if err != nil { + return false, err + } + + if !hmp { + return false, nil + } + + // Post actors v2, also check MinerEligibleForElection with base ts + + pact, err := sm.LoadActor(ctx, power.Address, baseTs) + if err != nil { + return false, xerrors.Errorf("loading power actor state: %w", err) + } + + pstate, err := power.Load(sm.cs.ActorStore(ctx), pact) + if err != nil { + return false, err + } + + mact, err := sm.LoadActor(ctx, addr, baseTs) + if err != nil { + return false, xerrors.Errorf("loading miner actor state: %w", err) + } + + mstate, err := miner.Load(sm.cs.ActorStore(ctx), mact) + if err != nil { + return false, err + } + + // Non-empty power claim. + if claim, found, err := pstate.MinerPower(addr); err != nil { + return false, err + } else if !found { + return false, err + } else if claim.QualityAdjPower.LessThanEqual(big.Zero()) { + return false, err + } + + // No fee debt. + if debt, err := mstate.FeeDebt(); err != nil { + return false, err + } else if !debt.IsZero() { + return false, err + } + + // No active consensus faults. + if mInfo, err := mstate.Info(); err != nil { + return false, err + } else if baseTs.Height() <= mInfo.ConsensusFaultElapsed { + return false, nil + } + + return true, nil +} + +func (sm *StateManager) GetPaychState(ctx context.Context, addr address.Address, ts *types.TipSet) (*types.Actor, paych.State, error) { + st, err := sm.ParentState(ts) + if err != nil { + return nil, nil, err + } + + act, err := st.GetActor(addr) + if err != nil { + return nil, nil, err + } + + actState, err := paych.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return nil, nil, err + } + return act, actState, nil +} + +func (sm *StateManager) GetMarketState(ctx context.Context, ts *types.TipSet) (market.State, error) { + st, err := sm.ParentState(ts) + if err != nil { + return nil, err + } + + act, err := st.GetActor(market.Address) + if err != nil { + return nil, err + } + + actState, err := market.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return nil, err + } + return actState, nil +} + +func (sm *StateManager) GetVerifregState(ctx context.Context, ts *types.TipSet) (verifreg.State, error) { + st, err := sm.ParentState(ts) + if err != nil { + return nil, err + } + + act, err := st.GetActor(verifreg.Address) + if err != nil { + return nil, err + } + + actState, err := verifreg.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return nil, err + } + return actState, nil +} + +func (sm *StateManager) MarketBalance(ctx context.Context, addr address.Address, ts *types.TipSet) (api.MarketBalance, error) { + mstate, err := sm.GetMarketState(ctx, ts) + if err != nil { + return api.MarketBalance{}, err + } + + addr, err = sm.LookupID(ctx, addr, ts) + if err != nil { + return api.MarketBalance{}, err + } + + var out api.MarketBalance + + et, err := mstate.EscrowTable() + if err != nil { + return api.MarketBalance{}, err + } + out.Escrow, err = et.Get(addr) + if err != nil { + return api.MarketBalance{}, xerrors.Errorf("getting escrow balance: %w", err) + } + + lt, err := mstate.LockedTable() + if err != nil { + return api.MarketBalance{}, err + } + out.Locked, err = lt.Get(addr) + if err != nil { + return api.MarketBalance{}, xerrors.Errorf("getting locked balance: %w", err) + } + + return out, nil +} + +var _ StateManagerAPI = (*StateManager)(nil) diff --git a/chain/stmgr/call.go b/chain/stmgr/call.go index b9635696f..61056528f 100644 --- a/chain/stmgr/call.go +++ b/chain/stmgr/call.go @@ -2,108 +2,285 @@ package stmgr import ( "context" + "errors" "fmt" - "github.com/filecoin-project/specs-actors/actors/abi" "github.com/ipfs/go-cid" + cbor "github.com/ipfs/go-ipld-cbor" "go.opencensus.io/trace" "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/crypto" + "github.com/filecoin-project/go-state-types/network" + "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/rand" + "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/vm" ) -func (sm *StateManager) CallRaw(ctx context.Context, msg *types.Message, bstate cid.Cid, r vm.Rand, bheight abi.ChainEpoch) (*api.InvocResult, error) { - ctx, span := trace.StartSpan(ctx, "statemanager.CallRaw") - defer span.End() +var ErrExpensiveFork = errors.New("refusing explicit call due to state fork at epoch") - vmi, err := vm.NewVM(bstate, bheight, r, sm.cs.Blockstore(), sm.cs.VMSys()) - if err != nil { - return nil, xerrors.Errorf("failed to set up vm: %w", err) - } +// Call applies the given message to the given tipset's parent state, at the epoch following the +// tipset's parent. In the presence of null blocks, the height at which the message is invoked may +// be less than the specified tipset. +func (sm *StateManager) Call(ctx context.Context, msg *types.Message, ts *types.TipSet) (*api.InvocResult, error) { + // Copy the message as we modify it below. + msgCopy := *msg + msg = &msgCopy if msg.GasLimit == 0 { - msg.GasLimit = 10000000000 + msg.GasLimit = build.BlockGasLimit } - if msg.GasPrice == types.EmptyInt { - msg.GasPrice = types.NewInt(0) + if msg.GasFeeCap == types.EmptyInt { + msg.GasFeeCap = types.NewInt(0) + } + if msg.GasPremium == types.EmptyInt { + msg.GasPremium = types.NewInt(0) } if msg.Value == types.EmptyInt { msg.Value = types.NewInt(0) } + return sm.callInternal(ctx, msg, nil, ts, cid.Undef, sm.GetNetworkVersion, false, false) +} + +// CallWithGas calculates the state for a given tipset, and then applies the given message on top of that state. +func (sm *StateManager) CallWithGas(ctx context.Context, msg *types.Message, priorMsgs []types.ChainMsg, ts *types.TipSet, applyTsMessages bool) (*api.InvocResult, error) { + return sm.callInternal(ctx, msg, priorMsgs, ts, cid.Undef, sm.GetNetworkVersion, true, applyTsMessages) +} + +// CallAtStateAndVersion allows you to specify a message to execute on the given stateCid and network version. +// This should mostly be used for gas modelling on a migrated state. +// Tipset here is not needed because stateCid and network version fully describe execution we want. The internal function +// will get the heaviest tipset for use for things like basefee, which we don't really care about here. +func (sm *StateManager) CallAtStateAndVersion(ctx context.Context, msg *types.Message, stateCid cid.Cid, v network.Version) (*api.InvocResult, error) { + nvGetter := func(context.Context, abi.ChainEpoch) network.Version { + return v + } + + return sm.callInternal(ctx, msg, nil, nil, stateCid, nvGetter, true, false) +} + +// - If no tipset is specified, the first tipset without an expensive migration or one in its parent is used. +// - If executing a message at a given tipset or its parent would trigger an expensive migration, the call will +// fail with ErrExpensiveFork. +func (sm *StateManager) callInternal(ctx context.Context, msg *types.Message, priorMsgs []types.ChainMsg, ts *types.TipSet, stateCid cid.Cid, nvGetter rand.NetworkVersionGetter, checkGas, applyTsMessages bool) (*api.InvocResult, error) { + ctx, span := trace.StartSpan(ctx, "statemanager.callInternal") + defer span.End() + + // Copy the message as we'll be modifying the nonce. + msgCopy := *msg + msg = &msgCopy + + var err error + var pts *types.TipSet + if ts == nil { + ts = sm.cs.GetHeaviestTipSet() + + // Search back till we find a height with no fork, or we reach the beginning. + // We need the _previous_ height to have no fork, because we'll + // run the fork logic in `sm.TipSetState`. We need the _current_ + // height to have no fork, because we'll run it inside this + // function before executing the given message. + for ts.Height() > 0 { + pts, err = sm.cs.GetTipSetFromKey(ctx, ts.Parents()) + if err != nil { + return nil, xerrors.Errorf("failed to find a non-forking epoch: %w", err) + } + // Checks for expensive forks from the parents to the tipset, including nil tipsets + if !sm.hasExpensiveForkBetween(pts.Height(), ts.Height()+1) { + break + } + + ts = pts + } + } else if ts.Height() > 0 { + pts, err = sm.cs.GetTipSetFromKey(ctx, ts.Parents()) + if err != nil { + return nil, xerrors.Errorf("failed to find a non-forking epoch: %w", err) + } + if sm.hasExpensiveForkBetween(pts.Height(), ts.Height()+1) { + return nil, ErrExpensiveFork + } + } + + // Unless executing on a specific state cid, apply all the messages from the current tipset + // first. Unfortunately, we can't just execute the tipset, because that will run cron. We + // don't want to apply miner messages after cron runs in a given epoch. + if stateCid == cid.Undef { + stateCid = ts.ParentState() + } + tsMsgs, err := sm.cs.MessagesForTipset(ctx, ts) + if err != nil { + return nil, xerrors.Errorf("failed to lookup messages for parent tipset: %w", err) + } + + if applyTsMessages { + priorMsgs = append(tsMsgs, priorMsgs...) + } else { + var filteredTsMsgs []types.ChainMsg + for _, tsMsg := range tsMsgs { + //TODO we should technically be normalizing the filecoin address of from when we compare here + if tsMsg.VMMessage().From == msg.VMMessage().From { + filteredTsMsgs = append(filteredTsMsgs, tsMsg) + } + } + priorMsgs = append(filteredTsMsgs, priorMsgs...) + } + + // Technically, the tipset we're passing in here should be ts+1, but that may not exist. + stateCid, err = sm.HandleStateForks(ctx, stateCid, ts.Height(), nil, ts) + if err != nil { + return nil, fmt.Errorf("failed to handle fork: %w", err) + } + if span.IsRecordingEvents() { span.AddAttributes( trace.Int64Attribute("gas_limit", msg.GasLimit), - trace.Int64Attribute("gas_price", int64(msg.GasPrice.Uint64())), + trace.StringAttribute("gas_feecap", msg.GasFeeCap.String()), trace.StringAttribute("value", msg.Value.String()), ) } - fromActor, err := vmi.StateTree().GetActor(msg.From) + buffStore := blockstore.NewTieredBstore(sm.cs.StateBlockstore(), blockstore.NewMemorySync()) + vmopt := &vm.VMOpts{ + StateBase: stateCid, + Epoch: ts.Height(), + Timestamp: ts.MinTimestamp(), + Rand: rand.NewStateRand(sm.cs, ts.Cids(), sm.beacon, nvGetter), + Bstore: buffStore, + Actors: sm.tsExec.NewActorRegistry(), + Syscalls: sm.Syscalls, + CircSupplyCalc: sm.GetVMCirculatingSupply, + NetworkVersion: nvGetter(ctx, ts.Height()), + BaseFee: ts.Blocks()[0].ParentBaseFee, + LookbackState: LookbackStateGetterForTipset(sm, ts), + TipSetGetter: TipSetGetterForTipset(sm.cs, ts), + Tracing: true, + } + vmi, err := sm.newVM(ctx, vmopt) + if err != nil { + return nil, xerrors.Errorf("failed to set up vm: %w", err) + } + for i, m := range priorMsgs { + _, err = vmi.ApplyMessage(ctx, m) + if err != nil { + return nil, xerrors.Errorf("applying prior message (%d, %s): %w", i, m.Cid(), err) + } + } + + // We flush to get the VM's view of the state tree after applying the above messages + // This is needed to get the correct nonce from the actor state to match the VM + stateCid, err = vmi.Flush(ctx) + if err != nil { + return nil, xerrors.Errorf("flushing vm: %w", err) + } + + stTree, err := state.LoadStateTree(cbor.NewCborStore(buffStore), stateCid) + if err != nil { + return nil, xerrors.Errorf("loading state tree: %w", err) + } + + fromActor, err := stTree.GetActor(msg.From) if err != nil { return nil, xerrors.Errorf("call raw get actor: %s", err) } msg.Nonce = fromActor.Nonce - // TODO: maybe just use the invoker directly? - ret, err := vmi.ApplyImplicitMessage(ctx, msg) - if err != nil { - return nil, xerrors.Errorf("apply message failed: %w", err) + // If the fee cap is set to zero, make gas free. + if msg.GasFeeCap.NilOrZero() { + // Now estimate with a new VM with no base fee. + vmopt.BaseFee = big.Zero() + vmopt.StateBase = stateCid + + vmi, err = sm.newVM(ctx, vmopt) + if err != nil { + return nil, xerrors.Errorf("failed to set up estimation vm: %w", err) + } + } + + var ret *vm.ApplyRet + var gasInfo api.MsgGasCost + if checkGas { + fromKey, err := sm.ResolveToDeterministicAddress(ctx, msg.From, ts) + if err != nil { + return nil, xerrors.Errorf("could not resolve key: %w", err) + } + + var msgApply types.ChainMsg + + switch fromKey.Protocol() { + case address.BLS: + msgApply = msg + case address.SECP256K1: + msgApply = &types.SignedMessage{ + Message: *msg, + Signature: crypto.Signature{ + Type: crypto.SigTypeSecp256k1, + Data: make([]byte, 65), + }, + } + case address.Delegated: + msgApply = &types.SignedMessage{ + Message: *msg, + Signature: crypto.Signature{ + Type: crypto.SigTypeDelegated, + Data: make([]byte, 65), + }, + } + } + + ret, err = vmi.ApplyMessage(ctx, msgApply) + if err != nil { + return nil, xerrors.Errorf("gas estimation failed: %w", err) + } + gasInfo = MakeMsgGasCost(msg, ret) + } else { + ret, err = vmi.ApplyImplicitMessage(ctx, msg) + if err != nil && ret == nil { + return nil, xerrors.Errorf("apply message failed: %w", err) + } } var errs string if ret.ActorErr != nil { errs = ret.ActorErr.Error() - log.Warnf("chain call failed: %s", ret.ActorErr) } return &api.InvocResult{ - Msg: msg, - MsgRct: &ret.MessageReceipt, - InternalExecutions: ret.InternalExecutions, - Error: errs, - Duration: ret.Duration, - }, nil - -} - -func (sm *StateManager) Call(ctx context.Context, msg *types.Message, ts *types.TipSet) (*api.InvocResult, error) { - if ts == nil { - ts = sm.cs.GetHeaviestTipSet() - } - - state := ts.ParentState() - - r := store.NewChainRand(sm.cs, ts.Cids(), ts.Height()) - - return sm.CallRaw(ctx, msg, state, r, ts.Height()) + MsgCid: msg.Cid(), + Msg: msg, + MsgRct: &ret.MessageReceipt, + GasCost: gasInfo, + ExecutionTrace: ret.ExecutionTrace, + Error: errs, + Duration: ret.Duration, + }, err } var errHaltExecution = fmt.Errorf("halt") func (sm *StateManager) Replay(ctx context.Context, ts *types.TipSet, mcid cid.Cid) (*types.Message, *vm.ApplyRet, error) { - var outm *types.Message - var outr *vm.ApplyRet + var finder messageFinder + // message to find + finder.mcid = mcid - _, _, err := sm.computeTipSetState(ctx, ts.Blocks(), func(c cid.Cid, m *types.Message, ret *vm.ApplyRet) error { - if c == mcid { - outm = m - outr = ret - return errHaltExecution - } - return nil - }) - if err != nil && err != errHaltExecution { + _, _, err := sm.tsExec.ExecuteTipSet(ctx, sm, ts, &finder, true) + if err != nil && !xerrors.Is(err, errHaltExecution) { return nil, nil, xerrors.Errorf("unexpected error during execution: %w", err) } - if outr == nil { + if finder.outr == nil { return nil, nil, xerrors.Errorf("given message not found in tipset") } - return outm, outr, nil + return finder.outm, finder.outr, nil } diff --git a/chain/stmgr/execute.go b/chain/stmgr/execute.go new file mode 100644 index 000000000..bed857833 --- /dev/null +++ b/chain/stmgr/execute.go @@ -0,0 +1,174 @@ +package stmgr + +import ( + "context" + "fmt" + + "github.com/ipfs/go-cid" + "go.opencensus.io/trace" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/chain/types" +) + +func (sm *StateManager) TipSetState(ctx context.Context, ts *types.TipSet) (st cid.Cid, rec cid.Cid, err error) { + ctx, span := trace.StartSpan(ctx, "tipSetState") + defer span.End() + if span.IsRecordingEvents() { + span.AddAttributes(trace.StringAttribute("tipset", fmt.Sprint(ts.Cids()))) + } + + ck := cidsToKey(ts.Cids()) + sm.stlk.Lock() + cw, cwok := sm.compWait[ck] + if cwok { + sm.stlk.Unlock() + span.AddAttributes(trace.BoolAttribute("waited", true)) + select { + case <-cw: + sm.stlk.Lock() + case <-ctx.Done(): + return cid.Undef, cid.Undef, ctx.Err() + } + } + cached, ok := sm.stCache[ck] + if ok { + sm.stlk.Unlock() + span.AddAttributes(trace.BoolAttribute("cache", true)) + return cached[0], cached[1], nil + } + ch := make(chan struct{}) + sm.compWait[ck] = ch + + defer func() { + sm.stlk.Lock() + delete(sm.compWait, ck) + if st != cid.Undef { + sm.stCache[ck] = []cid.Cid{st, rec} + } + sm.stlk.Unlock() + close(ch) + }() + + sm.stlk.Unlock() + + if ts.Height() == 0 { + // NB: This is here because the process that executes blocks requires that the + // block miner reference a valid miner in the state tree. Unless we create some + // magical genesis miner, this won't work properly, so we short circuit here + // This avoids the question of 'who gets paid the genesis block reward'. + // This also makes us not attempt to lookup the tipset state with + // tryLookupTipsetState, which would cause a very long, very slow walk. + return ts.Blocks()[0].ParentStateRoot, ts.Blocks()[0].ParentMessageReceipts, nil + } + + // First, try to find the tipset in the current chain. If found, we can avoid re-executing + // it. + if st, rec, found := tryLookupTipsetState(ctx, sm.cs, ts); found { + return st, rec, nil + } + + st, rec, err = sm.tsExec.ExecuteTipSet(ctx, sm, ts, sm.tsExecMonitor, false) + if err != nil { + return cid.Undef, cid.Undef, err + } + + return st, rec, nil +} + +// Try to lookup a state & receipt CID for a given tipset by walking the chain instead of executing +// it. This will only successfully return the state/receipt CIDs if they're found in the state +// store. +// +// NOTE: This _won't_ recursively walk the receipt/state trees. It assumes that having the root +// implies having the rest of the tree. However, lotus generally makes that assumption anyways. +func tryLookupTipsetState(ctx context.Context, cs *store.ChainStore, ts *types.TipSet) (cid.Cid, cid.Cid, bool) { + nextTs, err := cs.GetTipsetByHeight(ctx, ts.Height()+1, nil, false) + if err != nil { + // Nothing to see here. The requested height may be beyond the current head. + return cid.Undef, cid.Undef, false + } + + // Make sure we're on the correct fork. + if nextTs.Parents() != ts.Key() { + // Also nothing to see here. This just means that the requested tipset is on a + // different fork. + return cid.Undef, cid.Undef, false + } + + stateCid := nextTs.ParentState() + receiptCid := nextTs.ParentMessageReceipts() + + // Make sure we have the parent state. + if hasState, err := cs.StateBlockstore().Has(ctx, stateCid); err != nil { + log.Errorw("failed to lookup state-root in blockstore", "cid", stateCid, "error", err) + return cid.Undef, cid.Undef, false + } else if !hasState { + // We have the chain but don't have the state. It looks like we need to try + // executing? + return cid.Undef, cid.Undef, false + } + + // Make sure we have the receipts. + if hasReceipts, err := cs.ChainBlockstore().Has(ctx, receiptCid); err != nil { + log.Errorw("failed to lookup receipts in blockstore", "cid", receiptCid, "error", err) + return cid.Undef, cid.Undef, false + } else if !hasReceipts { + // If we don't have the receipts, re-execute and try again. + return cid.Undef, cid.Undef, false + } + + return stateCid, receiptCid, true +} + +func (sm *StateManager) ExecutionTraceWithMonitor(ctx context.Context, ts *types.TipSet, em ExecMonitor) (cid.Cid, error) { + st, _, err := sm.tsExec.ExecuteTipSet(ctx, sm, ts, em, true) + return st, err +} + +func (sm *StateManager) ExecutionTrace(ctx context.Context, ts *types.TipSet) (cid.Cid, []*api.InvocResult, error) { + tsKey := ts.Key() + + // check if we have the trace for this tipset in the cache + if execTraceCacheSize > 0 { + sm.execTraceCacheLock.Lock() + if entry, ok := sm.execTraceCache.Get(tsKey); ok { + // we have to make a deep copy since caller can modify the invocTrace + // and we don't want that to change what we store in cache + invocTraceCopy := makeDeepCopy(entry.invocTrace) + sm.execTraceCacheLock.Unlock() + return entry.postStateRoot, invocTraceCopy, nil + } + sm.execTraceCacheLock.Unlock() + } + + var invocTrace []*api.InvocResult + st, err := sm.ExecutionTraceWithMonitor(ctx, ts, &InvocationTracer{trace: &invocTrace}) + if err != nil { + return cid.Undef, nil, err + } + + if execTraceCacheSize > 0 { + invocTraceCopy := makeDeepCopy(invocTrace) + + sm.execTraceCacheLock.Lock() + sm.execTraceCache.Add(tsKey, tipSetCacheEntry{st, invocTraceCopy}) + sm.execTraceCacheLock.Unlock() + } + + return st, invocTrace, nil +} + +func makeDeepCopy(invocTrace []*api.InvocResult) []*api.InvocResult { + c := make([]*api.InvocResult, len(invocTrace)) + for i, ir := range invocTrace { + if ir == nil { + continue + } + tmp := *ir + c[i] = &tmp + } + + return c +} diff --git a/chain/stmgr/forks.go b/chain/stmgr/forks.go index 7f8dc579b..1f9977d96 100644 --- a/chain/stmgr/forks.go +++ b/chain/stmgr/forks.go @@ -1,25 +1,507 @@ package stmgr import ( + "bytes" "context" + "encoding/binary" + "os" + "sort" + "strings" + "sync" + "time" - "github.com/filecoin-project/specs-actors/actors/abi" "github.com/ipfs/go-cid" + "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" + "github.com/filecoin-project/specs-actors/v8/actors/migration/nv16" + + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/state" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/vm" ) -var ForksAtHeight = map[abi.ChainEpoch]func(context.Context, *StateManager, cid.Cid) (cid.Cid, error){} +// EnvDisablePreMigrations when set to '1' stops pre-migrations from running +const EnvDisablePreMigrations = "LOTUS_DISABLE_PRE_MIGRATIONS" -func (sm *StateManager) handleStateForks(ctx context.Context, pstate cid.Cid, height, parentH abi.ChainEpoch) (_ cid.Cid, err error) { - for i := parentH; i < height; i++ { - f, ok := ForksAtHeight[i] - if ok { - nstate, err := f(ctx, sm, pstate) - if err != nil { - return cid.Undef, err +// MigrationCache can be used to cache information used by a migration. This is primarily useful to +// "pre-compute" some migration state ahead of time, and make it accessible in the migration itself. +type MigrationCache interface { + Write(key string, value cid.Cid) error + Read(key string) (bool, cid.Cid, error) + Load(key string, loadFunc func() (cid.Cid, error)) (cid.Cid, error) +} + +// MigrationFunc is a migration function run at every upgrade. +// +// - The cache is a per-upgrade cache, pre-populated by pre-migrations. +// - The oldState is the state produced by the upgrade epoch. +// - The returned newState is the new state that will be used by the next epoch. +// - The height is the upgrade epoch height (already executed). +// - The tipset is the first non-null tipset after the upgrade height (the tipset in +// which the upgrade is executed). Do not assume that ts.Height() is the upgrade height. +// +// NOTE: In StateCompute and CallWithGas, the passed tipset is actually the tipset _before_ the +// upgrade. The tipset should really only be used for referencing the "current chain". +type MigrationFunc func( + ctx context.Context, + sm *StateManager, cache MigrationCache, + cb ExecMonitor, + oldState cid.Cid, + height abi.ChainEpoch, ts *types.TipSet, +) (newState cid.Cid, err error) + +// PreMigrationFunc is a function run _before_ a network upgrade to pre-compute part of the network +// upgrade and speed it up. +type PreMigrationFunc func( + ctx context.Context, + sm *StateManager, cache MigrationCache, + oldState cid.Cid, + height abi.ChainEpoch, ts *types.TipSet, +) error + +// PreMigration describes a pre-migration step to prepare for a network state upgrade. Pre-migrations +// are optimizations, are not guaranteed to run, and may be canceled and/or run multiple times. +type PreMigration struct { + // PreMigration is the pre-migration function to run at the specified time. This function is + // run asynchronously and must abort promptly when canceled. + PreMigration PreMigrationFunc + + // StartWithin specifies that this pre-migration should be started at most StartWithin + // epochs before the upgrade. + StartWithin abi.ChainEpoch + + // DontStartWithin specifies that this pre-migration should not be started DontStartWithin + // epochs before the final upgrade epoch. + // + // This should be set such that the pre-migration is likely to complete before StopWithin. + DontStartWithin abi.ChainEpoch + + // StopWithin specifies that this pre-migration should be stopped StopWithin epochs of the + // final upgrade epoch. + StopWithin abi.ChainEpoch +} + +type Upgrade struct { + Height abi.ChainEpoch + Network network.Version + Expensive bool + Migration MigrationFunc + + // PreMigrations specifies a set of pre-migration functions to run at the indicated epochs. + // These functions should fill the given cache with information that can speed up the + // eventual full migration at the upgrade epoch. + PreMigrations []PreMigration +} + +type UpgradeSchedule []Upgrade + +func (us UpgradeSchedule) Validate() error { + // Make sure each upgrade is valid. + for _, u := range us { + if u.Network <= 0 { + return xerrors.Errorf("cannot upgrade to version <= 0: %d", u.Network) + } + + for _, m := range u.PreMigrations { + if m.StartWithin <= 0 { + return xerrors.Errorf("pre-migration must specify a positive start-within epoch") } - pstate = nstate + + if m.DontStartWithin < 0 || m.StopWithin < 0 { + return xerrors.Errorf("pre-migration must specify non-negative epochs") + } + + if m.StartWithin <= m.StopWithin { + return xerrors.Errorf("pre-migration start-within must come before stop-within") + } + + // If we have a dont-start-within. + if m.DontStartWithin != 0 { + if m.DontStartWithin < m.StopWithin { + return xerrors.Errorf("pre-migration dont-start-within must come before stop-within") + } + if m.StartWithin <= m.DontStartWithin { + return xerrors.Errorf("pre-migration start-within must come after dont-start-within") + } + } + } + if !sort.SliceIsSorted(u.PreMigrations, func(i, j int) bool { + return u.PreMigrations[i].StartWithin > u.PreMigrations[j].StartWithin //nolint:scopelint,gosec + }) { + return xerrors.Errorf("pre-migrations must be sorted by start epoch") } } - return pstate, nil + // Make sure the upgrade order makes sense. + for i := 1; i < len(us); i++ { + prev := &us[i-1] + curr := &us[i] + if !(prev.Network <= curr.Network) { + return xerrors.Errorf("cannot downgrade from version %d to version %d", prev.Network, curr.Network) + } + // Make sure the heights make sense. + if prev.Height < 0 { + // Previous upgrade was disabled. + continue + } + if !(prev.Height < curr.Height) { + return xerrors.Errorf("upgrade heights must be strictly increasing: upgrade %d was at height %d, followed by upgrade %d at height %d", i-1, prev.Height, i, curr.Height) + } + } + return nil +} + +func (us UpgradeSchedule) GetNtwkVersion(e abi.ChainEpoch) (network.Version, error) { + // Traverse from newest to oldest returning upgrade active during epoch e + for i := len(us) - 1; i >= 0; i-- { + u := us[i] + // u.Height is the last epoch before u.Network becomes the active version + if u.Height < e { + return u.Network, nil + } + } + + return build.GenesisNetworkVersion, nil +} + +func (sm *StateManager) HandleStateForks(ctx context.Context, root cid.Cid, height abi.ChainEpoch, cb ExecMonitor, ts *types.TipSet) (cid.Cid, error) { + retCid := root + u := sm.stateMigrations[height] + if u != nil && u.upgrade != nil { + migCid, ok, err := u.migrationResultCache.Get(ctx, root) + if err == nil && ok { + log.Infow("CACHED migration", "height", height, "from", root, "to", migCid) + return migCid, nil + } else if err != nil { + log.Errorw("failed to lookup previous migration result", "err", err) + } + + startTime := time.Now() + log.Warnw("STARTING migration", "height", height, "from", root) + // Yes, we clone the cache, even for the final upgrade epoch. Why? Reverts. We may + // have to migrate multiple times. + tmpCache := u.cache.Clone() + retCid, err = u.upgrade(ctx, sm, tmpCache, cb, root, height, ts) + if err != nil { + log.Errorw("FAILED migration", "height", height, "from", root, "error", err) + return cid.Undef, err + } + // Yes, we update the cache, even for the final upgrade epoch. Why? Reverts. This + // can save us a _lot_ of time because very few actors will have changed if we + // do a small revert then need to re-run the migration. + u.cache.Update(tmpCache) + log.Warnw("COMPLETED migration", + "height", height, + "from", root, + "to", retCid, + "duration", time.Since(startTime), + ) + + // Only set if migration ran, we do not want a root => root mapping + if err := u.migrationResultCache.Store(ctx, root, retCid); err != nil { + log.Errorw("failed to store migration result", "err", err) + } + } + + return retCid, nil +} + +// Returns true executing tipsets between the specified heights would trigger an expensive +// migration. NOTE: migrations occurring _at_ the target height are not included, as they're +// executed _after_ the target height. +func (sm *StateManager) hasExpensiveForkBetween(parent, height abi.ChainEpoch) bool { + for h := parent; h < height; h++ { + if _, ok := sm.expensiveUpgrades[h]; ok { + return true + } + } + return false +} + +func (sm *StateManager) hasExpensiveFork(height abi.ChainEpoch) bool { + _, ok := sm.expensiveUpgrades[height] + return ok +} + +func runPreMigration(ctx context.Context, sm *StateManager, fn PreMigrationFunc, cache *nv16.MemMigrationCache, ts *types.TipSet) { + height := ts.Height() + parent := ts.ParentState() + + if disabled := os.Getenv(EnvDisablePreMigrations); strings.TrimSpace(disabled) == "1" { + log.Warnw("SKIPPING pre-migration", "height", height) + return + } + + startTime := time.Now() + + log.Warn("STARTING pre-migration") + // Clone the cache so we don't actually _update_ it + // till we're done. Otherwise, if we fail, the next + // migration to use the cache may assume that + // certain blocks exist, even if they don't. + tmpCache := cache.Clone() + err := fn(ctx, sm, tmpCache, parent, height, ts) + if err != nil { + log.Errorw("FAILED pre-migration", "error", err) + return + } + // Finally, if everything worked, update the cache. + cache.Update(tmpCache) + log.Warnw("COMPLETED pre-migration", "duration", time.Since(startTime)) +} + +func (sm *StateManager) preMigrationWorker(ctx context.Context) { + defer close(sm.shutdown) + + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + type op struct { + after abi.ChainEpoch + notAfter abi.ChainEpoch + run func(ts *types.TipSet) + } + + var wg sync.WaitGroup + defer wg.Wait() + + // Turn each pre-migration into an operation in a schedule. + var schedule []op + for upgradeEpoch, migration := range sm.stateMigrations { + cache := migration.cache + for _, prem := range migration.preMigrations { + preCtx, preCancel := context.WithCancel(ctx) + migrationFunc := prem.PreMigration + + afterEpoch := upgradeEpoch - prem.StartWithin + notAfterEpoch := upgradeEpoch - prem.DontStartWithin + stopEpoch := upgradeEpoch - prem.StopWithin + // We can't start after we stop. + if notAfterEpoch > stopEpoch { + notAfterEpoch = stopEpoch - 1 + } + + // Add an op to start a pre-migration. + schedule = append(schedule, op{ + after: afterEpoch, + notAfter: notAfterEpoch, + + // TODO: are these values correct? + run: func(ts *types.TipSet) { + wg.Add(1) + go func() { + defer wg.Done() + runPreMigration(preCtx, sm, migrationFunc, cache, ts) + }() + }, + }) + + // Add an op to cancel the pre-migration if it's still running. + schedule = append(schedule, op{ + after: stopEpoch, + notAfter: -1, + run: func(ts *types.TipSet) { preCancel() }, + }) + } + } + + // Then sort by epoch. + sort.Slice(schedule, func(i, j int) bool { + return schedule[i].after < schedule[j].after + }) + + // Finally, when the head changes, see if there's anything we need to do. + // + // We're intentionally ignoring reorgs as they don't matter for our purposes. + for change := range sm.cs.SubHeadChanges(ctx) { + for _, head := range change { + for len(schedule) > 0 { + op := &schedule[0] + if head.Val.Height() < op.after { + break + } + + // If we haven't passed the pre-migration height... + if op.notAfter < 0 || head.Val.Height() < op.notAfter { + op.run(head.Val) + } + schedule = schedule[1:] + } + } + } +} + +func DoTransfer(tree types.StateTree, from, to address.Address, amt abi.TokenAmount, cb func(trace types.ExecutionTrace)) error { + fromAct, err := tree.GetActor(from) + if err != nil { + return xerrors.Errorf("failed to get 'from' actor for transfer: %w", err) + } + + fromAct.Balance = types.BigSub(fromAct.Balance, amt) + if fromAct.Balance.Sign() < 0 { + return xerrors.Errorf("(sanity) deducted more funds from target account than it had (%s, %s)", from, types.FIL(amt)) + } + + if err := tree.SetActor(from, fromAct); err != nil { + return xerrors.Errorf("failed to persist from actor: %w", err) + } + + toAct, err := tree.GetActor(to) + if err != nil { + return xerrors.Errorf("failed to get 'to' actor for transfer: %w", err) + } + + toAct.Balance = types.BigAdd(toAct.Balance, amt) + + if err := tree.SetActor(to, toAct); err != nil { + return xerrors.Errorf("failed to persist to actor: %w", err) + } + + if cb != nil { + // record the transfer in execution traces + + cb(types.ExecutionTrace{ + Msg: types.MessageTrace{ + From: from, + To: to, + Value: amt, + }, + }) + } + + return nil +} + +func TerminateActor(ctx context.Context, tree *state.StateTree, addr address.Address, em ExecMonitor, epoch abi.ChainEpoch, ts *types.TipSet) error { + a, err := tree.GetActor(addr) + if xerrors.Is(err, types.ErrActorNotFound) { + return types.ErrActorNotFound + } else if err != nil { + return xerrors.Errorf("failed to get actor to delete: %w", err) + } + + var trace types.ExecutionTrace + if err := DoTransfer(tree, addr, builtin.BurntFundsActorAddr, a.Balance, func(t types.ExecutionTrace) { + trace = t + }); err != nil { + return xerrors.Errorf("transferring terminated actor's balance: %w", err) + } + + if em != nil { + // record the transfer in execution traces + + fakeMsg := MakeFakeMsg(builtin.SystemActorAddr, addr, big.Zero(), uint64(epoch)) + + if err := em.MessageApplied(ctx, ts, fakeMsg.Cid(), fakeMsg, &vm.ApplyRet{ + MessageReceipt: *MakeFakeRct(), + ActorErr: nil, + ExecutionTrace: trace, + Duration: 0, + GasCosts: nil, + }, false); err != nil { + return xerrors.Errorf("recording transfers: %w", err) + } + } + + err = tree.DeleteActor(addr) + if err != nil { + return xerrors.Errorf("deleting actor from tree: %w", err) + } + + ia, err := tree.GetActor(init_.Address) + if err != nil { + return xerrors.Errorf("loading init actor: %w", err) + } + + ias, err := init_.Load(&state.AdtStore{IpldStore: tree.Store}, ia) + if err != nil { + return xerrors.Errorf("loading init actor state: %w", err) + } + + if err := ias.Remove(addr); err != nil { + return xerrors.Errorf("deleting entry from address map: %w", err) + } + + nih, err := tree.Store.Put(ctx, ias) + if err != nil { + return xerrors.Errorf("writing new init actor state: %w", err) + } + + ia.Head = nih + + return tree.SetActor(init_.Address, ia) +} + +func SetNetworkName(ctx context.Context, store adt.Store, tree *state.StateTree, name string) error { + ia, err := tree.GetActor(init_.Address) + if err != nil { + return xerrors.Errorf("getting init actor: %w", err) + } + + initState, err := init_.Load(store, ia) + if err != nil { + return xerrors.Errorf("reading init state: %w", err) + } + + if err := initState.SetNetworkName(name); err != nil { + return xerrors.Errorf("setting network name: %w", err) + } + + ia.Head, err = store.Put(ctx, initState) + if err != nil { + return xerrors.Errorf("writing new init state: %w", err) + } + + if err := tree.SetActor(init_.Address, ia); err != nil { + return xerrors.Errorf("setting init actor: %w", err) + } + + return nil +} + +func MakeKeyAddr(splitAddr address.Address, count uint64) (address.Address, error) { + var b bytes.Buffer + if err := splitAddr.MarshalCBOR(&b); err != nil { + return address.Undef, xerrors.Errorf("marshalling split address: %w", err) + } + + if err := binary.Write(&b, binary.BigEndian, count); err != nil { + return address.Undef, xerrors.Errorf("writing count into a buffer: %w", err) + } + + if err := binary.Write(&b, binary.BigEndian, []byte("Ignition upgrade")); err != nil { + return address.Undef, xerrors.Errorf("writing fork name into a buffer: %w", err) + } + + addr, err := address.NewActorAddress(b.Bytes()) + if err != nil { + return address.Undef, xerrors.Errorf("create actor address: %w", err) + } + + return addr, nil +} + +func MakeFakeMsg(from address.Address, to address.Address, amt abi.TokenAmount, nonce uint64) *types.Message { + return &types.Message{ + From: from, + To: to, + Value: amt, + Nonce: nonce, + } +} + +func MakeFakeRct() *types.MessageReceipt { + return &types.MessageReceipt{ + ExitCode: 0, + Return: nil, + GasUsed: 0, + } } diff --git a/chain/stmgr/forks_test.go b/chain/stmgr/forks_test.go index a3b01cd84..bf8793488 100644 --- a/chain/stmgr/forks_test.go +++ b/chain/stmgr/forks_test.go @@ -1,47 +1,54 @@ +// stm: #integration package stmgr_test import ( "context" "fmt" "io" + "os" + "sync" "testing" - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/abi/big" - "github.com/filecoin-project/specs-actors/actors/builtin" - init_ "github.com/filecoin-project/specs-actors/actors/builtin/init" - "github.com/filecoin-project/specs-actors/actors/builtin/miner" - "github.com/filecoin-project/specs-actors/actors/builtin/power" - "github.com/filecoin-project/specs-actors/actors/builtin/verifreg" - "github.com/filecoin-project/specs-actors/actors/runtime" - "github.com/filecoin-project/specs-actors/actors/util/adt" + "github.com/ipfs/go-cid" + "github.com/ipfs/go-datastore" + ipldcbor "github.com/ipfs/go-ipld-cbor" + logging "github.com/ipfs/go-log/v2" + "github.com/stretchr/testify/require" + cbg "github.com/whyrusleeping/cbor-gen" "golang.org/x/xerrors" + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/cbor" + "github.com/filecoin-project/go-state-types/network" + rtt "github.com/filecoin-project/go-state-types/rt" + builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" + init2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/init" + rt2 "github.com/filecoin-project/specs-actors/v2/actors/runtime" + + "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/aerrors" + "github.com/filecoin-project/lotus/chain/actors/builtin" + _init "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/actors/policy" + "github.com/filecoin-project/lotus/chain/consensus" + "github.com/filecoin-project/lotus/chain/consensus/filcns" "github.com/filecoin-project/lotus/chain/gen" - "github.com/filecoin-project/lotus/chain/state" + "github.com/filecoin-project/lotus/chain/index" "github.com/filecoin-project/lotus/chain/stmgr" . "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/vm" _ "github.com/filecoin-project/lotus/lib/sigs/bls" _ "github.com/filecoin-project/lotus/lib/sigs/secp" - - "github.com/ipfs/go-cid" - blockstore "github.com/ipfs/go-ipfs-blockstore" - cbor "github.com/ipfs/go-ipld-cbor" - logging "github.com/ipfs/go-log" - cbg "github.com/whyrusleeping/cbor-gen" ) func init() { - miner.SupportedProofTypes = map[abi.RegisteredProof]struct{}{ - abi.RegisteredProof_StackedDRG2KiBSeal: {}, - } - power.ConsensusMinerMinPower = big.NewInt(2048) - verifreg.MinVerifiedDealSize = big.NewInt(256) + policy.SetSupportedProofTypes(abi.RegisteredSealProof_StackedDrg2KiBV1) + policy.SetConsensusMinerMinPower(abi.NewStoragePower(2048)) + policy.SetMinVerifiedDealSize(abi.NewStoragePower(256)) } const testForkHeight = 40 @@ -49,6 +56,10 @@ const testForkHeight = 40 type testActor struct { } +// must use existing actor that an account is allowed to exec. +func (testActor) Code() cid.Cid { return builtin0.PaymentChannelActorCodeID } +func (testActor) State() cbor.Er { return new(testActorState) } + type testActorState struct { HasUpgraded uint64 } @@ -69,25 +80,25 @@ func (tas *testActorState) UnmarshalCBOR(r io.Reader) error { return nil } -func (ta *testActor) Exports() []interface{} { +func (ta testActor) Exports() []interface{} { return []interface{}{ 1: ta.Constructor, 2: ta.TestMethod, } } -func (ta *testActor) Constructor(rt runtime.Runtime, params *adt.EmptyValue) *adt.EmptyValue { +func (ta *testActor) Constructor(rt rt2.Runtime, params *abi.EmptyValue) *abi.EmptyValue { rt.ValidateImmediateCallerAcceptAny() - rt.State().Create(&testActorState{11}) - fmt.Println("NEW ACTOR ADDRESS IS: ", rt.Message().Receiver()) + rt.StateCreate(&testActorState{11}) + //fmt.Println("NEW ACTOR ADDRESS IS: ", rt.Receiver()) - return adt.Empty + return abi.Empty } -func (ta *testActor) TestMethod(rt runtime.Runtime, params *adt.EmptyValue) *adt.EmptyValue { +func (ta *testActor) TestMethod(rt rt2.Runtime, params *abi.EmptyValue) *abi.EmptyValue { rt.ValidateImmediateCallerAcceptAny() var st testActorState - rt.State().Readonly(&st) + rt.StateReadonly(&st) if rt.CurrEpoch() > testForkHeight { if st.HasUpgraded != 55 { @@ -99,10 +110,13 @@ func (ta *testActor) TestMethod(rt runtime.Runtime, params *adt.EmptyValue) *adt } } - return adt.Empty + return abi.Empty } func TestForkHeightTriggers(t *testing.T) { + //stm: @CHAIN_STATETREE_GET_ACTOR_001, @CHAIN_STATETREE_FLUSH_001, @TOKEN_WALLET_SIGN_001 + //stm: @CHAIN_GEN_NEXT_TIPSET_001 + //stm: @CHAIN_STATE_RESOLVE_TO_KEY_ADDR_001, @CHAIN_STATE_SET_VM_CONSTRUCTOR_001 logging.SetAllLoggers(logging.LevelInfo) ctx := context.TODO() @@ -112,52 +126,60 @@ func TestForkHeightTriggers(t *testing.T) { t.Fatal(err) } - sm := NewStateManager(cg.ChainStore()) - - inv := vm.NewInvoker() - // predicting the address here... may break if other assumptions change taddr, err := address.NewIDAddress(1002) if err != nil { t.Fatal(err) } - stmgr.ForksAtHeight[testForkHeight] = func(ctx context.Context, sm *StateManager, pstate cid.Cid) (cid.Cid, error) { - cst := cbor.NewCborStore(sm.ChainStore().Blockstore()) - st, err := state.LoadStateTree(cst, pstate) - if err != nil { - return cid.Undef, err - } + sm, err := NewStateManager( + cg.ChainStore(), consensus.NewTipSetExecutor(filcns.RewardFunc), cg.StateManager().VMSys(), UpgradeSchedule{{ + Network: network.Version1, + Height: testForkHeight, + Migration: func(ctx context.Context, sm *StateManager, cache MigrationCache, cb ExecMonitor, + root cid.Cid, height abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { + cst := ipldcbor.NewCborStore(sm.ChainStore().StateBlockstore()) - act, err := st.GetActor(taddr) - if err != nil { - return cid.Undef, err - } + st, err := sm.StateTree(root) + if err != nil { + return cid.Undef, xerrors.Errorf("getting state tree: %w", err) + } - var tas testActorState - if err := cst.Get(ctx, act.Head, &tas); err != nil { - return cid.Undef, xerrors.Errorf("in fork handler, failed to run get: %w", err) - } + act, err := st.GetActor(taddr) + if err != nil { + return cid.Undef, err + } - tas.HasUpgraded = 55 + var tas testActorState + if err := cst.Get(ctx, act.Head, &tas); err != nil { + return cid.Undef, xerrors.Errorf("in fork handler, failed to run get: %w", err) + } - ns, err := cst.Put(ctx, &tas) - if err != nil { - return cid.Undef, err - } + tas.HasUpgraded = 55 - act.Head = ns + ns, err := cst.Put(ctx, &tas) + if err != nil { + return cid.Undef, err + } - if err := st.SetActor(taddr, act); err != nil { - return cid.Undef, err - } + act.Head = ns - return st.Flush(ctx) + if err := st.SetActor(taddr, act); err != nil { + return cid.Undef, err + } + + return st.Flush(ctx) + }}}, cg.BeaconSchedule(), datastore.NewMapDatastore(), index.DummyMsgIndex) + if err != nil { + t.Fatal(err) } - inv.Register(builtin.PaymentChannelActorCodeID, &testActor{}, &testActorState{}) - sm.SetVMConstructor(func(c cid.Cid, h abi.ChainEpoch, r vm.Rand, b blockstore.Blockstore, s runtime.Syscalls) (*vm.VM, error) { - nvm, err := vm.NewVM(c, h, r, b, s) + inv := consensus.NewActorRegistry() + registry := builtin.MakeRegistryLegacy([]rtt.VMActor{testActor{}}) + inv.Register(actorstypes.Version0, nil, registry) + + sm.SetVMConstructor(func(ctx context.Context, vmopt *vm.VMOpts) (vm.Interface, error) { + nvm, err := vm.NewLegacyVM(ctx, vmopt) if err != nil { return nil, err } @@ -169,20 +191,19 @@ func TestForkHeightTriggers(t *testing.T) { var msgs []*types.SignedMessage - enc, err := actors.SerializeParams(&init_.ExecParams{CodeCID: builtin.PaymentChannelActorCodeID}) + enc, err := actors.SerializeParams(&init2.ExecParams{CodeCID: (testActor{}).Code()}) if err != nil { t.Fatal(err) } m := &types.Message{ From: cg.Banker(), - To: builtin.InitActorAddr, - Method: builtin.MethodsInit.Exec, + To: _init.Address, + Method: _init.Methods.Exec, Params: enc, - GasLimit: 10000, - GasPrice: types.NewInt(0), + GasLimit: types.TestGasLimit, } - sig, err := cg.Wallet().Sign(ctx, cg.Banker(), m.Cid().Bytes()) + sig, err := cg.Wallet().WalletSign(ctx, cg.Banker(), m.Cid().Bytes(), api.MsgMeta{}) if err != nil { t.Fatal(err) } @@ -206,12 +227,11 @@ func TestForkHeightTriggers(t *testing.T) { Method: 2, Params: nil, Nonce: nonce, - GasLimit: 10000, - GasPrice: types.NewInt(0), + GasLimit: types.TestGasLimit, } nonce++ - sig, err := cg.Wallet().Sign(ctx, cg.Banker(), m.Cid().Bytes()) + sig, err := cg.Wallet().WalletSign(ctx, cg.Banker(), m.Cid().Bytes(), api.MsgMeta{}) if err != nil { return nil, err } @@ -231,3 +251,461 @@ func TestForkHeightTriggers(t *testing.T) { } } } + +func TestForkRefuseCall(t *testing.T) { + //stm: @CHAIN_GEN_NEXT_TIPSET_001, @CHAIN_GEN_NEXT_TIPSET_FROM_MINERS_001 + //stm: @CHAIN_STATE_RESOLVE_TO_KEY_ADDR_001, @CHAIN_STATE_SET_VM_CONSTRUCTOR_001, @CHAIN_STATE_CALL_001 + logging.SetAllLoggers(logging.LevelInfo) + + for after := 0; after < 3; after++ { + for before := 0; before < 3; before++ { + // Makes the lints happy... + after := after + before := before + t.Run(fmt.Sprintf("after:%d,before:%d", after, before), func(t *testing.T) { + testForkRefuseCall(t, before, after) + }) + } + } + +} +func testForkRefuseCall(t *testing.T, nullsBefore, nullsAfter int) { + ctx := context.TODO() + + cg, err := gen.NewGenerator() + if err != nil { + t.Fatal(err) + } + + var migrationCount int + sm, err := NewStateManager( + cg.ChainStore(), consensus.NewTipSetExecutor(filcns.RewardFunc), cg.StateManager().VMSys(), UpgradeSchedule{{ + Network: network.Version1, + Expensive: true, + Height: testForkHeight, + Migration: func(ctx context.Context, sm *StateManager, cache MigrationCache, cb ExecMonitor, + root cid.Cid, height abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { + migrationCount++ + return root, nil + }}}, cg.BeaconSchedule(), datastore.NewMapDatastore(), index.DummyMsgIndex) + if err != nil { + t.Fatal(err) + } + + inv := consensus.NewActorRegistry() + registry := builtin.MakeRegistryLegacy([]rtt.VMActor{testActor{}}) + inv.Register(actorstypes.Version0, nil, registry) + + sm.SetVMConstructor(func(ctx context.Context, vmopt *vm.VMOpts) (vm.Interface, error) { + nvm, err := vm.NewLegacyVM(ctx, vmopt) + if err != nil { + return nil, err + } + nvm.SetInvoker(inv) + return nvm, nil + }) + + cg.SetStateManager(sm) + + enc, err := actors.SerializeParams(&init2.ExecParams{CodeCID: (testActor{}).Code()}) + if err != nil { + t.Fatal(err) + } + + m := &types.Message{ + From: cg.Banker(), + To: _init.Address, + Method: _init.Methods.Exec, + Params: enc, + GasLimit: types.TestGasLimit, + Value: types.NewInt(0), + GasPremium: types.NewInt(0), + GasFeeCap: types.NewInt(0), + } + + nullStart := abi.ChainEpoch(testForkHeight - nullsBefore) + nullLength := abi.ChainEpoch(nullsBefore + nullsAfter) + + for i := 0; i < testForkHeight*2; i++ { + pts := cg.CurTipset.TipSet() + skip := abi.ChainEpoch(0) + if pts.Height() == nullStart { + skip = nullLength + } + ts, err := cg.NextTipSetFromMiners(pts, cg.Miners, skip) + if err != nil { + t.Fatal(err) + } + + parentHeight := pts.Height() + currentHeight := ts.TipSet.TipSet().Height() + + // CallWithGas calls on top of the given tipset. + ret, err := sm.CallWithGas(ctx, m, nil, ts.TipSet.TipSet(), true) + if parentHeight <= testForkHeight && currentHeight >= testForkHeight { + // If I had a fork, or I _will_ have a fork, it should fail. + require.Equal(t, ErrExpensiveFork, err) + } else { + require.NoError(t, err) + require.True(t, ret.MsgRct.ExitCode.IsSuccess()) + } + + // Call always applies the message to the "next block" after the tipset's parent state. + ret, err = sm.Call(ctx, m, ts.TipSet.TipSet()) + if parentHeight <= testForkHeight && currentHeight >= testForkHeight { + require.Equal(t, ErrExpensiveFork, err) + } else { + require.NoError(t, err) + require.True(t, ret.MsgRct.ExitCode.IsSuccess()) + } + + // Calls without a tipset should walk back to the last non-fork tipset. + // We _verify_ that the migration wasn't run multiple times at the end of the + // test. + ret, err = sm.CallWithGas(ctx, m, nil, nil, true) + require.NoError(t, err) + require.True(t, ret.MsgRct.ExitCode.IsSuccess()) + + ret, err = sm.Call(ctx, m, nil) + require.NoError(t, err) + require.True(t, ret.MsgRct.ExitCode.IsSuccess()) + } + // Make sure we didn't execute the migration multiple times. + require.Equal(t, migrationCount, 1) +} + +func TestForkPreMigration(t *testing.T) { + //stm: @CHAIN_GEN_NEXT_TIPSET_001, + //stm: @CHAIN_STATE_RESOLVE_TO_KEY_ADDR_001, @CHAIN_STATE_SET_VM_CONSTRUCTOR_001 + logging.SetAllLoggers(logging.LevelInfo) + + cg, err := gen.NewGenerator() + if err != nil { + t.Fatal(err) + } + + fooCid, err := abi.CidBuilder.Sum([]byte("foo")) + require.NoError(t, err) + + barCid, err := abi.CidBuilder.Sum([]byte("bar")) + require.NoError(t, err) + + failCid, err := abi.CidBuilder.Sum([]byte("fail")) + require.NoError(t, err) + + var wait20 sync.WaitGroup + wait20.Add(3) + + wasCanceled := make(chan struct{}) + + checkCache := func(t *testing.T, cache MigrationCache) { + found, value, err := cache.Read("foo") + require.NoError(t, err) + require.True(t, found) + require.Equal(t, fooCid, value) + + found, value, err = cache.Read("bar") + require.NoError(t, err) + require.True(t, found) + require.Equal(t, barCid, value) + + found, _, err = cache.Read("fail") + require.NoError(t, err) + require.False(t, found) + } + + counter := make(chan struct{}, 10) + + sm, err := NewStateManager( + cg.ChainStore(), consensus.NewTipSetExecutor(filcns.RewardFunc), cg.StateManager().VMSys(), UpgradeSchedule{{ + Network: network.Version1, + Height: testForkHeight, + Migration: func(ctx context.Context, sm *StateManager, cache MigrationCache, cb ExecMonitor, + root cid.Cid, height abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { + + // Make sure the test that should be canceled, is canceled. + select { + case <-wasCanceled: + case <-ctx.Done(): + return cid.Undef, ctx.Err() + } + + // the cache should be setup correctly. + checkCache(t, cache) + + counter <- struct{}{} + + return root, nil + }, + PreMigrations: []PreMigration{{ + StartWithin: 20, + PreMigration: func(ctx context.Context, _ *StateManager, cache MigrationCache, + _ cid.Cid, _ abi.ChainEpoch, _ *types.TipSet) error { + wait20.Done() + wait20.Wait() + + err := cache.Write("foo", fooCid) + require.NoError(t, err) + + counter <- struct{}{} + + return nil + }, + }, { + StartWithin: 20, + PreMigration: func(ctx context.Context, _ *StateManager, cache MigrationCache, + _ cid.Cid, _ abi.ChainEpoch, _ *types.TipSet) error { + wait20.Done() + wait20.Wait() + + err := cache.Write("bar", barCid) + require.NoError(t, err) + + counter <- struct{}{} + + return nil + }, + }, { + StartWithin: 20, + PreMigration: func(ctx context.Context, _ *StateManager, cache MigrationCache, + _ cid.Cid, _ abi.ChainEpoch, _ *types.TipSet) error { + wait20.Done() + wait20.Wait() + + err := cache.Write("fail", failCid) + require.NoError(t, err) + + counter <- struct{}{} + + // Fail this migration. The cached entry should not be persisted. + return fmt.Errorf("failed") + }, + }, { + StartWithin: 15, + StopWithin: 5, + PreMigration: func(ctx context.Context, _ *StateManager, cache MigrationCache, + _ cid.Cid, _ abi.ChainEpoch, _ *types.TipSet) error { + + <-ctx.Done() + close(wasCanceled) + + counter <- struct{}{} + + return nil + }, + }, { + StartWithin: 10, + PreMigration: func(ctx context.Context, _ *StateManager, cache MigrationCache, + _ cid.Cid, _ abi.ChainEpoch, _ *types.TipSet) error { + + checkCache(t, cache) + + counter <- struct{}{} + + return nil + }, + }}}, + }, cg.BeaconSchedule(), datastore.NewMapDatastore(), index.DummyMsgIndex) + if err != nil { + t.Fatal(err) + } + require.NoError(t, sm.Start(context.Background())) + defer func() { + require.NoError(t, sm.Stop(context.Background())) + }() + + inv := consensus.NewActorRegistry() + registry := builtin.MakeRegistryLegacy([]rtt.VMActor{testActor{}}) + inv.Register(actorstypes.Version0, nil, registry) + + sm.SetVMConstructor(func(ctx context.Context, vmopt *vm.VMOpts) (vm.Interface, error) { + nvm, err := vm.NewLegacyVM(ctx, vmopt) + if err != nil { + return nil, err + } + nvm.SetInvoker(inv) + return nvm, nil + }) + + cg.SetStateManager(sm) + + for i := 0; i < 50; i++ { + _, err := cg.NextTipSet() + if err != nil { + t.Fatal(err) + } + } + // We have 5 pre-migration steps, and the migration. They should all have written something + // to this channel. + require.Equal(t, 6, len(counter)) +} + +func TestDisablePreMigration(t *testing.T) { + logging.SetAllLoggers(logging.LevelInfo) + + cg, err := gen.NewGenerator() + require.NoError(t, err) + + err = os.Setenv(EnvDisablePreMigrations, "1") + require.NoError(t, err) + + defer func() { + err := os.Unsetenv(EnvDisablePreMigrations) + require.NoError(t, err) + }() + + counter := make(chan struct{}, 10) + + sm, err := NewStateManager( + cg.ChainStore(), + consensus.NewTipSetExecutor(filcns.RewardFunc), + cg.StateManager().VMSys(), + UpgradeSchedule{{ + Network: network.Version1, + Height: testForkHeight, + Migration: func(_ context.Context, _ *StateManager, _ MigrationCache, _ ExecMonitor, + root cid.Cid, _ abi.ChainEpoch, _ *types.TipSet) (cid.Cid, error) { + + counter <- struct{}{} + + return root, nil + }, + PreMigrations: []PreMigration{{ + StartWithin: 20, + PreMigration: func(ctx context.Context, _ *StateManager, _ MigrationCache, + _ cid.Cid, _ abi.ChainEpoch, _ *types.TipSet) error { + panic("should be skipped") + }, + }}}, + }, + cg.BeaconSchedule(), + datastore.NewMapDatastore(), + index.DummyMsgIndex, + ) + require.NoError(t, err) + require.NoError(t, sm.Start(context.Background())) + defer func() { + require.NoError(t, sm.Stop(context.Background())) + }() + + inv := consensus.NewActorRegistry() + registry := builtin.MakeRegistryLegacy([]rtt.VMActor{testActor{}}) + inv.Register(actorstypes.Version0, nil, registry) + + sm.SetVMConstructor(func(ctx context.Context, vmopt *vm.VMOpts) (vm.Interface, error) { + nvm, err := vm.NewLegacyVM(ctx, vmopt) + require.NoError(t, err) + nvm.SetInvoker(inv) + return nvm, nil + }) + + cg.SetStateManager(sm) + + for i := 0; i < 50; i++ { + _, err := cg.NextTipSet() + require.NoError(t, err) + } + + require.Equal(t, 1, len(counter)) +} + +func TestMigrtionCache(t *testing.T) { + logging.SetAllLoggers(logging.LevelInfo) + + cg, err := gen.NewGenerator() + require.NoError(t, err) + + counter := make(chan struct{}, 10) + metadataDs := datastore.NewMapDatastore() + + sm, err := NewStateManager( + cg.ChainStore(), + consensus.NewTipSetExecutor(filcns.RewardFunc), + cg.StateManager().VMSys(), + UpgradeSchedule{{ + Network: network.Version1, + Height: testForkHeight, + Migration: func(_ context.Context, _ *StateManager, _ MigrationCache, _ ExecMonitor, + root cid.Cid, _ abi.ChainEpoch, _ *types.TipSet) (cid.Cid, error) { + + counter <- struct{}{} + + return root, nil + }}, + }, + cg.BeaconSchedule(), + metadataDs, + index.DummyMsgIndex, + ) + require.NoError(t, err) + require.NoError(t, sm.Start(context.Background())) + defer func() { + require.NoError(t, sm.Stop(context.Background())) + }() + + inv := consensus.NewActorRegistry() + registry := builtin.MakeRegistryLegacy([]rtt.VMActor{testActor{}}) + inv.Register(actorstypes.Version0, nil, registry) + + sm.SetVMConstructor(func(ctx context.Context, vmopt *vm.VMOpts) (vm.Interface, error) { + nvm, err := vm.NewLegacyVM(ctx, vmopt) + require.NoError(t, err) + nvm.SetInvoker(inv) + return nvm, nil + }) + + cg.SetStateManager(sm) + + for i := 0; i < 50; i++ { + _, err := cg.NextTipSet() + require.NoError(t, err) + } + + ts, err := cg.ChainStore().GetTipsetByHeight(context.Background(), testForkHeight, nil, false) + require.NoError(t, err) + + root, _, err := stmgr.ComputeState(context.Background(), sm, testForkHeight+1, []*types.Message{}, ts) + require.NoError(t, err) + t.Log(root) + + require.Equal(t, 1, len(counter)) + + { + sm, err := NewStateManager( + cg.ChainStore(), + consensus.NewTipSetExecutor(filcns.RewardFunc), + cg.StateManager().VMSys(), + UpgradeSchedule{{ + Network: network.Version1, + Height: testForkHeight, + Migration: func(_ context.Context, _ *StateManager, _ MigrationCache, _ ExecMonitor, + root cid.Cid, _ abi.ChainEpoch, _ *types.TipSet) (cid.Cid, error) { + + counter <- struct{}{} + + return root, nil + }}, + }, + cg.BeaconSchedule(), + metadataDs, + index.DummyMsgIndex, + ) + require.NoError(t, err) + sm.SetVMConstructor(func(ctx context.Context, vmopt *vm.VMOpts) (vm.Interface, error) { + nvm, err := vm.NewLegacyVM(ctx, vmopt) + require.NoError(t, err) + nvm.SetInvoker(inv) + return nvm, nil + }) + + ctx := context.Background() + + base, _, err := sm.ExecutionTrace(ctx, ts) + require.NoError(t, err) + _, err = sm.HandleStateForks(context.Background(), base, ts.Height(), nil, ts) + require.NoError(t, err) + + // Should not have increased as we should be using the cached results in the metadataDs + require.Equal(t, 1, len(counter)) + } +} diff --git a/chain/stmgr/read.go b/chain/stmgr/read.go new file mode 100644 index 000000000..4543f63b3 --- /dev/null +++ b/chain/stmgr/read.go @@ -0,0 +1,74 @@ +package stmgr + +import ( + "context" + + "github.com/ipfs/go-cid" + cbor "github.com/ipfs/go-ipld-cbor" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + + "github.com/filecoin-project/lotus/chain/state" + "github.com/filecoin-project/lotus/chain/types" +) + +func (sm *StateManager) ParentStateTsk(ctx context.Context, tsk types.TipSetKey) (*state.StateTree, error) { + ts, err := sm.cs.GetTipSetFromKey(ctx, tsk) + if err != nil { + return nil, xerrors.Errorf("loading tipset %s: %w", tsk, err) + } + return sm.ParentState(ts) +} + +func (sm *StateManager) ParentState(ts *types.TipSet) (*state.StateTree, error) { + cst := cbor.NewCborStore(sm.cs.StateBlockstore()) + state, err := state.LoadStateTree(cst, sm.parentState(ts)) + if err != nil { + return nil, xerrors.Errorf("load state tree: %w", err) + } + + return state, nil +} + +func (sm *StateManager) parentState(ts *types.TipSet) cid.Cid { + if ts == nil { + ts = sm.cs.GetHeaviestTipSet() + } + + return ts.ParentState() +} + +func (sm *StateManager) StateTree(st cid.Cid) (*state.StateTree, error) { + cst := cbor.NewCborStore(sm.cs.StateBlockstore()) + state, err := state.LoadStateTree(cst, st) + if err != nil { + return nil, xerrors.Errorf("load state tree: %w", err) + } + + return state, nil +} + +func (sm *StateManager) LoadActor(_ context.Context, addr address.Address, ts *types.TipSet) (*types.Actor, error) { + state, err := sm.ParentState(ts) + if err != nil { + return nil, err + } + return state.GetActor(addr) +} + +func (sm *StateManager) LoadActorTsk(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*types.Actor, error) { + state, err := sm.ParentStateTsk(ctx, tsk) + if err != nil { + return nil, err + } + return state.GetActor(addr) +} + +func (sm *StateManager) LoadActorRaw(_ context.Context, addr address.Address, st cid.Cid) (*types.Actor, error) { + state, err := sm.StateTree(st) + if err != nil { + return nil, err + } + return state.GetActor(addr) +} diff --git a/chain/stmgr/rpc/rpcstatemanager.go b/chain/stmgr/rpc/rpcstatemanager.go new file mode 100644 index 000000000..9186501ea --- /dev/null +++ b/chain/stmgr/rpc/rpcstatemanager.go @@ -0,0 +1,59 @@ +package rpcstmgr + +import ( + "context" + + cbor "github.com/ipfs/go-ipld-cbor" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin/paych" + "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/filecoin-project/lotus/chain/types" +) + +type RPCStateManager struct { + gapi api.Gateway + cstore *cbor.BasicIpldStore +} + +func NewRPCStateManager(api api.Gateway) *RPCStateManager { + cstore := cbor.NewCborStore(blockstore.NewAPIBlockstore(api)) + return &RPCStateManager{gapi: api, cstore: cstore} +} + +func (s *RPCStateManager) GetPaychState(ctx context.Context, addr address.Address, ts *types.TipSet) (*types.Actor, paych.State, error) { + act, err := s.gapi.StateGetActor(ctx, addr, ts.Key()) + if err != nil { + return nil, nil, err + } + + actState, err := paych.Load(adt.WrapStore(ctx, s.cstore), act) + if err != nil { + return nil, nil, err + } + return act, actState, nil + +} + +func (s *RPCStateManager) LoadActorTsk(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*types.Actor, error) { + return s.gapi.StateGetActor(ctx, addr, tsk) +} + +func (s *RPCStateManager) LookupID(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) { + return s.gapi.StateLookupID(ctx, addr, ts.Key()) +} + +func (s *RPCStateManager) ResolveToDeterministicAddress(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) { + return s.gapi.StateAccountKey(ctx, addr, ts.Key()) +} + +func (s *RPCStateManager) Call(ctx context.Context, msg *types.Message, ts *types.TipSet) (*api.InvocResult, error) { + return nil, xerrors.Errorf("RPCStateManager does not implement StateManager.Call") +} + +var _ stmgr.StateManagerAPI = (*RPCStateManager)(nil) diff --git a/chain/stmgr/searchwait.go b/chain/stmgr/searchwait.go new file mode 100644 index 000000000..356ace23c --- /dev/null +++ b/chain/stmgr/searchwait.go @@ -0,0 +1,353 @@ +package stmgr + +import ( + "context" + "errors" + "fmt" + + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/chain/index" + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/chain/types" +) + +// WaitForMessage blocks until a message appears on chain. It looks backwards in the chain to see if this has already +// happened, with an optional limit to how many epochs it will search. It guarantees that the message has been on +// chain for at least confidence epochs without being reverted before returning. +func (sm *StateManager) WaitForMessage(ctx context.Context, mcid cid.Cid, confidence uint64, lookbackLimit abi.ChainEpoch, allowReplaced bool) (*types.TipSet, *types.MessageReceipt, cid.Cid, error) { + // TODO use the index to speed this up. + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + msg, err := sm.cs.GetCMessage(ctx, mcid) + if err != nil { + return nil, nil, cid.Undef, fmt.Errorf("failed to load message: %w", err) + } + + tsub := sm.cs.SubHeadChanges(ctx) + + head, ok := <-tsub + if !ok { + return nil, nil, cid.Undef, fmt.Errorf("SubHeadChanges stream was invalid") + } + + if len(head) != 1 { + return nil, nil, cid.Undef, fmt.Errorf("SubHeadChanges first entry should have been one item") + } + + if head[0].Type != store.HCCurrent { + return nil, nil, cid.Undef, fmt.Errorf("expected current head on SHC stream (got %s)", head[0].Type) + } + + r, foundMsg, err := sm.tipsetExecutedMessage(ctx, head[0].Val, mcid, msg.VMMessage(), allowReplaced) + if err != nil { + return nil, nil, cid.Undef, err + } + + if r != nil { + return head[0].Val, r, foundMsg, nil + } + + var backTs *types.TipSet + var backRcp *types.MessageReceipt + var backFm cid.Cid + backSearchWait := make(chan struct{}) + go func() { + fts, r, foundMsg, err := sm.searchForIndexedMsg(ctx, mcid, msg) + + found := (err == nil && r != nil && foundMsg.Defined()) + if !found { + fts, r, foundMsg, err = sm.searchBackForMsg(ctx, head[0].Val, msg, lookbackLimit, allowReplaced) + if err != nil { + log.Warnf("failed to look back through chain for message: %v", err) + return + } + } + + backTs = fts + backRcp = r + backFm = foundMsg + close(backSearchWait) + }() + + var candidateTs *types.TipSet + var candidateRcp *types.MessageReceipt + var candidateFm cid.Cid + heightOfHead := head[0].Val.Height() + reverts := map[types.TipSetKey]bool{} + + for { + select { + case notif, ok := <-tsub: + if !ok { + return nil, nil, cid.Undef, ctx.Err() + } + for _, val := range notif { + switch val.Type { + case store.HCRevert: + if val.Val.Equals(candidateTs) { + candidateTs = nil + candidateRcp = nil + candidateFm = cid.Undef + } + if backSearchWait != nil { + reverts[val.Val.Key()] = true + } + case store.HCApply: + if candidateTs != nil && val.Val.Height() >= candidateTs.Height()+abi.ChainEpoch(confidence) { + return candidateTs, candidateRcp, candidateFm, nil + } + r, foundMsg, err := sm.tipsetExecutedMessage(ctx, val.Val, mcid, msg.VMMessage(), allowReplaced) + if err != nil { + return nil, nil, cid.Undef, err + } + if r != nil { + if confidence == 0 { + return val.Val, r, foundMsg, err + } + candidateTs = val.Val + candidateRcp = r + candidateFm = foundMsg + } + heightOfHead = val.Val.Height() + } + } + case <-backSearchWait: + // check if we found the message in the chain and that is hasn't been reverted since we started searching + if backTs != nil && !reverts[backTs.Key()] { + // if head is at or past confidence interval, return immediately + if heightOfHead >= backTs.Height()+abi.ChainEpoch(confidence) { + return backTs, backRcp, backFm, nil + } + + // wait for confidence interval + candidateTs = backTs + candidateRcp = backRcp + candidateFm = backFm + } + reverts = nil + backSearchWait = nil + case <-ctx.Done(): + return nil, nil, cid.Undef, ctx.Err() + } + } +} + +func (sm *StateManager) SearchForMessage(ctx context.Context, head *types.TipSet, mcid cid.Cid, lookbackLimit abi.ChainEpoch, allowReplaced bool) (*types.TipSet, *types.MessageReceipt, cid.Cid, error) { + msg, err := sm.cs.GetCMessage(ctx, mcid) + if err != nil { + return nil, nil, cid.Undef, fmt.Errorf("failed to load message: %w", err) + } + + r, foundMsg, err := sm.tipsetExecutedMessage(ctx, head, mcid, msg.VMMessage(), allowReplaced) + if err != nil { + return nil, nil, cid.Undef, err + } + + if r != nil { + return head, r, foundMsg, nil + } + + fts, r, foundMsg, err := sm.searchForIndexedMsg(ctx, mcid, msg) + + switch { + case err == nil: + if r != nil && foundMsg.Defined() { + return fts, r, foundMsg, nil + } + + // debug log this, it's noteworthy + if r == nil { + log.Debugf("missing receipt for message in index for %s", mcid) + } + if !foundMsg.Defined() { + log.Debugf("message %s not found", mcid) + } + + case errors.Is(err, index.ErrNotFound): + // ok for the index to have incomplete data + + default: + log.Warnf("error searching message index: %s", err) + } + + fts, r, foundMsg, err = sm.searchBackForMsg(ctx, head, msg, lookbackLimit, allowReplaced) + + if err != nil { + log.Warnf("failed to look back through chain for message %s", mcid) + return nil, nil, cid.Undef, err + } + + if fts == nil { + return nil, nil, cid.Undef, nil + } + + return fts, r, foundMsg, nil +} + +func (sm *StateManager) searchForIndexedMsg(ctx context.Context, mcid cid.Cid, m types.ChainMsg) (*types.TipSet, *types.MessageReceipt, cid.Cid, error) { + minfo, err := sm.msgIndex.GetMsgInfo(ctx, mcid) + if err != nil { + return nil, nil, cid.Undef, xerrors.Errorf("error looking up message in index: %w", err) + } + + // check the height against the current tipset; minimum execution confidence requires that the + // inclusion tipset height is lower than the current head + 1 + curTs := sm.cs.GetHeaviestTipSet() + if curTs.Height() <= minfo.Epoch+1 { + return nil, nil, cid.Undef, xerrors.Errorf("indexed message does not appear before the current tipset; index epoch: %d, current epoch: %d", minfo.Epoch, curTs.Height()) + } + + // now get the execution tipset + // TODO optimization: the index should have it implicitly so we can return it in the msginfo. + xts, err := sm.cs.GetTipsetByHeight(ctx, minfo.Epoch+1, curTs, false) + if err != nil { + return nil, nil, cid.Undef, xerrors.Errorf("error looking up execution tipset: %w", err) + } + + // check that the parent of the execution index is indeed the inclusion tipset + parent := xts.Parents() + parentCid, err := parent.Cid() + if err != nil { + return nil, nil, cid.Undef, xerrors.Errorf("error computing tipset cid: %w", err) + } + + if !parentCid.Equals(minfo.TipSet) { + return nil, nil, cid.Undef, xerrors.Errorf("inclusion tipset mismatch: have %s, expected %s", parentCid, minfo.TipSet) + } + + r, foundMsg, err := sm.tipsetExecutedMessage(ctx, xts, mcid, m.VMMessage(), false) + if err != nil { + return nil, nil, cid.Undef, xerrors.Errorf("error in tipstExecutedMessage: %w", err) + } + return xts, r, foundMsg, nil +} + +// searchBackForMsg searches up to limit tipsets backwards from the given +// tipset for a message receipt. +// If limit is +// - 0 then no tipsets are searched +// - 5 then five tipset are searched +// - LookbackNoLimit then there is no limit +func (sm *StateManager) searchBackForMsg(ctx context.Context, from *types.TipSet, m types.ChainMsg, limit abi.ChainEpoch, allowReplaced bool) (*types.TipSet, *types.MessageReceipt, cid.Cid, error) { + limitHeight := from.Height() - limit + noLimit := limit == LookbackNoLimit + + cur := from + curActor, err := sm.LoadActor(ctx, m.VMMessage().From, cur) + if err != nil { + return nil, nil, cid.Undef, xerrors.Errorf("failed to load initital tipset") + } + + mFromId, err := sm.LookupID(ctx, m.VMMessage().From, from) + if err != nil { + return nil, nil, cid.Undef, xerrors.Errorf("looking up From id address: %w", err) + } + + mNonce := m.VMMessage().Nonce + + for { + // If we've reached the genesis block, or we've reached the limit of + // how far back to look + if cur.Height() == 0 || !noLimit && cur.Height() <= limitHeight { + // it ain't here! + return nil, nil, cid.Undef, nil + } + + select { + case <-ctx.Done(): + return nil, nil, cid.Undef, nil + default: + } + + // we either have no messages from the sender, or the latest message we found has a lower nonce than the one being searched for, + // either way, no reason to lookback, it ain't there + if curActor == nil || curActor.Nonce == 0 || curActor.Nonce < mNonce { + return nil, nil, cid.Undef, nil + } + + pts, err := sm.cs.LoadTipSet(ctx, cur.Parents()) + if err != nil { + return nil, nil, cid.Undef, xerrors.Errorf("failed to load tipset during msg wait searchback: %w", err) + } + + act, err := sm.LoadActor(ctx, mFromId, pts) + actorNoExist := errors.Is(err, types.ErrActorNotFound) + if err != nil && !actorNoExist { + return nil, nil, cid.Cid{}, xerrors.Errorf("failed to load the actor: %w", err) + } + + // check that between cur and parent tipset the nonce fell into range of our message + if actorNoExist || (curActor.Nonce > mNonce && act.Nonce <= mNonce) { + r, foundMsg, err := sm.tipsetExecutedMessage(ctx, cur, m.Cid(), m.VMMessage(), allowReplaced) + if err != nil { + return nil, nil, cid.Undef, xerrors.Errorf("checking for message execution during lookback: %w", err) + } + + if r != nil { + return cur, r, foundMsg, nil + } + } + + cur = pts + curActor = act + } +} + +func (sm *StateManager) tipsetExecutedMessage(ctx context.Context, ts *types.TipSet, msg cid.Cid, vmm *types.Message, allowReplaced bool) (*types.MessageReceipt, cid.Cid, error) { + // The genesis block did not execute any messages + if ts.Height() == 0 { + return nil, cid.Undef, nil + } + + pts, err := sm.cs.LoadTipSet(ctx, ts.Parents()) + if err != nil { + return nil, cid.Undef, err + } + + cm, err := sm.cs.MessagesForTipset(ctx, pts) + if err != nil { + return nil, cid.Undef, err + } + + for ii := range cm { + // iterate in reverse because we going backwards through the chain + i := len(cm) - ii - 1 + m := cm[i] + + if m.VMMessage().From == vmm.From { // cheaper to just check origin first + if m.VMMessage().Nonce == vmm.Nonce { + if !m.VMMessage().EqualCall(vmm) { + // this is an entirely different message, fail + return nil, cid.Undef, xerrors.Errorf("found message with equal nonce as the one we are looking for that is NOT a valid replacement message (F:%s n %d, TS: %s n%d)", + msg, vmm.Nonce, m.Cid(), m.VMMessage().Nonce) + } + + if m.Cid() != msg { + if !allowReplaced { + log.Warnw("found message with equal nonce and call params but different CID", + "wanted", msg, "found", m.Cid(), "nonce", vmm.Nonce, "from", vmm.From) + return nil, cid.Undef, xerrors.Errorf("found message with equal nonce as the one we are looking for (F:%s n %d, TS: %s n%d)", + msg, vmm.Nonce, m.Cid(), m.VMMessage().Nonce) + } + } + + pr, err := sm.cs.GetParentReceipt(ctx, ts.Blocks()[0], i) + if err != nil { + return nil, cid.Undef, err + } + + return pr, m.Cid(), nil + } + if m.VMMessage().Nonce < vmm.Nonce { + return nil, cid.Undef, nil // don't bother looking further + } + } + } + + return nil, cid.Undef, nil +} diff --git a/chain/stmgr/searchwait_test.go b/chain/stmgr/searchwait_test.go new file mode 100644 index 000000000..b23b22376 --- /dev/null +++ b/chain/stmgr/searchwait_test.go @@ -0,0 +1,140 @@ +// stm: #unit +package stmgr_test + +import ( + "context" + "testing" + + "github.com/filecoin-project/go-state-types/big" + + "github.com/filecoin-project/lotus/chain/gen" + _ "github.com/filecoin-project/lotus/lib/sigs/bls" + _ "github.com/filecoin-project/lotus/lib/sigs/secp" +) + +func TestSearchForMessageReplacements(t *testing.T) { + //stm: @CHAIN_GEN_NEXT_TIPSET_001 + //stm: @CHAIN_STATE_SEARCH_MSG_001 + ctx := context.Background() + cg, err := gen.NewGenerator() + if err != nil { + t.Fatal(err) + } + + mts1, err := cg.NextTipSet() + if err != nil { + t.Fatal(err) + } + + m := mts1.Messages[0] + + mts2, err := cg.NextTipSet() + if err != nil { + t.Fatal(err) + } + + // Step 1: Searching for the executed msg with replacements allowed succeeds + ts, r, mcid, err := cg.StateManager().SearchForMessage(ctx, mts2.TipSet.TipSet(), m.Cid(), 100, true) + if err != nil { + t.Fatal(err) + } + + if !ts.Equals(mts2.TipSet.TipSet()) { + t.Fatal("searched tipset wasn't as expected") + } + + if r.ExitCode != 0 { + t.Fatal("searched msg wasn't successfully executed") + } + + if mcid != m.Cid() { + t.Fatal("searched msg wasn't identical to queried msg as expected") + } + + // Step 2: Searching for the executed msg with replacements disallowed also succeeds + ts, r, mcid, err = cg.StateManager().SearchForMessage(ctx, mts2.TipSet.TipSet(), m.Cid(), 100, true) + if err != nil { + t.Fatal(err) + } + + if !ts.Equals(mts2.TipSet.TipSet()) { + t.Fatal("searched tipset wasn't as expected") + } + + if r.ExitCode != 0 { + t.Fatal("searched msg wasn't successfully executed") + } + + if mcid != m.Cid() { + t.Fatal("searched msg wasn't identical to queried msg as expected") + } + + // rm is a valid replacement message for m + rm := m.Message + rm.GasLimit = m.Message.GasLimit + 1 + + rmb, err := rm.ToStorageBlock() + if err != nil { + t.Fatal(err) + } + + err = cg.Blockstore().Put(ctx, rmb) + if err != nil { + t.Fatal(err) + } + + // Step 3: Searching for the replacement msg with replacements allowed succeeds + ts, r, mcid, err = cg.StateManager().SearchForMessage(ctx, mts2.TipSet.TipSet(), rm.Cid(), 100, true) + if err != nil { + t.Fatal(err) + } + + if !ts.Equals(mts2.TipSet.TipSet()) { + t.Fatal("searched tipset wasn't as expected") + } + + if r.ExitCode != 0 { + t.Fatal("searched msg wasn't successfully executed") + } + + if mcid == rm.Cid() { + t.Fatal("searched msg was identical to queried msg, not as expected") + } + + if mcid != m.Cid() { + t.Fatal("searched msg wasn't identical to executed msg as expected") + } + + // Step 4: Searching for the replacement msg with replacements disallowed fails + _, _, _, err = cg.StateManager().SearchForMessage(ctx, mts2.TipSet.TipSet(), rm.Cid(), 100, false) + if err == nil { + t.Fatal("expected search to fail") + } + + // nrm is NOT a valid replacement message for m + nrm := m.Message + nrm.Value = big.Add(m.Message.Value, m.Message.Value) + + nrmb, err := nrm.ToStorageBlock() + if err != nil { + t.Fatal(err) + } + + err = cg.Blockstore().Put(ctx, nrmb) + if err != nil { + t.Fatal(err) + } + + // Step 5: Searching for the not-replacement msg with replacements allowed fails + _, _, _, err = cg.StateManager().SearchForMessage(ctx, mts2.TipSet.TipSet(), nrm.Cid(), 100, true) + if err == nil { + t.Fatal("expected search to fail") + } + + // Step 6: Searching for the not-replacement msg with replacements disallowed also fails + _, _, _, err = cg.StateManager().SearchForMessage(ctx, mts2.TipSet.TipSet(), nrm.Cid(), 100, false) + if err == nil { + t.Fatal("expected search to fail") + } + +} diff --git a/chain/stmgr/stmgr.go b/chain/stmgr/stmgr.go index e291fe623..12b991e57 100644 --- a/chain/stmgr/stmgr.go +++ b/chain/stmgr/stmgr.go @@ -3,53 +3,253 @@ package stmgr import ( "context" "fmt" + "os" + "strconv" "sync" + lru "github.com/hashicorp/golang-lru/v2" + "github.com/ipfs/go-cid" + dstore "github.com/ipfs/go-datastore" + cbor "github.com/ipfs/go-ipld-cbor" + ipld "github.com/ipfs/go-ipld-format" + logging "github.com/ipfs/go-log/v2" + "golang.org/x/xerrors" + "github.com/filecoin-project/go-address" - amt "github.com/filecoin-project/go-amt-ipld/v2" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/go-state-types/network" + "github.com/filecoin-project/specs-actors/v8/actors/migration/nv16" + "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors/adt" + _init "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/actors/builtin/paych" + "github.com/filecoin-project/lotus/chain/actors/policy" + "github.com/filecoin-project/lotus/chain/beacon" + "github.com/filecoin-project/lotus/chain/index" + "github.com/filecoin-project/lotus/chain/rand" "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/vm" - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/abi/big" - "github.com/filecoin-project/specs-actors/actors/builtin" - "github.com/filecoin-project/specs-actors/actors/builtin/market" - "github.com/filecoin-project/specs-actors/actors/builtin/reward" - "github.com/filecoin-project/specs-actors/actors/runtime" - "github.com/filecoin-project/specs-actors/actors/util/adt" - cbg "github.com/whyrusleeping/cbor-gen" - "golang.org/x/xerrors" - bls "github.com/filecoin-project/filecoin-ffi" - "github.com/ipfs/go-cid" - hamt "github.com/ipfs/go-hamt-ipld" - blockstore "github.com/ipfs/go-ipfs-blockstore" - cbor "github.com/ipfs/go-ipld-cbor" - logging "github.com/ipfs/go-log/v2" - "go.opencensus.io/trace" + // Used for genesis. + msig0 "github.com/filecoin-project/specs-actors/actors/builtin/multisig" ) +const LookbackNoLimit = api.LookbackNoLimit +const ReceiptAmtBitwidth = 3 + +var execTraceCacheSize = 16 var log = logging.Logger("statemgr") +type StateManagerAPI interface { + Call(ctx context.Context, msg *types.Message, ts *types.TipSet) (*api.InvocResult, error) + GetPaychState(ctx context.Context, addr address.Address, ts *types.TipSet) (*types.Actor, paych.State, error) + LoadActorTsk(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*types.Actor, error) + LookupID(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) + ResolveToDeterministicAddress(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) +} + +type versionSpec struct { + networkVersion network.Version + atOrBelow abi.ChainEpoch +} + +type migration struct { + upgrade MigrationFunc + preMigrations []PreMigration + cache *nv16.MemMigrationCache + migrationResultCache *migrationResultCache +} + +type migrationResultCache struct { + ds dstore.Batching + keyPrefix string +} + +func (m *migrationResultCache) keyForMigration(root cid.Cid) dstore.Key { + kStr := fmt.Sprintf("%s/%s", m.keyPrefix, root) + return dstore.NewKey(kStr) +} + +func init() { + if s := os.Getenv("LOTUS_EXEC_TRACE_CACHE_SIZE"); s != "" { + letc, err := strconv.Atoi(s) + if err != nil { + log.Errorf("failed to parse 'LOTUS_EXEC_TRACE_CACHE_SIZE' env var: %s", err) + } else { + execTraceCacheSize = letc + } + } +} + +func (m *migrationResultCache) Get(ctx context.Context, root cid.Cid) (cid.Cid, bool, error) { + k := m.keyForMigration(root) + + bs, err := m.ds.Get(ctx, k) + if ipld.IsNotFound(err) { + return cid.Undef, false, nil + } else if err != nil { + return cid.Undef, false, xerrors.Errorf("error loading migration result: %w", err) + } + + c, err := cid.Parse(bs) + if err != nil { + return cid.Undef, false, xerrors.Errorf("error parsing migration result: %w", err) + } + + return c, true, nil +} + +func (m *migrationResultCache) Store(ctx context.Context, root cid.Cid, resultCid cid.Cid) error { + k := m.keyForMigration(root) + if err := m.ds.Put(ctx, k, resultCid.Bytes()); err != nil { + return err + } + + return nil +} + +type Executor interface { + NewActorRegistry() *vm.ActorRegistry + ExecuteTipSet(ctx context.Context, sm *StateManager, ts *types.TipSet, em ExecMonitor, vmTracing bool) (stateroot cid.Cid, rectsroot cid.Cid, err error) +} + type StateManager struct { cs *store.ChainStore - stCache map[string][]cid.Cid - compWait map[string]chan struct{} - stlk sync.Mutex - newVM func(cid.Cid, abi.ChainEpoch, vm.Rand, blockstore.Blockstore, runtime.Syscalls) (*vm.VM, error) + cancel context.CancelFunc + shutdown chan struct{} + + // Determines the network version at any given epoch. + networkVersions []versionSpec + latestVersion network.Version + + // Maps chain epochs to migrations. + stateMigrations map[abi.ChainEpoch]*migration + // A set of potentially expensive/time consuming upgrades. Explicit + // calls for, e.g., gas estimation fail against this epoch with + // ErrExpensiveFork. + expensiveUpgrades map[abi.ChainEpoch]struct{} + + stCache map[string][]cid.Cid + tCache treeCache + compWait map[string]chan struct{} + stlk sync.Mutex + genesisMsigLk sync.Mutex + newVM func(context.Context, *vm.VMOpts) (vm.Interface, error) + Syscalls vm.SyscallBuilder + preIgnitionVesting []msig0.State + postIgnitionVesting []msig0.State + postCalicoVesting []msig0.State + + genesisPledge abi.TokenAmount + + tsExec Executor + tsExecMonitor ExecMonitor + beacon beacon.Schedule + + msgIndex index.MsgIndex + + // We keep a small cache for calls to ExecutionTrace which helps improve + // performance for node operators like exchanges and block explorers + execTraceCache *lru.ARCCache[types.TipSetKey, tipSetCacheEntry] + // We need a lock while making the copy as to prevent other callers + // overwrite the cache while making the copy + execTraceCacheLock sync.Mutex } -func NewStateManager(cs *store.ChainStore) *StateManager { - return &StateManager{ - newVM: vm.NewVM, - cs: cs, - stCache: make(map[string][]cid.Cid), - compWait: make(map[string]chan struct{}), +// Caches a single state tree +type treeCache struct { + root cid.Cid + tree *state.StateTree +} + +type tipSetCacheEntry struct { + postStateRoot cid.Cid + invocTrace []*api.InvocResult +} + +func NewStateManager(cs *store.ChainStore, exec Executor, sys vm.SyscallBuilder, us UpgradeSchedule, beacon beacon.Schedule, metadataDs dstore.Batching, msgIndex index.MsgIndex) (*StateManager, error) { + // If we have upgrades, make sure they're in-order and make sense. + if err := us.Validate(); err != nil { + return nil, err } + + stateMigrations := make(map[abi.ChainEpoch]*migration, len(us)) + expensiveUpgrades := make(map[abi.ChainEpoch]struct{}, len(us)) + var networkVersions []versionSpec + lastVersion := build.GenesisNetworkVersion + if len(us) > 0 { + // If we have any upgrades, process them and create a version + // schedule. + for _, upgrade := range us { + if upgrade.Migration != nil || upgrade.PreMigrations != nil { + migration := &migration{ + upgrade: upgrade.Migration, + preMigrations: upgrade.PreMigrations, + cache: nv16.NewMemMigrationCache(), + migrationResultCache: &migrationResultCache{ + keyPrefix: fmt.Sprintf("/migration-cache/nv%d", upgrade.Network), + ds: metadataDs, + }, + } + + stateMigrations[upgrade.Height] = migration + } + if upgrade.Expensive { + expensiveUpgrades[upgrade.Height] = struct{}{} + } + + networkVersions = append(networkVersions, versionSpec{ + networkVersion: lastVersion, + atOrBelow: upgrade.Height, + }) + lastVersion = upgrade.Network + } + } + + log.Debugf("execTraceCache size: %d", execTraceCacheSize) + var execTraceCache *lru.ARCCache[types.TipSetKey, tipSetCacheEntry] + var err error + if execTraceCacheSize > 0 { + execTraceCache, err = lru.NewARC[types.TipSetKey, tipSetCacheEntry](execTraceCacheSize) + if err != nil { + return nil, err + } + } + + return &StateManager{ + networkVersions: networkVersions, + latestVersion: lastVersion, + stateMigrations: stateMigrations, + expensiveUpgrades: expensiveUpgrades, + newVM: vm.NewVM, + Syscalls: sys, + cs: cs, + tsExec: exec, + stCache: make(map[string][]cid.Cid), + beacon: beacon, + tCache: treeCache{ + root: cid.Undef, + tree: nil, + }, + compWait: make(map[string]chan struct{}), + msgIndex: msgIndex, + execTraceCache: execTraceCache, + }, nil +} + +func NewStateManagerWithUpgradeScheduleAndMonitor(cs *store.ChainStore, exec Executor, sys vm.SyscallBuilder, us UpgradeSchedule, b beacon.Schedule, em ExecMonitor, metadataDs dstore.Batching, msgIndex index.MsgIndex) (*StateManager, error) { + sm, err := NewStateManager(cs, exec, sys, us, b, metadataDs, msgIndex) + if err != nil { + return nil, err + } + sm.tsExecMonitor = em + return sm, nil } func cidsToKey(cids []cid.Cid) string { @@ -60,356 +260,46 @@ func cidsToKey(cids []cid.Cid) string { return out } -func (sm *StateManager) TipSetState(ctx context.Context, ts *types.TipSet) (st cid.Cid, rec cid.Cid, err error) { - ctx, span := trace.StartSpan(ctx, "tipSetState") - defer span.End() - if span.IsRecordingEvents() { - span.AddAttributes(trace.StringAttribute("tipset", fmt.Sprint(ts.Cids()))) - } +// Start starts the state manager's optional background processes. At the moment, this schedules +// pre-migration functions to run ahead of network upgrades. +// +// This method is not safe to invoke from multiple threads or concurrently with Stop. +func (sm *StateManager) Start(context.Context) error { + var ctx context.Context + ctx, sm.cancel = context.WithCancel(context.Background()) + sm.shutdown = make(chan struct{}) + go sm.preMigrationWorker(ctx) + return nil +} - ck := cidsToKey(ts.Cids()) - sm.stlk.Lock() - cw, cwok := sm.compWait[ck] - if cwok { - sm.stlk.Unlock() - span.AddAttributes(trace.BoolAttribute("waited", true)) +// Stop starts the state manager's background processes. +// +// This method is not safe to invoke concurrently with Start. +func (sm *StateManager) Stop(ctx context.Context) error { + if sm.cancel != nil { + sm.cancel() select { - case <-cw: - sm.stlk.Lock() + case <-sm.shutdown: case <-ctx.Done(): - return cid.Undef, cid.Undef, ctx.Err() + return ctx.Err() } } - cached, ok := sm.stCache[ck] - if ok { - sm.stlk.Unlock() - span.AddAttributes(trace.BoolAttribute("cache", true)) - return cached[0], cached[1], nil - } - ch := make(chan struct{}) - sm.compWait[ck] = ch - - defer func() { - sm.stlk.Lock() - delete(sm.compWait, ck) - if st != cid.Undef { - sm.stCache[ck] = []cid.Cid{st, rec} - } - sm.stlk.Unlock() - close(ch) - }() - - sm.stlk.Unlock() - - if ts.Height() == 0 { - // NB: This is here because the process that executes blocks requires that the - // block miner reference a valid miner in the state tree. Unless we create some - // magical genesis miner, this won't work properly, so we short circuit here - // This avoids the question of 'who gets paid the genesis block reward' - return ts.Blocks()[0].ParentStateRoot, ts.Blocks()[0].ParentMessageReceipts, nil - } - - st, rec, err = sm.computeTipSetState(ctx, ts.Blocks(), nil) - if err != nil { - return cid.Undef, cid.Undef, err - } - - return st, rec, nil -} - -func (sm *StateManager) ExecutionTrace(ctx context.Context, ts *types.TipSet) (cid.Cid, []*api.InvocResult, error) { - var trace []*api.InvocResult - st, _, err := sm.computeTipSetState(ctx, ts.Blocks(), func(mcid cid.Cid, msg *types.Message, ret *vm.ApplyRet) error { - ir := &api.InvocResult{ - Msg: msg, - MsgRct: &ret.MessageReceipt, - InternalExecutions: ret.InternalExecutions, - Duration: ret.Duration, - } - if ret.ActorErr != nil { - ir.Error = ret.ActorErr.Error() - } - trace = append(trace, ir) - return nil - }) - if err != nil { - return cid.Undef, nil, err - } - - return st, trace, nil -} - -type BlockMessages struct { - Miner address.Address - BlsMessages []types.ChainMsg - SecpkMessages []types.ChainMsg - TicketCount int64 -} - -type ExecCallback func(cid.Cid, *types.Message, *vm.ApplyRet) error - -func (sm *StateManager) ApplyBlocks(ctx context.Context, pstate cid.Cid, bms []BlockMessages, epoch abi.ChainEpoch, r vm.Rand, cb ExecCallback) (cid.Cid, cid.Cid, error) { - vmi, err := sm.newVM(pstate, epoch, r, sm.cs.Blockstore(), sm.cs.VMSys()) - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("instantiating VM failed: %w", err) - } - - var receipts []cbg.CBORMarshaler - processedMsgs := map[cid.Cid]bool{} - for _, b := range bms { - penalty := types.NewInt(0) - gasReward := big.Zero() - - for _, cm := range append(b.BlsMessages, b.SecpkMessages...) { - m := cm.VMMessage() - if _, found := processedMsgs[m.Cid()]; found { - continue - } - r, err := vmi.ApplyMessage(ctx, cm) - if err != nil { - return cid.Undef, cid.Undef, err - } - - receipts = append(receipts, &r.MessageReceipt) - gasReward = big.Add(gasReward, big.Mul(m.GasPrice, big.NewInt(r.GasUsed))) - penalty = big.Add(penalty, r.Penalty) - - if cb != nil { - if err := cb(cm.Cid(), m, r); err != nil { - return cid.Undef, cid.Undef, err - } - } - processedMsgs[m.Cid()] = true - } - - var err error - params, err := actors.SerializeParams(&reward.AwardBlockRewardParams{ - Miner: b.Miner, - Penalty: penalty, - GasReward: gasReward, - TicketCount: 1, // TODO: no longer need ticket count here. - }) - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("failed to serialize award params: %w", err) - } - - sysAct, err := vmi.StateTree().GetActor(builtin.SystemActorAddr) - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("failed to get system actor: %w", err) - } - - rwMsg := &types.Message{ - From: builtin.SystemActorAddr, - To: builtin.RewardActorAddr, - Nonce: sysAct.Nonce, - Value: types.NewInt(0), - GasPrice: types.NewInt(0), - GasLimit: 1 << 30, - Method: builtin.MethodsReward.AwardBlockReward, - Params: params, - } - ret, err := vmi.ApplyImplicitMessage(ctx, rwMsg) - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("failed to apply reward message for miner %s: %w", b.Miner, err) - } - if cb != nil { - if err := cb(rwMsg.Cid(), rwMsg, ret); err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("callback failed on reward message: %w", err) - } - } - - if ret.ExitCode != 0 { - return cid.Undef, cid.Undef, xerrors.Errorf("reward application message failed (exit %d): %s", ret.ExitCode, ret.ActorErr) - } - - } - - // TODO: this nonce-getting is a tiny bit ugly - ca, err := vmi.StateTree().GetActor(builtin.SystemActorAddr) - if err != nil { - return cid.Undef, cid.Undef, err - } - - cronMsg := &types.Message{ - To: builtin.CronActorAddr, - From: builtin.SystemActorAddr, - Nonce: ca.Nonce, - Value: types.NewInt(0), - GasPrice: types.NewInt(0), - GasLimit: 1 << 30, // Make super sure this is never too little - Method: builtin.MethodsCron.EpochTick, - Params: nil, - } - ret, err := vmi.ApplyImplicitMessage(ctx, cronMsg) - if err != nil { - return cid.Undef, cid.Undef, err - } - if cb != nil { - if err := cb(cronMsg.Cid(), cronMsg, ret); err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("callback failed on cron message: %w", err) - } - } - if ret.ExitCode != 0 { - return cid.Undef, cid.Undef, xerrors.Errorf("CheckProofSubmissions exit was non-zero: %d", ret.ExitCode) - } - - bs := cbor.NewCborStore(sm.cs.Blockstore()) - rectroot, err := amt.FromArray(ctx, bs, receipts) - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("failed to build receipts amt: %w", err) - } - - st, err := vmi.Flush(ctx) - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("vm flush failed: %w", err) - } - - return st, rectroot, nil -} - -func (sm *StateManager) computeTipSetState(ctx context.Context, blks []*types.BlockHeader, cb ExecCallback) (cid.Cid, cid.Cid, error) { - ctx, span := trace.StartSpan(ctx, "computeTipSetState") - defer span.End() - - for i := 0; i < len(blks); i++ { - for j := i + 1; j < len(blks); j++ { - if blks[i].Miner == blks[j].Miner { - return cid.Undef, cid.Undef, - xerrors.Errorf("duplicate miner in a tipset (%s %s)", - blks[i].Miner, blks[j].Miner) - } - } - } - - pstate := blks[0].ParentStateRoot - if len(blks[0].Parents) > 0 { // don't support forks on genesis - parent, err := sm.cs.GetBlock(blks[0].Parents[0]) - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("getting parent block: %w", err) - } - - pstate, err = sm.handleStateForks(ctx, blks[0].ParentStateRoot, blks[0].Height, parent.Height) - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("error handling state forks: %w", err) - } - } - - cids := make([]cid.Cid, len(blks)) - for i, v := range blks { - cids[i] = v.Cid() - } - - r := store.NewChainRand(sm.cs, cids, blks[0].Height) - - var blkmsgs []BlockMessages - for _, b := range blks { - bms, sms, err := sm.cs.MessagesForBlock(b) - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("failed to get messages for block: %w", err) - } - - bm := BlockMessages{ - Miner: b.Miner, - BlsMessages: make([]types.ChainMsg, 0, len(bms)), - SecpkMessages: make([]types.ChainMsg, 0, len(sms)), - TicketCount: 1, //int64(len(b.EPostProof.Proofs)), // TODO fix this - } - - for _, m := range bms { - bm.BlsMessages = append(bm.BlsMessages, m) - } - - for _, m := range sms { - bm.SecpkMessages = append(bm.SecpkMessages, m) - } - - blkmsgs = append(blkmsgs, bm) - } - - return sm.ApplyBlocks(ctx, pstate, blkmsgs, abi.ChainEpoch(blks[0].Height), r, cb) -} - -func (sm *StateManager) parentState(ts *types.TipSet) cid.Cid { - if ts == nil { - ts = sm.cs.GetHeaviestTipSet() - } - - return ts.ParentState() -} - -func (sm *StateManager) GetActor(addr address.Address, ts *types.TipSet) (*types.Actor, error) { - cst := cbor.NewCborStore(sm.cs.Blockstore()) - state, err := state.LoadStateTree(cst, sm.parentState(ts)) - if err != nil { - return nil, xerrors.Errorf("load state tree: %w", err) - } - - return state.GetActor(addr) -} - -func (sm *StateManager) getActorRaw(addr address.Address, st cid.Cid) (*types.Actor, error) { - cst := cbor.NewCborStore(sm.cs.Blockstore()) - state, err := state.LoadStateTree(cst, st) - if err != nil { - return nil, xerrors.Errorf("load state tree: %w", err) - } - - return state.GetActor(addr) -} - -func (sm *StateManager) GetBalance(addr address.Address, ts *types.TipSet) (types.BigInt, error) { - act, err := sm.GetActor(addr, ts) - if err != nil { - if xerrors.Is(err, types.ErrActorNotFound) { - return types.NewInt(0), nil - } - return types.EmptyInt, xerrors.Errorf("get actor: %w", err) - } - - return act.Balance, nil + return nil } func (sm *StateManager) ChainStore() *store.ChainStore { return sm.cs } -func (sm *StateManager) LoadActorState(ctx context.Context, a address.Address, out interface{}, ts *types.TipSet) (*types.Actor, error) { - act, err := sm.GetActor(a, ts) - if err != nil { - return nil, err - } - - cst := cbor.NewCborStore(sm.cs.Blockstore()) - if err := cst.Get(ctx, act.Head, out); err != nil { - var r cbg.Deferred - cst.Get(ctx, act.Head, &r) - fmt.Printf("badhead %x\n", r.Raw) - - return nil, err - } - - return act, nil +func (sm *StateManager) Beacon() beacon.Schedule { + return sm.beacon } -func (sm *StateManager) LoadActorStateRaw(ctx context.Context, a address.Address, out interface{}, st cid.Cid) (*types.Actor, error) { - act, err := sm.getActorRaw(a, st) - if err != nil { - return nil, err - } - - cst := cbor.NewCborStore(sm.cs.Blockstore()) - if err := cst.Get(ctx, act.Head, out); err != nil { - return nil, err - } - - return act, nil -} - -// Similar to `vm.ResolveToKeyAddr` but does not allow `Actor` type of addresses. Uses the `TipSet` `ts` -// to generate the VM state. -func (sm *StateManager) ResolveToKeyAddress(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) { +// ResolveToDeterministicAddress is similar to `vm.ResolveToDeterministicAddr` but does not allow `Actor` type of addresses. +// Uses the `TipSet` `ts` to generate the VM state. +func (sm *StateManager) ResolveToDeterministicAddress(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) { switch addr.Protocol() { - case address.BLS, address.SECP256K1: + case address.BLS, address.SECP256K1, address.Delegated: return addr, nil case address.Actor: return address.Undef, xerrors.New("cannot resolve actor address to key address") @@ -420,22 +310,81 @@ func (sm *StateManager) ResolveToKeyAddress(ctx context.Context, addr address.Ad ts = sm.cs.GetHeaviestTipSet() } + cst := cbor.NewCborStore(sm.cs.StateBlockstore()) + + // First try to resolve the actor in the parent state, so we don't have to compute anything. + tree, err := state.LoadStateTree(cst, ts.ParentState()) + if err != nil { + return address.Undef, xerrors.Errorf("failed to load parent state tree at tipset %s: %w", ts.Parents(), err) + } + + resolved, err := vm.ResolveToDeterministicAddr(tree, cst, addr) + if err == nil { + return resolved, nil + } + + // If that fails, compute the tip-set and try again. st, _, err := sm.TipSetState(ctx, ts) if err != nil { - return address.Undef, xerrors.Errorf("resolve address failed to get tipset state: %w", err) + return address.Undef, xerrors.Errorf("resolve address failed to get tipset %s state: %w", ts, err) } - cst := cbor.NewCborStore(sm.cs.Blockstore()) - tree, err := state.LoadStateTree(cst, st) + tree, err = state.LoadStateTree(cst, st) if err != nil { - return address.Undef, xerrors.Errorf("failed to load state tree") + return address.Undef, xerrors.Errorf("failed to load state tree at tipset %s: %w", ts, err) } - return vm.ResolveToKeyAddr(tree, cst, addr) + return vm.ResolveToDeterministicAddr(tree, cst, addr) } -func (sm *StateManager) GetBlsPublicKey(ctx context.Context, addr address.Address, ts *types.TipSet) (pubk bls.PublicKey, err error) { - kaddr, err := sm.ResolveToKeyAddress(ctx, addr, ts) +// ResolveToDeterministicAddressAtFinality is similar to stmgr.ResolveToDeterministicAddress but fails if the ID address being resolved isn't reorg-stable yet. +// It should not be used for consensus-critical subsystems. +func (sm *StateManager) ResolveToDeterministicAddressAtFinality(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) { + switch addr.Protocol() { + case address.BLS, address.SECP256K1, address.Delegated: + return addr, nil + case address.Actor: + return address.Undef, xerrors.New("cannot resolve actor address to key address") + default: + } + + if ts == nil { + ts = sm.cs.GetHeaviestTipSet() + } + + var err error + if ts.Height() > policy.ChainFinality { + ts, err = sm.ChainStore().GetTipsetByHeight(ctx, ts.Height()-policy.ChainFinality, ts, true) + if err != nil { + return address.Undef, xerrors.Errorf("failed to load lookback tipset: %w", err) + } + } + + cst := cbor.NewCborStore(sm.cs.StateBlockstore()) + tree := sm.tCache.tree + + if tree == nil || sm.tCache.root != ts.ParentState() { + tree, err = state.LoadStateTree(cst, ts.ParentState()) + if err != nil { + return address.Undef, xerrors.Errorf("failed to load parent state tree: %w", err) + } + + sm.tCache = treeCache{ + root: ts.ParentState(), + tree: tree, + } + } + + resolved, err := vm.ResolveToDeterministicAddr(tree, cst, addr) + if err == nil { + return resolved, nil + } + + return address.Undef, xerrors.New("ID address not found in lookback state") +} + +func (sm *StateManager) GetBlsPublicKey(ctx context.Context, addr address.Address, ts *types.TipSet) (pubk []byte, err error) { + kaddr, err := sm.ResolveToDeterministicAddress(ctx, addr, ts) if err != nil { return pubk, xerrors.Errorf("failed to resolve address to key address: %w", err) } @@ -444,12 +393,11 @@ func (sm *StateManager) GetBlsPublicKey(ctx context.Context, addr address.Addres return pubk, xerrors.Errorf("address must be BLS address to load bls public key") } - copy(pubk[:], kaddr.Payload()) - return pubk, nil + return kaddr.Payload(), nil } func (sm *StateManager) LookupID(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) { - cst := cbor.NewCborStore(sm.cs.Blockstore()) + cst := cbor.NewCborStore(sm.cs.StateBlockstore()) state, err := state.LoadStateTree(cst, sm.parentState(ts)) if err != nil { return address.Undef, xerrors.Errorf("load state tree: %w", err) @@ -457,309 +405,52 @@ func (sm *StateManager) LookupID(ctx context.Context, addr address.Address, ts * return state.LookupID(addr) } -func (sm *StateManager) GetReceipt(ctx context.Context, msg cid.Cid, ts *types.TipSet) (*types.MessageReceipt, error) { - m, err := sm.cs.GetCMessage(msg) +func (sm *StateManager) LookupRobustAddress(ctx context.Context, idAddr address.Address, ts *types.TipSet) (address.Address, error) { + idAddrDecoded, err := address.IDFromAddress(idAddr) if err != nil { - return nil, fmt.Errorf("failed to load message: %w", err) + return address.Undef, xerrors.Errorf("failed to decode provided address as id addr: %w", err) } - r, err := sm.tipsetExecutedMessage(ts, msg, m.VMMessage()) + cst := cbor.NewCborStore(sm.cs.StateBlockstore()) + wrapStore := adt.WrapStore(ctx, cst) + + stateTree, err := state.LoadStateTree(cst, sm.parentState(ts)) if err != nil { - return nil, err + return address.Undef, xerrors.Errorf("load state tree: %w", err) } - if r != nil { - return r, nil - } - - _, r, err = sm.searchBackForMsg(ctx, ts, m) + initActor, err := stateTree.GetActor(_init.Address) if err != nil { - return nil, fmt.Errorf("failed to look back through chain for message: %w", err) + return address.Undef, xerrors.Errorf("load init actor: %w", err) } - return r, nil -} - -func (sm *StateManager) WaitForMessage(ctx context.Context, mcid cid.Cid) (*types.TipSet, *types.MessageReceipt, error) { - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - msg, err := sm.cs.GetCMessage(mcid) + initState, err := _init.Load(wrapStore, initActor) if err != nil { - return nil, nil, fmt.Errorf("failed to load message: %w", err) + return address.Undef, xerrors.Errorf("load init state: %w", err) } + robustAddr := address.Undef - tsub := sm.cs.SubHeadChanges(ctx) - - head, ok := <-tsub - if !ok { - return nil, nil, fmt.Errorf("SubHeadChanges stream was invalid") - } - - if len(head) != 1 { - return nil, nil, fmt.Errorf("SubHeadChanges first entry should have been one item") - } - - if head[0].Type != store.HCCurrent { - return nil, nil, fmt.Errorf("expected current head on SHC stream (got %s)", head[0].Type) - } - - r, err := sm.tipsetExecutedMessage(head[0].Val, mcid, msg.VMMessage()) - if err != nil { - return nil, nil, err - } - - if r != nil { - return head[0].Val, r, nil - } - - var backTs *types.TipSet - var backRcp *types.MessageReceipt - backSearchWait := make(chan struct{}) - go func() { - fts, r, err := sm.searchBackForMsg(ctx, head[0].Val, msg) - if err != nil { - log.Warnf("failed to look back through chain for message: %w", err) - return + err = initState.ForEachActor(func(id abi.ActorID, addr address.Address) error { + if uint64(id) == idAddrDecoded { + robustAddr = addr + // Hacky way to early return from ForEach + return xerrors.New("robust address found") } - - backTs = fts - backRcp = r - close(backSearchWait) - }() - - for { - select { - case notif, ok := <-tsub: - if !ok { - return nil, nil, ctx.Err() - } - for _, val := range notif { - switch val.Type { - case store.HCRevert: - continue - case store.HCApply: - r, err := sm.tipsetExecutedMessage(val.Val, mcid, msg.VMMessage()) - if err != nil { - return nil, nil, err - } - if r != nil { - return val.Val, r, nil - } - } - } - case <-backSearchWait: - if backTs != nil { - return backTs, backRcp, nil - } - backSearchWait = nil - case <-ctx.Done(): - return nil, nil, ctx.Err() - } - } -} - -func (sm *StateManager) SearchForMessage(ctx context.Context, mcid cid.Cid) (*types.TipSet, *types.MessageReceipt, error) { - msg, err := sm.cs.GetCMessage(mcid) - if err != nil { - return nil, nil, fmt.Errorf("failed to load message: %w", err) - } - - head := sm.cs.GetHeaviestTipSet() - - r, err := sm.tipsetExecutedMessage(head, mcid, msg.VMMessage()) - if err != nil { - return nil, nil, err - } - - if r != nil { - return head, r, nil - } - - fts, r, err := sm.searchBackForMsg(ctx, head, msg) - - if err != nil { - log.Warnf("failed to look back through chain for message %s", mcid) - return nil, nil, err - } - - if fts == nil { - return nil, nil, nil - } - - return fts, r, nil -} - -func (sm *StateManager) searchBackForMsg(ctx context.Context, from *types.TipSet, m types.ChainMsg) (*types.TipSet, *types.MessageReceipt, error) { - - cur := from - for { - if cur.Height() == 0 { - // it ain't here! - return nil, nil, nil - } - - select { - case <-ctx.Done(): - return nil, nil, nil - default: - } - - act, err := sm.GetActor(m.VMMessage().From, cur) - if err != nil { - return nil, nil, err - } - - if act.Nonce < m.VMMessage().Nonce { - // nonce on chain is before message nonce we're looking for, its - // not going to be here - return nil, nil, nil - } - - ts, err := sm.cs.LoadTipSet(cur.Parents()) - if err != nil { - return nil, nil, fmt.Errorf("failed to load tipset during msg wait searchback: %w", err) - } - - r, err := sm.tipsetExecutedMessage(ts, m.Cid(), m.VMMessage()) - if err != nil { - return nil, nil, fmt.Errorf("checking for message execution during lookback: %w", err) - } - - if r != nil { - return ts, r, nil - } - - cur = ts - } -} - -func (sm *StateManager) tipsetExecutedMessage(ts *types.TipSet, msg cid.Cid, vmm *types.Message) (*types.MessageReceipt, error) { - // The genesis block did not execute any messages - if ts.Height() == 0 { - return nil, nil - } - - pts, err := sm.cs.LoadTipSet(ts.Parents()) - if err != nil { - return nil, err - } - - cm, err := sm.cs.MessagesForTipset(pts) - if err != nil { - return nil, err - } - - for ii := range cm { - // iterate in reverse because we going backwards through the chain - i := len(cm) - ii - 1 - m := cm[i] - - if m.VMMessage().From == vmm.From { // cheaper to just check origin first - if m.VMMessage().Nonce == vmm.Nonce { - if m.Cid() == msg { - return sm.cs.GetParentReceipt(ts.Blocks()[0], i) - } - - // this should be that message - return nil, xerrors.Errorf("found message with equal nonce as the one we are looking for (F:%s n %d, TS: %s n%d)", - msg, vmm.Nonce, m.Cid(), m.VMMessage().Nonce) - } - if m.VMMessage().Nonce < vmm.Nonce { - return nil, nil // don't bother looking further - } - } - } - - return nil, nil -} - -func (sm *StateManager) ListAllActors(ctx context.Context, ts *types.TipSet) ([]address.Address, error) { - if ts == nil { - ts = sm.cs.GetHeaviestTipSet() - } - st, _, err := sm.TipSetState(ctx, ts) - if err != nil { - return nil, err - } - - cst := cbor.NewCborStore(sm.cs.Blockstore()) - r, err := hamt.LoadNode(ctx, cst, st, hamt.UseTreeBitWidth(5)) - if err != nil { - return nil, err - } - - var out []address.Address - err = r.ForEach(ctx, func(k string, val interface{}) error { - addr, err := address.NewFromBytes([]byte(k)) - if err != nil { - return xerrors.Errorf("address in state tree was not valid: %w", err) - } - out = append(out, addr) return nil }) - if err != nil { - return nil, err - } - - return out, nil -} - -func (sm *StateManager) MarketBalance(ctx context.Context, addr address.Address, ts *types.TipSet) (api.MarketBalance, error) { - var state market.State - _, err := sm.LoadActorState(ctx, builtin.StorageMarketActorAddr, &state, ts) - if err != nil { - return api.MarketBalance{}, err - } - - addr, err = sm.LookupID(ctx, addr, ts) - if err != nil { - return api.MarketBalance{}, err - } - - var out api.MarketBalance - - et, err := adt.AsBalanceTable(sm.cs.Store(ctx), state.EscrowTable) - if err != nil { - return api.MarketBalance{}, err - } - ehas, err := et.Has(addr) - if err != nil { - return api.MarketBalance{}, err - } - if ehas { - out.Escrow, err = et.Get(addr) - if err != nil { - return api.MarketBalance{}, xerrors.Errorf("getting escrow balance: %w", err) + if robustAddr == address.Undef { + if err == nil { + return address.Undef, xerrors.Errorf("Address %s not found", idAddr.String()) } - } else { - out.Escrow = big.Zero() + return address.Undef, xerrors.Errorf("finding address: %w", err) } - - lt, err := adt.AsBalanceTable(sm.cs.Store(ctx), state.LockedTable) - if err != nil { - return api.MarketBalance{}, err - } - lhas, err := lt.Has(addr) - if err != nil { - return api.MarketBalance{}, err - } - if lhas { - out.Locked, err = lt.Get(addr) - if err != nil { - return api.MarketBalance{}, xerrors.Errorf("getting locked balance: %w", err) - } - } else { - out.Locked = big.Zero() - } - - return out, nil + return robustAddr, nil } func (sm *StateManager) ValidateChain(ctx context.Context, ts *types.TipSet) error { tschain := []*types.TipSet{ts} for ts.Height() != 0 { - next, err := sm.cs.LoadTipSet(ts.Parents()) + next, err := sm.cs.LoadTipSet(ctx, ts.Parents()) if err != nil { return err } @@ -785,6 +476,50 @@ func (sm *StateManager) ValidateChain(ctx context.Context, ts *types.TipSet) err return nil } -func (sm *StateManager) SetVMConstructor(nvm func(cid.Cid, abi.ChainEpoch, vm.Rand, blockstore.Blockstore, runtime.Syscalls) (*vm.VM, error)) { +func (sm *StateManager) SetVMConstructor(nvm func(context.Context, *vm.VMOpts) (vm.Interface, error)) { sm.newVM = nvm } + +func (sm *StateManager) VMConstructor() func(context.Context, *vm.VMOpts) (vm.Interface, error) { + return func(ctx context.Context, opts *vm.VMOpts) (vm.Interface, error) { + return sm.newVM(ctx, opts) + } +} + +func (sm *StateManager) GetNetworkVersion(ctx context.Context, height abi.ChainEpoch) network.Version { + // The epochs here are the _last_ epoch for every version, or -1 if the + // version is disabled. + for _, spec := range sm.networkVersions { + if height <= spec.atOrBelow { + return spec.networkVersion + } + } + return sm.latestVersion +} + +func (sm *StateManager) VMSys() vm.SyscallBuilder { + return sm.Syscalls +} + +func (sm *StateManager) GetRandomnessFromBeacon(ctx context.Context, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte, tsk types.TipSetKey) (abi.Randomness, error) { + pts, err := sm.ChainStore().GetTipSetFromKey(ctx, tsk) + if err != nil { + return nil, xerrors.Errorf("loading tipset %s: %w", tsk, err) + } + + r := rand.NewStateRand(sm.ChainStore(), pts.Cids(), sm.beacon, sm.GetNetworkVersion) + + return r.GetBeaconRandomness(ctx, personalization, randEpoch, entropy) + +} + +func (sm *StateManager) GetRandomnessFromTickets(ctx context.Context, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte, tsk types.TipSetKey) (abi.Randomness, error) { + pts, err := sm.ChainStore().LoadTipSet(ctx, tsk) + if err != nil { + return nil, xerrors.Errorf("loading tipset key: %w", err) + } + + r := rand.NewStateRand(sm.ChainStore(), pts.Cids(), sm.beacon, sm.GetNetworkVersion) + + return r.GetChainRandomness(ctx, personalization, randEpoch, entropy) +} diff --git a/chain/stmgr/supply.go b/chain/stmgr/supply.go new file mode 100644 index 000000000..b48f9af43 --- /dev/null +++ b/chain/stmgr/supply.go @@ -0,0 +1,479 @@ +package stmgr + +import ( + "context" + + "github.com/ipfs/go-cid" + cbor "github.com/ipfs/go-ipld-cbor" + "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" + msig0 "github.com/filecoin-project/specs-actors/actors/builtin/multisig" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" + _init "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/actors/builtin/market" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/builtin/multisig" + "github.com/filecoin-project/lotus/chain/actors/builtin/power" + "github.com/filecoin-project/lotus/chain/actors/builtin/reward" + "github.com/filecoin-project/lotus/chain/actors/builtin/verifreg" + "github.com/filecoin-project/lotus/chain/state" + "github.com/filecoin-project/lotus/chain/types" +) + +// sets up information about the vesting schedule +func (sm *StateManager) setupGenesisVestingSchedule(ctx context.Context) error { + + gb, err := sm.cs.GetGenesis(ctx) + if err != nil { + return xerrors.Errorf("getting genesis block: %w", err) + } + + gts, err := types.NewTipSet([]*types.BlockHeader{gb}) + if err != nil { + return xerrors.Errorf("getting genesis tipset: %w", err) + } + + st, _, err := sm.TipSetState(ctx, gts) + if err != nil { + return xerrors.Errorf("getting genesis tipset state: %w", err) + } + + cst := cbor.NewCborStore(sm.cs.StateBlockstore()) + sTree, err := state.LoadStateTree(cst, st) + if err != nil { + return xerrors.Errorf("loading state tree: %w", err) + } + + gp, err := getFilPowerLocked(ctx, sTree) + if err != nil { + return xerrors.Errorf("setting up genesis pledge: %w", err) + } + + sm.genesisMsigLk.Lock() + defer sm.genesisMsigLk.Unlock() + sm.genesisPledge = gp + + totalsByEpoch := make(map[abi.ChainEpoch]abi.TokenAmount) + + // 6 months + sixMonths := abi.ChainEpoch(183 * builtin.EpochsInDay) + totalsByEpoch[sixMonths] = big.NewInt(49_929_341) + totalsByEpoch[sixMonths] = big.Add(totalsByEpoch[sixMonths], big.NewInt(32_787_700)) + + // 1 year + oneYear := abi.ChainEpoch(365 * builtin.EpochsInDay) + totalsByEpoch[oneYear] = big.NewInt(22_421_712) + + // 2 years + twoYears := abi.ChainEpoch(2 * 365 * builtin.EpochsInDay) + totalsByEpoch[twoYears] = big.NewInt(7_223_364) + + // 3 years + threeYears := abi.ChainEpoch(3 * 365 * builtin.EpochsInDay) + totalsByEpoch[threeYears] = big.NewInt(87_637_883) + + // 6 years + sixYears := abi.ChainEpoch(6 * 365 * builtin.EpochsInDay) + totalsByEpoch[sixYears] = big.NewInt(100_000_000) + totalsByEpoch[sixYears] = big.Add(totalsByEpoch[sixYears], big.NewInt(300_000_000)) + + sm.preIgnitionVesting = make([]msig0.State, 0, len(totalsByEpoch)) + for k, v := range totalsByEpoch { + ns := msig0.State{ + InitialBalance: v, + UnlockDuration: k, + PendingTxns: cid.Undef, + } + sm.preIgnitionVesting = append(sm.preIgnitionVesting, ns) + } + + return nil +} + +// sets up information about the vesting schedule post the ignition upgrade +func (sm *StateManager) setupPostIgnitionVesting(ctx context.Context) error { + + totalsByEpoch := make(map[abi.ChainEpoch]abi.TokenAmount) + + // 6 months + sixMonths := abi.ChainEpoch(183 * builtin.EpochsInDay) + totalsByEpoch[sixMonths] = big.NewInt(49_929_341) + totalsByEpoch[sixMonths] = big.Add(totalsByEpoch[sixMonths], big.NewInt(32_787_700)) + + // 1 year + oneYear := abi.ChainEpoch(365 * builtin.EpochsInDay) + totalsByEpoch[oneYear] = big.NewInt(22_421_712) + + // 2 years + twoYears := abi.ChainEpoch(2 * 365 * builtin.EpochsInDay) + totalsByEpoch[twoYears] = big.NewInt(7_223_364) + + // 3 years + threeYears := abi.ChainEpoch(3 * 365 * builtin.EpochsInDay) + totalsByEpoch[threeYears] = big.NewInt(87_637_883) + + // 6 years + sixYears := abi.ChainEpoch(6 * 365 * builtin.EpochsInDay) + totalsByEpoch[sixYears] = big.NewInt(100_000_000) + totalsByEpoch[sixYears] = big.Add(totalsByEpoch[sixYears], big.NewInt(300_000_000)) + + sm.genesisMsigLk.Lock() + defer sm.genesisMsigLk.Unlock() + sm.postIgnitionVesting = make([]msig0.State, 0, len(totalsByEpoch)) + for k, v := range totalsByEpoch { + ns := msig0.State{ + // In the pre-ignition logic, we incorrectly set this value in Fil, not attoFil, an off-by-10^18 error + InitialBalance: big.Mul(v, big.NewInt(int64(build.FilecoinPrecision))), + UnlockDuration: k, + PendingTxns: cid.Undef, + // In the pre-ignition logic, the start epoch was 0. This changes in the fork logic of the Ignition upgrade itself. + StartEpoch: build.UpgradeLiftoffHeight, + } + sm.postIgnitionVesting = append(sm.postIgnitionVesting, ns) + } + + return nil +} + +// sets up information about the vesting schedule post the calico upgrade +func (sm *StateManager) setupPostCalicoVesting(ctx context.Context) error { + + totalsByEpoch := make(map[abi.ChainEpoch]abi.TokenAmount) + + // 0 days + zeroDays := abi.ChainEpoch(0) + totalsByEpoch[zeroDays] = big.NewInt(10_632_000) + + // 6 months + sixMonths := abi.ChainEpoch(183 * builtin.EpochsInDay) + totalsByEpoch[sixMonths] = big.NewInt(19_015_887) + totalsByEpoch[sixMonths] = big.Add(totalsByEpoch[sixMonths], big.NewInt(32_787_700)) + + // 1 year + oneYear := abi.ChainEpoch(365 * builtin.EpochsInDay) + totalsByEpoch[oneYear] = big.NewInt(22_421_712) + totalsByEpoch[oneYear] = big.Add(totalsByEpoch[oneYear], big.NewInt(9_400_000)) + + // 2 years + twoYears := abi.ChainEpoch(2 * 365 * builtin.EpochsInDay) + totalsByEpoch[twoYears] = big.NewInt(7_223_364) + + // 3 years + threeYears := abi.ChainEpoch(3 * 365 * builtin.EpochsInDay) + totalsByEpoch[threeYears] = big.NewInt(87_637_883) + totalsByEpoch[threeYears] = big.Add(totalsByEpoch[threeYears], big.NewInt(898_958)) + + // 6 years + sixYears := abi.ChainEpoch(6 * 365 * builtin.EpochsInDay) + totalsByEpoch[sixYears] = big.NewInt(100_000_000) + totalsByEpoch[sixYears] = big.Add(totalsByEpoch[sixYears], big.NewInt(300_000_000)) + totalsByEpoch[sixYears] = big.Add(totalsByEpoch[sixYears], big.NewInt(9_805_053)) + + sm.genesisMsigLk.Lock() + defer sm.genesisMsigLk.Unlock() + + sm.postCalicoVesting = make([]msig0.State, 0, len(totalsByEpoch)) + for k, v := range totalsByEpoch { + ns := msig0.State{ + InitialBalance: big.Mul(v, big.NewInt(int64(build.FilecoinPrecision))), + UnlockDuration: k, + PendingTxns: cid.Undef, + StartEpoch: build.UpgradeLiftoffHeight, + } + sm.postCalicoVesting = append(sm.postCalicoVesting, ns) + } + + return nil +} + +// GetVestedFunds returns all funds that have "left" actors that are in the genesis state: +// - For Multisigs, it counts the actual amounts that have vested at the given epoch +// - For Accounts, it counts max(currentBalance - genesisBalance, 0). +func (sm *StateManager) GetFilVested(ctx context.Context, height abi.ChainEpoch) (abi.TokenAmount, error) { + vf := big.Zero() + + // TODO: combine all this? + if sm.preIgnitionVesting == nil || sm.genesisPledge.IsZero() { + err := sm.setupGenesisVestingSchedule(ctx) + if err != nil { + return vf, xerrors.Errorf("failed to setup pre-ignition vesting schedule: %w", err) + } + + } + if sm.postIgnitionVesting == nil { + err := sm.setupPostIgnitionVesting(ctx) + if err != nil { + return vf, xerrors.Errorf("failed to setup post-ignition vesting schedule: %w", err) + } + + } + if sm.postCalicoVesting == nil { + err := sm.setupPostCalicoVesting(ctx) + if err != nil { + return vf, xerrors.Errorf("failed to setup post-calico vesting schedule: %w", err) + } + } + + if height <= build.UpgradeIgnitionHeight { + for _, v := range sm.preIgnitionVesting { + au := big.Sub(v.InitialBalance, v.AmountLocked(height)) + vf = big.Add(vf, au) + } + } else if height <= build.UpgradeCalicoHeight { + for _, v := range sm.postIgnitionVesting { + // In the pre-ignition logic, we simply called AmountLocked(height), assuming startEpoch was 0. + // The start epoch changed in the Ignition upgrade. + au := big.Sub(v.InitialBalance, v.AmountLocked(height-v.StartEpoch)) + vf = big.Add(vf, au) + } + } else { + for _, v := range sm.postCalicoVesting { + // In the pre-ignition logic, we simply called AmountLocked(height), assuming startEpoch was 0. + // The start epoch changed in the Ignition upgrade. + au := big.Sub(v.InitialBalance, v.AmountLocked(height-v.StartEpoch)) + vf = big.Add(vf, au) + } + } + + // After UpgradeAssemblyHeight these funds are accounted for in GetFilReserveDisbursed + if height <= build.UpgradeAssemblyHeight { + // continue to use preIgnitionGenInfos, nothing changed at the Ignition epoch + vf = big.Add(vf, sm.genesisPledge) + } + + return vf, nil +} + +func GetFilReserveDisbursed(ctx context.Context, st *state.StateTree) (abi.TokenAmount, error) { + ract, err := st.GetActor(builtin.ReserveAddress) + if err != nil { + return big.Zero(), xerrors.Errorf("failed to get reserve actor: %w", err) + } + + // If money enters the reserve actor, this could lead to a negative term + return big.Sub(big.NewFromGo(build.InitialFilReserved), ract.Balance), nil +} + +func GetFilMined(ctx context.Context, st *state.StateTree) (abi.TokenAmount, error) { + ractor, err := st.GetActor(reward.Address) + if err != nil { + return big.Zero(), xerrors.Errorf("failed to load reward actor state: %w", err) + } + + rst, err := reward.Load(adt.WrapStore(ctx, st.Store), ractor) + if err != nil { + return big.Zero(), err + } + + return rst.TotalStoragePowerReward() +} + +func getFilMarketLocked(ctx context.Context, st *state.StateTree) (abi.TokenAmount, error) { + act, err := st.GetActor(market.Address) + if err != nil { + return big.Zero(), xerrors.Errorf("failed to load market actor: %w", err) + } + + mst, err := market.Load(adt.WrapStore(ctx, st.Store), act) + if err != nil { + return big.Zero(), xerrors.Errorf("failed to load market state: %w", err) + } + + return mst.TotalLocked() +} + +func getFilPowerLocked(ctx context.Context, st *state.StateTree) (abi.TokenAmount, error) { + pactor, err := st.GetActor(power.Address) + if err != nil { + return big.Zero(), xerrors.Errorf("failed to load power actor: %w", err) + } + + pst, err := power.Load(adt.WrapStore(ctx, st.Store), pactor) + if err != nil { + return big.Zero(), xerrors.Errorf("failed to load power state: %w", err) + } + + return pst.TotalLocked() +} + +func GetFilLocked(ctx context.Context, st *state.StateTree) (abi.TokenAmount, error) { + + filMarketLocked, err := getFilMarketLocked(ctx, st) + if err != nil { + return big.Zero(), xerrors.Errorf("failed to get filMarketLocked: %w", err) + } + + filPowerLocked, err := getFilPowerLocked(ctx, st) + if err != nil { + return big.Zero(), xerrors.Errorf("failed to get filPowerLocked: %w", err) + } + + return types.BigAdd(filMarketLocked, filPowerLocked), nil +} + +func GetFilBurnt(ctx context.Context, st *state.StateTree) (abi.TokenAmount, error) { + burnt, err := st.GetActor(builtin.BurntFundsActorAddr) + if err != nil { + return big.Zero(), xerrors.Errorf("failed to load burnt actor: %w", err) + } + + return burnt.Balance, nil +} + +func (sm *StateManager) GetVMCirculatingSupply(ctx context.Context, height abi.ChainEpoch, st *state.StateTree) (abi.TokenAmount, error) { + cs, err := sm.GetVMCirculatingSupplyDetailed(ctx, height, st) + if err != nil { + return types.EmptyInt, err + } + + return cs.FilCirculating, err +} + +func (sm *StateManager) GetVMCirculatingSupplyDetailed(ctx context.Context, height abi.ChainEpoch, st *state.StateTree) (api.CirculatingSupply, error) { + filVested, err := sm.GetFilVested(ctx, height) + if err != nil { + return api.CirculatingSupply{}, xerrors.Errorf("failed to calculate filVested: %w", err) + } + + filReserveDisbursed := big.Zero() + if height > build.UpgradeAssemblyHeight { + filReserveDisbursed, err = GetFilReserveDisbursed(ctx, st) + if err != nil { + return api.CirculatingSupply{}, xerrors.Errorf("failed to calculate filReserveDisbursed: %w", err) + } + } + + filMined, err := GetFilMined(ctx, st) + if err != nil { + return api.CirculatingSupply{}, xerrors.Errorf("failed to calculate filMined: %w", err) + } + + filBurnt, err := GetFilBurnt(ctx, st) + if err != nil { + return api.CirculatingSupply{}, xerrors.Errorf("failed to calculate filBurnt: %w", err) + } + + filLocked, err := GetFilLocked(ctx, st) + if err != nil { + return api.CirculatingSupply{}, xerrors.Errorf("failed to calculate filLocked: %w", err) + } + + ret := types.BigAdd(filVested, filMined) + ret = types.BigAdd(ret, filReserveDisbursed) + ret = types.BigSub(ret, filBurnt) + ret = types.BigSub(ret, filLocked) + + if ret.LessThan(big.Zero()) { + ret = big.Zero() + } + + return api.CirculatingSupply{ + FilVested: filVested, + FilMined: filMined, + FilBurnt: filBurnt, + FilLocked: filLocked, + FilCirculating: ret, + FilReserveDisbursed: filReserveDisbursed, + }, nil +} + +func (sm *StateManager) GetCirculatingSupply(ctx context.Context, height abi.ChainEpoch, st *state.StateTree) (abi.TokenAmount, error) { + circ := big.Zero() + unCirc := big.Zero() + err := st.ForEach(func(a address.Address, actor *types.Actor) error { + switch { + case actor.Balance.IsZero(): + // Do nothing for zero-balance actors + break + case a == _init.Address || + a == reward.Address || + a == verifreg.Address || + // The power actor itself should never receive funds + a == power.Address || + a == builtin.SystemActorAddr || + a == builtin.CronActorAddr || + a == builtin.BurntFundsActorAddr || + a == builtin.SaftAddress || + a == builtin.ReserveAddress || + a == builtin.EthereumAddressManagerActorAddr: + + unCirc = big.Add(unCirc, actor.Balance) + + case a == market.Address: + mst, err := market.Load(sm.cs.ActorStore(ctx), actor) + if err != nil { + return err + } + + lb, err := mst.TotalLocked() + if err != nil { + return err + } + + circ = big.Add(circ, big.Sub(actor.Balance, lb)) + unCirc = big.Add(unCirc, lb) + + case builtin.IsAccountActor(actor.Code) || + builtin.IsPaymentChannelActor(actor.Code) || + builtin.IsEthAccountActor(actor.Code) || + builtin.IsEvmActor(actor.Code) || + builtin.IsPlaceholderActor(actor.Code): + + circ = big.Add(circ, actor.Balance) + + case builtin.IsStorageMinerActor(actor.Code): + mst, err := miner.Load(sm.cs.ActorStore(ctx), actor) + if err != nil { + return err + } + + ab, err := mst.AvailableBalance(actor.Balance) + + if err == nil { + circ = big.Add(circ, ab) + unCirc = big.Add(unCirc, big.Sub(actor.Balance, ab)) + } else { + // Assume any error is because the miner state is "broken" (lower actor balance than locked funds) + // In this case, the actor's entire balance is considered "uncirculating" + unCirc = big.Add(unCirc, actor.Balance) + } + + case builtin.IsMultisigActor(actor.Code): + mst, err := multisig.Load(sm.cs.ActorStore(ctx), actor) + if err != nil { + return err + } + + lb, err := mst.LockedBalance(height) + if err != nil { + return err + } + + ab := big.Sub(actor.Balance, lb) + circ = big.Add(circ, big.Max(ab, big.Zero())) + unCirc = big.Add(unCirc, big.Min(actor.Balance, lb)) + default: + return xerrors.Errorf("unexpected actor: %s", a) + } + + return nil + }) + + if err != nil { + return types.EmptyInt, err + } + + total := big.Add(circ, unCirc) + if !total.Equals(types.TotalFilecoinInt) { + return types.EmptyInt, xerrors.Errorf("total filecoin didn't add to expected amount: %s != %s", total, types.TotalFilecoinInt) + } + + return circ, nil +} diff --git a/chain/stmgr/tracers.go b/chain/stmgr/tracers.go new file mode 100644 index 000000000..408205de0 --- /dev/null +++ b/chain/stmgr/tracers.go @@ -0,0 +1,57 @@ +package stmgr + +import ( + "context" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/vm" +) + +type ExecMonitor interface { + // MessageApplied is called after a message has been applied. Returning an error will halt execution of any further messages. + MessageApplied(ctx context.Context, ts *types.TipSet, mcid cid.Cid, msg *types.Message, ret *vm.ApplyRet, implicit bool) error +} + +var _ ExecMonitor = (*InvocationTracer)(nil) + +type InvocationTracer struct { + trace *[]*api.InvocResult +} + +func (i *InvocationTracer) MessageApplied(ctx context.Context, ts *types.TipSet, mcid cid.Cid, msg *types.Message, ret *vm.ApplyRet, implicit bool) error { + ir := &api.InvocResult{ + MsgCid: mcid, + Msg: msg, + MsgRct: &ret.MessageReceipt, + ExecutionTrace: ret.ExecutionTrace, + Duration: ret.Duration, + } + if ret.ActorErr != nil { + ir.Error = ret.ActorErr.Error() + } + if ret.GasCosts != nil { + ir.GasCost = MakeMsgGasCost(msg, ret) + } + *i.trace = append(*i.trace, ir) + return nil +} + +var _ ExecMonitor = (*messageFinder)(nil) + +type messageFinder struct { + mcid cid.Cid // the message cid to find + outm *types.Message + outr *vm.ApplyRet +} + +func (m *messageFinder) MessageApplied(ctx context.Context, ts *types.TipSet, mcid cid.Cid, msg *types.Message, ret *vm.ApplyRet, implicit bool) error { + if m.mcid == mcid { + m.outm = msg + m.outr = ret + return errHaltExecution // message was found, no need to continue + } + return nil +} diff --git a/chain/stmgr/utils.go b/chain/stmgr/utils.go index 0ae7abb4e..5e3bbd278 100644 --- a/chain/stmgr/utils.go +++ b/chain/stmgr/utils.go @@ -1,31 +1,26 @@ package stmgr import ( - "bytes" "context" - "os" + "errors" + "fmt" + "reflect" - cid "github.com/ipfs/go-cid" - blockstore "github.com/ipfs/go-ipfs-blockstore" - cbor "github.com/ipfs/go-ipld-cbor" + "github.com/ipfs/go-cid" cbg "github.com/whyrusleeping/cbor-gen" "golang.org/x/xerrors" "github.com/filecoin-project/go-address" - amt "github.com/filecoin-project/go-amt-ipld/v2" - "github.com/filecoin-project/sector-storage/ffiwrapper" - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/builtin" - init_ "github.com/filecoin-project/specs-actors/actors/builtin/init" - "github.com/filecoin-project/specs-actors/actors/builtin/market" - "github.com/filecoin-project/specs-actors/actors/builtin/miner" - "github.com/filecoin-project/specs-actors/actors/builtin/power" - "github.com/filecoin-project/specs-actors/actors/crypto" - "github.com/filecoin-project/specs-actors/actors/util/adt" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/manifest" + gstStore "github.com/filecoin-project/go-state-types/store" "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/beacon" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/actors/builtin/system" + "github.com/filecoin-project/lotus/chain/actors/policy" + "github.com/filecoin-project/lotus/chain/rand" "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" @@ -33,340 +28,41 @@ import ( "github.com/filecoin-project/lotus/node/modules/dtypes" ) -func GetNetworkName(ctx context.Context, sm *StateManager, st cid.Cid) (dtypes.NetworkName, error) { - var state init_.State - _, err := sm.LoadActorStateRaw(ctx, builtin.InitActorAddr, &state, st) +var ErrMetadataNotFound = errors.New("actor metadata not found") + +func GetReturnType(ctx context.Context, sm *StateManager, to address.Address, method abi.MethodNum, ts *types.TipSet) (cbg.CBORUnmarshaler, error) { + act, err := sm.LoadActor(ctx, to, ts) if err != nil { - return "", xerrors.Errorf("(get sset) failed to load miner actor state: %w", err) - } - - return dtypes.NetworkName(state.NetworkName), nil -} - -func GetMinerWorkerRaw(ctx context.Context, sm *StateManager, st cid.Cid, maddr address.Address) (address.Address, error) { - var mas miner.State - _, err := sm.LoadActorStateRaw(ctx, maddr, &mas, st) - if err != nil { - return address.Undef, xerrors.Errorf("(get sset) failed to load miner actor state: %w", err) - } - - cst := cbor.NewCborStore(sm.cs.Blockstore()) - state, err := state.LoadStateTree(cst, st) - if err != nil { - return address.Undef, xerrors.Errorf("load state tree: %w", err) - } - - return vm.ResolveToKeyAddr(state, cst, mas.Info.Worker) -} - -func GetPower(ctx context.Context, sm *StateManager, ts *types.TipSet, maddr address.Address) (power.Claim, power.Claim, error) { - return GetPowerRaw(ctx, sm, ts.ParentState(), maddr) -} - -func GetPowerRaw(ctx context.Context, sm *StateManager, st cid.Cid, maddr address.Address) (power.Claim, power.Claim, error) { - var ps power.State - _, err := sm.LoadActorStateRaw(ctx, builtin.StoragePowerActorAddr, &ps, st) - if err != nil { - return power.Claim{}, power.Claim{}, xerrors.Errorf("(get sset) failed to load power actor state: %w", err) - } - - var mpow power.Claim - if maddr != address.Undef { - cm, err := adt.AsMap(sm.cs.Store(ctx), ps.Claims) - if err != nil { - return power.Claim{}, power.Claim{}, err - } - - var claim power.Claim - if _, err := cm.Get(adt.AddrKey(maddr), &claim); err != nil { - return power.Claim{}, power.Claim{}, err - } - - mpow = claim - } - - return mpow, power.Claim{ - RawBytePower: ps.TotalRawBytePower, - QualityAdjPower: ps.TotalQualityAdjPower, - }, nil -} - -func SectorSetSizes(ctx context.Context, sm *StateManager, maddr address.Address, ts *types.TipSet) (api.MinerSectors, error) { - var mas miner.State - _, err := sm.LoadActorState(ctx, maddr, &mas, ts) - if err != nil { - return api.MinerSectors{}, xerrors.Errorf("(get sset) failed to load miner actor state: %w", err) - } - - notProving, err := abi.BitFieldUnion(mas.Faults, mas.Recoveries) - if err != nil { - return api.MinerSectors{}, err - } - - npc, err := notProving.Count() - if err != nil { - return api.MinerSectors{}, err - } - - blks := cbor.NewCborStore(sm.ChainStore().Blockstore()) - ss, err := amt.LoadAMT(ctx, blks, mas.Sectors) - if err != nil { - return api.MinerSectors{}, err - } - - return api.MinerSectors{ - Sset: ss.Count, - Pset: ss.Count - npc, - }, nil -} - -func PreCommitInfo(ctx context.Context, sm *StateManager, maddr address.Address, sid abi.SectorNumber, ts *types.TipSet) (miner.SectorPreCommitOnChainInfo, error) { - var mas miner.State - _, err := sm.LoadActorState(ctx, maddr, &mas, ts) - if err != nil { - return miner.SectorPreCommitOnChainInfo{}, xerrors.Errorf("(get sset) failed to load miner actor state: %w", err) - } - - i, ok, err := mas.GetPrecommittedSector(sm.cs.Store(ctx), sid) - if err != nil { - return miner.SectorPreCommitOnChainInfo{}, err - } - if !ok { - return miner.SectorPreCommitOnChainInfo{}, xerrors.New("precommit not found") - } - - return *i, nil -} - -func GetMinerSectorSet(ctx context.Context, sm *StateManager, ts *types.TipSet, maddr address.Address, filter *abi.BitField, filterOut bool) ([]*api.ChainSectorInfo, error) { - var mas miner.State - _, err := sm.LoadActorState(ctx, maddr, &mas, ts) - if err != nil { - return nil, xerrors.Errorf("(get sset) failed to load miner actor state: %w", err) - } - - return LoadSectorsFromSet(ctx, sm.ChainStore().Blockstore(), mas.Sectors, filter, filterOut) -} - -func GetSectorsForWinningPoSt(ctx context.Context, pv ffiwrapper.Verifier, sm *StateManager, st cid.Cid, maddr address.Address, rand abi.PoStRandomness) ([]abi.SectorInfo, error) { - var mas miner.State - _, err := sm.LoadActorStateRaw(ctx, maddr, &mas, st) - if err != nil { - return nil, xerrors.Errorf("(get sectors) failed to load miner actor state: %w", err) - } - - // TODO: Optimization: we could avoid loaditg the whole proving set here if we had AMT.GetNth with bitfield filtering - sectorSet, err := GetProvingSetRaw(ctx, sm, mas) - if err != nil { - return nil, xerrors.Errorf("getting proving set: %w", err) - } - - if len(sectorSet) == 0 { - return nil, nil - } - - spt, err := ffiwrapper.SealProofTypeFromSectorSize(mas.Info.SectorSize) - if err != nil { - return nil, xerrors.Errorf("getting seal proof type: %w", err) - } - - wpt, err := spt.RegisteredWinningPoStProof() - if err != nil { - return nil, xerrors.Errorf("getting window proof type: %w", err) - } - - mid, err := address.IDFromAddress(maddr) - if err != nil { - return nil, xerrors.Errorf("getting miner ID: %w", err) - } - - ids, err := pv.GenerateWinningPoStSectorChallenge(ctx, wpt, abi.ActorID(mid), rand, uint64(len(sectorSet))) - if err != nil { - return nil, xerrors.Errorf("generating winning post challenges: %w", err) - } - - out := make([]abi.SectorInfo, len(ids)) - for i, n := range ids { - out[i] = abi.SectorInfo{ - RegisteredProof: wpt, - SectorNumber: sectorSet[n].ID, - SealedCID: sectorSet[n].Info.Info.SealedCID, - } - } - - return out, nil -} - -func StateMinerInfo(ctx context.Context, sm *StateManager, ts *types.TipSet, maddr address.Address) (miner.MinerInfo, error) { - var mas miner.State - _, err := sm.LoadActorStateRaw(ctx, maddr, &mas, ts.ParentState()) - if err != nil { - return miner.MinerInfo{}, xerrors.Errorf("(get ssize) failed to load miner actor state: %w", err) - } - - return mas.Info, nil -} - -func GetMinerSlashed(ctx context.Context, sm *StateManager, ts *types.TipSet, maddr address.Address) (bool, error) { - var mas miner.State - _, err := sm.LoadActorState(ctx, maddr, &mas, ts) - if err != nil { - return false, xerrors.Errorf("(get miner slashed) failed to load miner actor state") - } - - var spas power.State - _, err = sm.LoadActorState(ctx, builtin.StoragePowerActorAddr, &spas, ts) - if err != nil { - return false, xerrors.Errorf("(get miner slashed) failed to load power actor state") - } - - store := sm.cs.Store(ctx) - - claims, err := adt.AsMap(store, spas.Claims) - if err != nil { - return false, err - } - - ok, err := claims.Get(power.AddrKey(maddr), nil) - if err != nil { - return false, err - } - if !ok { - return true, nil - } - - return false, nil -} - -func GetMinerDeadlines(ctx context.Context, sm *StateManager, ts *types.TipSet, maddr address.Address) (*miner.Deadlines, error) { - var mas miner.State - _, err := sm.LoadActorState(ctx, maddr, &mas, ts) - if err != nil { - return nil, xerrors.Errorf("(get ssize) failed to load miner actor state: %w", err) - } - - return mas.LoadDeadlines(sm.cs.Store(ctx)) -} - -func GetMinerFaults(ctx context.Context, sm *StateManager, ts *types.TipSet, maddr address.Address) (*abi.BitField, error) { - var mas miner.State - _, err := sm.LoadActorState(ctx, maddr, &mas, ts) - if err != nil { - return nil, xerrors.Errorf("(get faults) failed to load miner actor state: %w", err) - } - - return mas.Faults, nil -} - -func GetMinerRecoveries(ctx context.Context, sm *StateManager, ts *types.TipSet, maddr address.Address) (*abi.BitField, error) { - var mas miner.State - _, err := sm.LoadActorState(ctx, maddr, &mas, ts) - if err != nil { - return nil, xerrors.Errorf("(get recoveries) failed to load miner actor state: %w", err) - } - - return mas.Recoveries, nil -} - -func GetStorageDeal(ctx context.Context, sm *StateManager, dealId abi.DealID, ts *types.TipSet) (*api.MarketDeal, error) { - var state market.State - if _, err := sm.LoadActorState(ctx, builtin.StorageMarketActorAddr, &state, ts); err != nil { - return nil, err - } - - da, err := amt.LoadAMT(ctx, cbor.NewCborStore(sm.ChainStore().Blockstore()), state.Proposals) - if err != nil { - return nil, err - } - - var dp market.DealProposal - if err := da.Get(ctx, uint64(dealId), &dp); err != nil { - return nil, err - } - - sa, err := market.AsDealStateArray(sm.ChainStore().Store(ctx), state.States) - if err != nil { - return nil, err - } - - st, found, err := sa.Get(dealId) - if err != nil { - return nil, err + return nil, xerrors.Errorf("(get sset) failed to load actor: %w", err) } + m, found := sm.tsExec.NewActorRegistry().Methods[act.Code][method] if !found { - st = &market.DealState{ - SectorStartEpoch: -1, - LastUpdatedEpoch: -1, - SlashEpoch: -1, - } + return nil, fmt.Errorf("unknown method %d for actor %s: %w", method, act.Code, ErrMetadataNotFound) } - return &api.MarketDeal{ - Proposal: dp, - State: *st, - }, nil + return reflect.New(m.Ret.Elem()).Interface().(cbg.CBORUnmarshaler), nil } -func ListMinerActors(ctx context.Context, sm *StateManager, ts *types.TipSet) ([]address.Address, error) { - var state power.State - if _, err := sm.LoadActorState(ctx, builtin.StoragePowerActorAddr, &state, ts); err != nil { - return nil, err +func GetParamType(ar *vm.ActorRegistry, actCode cid.Cid, method abi.MethodNum) (cbg.CBORUnmarshaler, error) { + m, found := ar.Methods[actCode][method] + if !found { + return nil, fmt.Errorf("unknown method %d for actor %s: %w", method, actCode, ErrMetadataNotFound) } - - m, err := adt.AsMap(sm.cs.Store(ctx), state.Claims) - if err != nil { - return nil, err - } - - var miners []address.Address - err = m.ForEach(nil, func(k string) error { - a, err := address.NewFromBytes([]byte(k)) - if err != nil { - return err - } - miners = append(miners, a) - return nil - }) - if err != nil { - return nil, err - } - - return miners, nil + return reflect.New(m.Params.Elem()).Interface().(cbg.CBORUnmarshaler), nil } -func LoadSectorsFromSet(ctx context.Context, bs blockstore.Blockstore, ssc cid.Cid, filter *abi.BitField, filterOut bool) ([]*api.ChainSectorInfo, error) { - a, err := amt.LoadAMT(ctx, cbor.NewCborStore(bs), ssc) +func GetNetworkName(ctx context.Context, sm *StateManager, st cid.Cid) (dtypes.NetworkName, error) { + act, err := sm.LoadActorRaw(ctx, init_.Address, st) if err != nil { - return nil, err + return "", err + } + ias, err := init_.Load(sm.cs.ActorStore(ctx), act) + if err != nil { + return "", err } - var sset []*api.ChainSectorInfo - if err := a.ForEach(ctx, func(i uint64, v *cbg.Deferred) error { - if filter != nil { - set, err := filter.IsSet(i) - if err != nil { - return xerrors.Errorf("filter check error: %w", err) - } - if set == filterOut { - return nil - } - } - - var oci miner.SectorOnChainInfo - if err := cbor.DecodeInto(v.Raw, &oci); err != nil { - return err - } - sset = append(sset, &api.ChainSectorInfo{ - Info: oci, - ID: abi.SectorNumber(i), - }) - return nil - }); err != nil { - return nil, err - } - - return sset, nil + return ias.NetworkName() } func ComputeState(ctx context.Context, sm *StateManager, height abi.ChainEpoch, msgs []*types.Message, ts *types.TipSet) (cid.Cid, []*api.InvocResult, error) { @@ -376,16 +72,37 @@ func ComputeState(ctx context.Context, sm *StateManager, height abi.ChainEpoch, base, trace, err := sm.ExecutionTrace(ctx, ts) if err != nil { - return cid.Undef, nil, err + return cid.Undef, nil, xerrors.Errorf("failed to compute base state: %w", err) } - fstate, err := sm.handleStateForks(ctx, base, height, ts.Height()) - if err != nil { - return cid.Undef, nil, err + for i := ts.Height(); i < height; i++ { + // Technically, the tipset we're passing in here should be ts+1, but that may not exist. + base, err = sm.HandleStateForks(ctx, base, i, &InvocationTracer{trace: &trace}, ts) + if err != nil { + return cid.Undef, nil, xerrors.Errorf("error handling state forks: %w", err) + } + + // We intentionally don't run cron here, as we may be trying to look into the + // future. It's not guaranteed to be accurate... but that's fine. } - r := store.NewChainRand(sm.cs, ts.Cids(), height) - vmi, err := vm.NewVM(fstate, height, r, sm.cs.Blockstore(), sm.cs.VMSys()) + r := rand.NewStateRand(sm.cs, ts.Cids(), sm.beacon, sm.GetNetworkVersion) + vmopt := &vm.VMOpts{ + StateBase: base, + Epoch: height, + Timestamp: ts.MinTimestamp(), + Rand: r, + Bstore: sm.cs.StateBlockstore(), + Actors: sm.tsExec.NewActorRegistry(), + Syscalls: sm.Syscalls, + CircSupplyCalc: sm.GetVMCirculatingSupply, + NetworkVersion: sm.GetNetworkVersion(ctx, height), + BaseFee: ts.Blocks()[0].ParentBaseFee, + LookbackState: LookbackStateGetterForTipset(sm, ts), + TipSetGetter: TipSetGetterForTipset(sm.cs, ts), + Tracing: true, + } + vmi, err := sm.newVM(ctx, vmopt) if err != nil { return cid.Undef, nil, err } @@ -399,6 +116,21 @@ func ComputeState(ctx context.Context, sm *StateManager, height abi.ChainEpoch, if ret.ExitCode != 0 { log.Infof("compute state apply message %d failed (exit: %d): %s", i, ret.ExitCode, ret.ActorErr) } + + ir := &api.InvocResult{ + MsgCid: msg.Cid(), + Msg: msg, + MsgRct: &ret.MessageReceipt, + ExecutionTrace: ret.ExecutionTrace, + Duration: ret.Duration, + } + if ret.ActorErr != nil { + ir.Error = ret.ActorErr.Error() + } + if ret.GasCosts != nil { + ir.GasCost = MakeMsgGasCost(msg, ret) + } + trace = append(trace, ir) } root, err := vmi.Flush(ctx) @@ -409,115 +141,130 @@ func ComputeState(ctx context.Context, sm *StateManager, height abi.ChainEpoch, return root, trace, nil } -func GetProvingSetRaw(ctx context.Context, sm *StateManager, mas miner.State) ([]*api.ChainSectorInfo, error) { - notProving, err := abi.BitFieldUnion(mas.Faults, mas.Recoveries) - if err != nil { - return nil, err +func LookbackStateGetterForTipset(sm *StateManager, ts *types.TipSet) vm.LookbackStateGetter { + return func(ctx context.Context, round abi.ChainEpoch) (*state.StateTree, error) { + _, st, err := GetLookbackTipSetForRound(ctx, sm, ts, round) + if err != nil { + return nil, err + } + return sm.StateTree(st) } - - provset, err := LoadSectorsFromSet(ctx, sm.cs.Blockstore(), mas.Sectors, notProving, true) - if err != nil { - return nil, xerrors.Errorf("failed to get proving set: %w", err) - } - - return provset, nil } -func GetLookbackTipSetForRound(ctx context.Context, sm *StateManager, ts *types.TipSet, round abi.ChainEpoch) (*types.TipSet, error) { +func TipSetGetterForTipset(cs *store.ChainStore, ts *types.TipSet) vm.TipSetGetter { + return func(ctx context.Context, round abi.ChainEpoch) (types.TipSetKey, error) { + ts, err := cs.GetTipsetByHeight(ctx, round, ts, true) + if err != nil { + return types.EmptyTSK, err + } + return ts.Key(), nil + } +} + +func GetLookbackTipSetForRound(ctx context.Context, sm *StateManager, ts *types.TipSet, round abi.ChainEpoch) (*types.TipSet, cid.Cid, error) { var lbr abi.ChainEpoch - if round > build.WinningPoStSectorSetLookback { - lbr = round - build.WinningPoStSectorSetLookback + lb := policy.GetWinningPoStSectorSetLookback(sm.GetNetworkVersion(ctx, round)) + if round > lb { + lbr = round - lb } // more null blocks than our lookback - if lbr > ts.Height() { - return ts, nil - } - - lbts, err := sm.ChainStore().GetTipsetByHeight(ctx, lbr, ts, true) - if err != nil { - return nil, xerrors.Errorf("failed to get lookback tipset: %w", err) - } - - return lbts, nil -} - -func MinerGetBaseInfo(ctx context.Context, sm *StateManager, bcn beacon.RandomBeacon, tsk types.TipSetKey, round abi.ChainEpoch, maddr address.Address, pv ffiwrapper.Verifier) (*api.MiningBaseInfo, error) { - ts, err := sm.ChainStore().LoadTipSet(tsk) - if err != nil { - return nil, xerrors.Errorf("failed to load tipset for mining base: %w", err) - } - - prev, err := sm.ChainStore().GetLatestBeaconEntry(ts) - if err != nil { - if os.Getenv("LOTUS_IGNORE_DRAND") != "_yes_" { - return nil, xerrors.Errorf("failed to get latest beacon entry: %w", err) + if lbr >= ts.Height() { + // This should never happen at this point, but may happen before + // network version 3 (where the lookback was only 10 blocks). + st, _, err := sm.TipSetState(ctx, ts) + if err != nil { + return nil, cid.Undef, err } - - prev = &types.BeaconEntry{} + return ts, st, nil } - entries, err := beacon.BeaconEntriesForBlock(ctx, bcn, round, *prev) + // Get the tipset after the lookback tipset, or the next non-null one. + nextTs, err := sm.ChainStore().GetTipsetByHeight(ctx, lbr+1, ts, false) if err != nil { - return nil, err + return nil, cid.Undef, xerrors.Errorf("failed to get lookback tipset+1: %w", err) } - rbase := *prev - if len(entries) > 0 { - rbase = entries[len(entries)-1] + if lbr > nextTs.Height() { + return nil, cid.Undef, xerrors.Errorf("failed to find non-null tipset %s (%d) which is known to exist, found %s (%d)", ts.Key(), ts.Height(), nextTs.Key(), nextTs.Height()) + } - lbts, err := GetLookbackTipSetForRound(ctx, sm, ts, round) + lbts, err := sm.ChainStore().GetTipSetFromKey(ctx, nextTs.Parents()) if err != nil { - return nil, xerrors.Errorf("getting lookback miner actor state: %w", err) + return nil, cid.Undef, xerrors.Errorf("failed to resolve lookback tipset: %w", err) } - lbst, _, err := sm.TipSetState(ctx, lbts) - if err != nil { - return nil, err - } - - var mas miner.State - if _, err := sm.LoadActorStateRaw(ctx, maddr, &mas, lbst); err != nil { - return nil, err - } - - buf := new(bytes.Buffer) - if err := maddr.MarshalCBOR(buf); err != nil { - return nil, xerrors.Errorf("failed to marshal miner address: %w", err) - } - - prand, err := store.DrawRandomness(rbase.Data, crypto.DomainSeparationTag_WinningPoStChallengeSeed, round, buf.Bytes()) - if err != nil { - return nil, xerrors.Errorf("failed to get randomness for winning post: %w", err) - } - - sectors, err := GetSectorsForWinningPoSt(ctx, pv, sm, lbst, maddr, prand) - if err != nil { - return nil, xerrors.Errorf("getting wpost proving set: %w", err) - } - - if len(sectors) == 0 { - return nil, nil - } - - mpow, tpow, err := GetPowerRaw(ctx, sm, lbst, maddr) - if err != nil { - return nil, xerrors.Errorf("failed to get power: %w", err) - } - - worker, err := sm.ResolveToKeyAddress(ctx, mas.GetWorker(), ts) - if err != nil { - return nil, xerrors.Errorf("resolving worker address: %w", err) - } - - return &api.MiningBaseInfo{ - MinerPower: mpow.QualityAdjPower, - NetworkPower: tpow.QualityAdjPower, - Sectors: sectors, - WorkerKey: worker, - SectorSize: mas.Info.SectorSize, - PrevBeaconEntry: *prev, - BeaconEntries: entries, - }, nil + return lbts, nextTs.ParentState(), nil +} + +func CheckTotalFIL(ctx context.Context, cs *store.ChainStore, ts *types.TipSet) (abi.TokenAmount, error) { + str, err := state.LoadStateTree(cs.ActorStore(ctx), ts.ParentState()) + if err != nil { + return abi.TokenAmount{}, err + } + + sum := types.NewInt(0) + err = str.ForEach(func(a address.Address, act *types.Actor) error { + sum = types.BigAdd(sum, act.Balance) + return nil + }) + if err != nil { + return abi.TokenAmount{}, err + } + + return sum, nil +} + +func MakeMsgGasCost(msg *types.Message, ret *vm.ApplyRet) api.MsgGasCost { + return api.MsgGasCost{ + Message: msg.Cid(), + GasUsed: big.NewInt(ret.GasUsed), + BaseFeeBurn: ret.GasCosts.BaseFeeBurn, + OverEstimationBurn: ret.GasCosts.OverEstimationBurn, + MinerPenalty: ret.GasCosts.MinerPenalty, + MinerTip: ret.GasCosts.MinerTip, + Refund: ret.GasCosts.Refund, + TotalCost: big.Sub(msg.RequiredFunds(), ret.GasCosts.Refund), + } +} + +func (sm *StateManager) ListAllActors(ctx context.Context, ts *types.TipSet) ([]address.Address, error) { + stateTree, err := sm.StateTree(sm.parentState(ts)) + if err != nil { + return nil, err + } + + var out []address.Address + err = stateTree.ForEach(func(addr address.Address, act *types.Actor) error { + out = append(out, addr) + return nil + }) + if err != nil { + return nil, err + } + + return out, nil +} + +func GetManifestData(ctx context.Context, st *state.StateTree) (*manifest.ManifestData, error) { + wrapStore := gstStore.WrapStore(ctx, st.Store) + + systemActor, err := st.GetActor(system.Address) + if err != nil { + return nil, fmt.Errorf("failed to get system actor: %w", err) + } + systemActorState, err := system.Load(wrapStore, systemActor) + if err != nil { + return nil, xerrors.Errorf("failed to load system actor state: %w", err) + } + + actorsManifestDataCid := systemActorState.GetBuiltinActors() + + var mfData manifest.ManifestData + if err := wrapStore.Get(ctx, actorsManifestDataCid, &mfData); err != nil { + return nil, xerrors.Errorf("error fetching data: %w", err) + } + + return &mfData, nil } diff --git a/chain/store/basefee.go b/chain/store/basefee.go new file mode 100644 index 000000000..3b6af5c07 --- /dev/null +++ b/chain/store/basefee.go @@ -0,0 +1,85 @@ +package store + +import ( + "context" + + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + "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" +) + +func ComputeNextBaseFee(baseFee types.BigInt, gasLimitUsed int64, noOfBlocks int, epoch abi.ChainEpoch) types.BigInt { + // deta := gasLimitUsed/noOfBlocks - build.BlockGasTarget + // change := baseFee * deta / BlockGasTarget + // nextBaseFee = baseFee + change + // nextBaseFee = max(nextBaseFee, build.MinimumBaseFee) + + var delta int64 + if epoch > build.UpgradeSmokeHeight { + delta = gasLimitUsed / int64(noOfBlocks) + delta -= build.BlockGasTarget + } else { + delta = build.PackingEfficiencyDenom * gasLimitUsed / (int64(noOfBlocks) * build.PackingEfficiencyNum) + delta -= build.BlockGasTarget + } + + // cap change at 12.5% (BaseFeeMaxChangeDenom) by capping delta + if delta > build.BlockGasTarget { + delta = build.BlockGasTarget + } + if delta < -build.BlockGasTarget { + delta = -build.BlockGasTarget + } + + change := big.Mul(baseFee, big.NewInt(delta)) + change = big.Div(change, big.NewInt(build.BlockGasTarget)) + change = big.Div(change, big.NewInt(build.BaseFeeMaxChangeDenom)) + + nextBaseFee := big.Add(baseFee, change) + if big.Cmp(nextBaseFee, big.NewInt(build.MinimumBaseFee)) < 0 { + nextBaseFee = big.NewInt(build.MinimumBaseFee) + } + return nextBaseFee +} + +func (cs *ChainStore) ComputeBaseFee(ctx context.Context, ts *types.TipSet) (abi.TokenAmount, error) { + if build.UpgradeBreezeHeight >= 0 && ts.Height() > build.UpgradeBreezeHeight && ts.Height() < build.UpgradeBreezeHeight+build.BreezeGasTampingDuration { + return abi.NewTokenAmount(100), nil + } + + zero := abi.NewTokenAmount(0) + + // totalLimit is sum of GasLimits of unique messages in a tipset + totalLimit := int64(0) + + seen := make(map[cid.Cid]struct{}) + + for _, b := range ts.Blocks() { + msg1, msg2, err := cs.MessagesForBlock(ctx, b) + if err != nil { + return zero, xerrors.Errorf("error getting messages for: %s: %w", b.Cid(), err) + } + for _, m := range msg1 { + c := m.Cid() + if _, ok := seen[c]; !ok { + totalLimit += m.GasLimit + seen[c] = struct{}{} + } + } + for _, m := range msg2 { + c := m.Cid() + if _, ok := seen[c]; !ok { + totalLimit += m.Message.GasLimit + seen[c] = struct{}{} + } + } + } + parentBaseFee := ts.Blocks()[0].ParentBaseFee + + return ComputeNextBaseFee(parentBaseFee, totalLimit, len(ts.Blocks()), ts.Height()), nil +} diff --git a/chain/store/basefee_test.go b/chain/store/basefee_test.go new file mode 100644 index 000000000..8dd61f709 --- /dev/null +++ b/chain/store/basefee_test.go @@ -0,0 +1,41 @@ +//stm: #unit + +package store + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/types" +) + +func TestBaseFee(t *testing.T) { + //stm: @CHAIN_STORE_COMPUTE_NEXT_BASE_FEE_001 + tests := []struct { + basefee uint64 + limitUsed int64 + noOfBlocks int + preSmoke, postSmoke uint64 + }{ + {100e6, 0, 1, 87.5e6, 87.5e6}, + {100e6, 0, 5, 87.5e6, 87.5e6}, + {100e6, build.BlockGasTarget, 1, 103.125e6, 100e6}, + {100e6, build.BlockGasTarget * 2, 2, 103.125e6, 100e6}, + {100e6, build.BlockGasLimit * 2, 2, 112.5e6, 112.5e6}, + {100e6, (build.BlockGasLimit * 15) / 10, 2, 110937500, 106.250e6}, + } + + for _, test := range tests { + test := test + t.Run(fmt.Sprintf("%v", test), func(t *testing.T) { + preSmoke := ComputeNextBaseFee(types.NewInt(test.basefee), test.limitUsed, test.noOfBlocks, build.UpgradeSmokeHeight-1) + assert.Equal(t, fmt.Sprintf("%d", test.preSmoke), preSmoke.String()) + + postSmoke := ComputeNextBaseFee(types.NewInt(test.basefee), test.limitUsed, test.noOfBlocks, build.UpgradeSmokeHeight+1) + assert.Equal(t, fmt.Sprintf("%d", test.postSmoke), postSmoke.String()) + }) + } +} diff --git a/chain/store/checkpoint_test.go b/chain/store/checkpoint_test.go new file mode 100644 index 000000000..bc2cb5e73 --- /dev/null +++ b/chain/store/checkpoint_test.go @@ -0,0 +1,95 @@ +// stm: #unit +package store_test + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/lotus/chain/gen" +) + +func TestChainCheckpoint(t *testing.T) { + //stm: @CHAIN_GEN_NEXT_TIPSET_FROM_MINERS_001 + //stm: @CHAIN_STORE_GET_TIPSET_FROM_KEY_001, @CHAIN_STORE_SET_HEAD_001, @CHAIN_STORE_GET_HEAVIEST_TIPSET_001 + //stm: @CHAIN_STORE_SET_CHECKPOINT_001, @CHAIN_STORE_MAYBE_TAKE_HEAVIER_TIPSET_001, @CHAIN_STORE_REMOVE_CHECKPOINT_001 + ctx := context.Background() + + cg, err := gen.NewGenerator() + if err != nil { + t.Fatal(err) + } + + // Let the first miner mine some blocks. + last := cg.CurTipset.TipSet() + for i := 0; i < 4; i++ { + ts, err := cg.NextTipSetFromMiners(last, cg.Miners[:1], 0) + require.NoError(t, err) + + last = ts.TipSet.TipSet() + } + + cs := cg.ChainStore() + + checkpoint := last + checkpointParents, err := cs.GetTipSetFromKey(ctx, checkpoint.Parents()) + require.NoError(t, err) + + // Set the head to the block before the checkpoint. + err = cs.SetHead(ctx, checkpointParents) + require.NoError(t, err) + + // Verify it worked. + head := cs.GetHeaviestTipSet() + require.True(t, head.Equals(checkpointParents)) + + // Try to set the checkpoint in the future, it should fail. + err = cs.SetCheckpoint(ctx, checkpoint) + require.Error(t, err) + + // Then move the head back. + err = cs.SetHead(ctx, checkpoint) + require.NoError(t, err) + + // Verify it worked. + head = cs.GetHeaviestTipSet() + require.True(t, head.Equals(checkpoint)) + + // And checkpoint it. + err = cs.SetCheckpoint(ctx, checkpoint) + require.NoError(t, err) + + // Let the second miner miner mine a fork + last = checkpointParents + for i := 0; i < 4; i++ { + ts, err := cg.NextTipSetFromMiners(last, cg.Miners[1:], 0) + require.NoError(t, err) + + last = ts.TipSet.TipSet() + } + + // See if the chain will take the fork, it shouldn't. + err = cs.MaybeTakeHeavierTipSet(context.Background(), last) + require.NoError(t, err) + head = cs.GetHeaviestTipSet() + require.True(t, head.Equals(checkpoint)) + + // Remove the checkpoint. + err = cs.RemoveCheckpoint(ctx) + require.NoError(t, err) + + // Now switch to the other fork. + err = cs.MaybeTakeHeavierTipSet(context.Background(), last) + require.NoError(t, err) + head = cs.GetHeaviestTipSet() + require.True(t, head.Equals(last)) + + // Setting a checkpoint on the other fork should fail. + err = cs.SetCheckpoint(ctx, checkpoint) + require.Error(t, err) + + // Setting a checkpoint on this fork should succeed. + err = cs.SetCheckpoint(ctx, checkpointParents) + require.NoError(t, err) +} diff --git a/chain/store/coalescer.go b/chain/store/coalescer.go new file mode 100644 index 000000000..db4cbdadc --- /dev/null +++ b/chain/store/coalescer.go @@ -0,0 +1,218 @@ +package store + +import ( + "context" + "time" + + "github.com/filecoin-project/lotus/chain/types" +) + +// WrapHeadChangeCoalescer wraps a ReorgNotifee with a head change coalescer. +// minDelay is the minimum coalesce delay; when a head change is first received, the coalescer will +// +// wait for that long to coalesce more head changes. +// +// maxDelay is the maximum coalesce delay; the coalescer will not delay delivery of a head change +// +// more than that. +// +// mergeInterval is the interval that triggers additional coalesce delay; if the last head change was +// +// within the merge interval when the coalesce timer fires, then the coalesce time is extended +// by min delay and up to max delay total. +func WrapHeadChangeCoalescer(fn ReorgNotifee, minDelay, maxDelay, mergeInterval time.Duration) ReorgNotifee { + c := NewHeadChangeCoalescer(fn, minDelay, maxDelay, mergeInterval) + return c.HeadChange +} + +// HeadChangeCoalescer is a stateful reorg notifee which coalesces incoming head changes +// with pending head changes to reduce state computations from head change notifications. +type HeadChangeCoalescer struct { + notify ReorgNotifee + + ctx context.Context + cancel func() + + eventq chan headChange + + revert []*types.TipSet + apply []*types.TipSet +} + +type headChange struct { + revert, apply []*types.TipSet +} + +// NewHeadChangeCoalescer creates a HeadChangeCoalescer. +func NewHeadChangeCoalescer(fn ReorgNotifee, minDelay, maxDelay, mergeInterval time.Duration) *HeadChangeCoalescer { + ctx, cancel := context.WithCancel(context.Background()) + c := &HeadChangeCoalescer{ + notify: fn, + ctx: ctx, + cancel: cancel, + eventq: make(chan headChange), + } + + go c.background(minDelay, maxDelay, mergeInterval) + + return c +} + +// HeadChange is the ReorgNotifee callback for the stateful coalescer; it receives an incoming +// head change and schedules dispatch of a coalesced head change in the background. +func (c *HeadChangeCoalescer) HeadChange(revert, apply []*types.TipSet) error { + select { + case c.eventq <- headChange{revert: revert, apply: apply}: + return nil + case <-c.ctx.Done(): + return c.ctx.Err() + } +} + +// Close closes the coalescer and cancels the background dispatch goroutine. +// Any further notification will result in an error. +func (c *HeadChangeCoalescer) Close() error { + select { + case <-c.ctx.Done(): + default: + c.cancel() + } + + return nil +} + +// Implementation details + +func (c *HeadChangeCoalescer) background(minDelay, maxDelay, mergeInterval time.Duration) { + var timerC <-chan time.Time + var first, last time.Time + + for { + select { + case evt := <-c.eventq: + c.coalesce(evt.revert, evt.apply) + + now := time.Now() + last = now + if first.IsZero() { + first = now + } + + if timerC == nil { + timerC = time.After(minDelay) + } + + case now := <-timerC: + sinceFirst := now.Sub(first) + sinceLast := now.Sub(last) + + if sinceLast < mergeInterval && sinceFirst < maxDelay { + // coalesce some more + maxWait := maxDelay - sinceFirst + wait := minDelay + if maxWait < wait { + wait = maxWait + } + + timerC = time.After(wait) + } else { + // dispatch + c.dispatch() + + first = time.Time{} + last = time.Time{} + timerC = nil + } + + case <-c.ctx.Done(): + if c.revert != nil || c.apply != nil { + c.dispatch() + } + return + } + } +} + +func (c *HeadChangeCoalescer) coalesce(revert, apply []*types.TipSet) { + // newly reverted tipsets cancel out with pending applys. + // similarly, newly applied tipsets cancel out with pending reverts. + + // pending tipsets + pendRevert := make(map[types.TipSetKey]struct{}, len(c.revert)) + for _, ts := range c.revert { + pendRevert[ts.Key()] = struct{}{} + } + + pendApply := make(map[types.TipSetKey]struct{}, len(c.apply)) + for _, ts := range c.apply { + pendApply[ts.Key()] = struct{}{} + } + + // incoming tipsets + reverting := make(map[types.TipSetKey]struct{}, len(revert)) + for _, ts := range revert { + reverting[ts.Key()] = struct{}{} + } + + applying := make(map[types.TipSetKey]struct{}, len(apply)) + for _, ts := range apply { + applying[ts.Key()] = struct{}{} + } + + // coalesced revert set + // - pending reverts are cancelled by incoming applys + // - incoming reverts are cancelled by pending applys + newRevert := c.merge(c.revert, revert, pendApply, applying) + + // coalesced apply set + // - pending applys are cancelled by incoming reverts + // - incoming applys are cancelled by pending reverts + newApply := c.merge(c.apply, apply, pendRevert, reverting) + + // commit the coalesced sets + c.revert = newRevert + c.apply = newApply +} + +func (c *HeadChangeCoalescer) merge(pend, incoming []*types.TipSet, cancel1, cancel2 map[types.TipSetKey]struct{}) []*types.TipSet { + result := make([]*types.TipSet, 0, len(pend)+len(incoming)) + for _, ts := range pend { + _, cancel := cancel1[ts.Key()] + if cancel { + continue + } + + _, cancel = cancel2[ts.Key()] + if cancel { + continue + } + + result = append(result, ts) + } + + for _, ts := range incoming { + _, cancel := cancel1[ts.Key()] + if cancel { + continue + } + + _, cancel = cancel2[ts.Key()] + if cancel { + continue + } + + result = append(result, ts) + } + + return result +} + +func (c *HeadChangeCoalescer) dispatch() { + err := c.notify(c.revert, c.apply) + if err != nil { + log.Errorf("error dispatching coalesced head change notification: %s", err) + } + + c.revert = nil + c.apply = nil +} diff --git a/chain/store/coalescer_test.go b/chain/store/coalescer_test.go new file mode 100644 index 000000000..278e6b181 --- /dev/null +++ b/chain/store/coalescer_test.go @@ -0,0 +1,74 @@ +// stm: #unit +package store + +import ( + "testing" + "time" + + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/mock" +) + +func TestHeadChangeCoalescer(t *testing.T) { + //stm: @CHAIN_STORE_COALESCE_HEAD_CHANGE_001 + notif := make(chan headChange, 1) + c := NewHeadChangeCoalescer(func(revert, apply []*types.TipSet) error { + notif <- headChange{apply: apply, revert: revert} + return nil + }, + 100*time.Millisecond, + 200*time.Millisecond, + 10*time.Millisecond, + ) + defer c.Close() //nolint + + b0 := mock.MkBlock(nil, 0, 0) + root := mock.TipSet(b0) + bA := mock.MkBlock(root, 1, 1) + tA := mock.TipSet(bA) + bB := mock.MkBlock(root, 1, 2) + tB := mock.TipSet(bB) + tAB := mock.TipSet(bA, bB) + bC := mock.MkBlock(root, 1, 3) + tABC := mock.TipSet(bA, bB, bC) + bD := mock.MkBlock(root, 1, 4) + tABCD := mock.TipSet(bA, bB, bC, bD) + bE := mock.MkBlock(root, 1, 5) + tABCDE := mock.TipSet(bA, bB, bC, bD, bE) + + c.HeadChange(nil, []*types.TipSet{tA}) //nolint + c.HeadChange(nil, []*types.TipSet{tB}) //nolint + c.HeadChange([]*types.TipSet{tA, tB}, []*types.TipSet{tAB}) //nolint + c.HeadChange([]*types.TipSet{tAB}, []*types.TipSet{tABC}) //nolint + + change := <-notif + + if len(change.revert) != 0 { + t.Fatalf("expected empty revert set but got %d elements", len(change.revert)) + } + if len(change.apply) != 1 { + t.Fatalf("expected single element apply set but got %d elements", len(change.apply)) + } + if change.apply[0] != tABC { + t.Fatalf("expected to apply tABC") + } + + c.HeadChange([]*types.TipSet{tABC}, []*types.TipSet{tABCD}) //nolint + c.HeadChange([]*types.TipSet{tABCD}, []*types.TipSet{tABCDE}) //nolint + + change = <-notif + + if len(change.revert) != 1 { + t.Fatalf("expected single element revert set but got %d elements", len(change.revert)) + } + if change.revert[0] != tABC { + t.Fatalf("expected to revert tABC") + } + if len(change.apply) != 1 { + t.Fatalf("expected single element apply set but got %d elements", len(change.apply)) + } + if change.apply[0] != tABCDE { + t.Fatalf("expected to revert tABC") + } + +} diff --git a/chain/store/fts.go b/chain/store/fts.go index f9ec4459e..477b2b0b0 100644 --- a/chain/store/fts.go +++ b/chain/store/fts.go @@ -1,8 +1,9 @@ package store import ( - "github.com/filecoin-project/lotus/chain/types" "github.com/ipfs/go-cid" + + "github.com/filecoin-project/lotus/chain/types" ) // FullTipSet is an expanded version of the TipSet that contains all the blocks and messages @@ -32,8 +33,11 @@ func (fts *FullTipSet) Cids() []cid.Cid { return cids } +// TipSet returns a narrower view of this FullTipSet elliding the block +// messages. func (fts *FullTipSet) TipSet() *types.TipSet { if fts.tipset != nil { + // FIXME: fts.tipset is actually never set. Should it memoize? return fts.tipset } diff --git a/chain/store/index.go b/chain/store/index.go new file mode 100644 index 000000000..8361f4db9 --- /dev/null +++ b/chain/store/index.go @@ -0,0 +1,203 @@ +package store + +import ( + "context" + "hash/maphash" + "os" + "strconv" + + "github.com/puzpuzpuz/xsync/v2" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/lib/shardedmutex" +) + +// DefaultChainIndexCacheSize no longer sets the maximum size, just the initial size of the map. +var DefaultChainIndexCacheSize = 1 << 15 + +func init() { + if s := os.Getenv("LOTUS_CHAIN_INDEX_CACHE"); s != "" { + lcic, err := strconv.Atoi(s) + if err != nil { + log.Errorf("failed to parse 'LOTUS_CHAIN_INDEX_CACHE' env var: %s", err) + } + DefaultChainIndexCacheSize = lcic + } + +} + +type ChainIndex struct { + indexCache *xsync.MapOf[types.TipSetKey, *lbEntry] + + fillCacheLock shardedmutex.ShardedMutexFor[types.TipSetKey] + + loadTipSet loadTipSetFunc + + skipLength abi.ChainEpoch +} +type loadTipSetFunc func(context.Context, types.TipSetKey) (*types.TipSet, error) + +func maphashTSK(s maphash.Seed, tsk types.TipSetKey) uint64 { + return maphash.Bytes(s, tsk.Bytes()) +} + +func NewChainIndex(lts loadTipSetFunc) *ChainIndex { + return &ChainIndex{ + indexCache: xsync.NewTypedMapOfPresized[types.TipSetKey, *lbEntry](maphashTSK, DefaultChainIndexCacheSize), + fillCacheLock: shardedmutex.NewFor(maphashTSK, 32), + loadTipSet: lts, + skipLength: 20, + } +} + +type lbEntry struct { + targetHeight abi.ChainEpoch + target types.TipSetKey +} + +func (ci *ChainIndex) GetTipsetByHeight(ctx context.Context, from *types.TipSet, to abi.ChainEpoch) (*types.TipSet, error) { + if from.Height()-to <= ci.skipLength { + return ci.walkBack(ctx, from, to) + } + + rounded, err := ci.roundDown(ctx, from) + if err != nil { + return nil, xerrors.Errorf("failed to round down: %w", err) + } + + cur := rounded.Key() + for { + lbe, ok := ci.indexCache.Load(cur) // check the cache + if !ok { + lk := ci.fillCacheLock.GetLock(cur) + lk.Lock() // if entry is missing, take the lock + lbe, ok = ci.indexCache.Load(cur) // check if someone else added it while we waited for lock + if !ok { + fc, err := ci.fillCache(ctx, cur) + if err != nil { + lk.Unlock() + return nil, xerrors.Errorf("failed to fill cache: %w", err) + } + lbe = fc + ci.indexCache.Store(cur, lbe) + } + lk.Unlock() + } + + if to == lbe.targetHeight { + ts, err := ci.loadTipSet(ctx, lbe.target) + if err != nil { + return nil, xerrors.Errorf("failed to load tipset: %w", err) + } + + return ts, nil + } + if to > lbe.targetHeight { + ts, err := ci.loadTipSet(ctx, cur) + if err != nil { + return nil, xerrors.Errorf("failed to load tipset: %w", err) + } + return ci.walkBack(ctx, ts, to) + } + + cur = lbe.target + } +} + +func (ci *ChainIndex) GetTipsetByHeightWithoutCache(ctx context.Context, from *types.TipSet, to abi.ChainEpoch) (*types.TipSet, error) { + return ci.walkBack(ctx, from, to) +} + +// Caller must hold indexCacheLk +func (ci *ChainIndex) fillCache(ctx context.Context, tsk types.TipSetKey) (*lbEntry, error) { + ts, err := ci.loadTipSet(ctx, tsk) + if err != nil { + return nil, xerrors.Errorf("failed to load tipset: %w", err) + } + + if ts.Height() == 0 { + return &lbEntry{ + targetHeight: 0, + target: tsk, + }, nil + } + + // will either be equal to ts.Height, or at least > ts.Parent.Height() + rheight := ci.roundHeight(ts.Height()) + + parent, err := ci.loadTipSet(ctx, ts.Parents()) + if err != nil { + return nil, err + } + + rheight -= ci.skipLength + if rheight < 0 { + rheight = 0 + } + + var skipTarget *types.TipSet + if parent.Height() < rheight { + skipTarget = parent + } else { + skipTarget, err = ci.walkBack(ctx, parent, rheight) + if err != nil { + return nil, xerrors.Errorf("fillCache walkback: %w", err) + } + } + + lbe := &lbEntry{ + targetHeight: skipTarget.Height(), + target: skipTarget.Key(), + } + + return lbe, nil +} + +// floors to nearest skipLength multiple +func (ci *ChainIndex) roundHeight(h abi.ChainEpoch) abi.ChainEpoch { + return (h / ci.skipLength) * ci.skipLength +} + +func (ci *ChainIndex) roundDown(ctx context.Context, ts *types.TipSet) (*types.TipSet, error) { + target := ci.roundHeight(ts.Height()) + + rounded, err := ci.walkBack(ctx, ts, target) + if err != nil { + return nil, xerrors.Errorf("failed to walk back: %w", err) + } + + return rounded, nil +} + +func (ci *ChainIndex) walkBack(ctx context.Context, from *types.TipSet, to abi.ChainEpoch) (*types.TipSet, error) { + if to > from.Height() { + return nil, xerrors.Errorf("looking for tipset with height greater than start point") + } + + if to == from.Height() { + return from, nil + } + + ts := from + + for { + pts, err := ci.loadTipSet(ctx, ts.Parents()) + if err != nil { + return nil, xerrors.Errorf("failed to load tipset: %w", err) + } + + if to > pts.Height() { + // in case pts is lower than the epoch we're looking for (null blocks) + // return a tipset above that height + return ts, nil + } + if to == pts.Height() { + return pts, nil + } + + ts = pts + } +} diff --git a/chain/store/index_test.go b/chain/store/index_test.go new file mode 100644 index 000000000..63a1abad0 --- /dev/null +++ b/chain/store/index_test.go @@ -0,0 +1,88 @@ +// stm: #unit +package store_test + +import ( + "bytes" + "context" + "testing" + + "github.com/ipfs/go-datastore" + syncds "github.com/ipfs/go-datastore/sync" + "github.com/stretchr/testify/assert" + + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/chain/consensus/filcns" + "github.com/filecoin-project/lotus/chain/gen" + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/chain/types/mock" +) + +func TestIndexSeeks(t *testing.T) { + //stm: @CHAIN_STORE_IMPORT_001 + //stm: @CHAIN_STORE_GET_TIPSET_BY_HEIGHT_001, @CHAIN_STORE_PUT_TIPSET_001, @CHAIN_STORE_SET_GENESIS_BLOCK_001 + //stm: @CHAIN_STORE_CLOSE_001 + cg, err := gen.NewGenerator() + if err != nil { + t.Fatal(err) + } + + gencar, err := cg.GenesisCar() + if err != nil { + t.Fatal(err) + } + + gen := cg.Genesis() + + ctx := context.TODO() + + nbs := blockstore.NewMemorySync() + cs := store.NewChainStore(nbs, nbs, syncds.MutexWrap(datastore.NewMapDatastore()), filcns.Weight, nil) + defer cs.Close() //nolint:errcheck + + _, err = cs.Import(ctx, bytes.NewReader(gencar)) + if err != nil { + t.Fatal(err) + } + + cur := mock.TipSet(gen) + if err := cs.PutTipSet(ctx, mock.TipSet(gen)); err != nil { + t.Fatal(err) + } + assert.NoError(t, cs.SetGenesis(ctx, gen)) + + // Put 113 blocks from genesis + for i := 0; i < 113; i++ { + nextts := mock.TipSet(mock.MkBlock(cur, 1, 1)) + + if err := cs.PutTipSet(ctx, nextts); err != nil { + t.Fatal(err) + } + cur = nextts + } + + // Put 50 null epochs + 1 block + skip := mock.MkBlock(cur, 1, 1) + skip.Height += 50 + + skipts := mock.TipSet(skip) + + if err := cs.PutTipSet(ctx, skipts); err != nil { + t.Fatal(err) + } + + ts, err := cs.GetTipsetByHeight(ctx, skip.Height-10, skipts, false) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, abi.ChainEpoch(164), ts.Height()) + + for i := 0; i <= 113; i++ { + ts3, err := cs.GetTipsetByHeight(ctx, abi.ChainEpoch(i), skipts, false) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, abi.ChainEpoch(i), ts3.Height()) + } +} diff --git a/chain/store/messages.go b/chain/store/messages.go new file mode 100644 index 000000000..3686f74f4 --- /dev/null +++ b/chain/store/messages.go @@ -0,0 +1,357 @@ +package store + +import ( + "context" + + block "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + cbor "github.com/ipfs/go-ipld-cbor" + ipld "github.com/ipfs/go-ipld-format" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" + + bstore "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/state" + "github.com/filecoin-project/lotus/chain/types" +) + +type storable interface { + ToStorageBlock() (block.Block, error) +} + +func PutMessage(ctx context.Context, bs bstore.Blockstore, m storable) (cid.Cid, error) { + b, err := m.ToStorageBlock() + if err != nil { + return cid.Undef, err + } + + if err := bs.Put(ctx, b); err != nil { + return cid.Undef, err + } + + return b.Cid(), nil +} + +func (cs *ChainStore) PutMessage(ctx context.Context, m storable) (cid.Cid, error) { + return PutMessage(ctx, cs.chainBlockstore, m) +} + +func (cs *ChainStore) GetCMessage(ctx context.Context, c cid.Cid) (types.ChainMsg, error) { + m, err := cs.GetMessage(ctx, c) + if err == nil { + return m, nil + } + if !ipld.IsNotFound(err) { + log.Warnf("GetCMessage: unexpected error getting unsigned message: %s", err) + } + + return cs.GetSignedMessage(ctx, c) +} + +func (cs *ChainStore) GetMessage(ctx context.Context, c cid.Cid) (*types.Message, error) { + var msg *types.Message + err := cs.chainLocalBlockstore.View(ctx, c, func(b []byte) (err error) { + msg, err = types.DecodeMessage(b) + return err + }) + return msg, err +} + +func (cs *ChainStore) GetSignedMessage(ctx context.Context, c cid.Cid) (*types.SignedMessage, error) { + var msg *types.SignedMessage + err := cs.chainLocalBlockstore.View(ctx, c, func(b []byte) (err error) { + msg, err = types.DecodeSignedMessage(b) + return err + }) + return msg, err +} + +func (cs *ChainStore) readAMTCids(root cid.Cid) ([]cid.Cid, error) { + ctx := context.TODO() + // block headers use adt0, for now. + a, err := blockadt.AsArray(cs.ActorStore(ctx), root) + if err != nil { + return nil, xerrors.Errorf("amt load: %w", err) + } + + var ( + cids []cid.Cid + cborCid cbg.CborCid + ) + if err := a.ForEach(&cborCid, func(i int64) error { + c := cid.Cid(cborCid) + cids = append(cids, c) + return nil + }); err != nil { + return nil, xerrors.Errorf("failed to traverse amt: %w", err) + } + + if uint64(len(cids)) != a.Length() { + return nil, xerrors.Errorf("found %d cids, expected %d", len(cids), a.Length()) + } + + return cids, nil +} + +type BlockMessages struct { + Miner address.Address + BlsMessages []types.ChainMsg + SecpkMessages []types.ChainMsg +} + +func (cs *ChainStore) BlockMsgsForTipset(ctx context.Context, ts *types.TipSet) ([]BlockMessages, error) { + // returned BlockMessages match block order in tipset + + applied := make(map[address.Address]uint64) + + cst := cbor.NewCborStore(cs.stateBlockstore) + st, err := state.LoadStateTree(cst, ts.Blocks()[0].ParentStateRoot) + if err != nil { + return nil, xerrors.Errorf("failed to load state tree at tipset %s: %w", ts, err) + } + + useIds := false + selectMsg := func(m *types.Message) (bool, error) { + var sender address.Address + if ts.Height() >= build.UpgradeHyperdriveHeight { + if useIds { + sender, err = st.LookupID(m.From) + if err != nil { + return false, xerrors.Errorf("failed to resolve sender: %w", err) + } + } else { + if m.From.Protocol() != address.ID { + // we haven't been told to use IDs, just use the robust addr + sender = m.From + } else { + // uh-oh, we actually have an ID-sender! + useIds = true + for robust, nonce := range applied { + resolved, err := st.LookupID(robust) + if err != nil { + return false, xerrors.Errorf("failed to resolve sender: %w", err) + } + applied[resolved] = nonce + } + + sender, err = st.LookupID(m.From) + if err != nil { + return false, xerrors.Errorf("failed to resolve sender: %w", err) + } + } + } + } else { + sender = m.From + } + + // The first match for a sender is guaranteed to have correct nonce -- the block isn't valid otherwise + if _, ok := applied[sender]; !ok { + applied[sender] = m.Nonce + } + + if applied[sender] != m.Nonce { + return false, nil + } + + applied[sender]++ + + return true, nil + } + + var out []BlockMessages + for _, b := range ts.Blocks() { + + bms, sms, err := cs.MessagesForBlock(ctx, b) + if err != nil { + return nil, xerrors.Errorf("failed to get messages for block: %w", err) + } + + bm := BlockMessages{ + Miner: b.Miner, + BlsMessages: make([]types.ChainMsg, 0, len(bms)), + SecpkMessages: make([]types.ChainMsg, 0, len(sms)), + } + + for _, bmsg := range bms { + b, err := selectMsg(bmsg.VMMessage()) + if err != nil { + return nil, xerrors.Errorf("failed to decide whether to select message for block: %w", err) + } + + if b { + bm.BlsMessages = append(bm.BlsMessages, bmsg) + } + } + + for _, smsg := range sms { + b, err := selectMsg(smsg.VMMessage()) + if err != nil { + return nil, xerrors.Errorf("failed to decide whether to select message for block: %w", err) + } + + if b { + bm.SecpkMessages = append(bm.SecpkMessages, smsg) + } + } + + out = append(out, bm) + } + + return out, nil +} + +func (cs *ChainStore) MessagesForTipset(ctx context.Context, ts *types.TipSet) ([]types.ChainMsg, error) { + bmsgs, err := cs.BlockMsgsForTipset(ctx, ts) + if err != nil { + return nil, err + } + + var out []types.ChainMsg + for _, bm := range bmsgs { + for _, blsm := range bm.BlsMessages { + out = append(out, blsm) + } + + for _, secm := range bm.SecpkMessages { + out = append(out, secm) + } + } + + return out, nil +} + +type mmCids struct { + bls []cid.Cid + secpk []cid.Cid +} + +func (cs *ChainStore) ReadMsgMetaCids(ctx context.Context, mmc cid.Cid) ([]cid.Cid, []cid.Cid, error) { + if mmcids, ok := cs.mmCache.Get(mmc); ok { + return mmcids.bls, mmcids.secpk, nil + } + + cst := cbor.NewCborStore(cs.chainLocalBlockstore) + var msgmeta types.MsgMeta + if err := cst.Get(ctx, mmc, &msgmeta); err != nil { + return nil, nil, xerrors.Errorf("failed to load msgmeta (%s): %w", mmc, err) + } + + blscids, err := cs.readAMTCids(msgmeta.BlsMessages) + if err != nil { + return nil, nil, xerrors.Errorf("loading bls message cids for block: %w", err) + } + + secpkcids, err := cs.readAMTCids(msgmeta.SecpkMessages) + if err != nil { + return nil, nil, xerrors.Errorf("loading secpk message cids for block: %w", err) + } + + cs.mmCache.Add(mmc, mmCids{ + bls: blscids, + secpk: secpkcids, + }) + + return blscids, secpkcids, nil +} + +func (cs *ChainStore) ReadReceipts(ctx context.Context, root cid.Cid) ([]types.MessageReceipt, error) { + a, err := blockadt.AsArray(cs.ActorStore(ctx), root) + if err != nil { + return nil, err + } + + receipts := make([]types.MessageReceipt, 0, a.Length()) + var rcpt types.MessageReceipt + if err := a.ForEach(&rcpt, func(i int64) error { + if int64(len(receipts)) != i { + return xerrors.Errorf("missing receipt %d", i) + } + receipts = append(receipts, rcpt) + return nil + }); err != nil { + return nil, err + } + return receipts, nil +} + +func (cs *ChainStore) MessagesForBlock(ctx context.Context, b *types.BlockHeader) ([]*types.Message, []*types.SignedMessage, error) { + blscids, secpkcids, err := cs.ReadMsgMetaCids(ctx, b.Messages) + if err != nil { + return nil, nil, err + } + + blsmsgs, err := cs.LoadMessagesFromCids(ctx, blscids) + if err != nil { + return nil, nil, xerrors.Errorf("loading bls messages for block: %w", err) + } + + secpkmsgs, err := cs.LoadSignedMessagesFromCids(ctx, secpkcids) + if err != nil { + return nil, nil, xerrors.Errorf("loading secpk messages for block: %w", err) + } + + return blsmsgs, secpkmsgs, nil +} + +func (cs *ChainStore) SecpkMessagesForBlock(ctx context.Context, b *types.BlockHeader) ([]*types.SignedMessage, error) { + _, secpkcids, err := cs.ReadMsgMetaCids(ctx, b.Messages) + if err != nil { + return nil, err + } + + secpkmsgs, err := cs.LoadSignedMessagesFromCids(ctx, secpkcids) + if err != nil { + return nil, xerrors.Errorf("loading secpk messages for block: %w", err) + } + + return secpkmsgs, nil +} + +func (cs *ChainStore) GetParentReceipt(ctx context.Context, b *types.BlockHeader, i int) (*types.MessageReceipt, error) { + // block headers use adt0, for now. + a, err := blockadt.AsArray(cs.ActorStore(ctx), b.ParentMessageReceipts) + if err != nil { + return nil, xerrors.Errorf("amt load: %w", err) + } + + var r types.MessageReceipt + if found, err := a.Get(uint64(i), &r); err != nil { + return nil, err + } else if !found { + return nil, xerrors.Errorf("failed to find receipt %d", i) + } + + return &r, nil +} + +func (cs *ChainStore) LoadMessagesFromCids(ctx context.Context, cids []cid.Cid) ([]*types.Message, error) { + msgs := make([]*types.Message, 0, len(cids)) + for i, c := range cids { + m, err := cs.GetMessage(ctx, c) + if err != nil { + return nil, xerrors.Errorf("failed to get message: (%s):%d: %w", c, i, err) + } + + msgs = append(msgs, m) + } + + return msgs, nil +} + +func (cs *ChainStore) LoadSignedMessagesFromCids(ctx context.Context, cids []cid.Cid) ([]*types.SignedMessage, error) { + msgs := make([]*types.SignedMessage, 0, len(cids)) + for i, c := range cids { + m, err := cs.GetSignedMessage(ctx, c) + if err != nil { + return nil, xerrors.Errorf("failed to get message: (%s):%d: %w", c, i, err) + } + + msgs = append(msgs, m) + } + + return msgs, nil +} diff --git a/chain/store/snapshot.go b/chain/store/snapshot.go new file mode 100644 index 000000000..92bc238a6 --- /dev/null +++ b/chain/store/snapshot.go @@ -0,0 +1,758 @@ +package store + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "sync" + "time" + + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + format "github.com/ipfs/go-ipld-format" + "github.com/ipld/go-car" + carutil "github.com/ipld/go-car/util" + carv2 "github.com/ipld/go-car/v2" + mh "github.com/multiformats/go-multihash" + cbg "github.com/whyrusleeping/cbor-gen" + "go.uber.org/atomic" + "golang.org/x/sync/errgroup" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-state-types/abi" + + bstore "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/types" +) + +const TipsetkeyBackfillRange = 2 * build.Finality + +func (cs *ChainStore) UnionStore() bstore.Blockstore { + return bstore.Union(cs.stateBlockstore, cs.chainBlockstore) +} + +func (cs *ChainStore) Export(ctx context.Context, ts *types.TipSet, inclRecentRoots abi.ChainEpoch, skipOldMsgs bool, w io.Writer) error { + h := &car.CarHeader{ + Roots: ts.Cids(), + Version: 1, + } + + if err := car.WriteHeader(h, w); err != nil { + return xerrors.Errorf("failed to write car header: %s", err) + } + + unionBs := cs.UnionStore() + return cs.WalkSnapshot(ctx, ts, inclRecentRoots, skipOldMsgs, true, func(c cid.Cid) error { + blk, err := unionBs.Get(ctx, c) + if err != nil { + return xerrors.Errorf("writing object to car, bs.Get: %w", err) + } + + if err := carutil.LdWrite(w, c.Bytes(), blk.RawData()); err != nil { + return xerrors.Errorf("failed to write block to car output: %w", err) + } + + return nil + }) +} + +func (cs *ChainStore) Import(ctx context.Context, r io.Reader) (*types.TipSet, error) { + // TODO: writing only to the state blockstore is incorrect. + // At this time, both the state and chain blockstores are backed by the + // universal store. When we physically segregate the stores, we will need + // to route state objects to the state blockstore, and chain objects to + // the chain blockstore. + + br, err := carv2.NewBlockReader(r) + if err != nil { + return nil, xerrors.Errorf("loadcar failed: %w", err) + } + + s := cs.StateBlockstore() + + parallelPuts := 5 + putThrottle := make(chan error, parallelPuts) + for i := 0; i < parallelPuts; i++ { + putThrottle <- nil + } + + var buf []blocks.Block + for { + blk, err := br.Next() + if err != nil { + if err == io.EOF { + if len(buf) > 0 { + if err := s.PutMany(ctx, buf); err != nil { + return nil, err + } + } + + break + } + return nil, err + } + + buf = append(buf, blk) + + if len(buf) > 1000 { + if lastErr := <-putThrottle; lastErr != nil { // consume one error to have the right to add one + return nil, lastErr + } + + go func(buf []blocks.Block) { + putThrottle <- s.PutMany(ctx, buf) + }(buf) + buf = nil + } + } + + // check errors + for i := 0; i < parallelPuts; i++ { + if lastErr := <-putThrottle; lastErr != nil { + return nil, lastErr + } + } + + root, err := cs.LoadTipSet(ctx, types.NewTipSetKey(br.Roots...)) + if err != nil { + return nil, xerrors.Errorf("failed to load root tipset from chainfile: %w", err) + } + + ts := root + tssToPersist := make([]*types.TipSet, 0, TipsetkeyBackfillRange) + for i := 0; i < int(TipsetkeyBackfillRange); i++ { + tssToPersist = append(tssToPersist, ts) + parentTsKey := ts.Parents() + ts, err = cs.LoadTipSet(ctx, parentTsKey) + if ts == nil || err != nil { + log.Warnf("Only able to load the last %d tipsets", i) + break + } + } + + if err := cs.PersistTipsets(ctx, tssToPersist); err != nil { + return nil, xerrors.Errorf("failed to persist tipsets: %w", err) + } + + return root, nil +} + +type walkSchedTaskType int + +const ( + finishTask walkSchedTaskType = -1 + blockTask walkSchedTaskType = iota + messageTask + receiptTask + stateTask + dagTask +) + +func (t walkSchedTaskType) String() string { + switch t { + case finishTask: + return "finish" + case blockTask: + return "block" + case messageTask: + return "message" + case receiptTask: + return "receipt" + case stateTask: + return "state" + case dagTask: + return "dag" + } + panic(fmt.Sprintf("unknow task %d", t)) +} + +type walkTask struct { + c cid.Cid + taskType walkSchedTaskType + topLevelTaskType walkSchedTaskType + blockCid cid.Cid + epoch abi.ChainEpoch +} + +// an ever growing FIFO +type taskFifo struct { + in chan walkTask + out chan walkTask + fifo []walkTask +} + +type taskResult struct { + c cid.Cid + b blocks.Block +} + +func newTaskFifo(bufferLen int) *taskFifo { + f := taskFifo{ + in: make(chan walkTask, bufferLen), + out: make(chan walkTask, bufferLen), + fifo: make([]walkTask, 0), + } + + go f.run() + + return &f +} + +func (f *taskFifo) Close() error { + close(f.in) + return nil +} + +func (f *taskFifo) run() { + for { + if len(f.fifo) > 0 { + // we have items in slice + // try to put next out or read something in. + // blocks if nothing works. + next := f.fifo[0] + select { + case f.out <- next: + f.fifo = f.fifo[1:] + case elem, ok := <-f.in: + if !ok { + // drain and close out. + for _, elem := range f.fifo { + f.out <- elem + } + close(f.out) + return + } + f.fifo = append(f.fifo, elem) + } + } else { + // no elements in fifo to put out. + // Try to read in and block. + // When done, try to put out or add to fifo. + select { + case elem, ok := <-f.in: + if !ok { + close(f.out) + return + } + select { + case f.out <- elem: + default: + f.fifo = append(f.fifo, elem) + } + } + } + } +} + +type walkSchedulerConfig struct { + numWorkers int + + head *types.TipSet // Tipset to start walking from. + tail *types.TipSet // Tipset to end at. + includeMessages bool + includeReceipts bool + includeState bool +} + +type walkScheduler struct { + ctx context.Context + cancel context.CancelFunc + + store bstore.Blockstore + cfg walkSchedulerConfig + writer io.Writer + + workerTasks *taskFifo + totalTasks atomic.Int64 + results chan taskResult + writeErrorChan chan error + + // tracks number of inflight tasks + //taskWg sync.WaitGroup + // launches workers and collects errors if any occur + workers *errgroup.Group + // set of CIDs already exported + seen sync.Map +} + +func newWalkScheduler(ctx context.Context, store bstore.Blockstore, cfg walkSchedulerConfig, w io.Writer) (*walkScheduler, error) { + ctx, cancel := context.WithCancel(ctx) + workers, ctx := errgroup.WithContext(ctx) + s := &walkScheduler{ + ctx: ctx, + cancel: cancel, + store: store, + cfg: cfg, + writer: w, + results: make(chan taskResult, cfg.numWorkers*64), + workerTasks: newTaskFifo(cfg.numWorkers * 64), + writeErrorChan: make(chan error, 1), + workers: workers, + } + + go func() { + defer close(s.writeErrorChan) + for r := range s.results { + // Write + if err := carutil.LdWrite(s.writer, r.c.Bytes(), r.b.RawData()); err != nil { + // abort operations + cancel() + s.writeErrorChan <- err + } + } + }() + + // workers + for i := 0; i < cfg.numWorkers; i++ { + f := func(n int) func() error { + return func() error { + return s.workerFunc(n) + } + }(i) + s.workers.Go(f) + } + + s.totalTasks.Add(int64(len(cfg.head.Blocks()))) + for _, b := range cfg.head.Blocks() { + select { + case <-ctx.Done(): + log.Errorw("context done while sending root tasks", ctx.Err()) + cancel() // kill workers + return nil, ctx.Err() + case s.workerTasks.in <- walkTask{ + c: b.Cid(), + taskType: blockTask, + topLevelTaskType: blockTask, + blockCid: b.Cid(), + epoch: cfg.head.Height(), + }: + } + } + + return s, nil +} + +func (s *walkScheduler) Wait() error { + err := s.workers.Wait() + // all workers done. One would have reached genesis and notified the + // rest to exit. Yet, there might be some pending tasks in the queue, + // so we need to run a "single worker". + if err != nil { + log.Errorw("export workers finished with error", "error", err) + } + + for { + if n := s.totalTasks.Load(); n == 0 { + break // finally fully done + } + select { + case task := <-s.workerTasks.out: + s.totalTasks.Add(-1) + if err != nil { + continue // just drain if errors happened. + } + err = s.processTask(task, 0) + } + } + close(s.results) + errWrite := <-s.writeErrorChan + if errWrite != nil { + log.Errorw("error writing to CAR file", "error", err) + return errWrite + } + s.workerTasks.Close() //nolint:errcheck + return err +} + +func (s *walkScheduler) enqueueIfNew(task walkTask) { + if task.c.Prefix().MhType == mh.IDENTITY { + //log.Infow("ignored", "cid", todo.c.String()) + return + } + + // This lets through RAW and CBOR blocks, the only two types that we + // end up writing to the exported CAR. + if task.c.Prefix().Codec != cid.Raw && task.c.Prefix().Codec != cid.DagCBOR { + //log.Infow("ignored", "cid", todo.c.String()) + return + } + if _, loaded := s.seen.LoadOrStore(task.c, struct{}{}); loaded { + // we already had it on the map + return + } + + log.Debugw("enqueue", "type", task.taskType.String(), "cid", task.c.String()) + s.totalTasks.Add(1) + s.workerTasks.in <- task +} + +func (s *walkScheduler) sendFinish(workerN int) error { + log.Infow("worker finished work", "worker", workerN) + s.totalTasks.Add(1) + s.workerTasks.in <- walkTask{ + taskType: finishTask, + } + return nil +} + +func (s *walkScheduler) workerFunc(workerN int) error { + log.Infow("starting worker", "worker", workerN) + for t := range s.workerTasks.out { + s.totalTasks.Add(-1) + select { + case <-s.ctx.Done(): + return s.ctx.Err() + default: + // A worker reached genesis, so we wind down and let others do + // the same. Exit. + if t.taskType == finishTask { + return s.sendFinish(workerN) + } + } + + err := s.processTask(t, workerN) + if err != nil { + return err + } + // continue + } + return nil +} + +func (s *walkScheduler) processTask(t walkTask, workerN int) error { + if t.taskType == finishTask { + return nil + } + + blk, err := s.store.Get(s.ctx, t.c) + if errors.Is(err, format.ErrNotFound{}) && t.topLevelTaskType == receiptTask { + log.Debugw("ignoring not-found block in Receipts", + "block", t.blockCid, + "epoch", t.epoch, + "cid", t.c) + return nil + } + if err != nil { + return xerrors.Errorf( + "blockstore.Get(%s). Task: %s. Block: %s (%s). Epoch: %d. Err: %w", + t.c, t.taskType, t.topLevelTaskType, t.blockCid, t.epoch, err) + } + + s.results <- taskResult{ + c: t.c, + b: blk, + } + + // We exported the ipld block. If it wasn't a CBOR block, there's nothing + // else to do and we can bail out early as it won't have any links + // etc. + if t.c.Prefix().Codec != cid.DagCBOR || t.c.Prefix().MhType == mh.IDENTITY { + return nil + } + + rawData := blk.RawData() + + // extract relevant dags to walk from the block + if t.taskType == blockTask { + var b types.BlockHeader + if err := b.UnmarshalCBOR(bytes.NewBuffer(rawData)); err != nil { + return xerrors.Errorf("unmarshalling block header (cid=%s): %w", blk, err) + } + if b.Height%1_000 == 0 { + log.Infow("block export", "height", b.Height) + } + if b.Height == 0 { + log.Info("exporting genesis block") + for i := range b.Parents { + s.enqueueIfNew(walkTask{ + c: b.Parents[i], + taskType: dagTask, + topLevelTaskType: blockTask, + blockCid: b.Parents[i], + epoch: 0, + }) + } + s.enqueueIfNew(walkTask{ + c: b.ParentStateRoot, + taskType: stateTask, + topLevelTaskType: stateTask, + blockCid: t.c, + epoch: 0, + }) + + return s.sendFinish(workerN) + } + // enqueue block parents + for i := range b.Parents { + s.enqueueIfNew(walkTask{ + c: b.Parents[i], + taskType: blockTask, + topLevelTaskType: blockTask, + blockCid: b.Parents[i], + epoch: b.Height, + }) + } + if s.cfg.tail.Height() >= b.Height { + log.Debugw("tail reached: only blocks will be exported from now until genesis", "cid", t.c.String()) + return nil + } + + if s.cfg.includeMessages { + // enqueue block messages + s.enqueueIfNew(walkTask{ + c: b.Messages, + taskType: messageTask, + topLevelTaskType: messageTask, + blockCid: t.c, + epoch: b.Height, + }) + } + if s.cfg.includeReceipts { + // enqueue block receipts + s.enqueueIfNew(walkTask{ + c: b.ParentMessageReceipts, + taskType: receiptTask, + topLevelTaskType: receiptTask, + blockCid: t.c, + epoch: b.Height, + }) + } + if s.cfg.includeState { + s.enqueueIfNew(walkTask{ + c: b.ParentStateRoot, + taskType: stateTask, + topLevelTaskType: stateTask, + blockCid: t.c, + epoch: b.Height, + }) + } + + return nil + } + + // Not a chain-block: we scan for CIDs in the raw block-data + err = cbg.ScanForLinks(bytes.NewReader(rawData), func(c cid.Cid) { + s.enqueueIfNew(walkTask{ + c: c, + taskType: dagTask, + topLevelTaskType: t.topLevelTaskType, + blockCid: t.blockCid, + epoch: t.epoch, + }) + }) + + if err != nil { + return xerrors.Errorf( + "ScanForLinks(%s). Task: %s. Block: %s (%s). Epoch: %d. Err: %w", + t.c, t.taskType, t.topLevelTaskType, t.blockCid, t.epoch, err) + } + return nil +} + +func (cs *ChainStore) ExportRange( + ctx context.Context, + w io.Writer, + head, tail *types.TipSet, + messages, receipts, stateroots bool, + workers int) error { + + h := &car.CarHeader{ + Roots: head.Cids(), + Version: 1, + } + + if err := car.WriteHeader(h, w); err != nil { + return xerrors.Errorf("failed to write car header: %s", err) + } + + start := time.Now() + log.Infow("walking snapshot range", + "head", head.Key(), + "tail", tail.Key(), + "messages", messages, + "receipts", receipts, + "stateroots", + stateroots, + "workers", workers) + + cfg := walkSchedulerConfig{ + numWorkers: workers, + head: head, + tail: tail, + includeMessages: messages, + includeState: stateroots, + includeReceipts: receipts, + } + + pw, err := newWalkScheduler(ctx, cs.UnionStore(), cfg, w) + if err != nil { + return err + } + + // wait until all workers are done. + err = pw.Wait() + if err != nil { + log.Errorw("walker scheduler", "error", err) + return err + } + + log.Infow("walking snapshot range complete", "duration", time.Since(start), "success", err == nil) + return nil +} + +func (cs *ChainStore) WalkSnapshot(ctx context.Context, ts *types.TipSet, inclRecentRoots abi.ChainEpoch, skipOldMsgs, skipMsgReceipts bool, cb func(cid.Cid) error) error { + if ts == nil { + ts = cs.GetHeaviestTipSet() + } + + seen := cid.NewSet() + walked := cid.NewSet() + + blocksToWalk := ts.Cids() + currentMinHeight := ts.Height() + + walkChain := func(blk cid.Cid) error { + if !seen.Visit(blk) { + return nil + } + + if err := cb(blk); err != nil { + return err + } + + data, err := cs.chainBlockstore.Get(ctx, blk) + if err != nil { + return xerrors.Errorf("getting block: %w", err) + } + + var b types.BlockHeader + if err := b.UnmarshalCBOR(bytes.NewBuffer(data.RawData())); err != nil { + return xerrors.Errorf("unmarshaling block header (cid=%s): %w", blk, err) + } + + if currentMinHeight > b.Height { + currentMinHeight = b.Height + if currentMinHeight%builtin.EpochsInDay == 0 { + log.Infow("export", "height", currentMinHeight) + } + } + + var cids []cid.Cid + if !skipOldMsgs || b.Height > ts.Height()-inclRecentRoots { + if walked.Visit(b.Messages) { + mcids, err := recurseLinks(ctx, cs.chainBlockstore, walked, b.Messages, []cid.Cid{b.Messages}) + if err != nil { + return xerrors.Errorf("recursing messages failed: %w", err) + } + cids = mcids + } + } + + if b.Height > 0 { + for _, p := range b.Parents { + blocksToWalk = append(blocksToWalk, p) + } + } else { + // include the genesis block + cids = append(cids, b.Parents...) + } + + out := cids + + if b.Height == 0 || b.Height > ts.Height()-inclRecentRoots { + if walked.Visit(b.ParentStateRoot) { + cids, err := recurseLinks(ctx, cs.stateBlockstore, walked, b.ParentStateRoot, []cid.Cid{b.ParentStateRoot}) + if err != nil { + return xerrors.Errorf("recursing genesis state failed: %w", err) + } + + out = append(out, cids...) + } + + if !skipMsgReceipts && walked.Visit(b.ParentMessageReceipts) { + out = append(out, b.ParentMessageReceipts) + } + } + + for _, c := range out { + if seen.Visit(c) { + prefix := c.Prefix() + + // Don't include identity CIDs. + if prefix.MhType == mh.IDENTITY { + continue + } + + // We only include raw and dagcbor, for now. + // Raw for "code" CIDs. + switch prefix.Codec { + case cid.Raw, cid.DagCBOR: + default: + continue + } + + if err := cb(c); err != nil { + return err + } + + } + } + + return nil + } + + log.Infow("export started") + exportStart := build.Clock.Now() + + for len(blocksToWalk) > 0 { + next := blocksToWalk[0] + blocksToWalk = blocksToWalk[1:] + if err := walkChain(next); err != nil { + return xerrors.Errorf("walk chain failed: %w", err) + } + } + + log.Infow("export finished", "duration", build.Clock.Now().Sub(exportStart).Seconds()) + + return nil +} + +func recurseLinks(ctx context.Context, bs bstore.Blockstore, walked *cid.Set, root cid.Cid, in []cid.Cid) ([]cid.Cid, error) { + if root.Prefix().Codec != cid.DagCBOR { + return in, nil + } + + data, err := bs.Get(ctx, root) + if err != nil { + return nil, xerrors.Errorf("recurse links get (%s) failed: %w", root, err) + } + + var rerr error + err = cbg.ScanForLinks(bytes.NewReader(data.RawData()), func(c cid.Cid) { + if rerr != nil { + // No error return on ScanForLinks :( + return + } + + // traversed this already... + if !walked.Visit(c) { + return + } + + in = append(in, c) + var err error + in, err = recurseLinks(ctx, bs, walked, c, in) + if err != nil { + rerr = err + } + }) + if err != nil { + return nil, xerrors.Errorf("scanning for links failed: %w", err) + } + + return in, rerr +} diff --git a/chain/store/store.go b/chain/store/store.go index 1d0668032..88103ac48 100644 --- a/chain/store/store.go +++ b/chain/store/store.go @@ -3,58 +3,111 @@ package store import ( "bytes" "context" - "encoding/binary" "encoding/json" - "io" + "errors" "os" + "strconv" + "strings" "sync" + "time" - "github.com/filecoin-project/specs-actors/actors/crypto" - "github.com/minio/blake2b-simd" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/runtime" - "github.com/filecoin-project/specs-actors/actors/util/adt" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/state" - "github.com/filecoin-project/lotus/chain/vm" - "github.com/filecoin-project/lotus/metrics" - "go.opencensus.io/stats" - "go.opencensus.io/trace" - "go.uber.org/multierr" - - amt "github.com/filecoin-project/go-amt-ipld/v2" - - "github.com/filecoin-project/lotus/chain/types" - - lru "github.com/hashicorp/golang-lru" + lru "github.com/hashicorp/golang-lru/v2" block "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" dstore "github.com/ipfs/go-datastore" - blockstore "github.com/ipfs/go-ipfs-blockstore" - bstore "github.com/ipfs/go-ipfs-blockstore" + "github.com/ipfs/go-datastore/query" cbor "github.com/ipfs/go-ipld-cbor" logging "github.com/ipfs/go-log/v2" - car "github.com/ipld/go-car" - carutil "github.com/ipld/go-car/util" - cbg "github.com/whyrusleeping/cbor-gen" - pubsub "github.com/whyrusleeping/pubsub" + "go.opencensus.io/stats" + "go.opencensus.io/trace" + "go.uber.org/multierr" + "golang.org/x/sync/errgroup" "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/pubsub" + + "github.com/filecoin-project/lotus/api" + bstore "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/journal" + "github.com/filecoin-project/lotus/metrics" ) var log = logging.Logger("chainstore") -var chainHeadKey = dstore.NewKey("head") +var ( + chainHeadKey = dstore.NewKey("head") + checkpointKey = dstore.NewKey("/chain/checks") + blockValidationCacheKeyPrefix = dstore.NewKey("blockValidation") +) +var DefaultTipSetCacheSize = 8192 +var DefaultMsgMetaCacheSize = 2048 + +var ErrNotifeeDone = errors.New("notifee is done and should be removed") + +func init() { + if s := os.Getenv("LOTUS_CHAIN_TIPSET_CACHE"); s != "" { + tscs, err := strconv.Atoi(s) + if err != nil { + log.Errorf("failed to parse 'LOTUS_CHAIN_TIPSET_CACHE' env var: %s", err) + } + DefaultTipSetCacheSize = tscs + } + + if s := os.Getenv("LOTUS_CHAIN_MSGMETA_CACHE"); s != "" { + mmcs, err := strconv.Atoi(s) + if err != nil { + log.Errorf("failed to parse 'LOTUS_CHAIN_MSGMETA_CACHE' env var: %s", err) + } + DefaultMsgMetaCacheSize = mmcs + } +} + +// ReorgNotifee represents a callback that gets called upon reorgs. +type ReorgNotifee = func(rev, app []*types.TipSet) error + +// Journal event types. +const ( + evtTypeHeadChange = iota +) + +type HeadChangeEvt struct { + From types.TipSetKey + FromHeight abi.ChainEpoch + To types.TipSetKey + ToHeight abi.ChainEpoch + RevertCount int + ApplyCount int +} + +type WeightFunc func(ctx context.Context, stateBs bstore.Blockstore, ts *types.TipSet) (types.BigInt, error) + +// ChainStore is the main point of access to chain data. +// +// Raw chain data is stored in the Blockstore, with relevant markers (genesis, +// latest head tipset references) being tracked in the Datastore (key-value +// store). +// +// To alleviate disk access, the ChainStore has two ARC caches: +// 1. a tipset cache +// 2. a block => messages references cache. type ChainStore struct { - bs bstore.Blockstore - ds dstore.Datastore + chainBlockstore bstore.Blockstore + stateBlockstore bstore.Blockstore + metadataDs dstore.Batching - heaviestLk sync.Mutex + weight WeightFunc + + chainLocalBlockstore bstore.Blockstore + + heaviestLk sync.RWMutex heaviest *types.TipSet + checkpoint *types.TipSet bestTips *pubsub.PubSub pubLk sync.Mutex @@ -62,29 +115,55 @@ type ChainStore struct { tstLk sync.Mutex tipsets map[abi.ChainEpoch][]cid.Cid - reorgCh chan<- reorg - headChangeNotifs []func(rev, app []*types.TipSet) error + cindex *ChainIndex - mmCache *lru.ARCCache - tsCache *lru.ARCCache + reorgCh chan<- reorg + reorgNotifeeCh chan ReorgNotifee - vmcalls runtime.Syscalls + mmCache *lru.ARCCache[cid.Cid, mmCids] + tsCache *lru.ARCCache[types.TipSetKey, *types.TipSet] + + evtTypes [1]journal.EventType + journal journal.Journal + + storeEvents bool + + cancelFn context.CancelFunc + wg sync.WaitGroup } -func NewChainStore(bs bstore.Blockstore, ds dstore.Batching, vmcalls runtime.Syscalls) *ChainStore { - c, _ := lru.NewARC(2048) - tsc, _ := lru.NewARC(4096) - cs := &ChainStore{ - bs: bs, - ds: ds, - bestTips: pubsub.New(64), - tipsets: make(map[abi.ChainEpoch][]cid.Cid), - mmCache: c, - tsCache: tsc, - vmcalls: vmcalls, +func NewChainStore(chainBs bstore.Blockstore, stateBs bstore.Blockstore, ds dstore.Batching, weight WeightFunc, j journal.Journal) *ChainStore { + c, _ := lru.NewARC[cid.Cid, mmCids](DefaultMsgMetaCacheSize) + tsc, _ := lru.NewARC[types.TipSetKey, *types.TipSet](DefaultTipSetCacheSize) + if j == nil { + j = journal.NilJournal() } - cs.reorgCh = cs.reorgWorker(context.TODO()) + ctx, cancel := context.WithCancel(context.Background()) + // unwraps the fallback store in case one is configured. + // some methods _need_ to operate on a local blockstore only. + localbs, _ := bstore.UnwrapFallbackStore(chainBs) + cs := &ChainStore{ + chainBlockstore: chainBs, + stateBlockstore: stateBs, + chainLocalBlockstore: localbs, + weight: weight, + metadataDs: ds, + bestTips: pubsub.New(64), + tipsets: make(map[abi.ChainEpoch][]cid.Cid), + mmCache: c, + tsCache: tsc, + cancelFn: cancel, + journal: j, + } + + cs.evtTypes = [1]journal.EventType{ + evtTypeHeadChange: j.RegisterEventType("sync", "head_change"), + } + + ci := NewChainIndex(cs.LoadTipSet) + + cs.cindex = ci hcnf := func(rev, app []*types.TipSet) error { cs.pubLk.Lock() @@ -110,20 +189,35 @@ func NewChainStore(bs bstore.Blockstore, ds dstore.Batching, vmcalls runtime.Sys } hcmetric := func(rev, app []*types.TipSet) error { - ctx := context.Background() for _, r := range app { - stats.Record(ctx, metrics.ChainNodeHeight.M(int64(r.Height()))) + stats.Record(context.Background(), metrics.ChainNodeHeight.M(int64(r.Height()))) } return nil } - cs.headChangeNotifs = append(cs.headChangeNotifs, hcnf, hcmetric) + cs.reorgNotifeeCh = make(chan ReorgNotifee) + cs.reorgCh = cs.reorgWorker(ctx, []ReorgNotifee{hcnf, hcmetric}) return cs } -func (cs *ChainStore) Load() error { - head, err := cs.ds.Get(chainHeadKey) +func (cs *ChainStore) Close() error { + cs.cancelFn() + cs.wg.Wait() + return nil +} + +func (cs *ChainStore) Load(ctx context.Context) error { + if err := cs.loadHead(ctx); err != nil { + return err + } + if err := cs.loadCheckpoint(ctx); err != nil { + return err + } + return nil +} +func (cs *ChainStore) loadHead(ctx context.Context) error { + head, err := cs.metadataDs.Get(ctx, chainHeadKey) if err == dstore.ErrNotFound { log.Warn("no previous chain state found") return nil @@ -137,7 +231,7 @@ func (cs *ChainStore) Load() error { return xerrors.Errorf("failed to unmarshal stored chain head: %w", err) } - ts, err := cs.LoadTipSet(types.NewTipSetKey(tscids...)) + ts, err := cs.LoadTipSet(ctx, types.NewTipSetKey(tscids...)) if err != nil { return xerrors.Errorf("loading tipset: %w", err) } @@ -147,13 +241,38 @@ func (cs *ChainStore) Load() error { return nil } -func (cs *ChainStore) writeHead(ts *types.TipSet) error { +func (cs *ChainStore) loadCheckpoint(ctx context.Context) error { + tskBytes, err := cs.metadataDs.Get(ctx, checkpointKey) + if err == dstore.ErrNotFound { + return nil + } + if err != nil { + return xerrors.Errorf("failed to load checkpoint from datastore: %w", err) + } + + var tsk types.TipSetKey + err = json.Unmarshal(tskBytes, &tsk) + if err != nil { + return err + } + + ts, err := cs.LoadTipSet(ctx, tsk) + if err != nil { + return xerrors.Errorf("loading tipset: %w", err) + } + + cs.checkpoint = ts + + return nil +} + +func (cs *ChainStore) writeHead(ctx context.Context, ts *types.TipSet) error { data, err := json.Marshal(ts.Cids()) if err != nil { return xerrors.Errorf("failed to marshal tipset: %w", err) } - if err := cs.ds.Put(chainHeadKey, data); err != nil { + if err := cs.metadataDs.Put(ctx, chainHeadKey, data); err != nil { return xerrors.Errorf("failed to write chain head to datastore: %w", err) } @@ -179,119 +298,33 @@ func (cs *ChainStore) SubHeadChanges(ctx context.Context) chan []*api.HeadChange }} go func() { - defer close(out) - var unsubOnce sync.Once + defer func() { + // Tell the caller we're done first, the following may block for a bit. + close(out) + + // Unsubscribe. + cs.bestTips.Unsub(subch) + + // Drain the channel. + for range subch { + } + }() for { select { case val, ok := <-subch: if !ok { - log.Warn("chain head sub exit loop") + // Shutting down. return } - if len(out) > 0 { - log.Warnf("head change sub is slow, has %d buffered entries", len(out)) - } select { case out <- val.([]*api.HeadChange): - case <-ctx.Done(): + default: + log.Errorf("closing head change subscription due to slow reader") + return } - case <-ctx.Done(): - unsubOnce.Do(func() { - go cs.bestTips.Unsub(subch) - }) - } - } - }() - return out -} - -func (cs *ChainStore) SubscribeHeadChanges(f func(rev, app []*types.TipSet) error) { - cs.headChangeNotifs = append(cs.headChangeNotifs, f) -} - -func (cs *ChainStore) SetGenesis(b *types.BlockHeader) error { - ts, err := types.NewTipSet([]*types.BlockHeader{b}) - if err != nil { - return err - } - - if err := cs.PutTipSet(context.TODO(), ts); err != nil { - return err - } - - return cs.ds.Put(dstore.NewKey("0"), b.Cid().Bytes()) -} - -func (cs *ChainStore) PutTipSet(ctx context.Context, ts *types.TipSet) error { - for _, b := range ts.Blocks() { - if err := cs.PersistBlockHeaders(b); err != nil { - return err - } - } - - expanded, err := cs.expandTipset(ts.Blocks()[0]) - if err != nil { - return xerrors.Errorf("errored while expanding tipset: %w", err) - } - log.Debugf("expanded %s into %s\n", ts.Cids(), expanded.Cids()) - - if err := cs.MaybeTakeHeavierTipSet(ctx, expanded); err != nil { - return xerrors.Errorf("MaybeTakeHeavierTipSet failed in PutTipSet: %w", err) - } - return nil -} - -func (cs *ChainStore) MaybeTakeHeavierTipSet(ctx context.Context, ts *types.TipSet) error { - cs.heaviestLk.Lock() - defer cs.heaviestLk.Unlock() - w, err := cs.Weight(ctx, ts) - if err != nil { - return err - } - heaviestW, err := cs.Weight(ctx, cs.heaviest) - if err != nil { - return err - } - - if w.GreaterThan(heaviestW) { - // TODO: don't do this for initial sync. Now that we don't have a - // difference between 'bootstrap sync' and 'caught up' sync, we need - // some other heuristic. - return cs.takeHeaviestTipSet(ctx, ts) - } - return nil -} - -type reorg struct { - old *types.TipSet - new *types.TipSet -} - -func (cs *ChainStore) reorgWorker(ctx context.Context) chan<- reorg { - out := make(chan reorg, 32) - go func() { - defer log.Warn("reorgWorker quit") - - for { - select { - case r := <-out: - revert, apply, err := cs.ReorgOps(r.old, r.new) - if err != nil { - log.Error("computing reorg ops failed: ", err) - continue - } - - // reverse the apply array - for i := len(apply)/2 - 1; i >= 0; i-- { - opp := len(apply) - 1 - i - apply[i], apply[opp] = apply[opp], apply[i] - } - - for _, hcf := range cs.headChangeNotifs { - if err := hcf(revert, apply); err != nil { - log.Error("head change func errored (BAD): ", err) - } + if len(out) > 5 { + log.Warnf("head change sub is slow, has %d buffered entries", len(out)) } case <-ctx.Done(): return @@ -301,46 +334,472 @@ func (cs *ChainStore) reorgWorker(ctx context.Context) chan<- reorg { return out } -func (cs *ChainStore) takeHeaviestTipSet(ctx context.Context, ts *types.TipSet) error { - _, span := trace.StartSpan(ctx, "takeHeaviestTipSet") - defer span.End() +func (cs *ChainStore) SubscribeHeadChanges(f ReorgNotifee) { + cs.reorgNotifeeCh <- f +} - if cs.heaviest != nil { // buf - if len(cs.reorgCh) > 0 { - log.Warnf("Reorg channel running behind, %d reorgs buffered", len(cs.reorgCh)) - } - cs.reorgCh <- reorg{ - old: cs.heaviest, - new: ts, - } - } else { - log.Warnf("no heaviest tipset found, using %s", ts.Cids()) - } +func (cs *ChainStore) IsBlockValidated(ctx context.Context, blkid cid.Cid) (bool, error) { + key := blockValidationCacheKeyPrefix.Instance(blkid.String()) - span.AddAttributes(trace.BoolAttribute("newHead", true)) + return cs.metadataDs.Has(ctx, key) +} - log.Infof("New heaviest tipset! %s (height=%d)", ts.Cids(), ts.Height()) - cs.heaviest = ts +func (cs *ChainStore) MarkBlockAsValidated(ctx context.Context, blkid cid.Cid) error { + key := blockValidationCacheKeyPrefix.Instance(blkid.String()) - if err := cs.writeHead(ts); err != nil { - log.Errorf("failed to write chain head: %s", err) - return nil + if err := cs.metadataDs.Put(ctx, key, []byte{0}); err != nil { + return xerrors.Errorf("cache block validation: %w", err) } return nil } -// SetHead sets the chainstores current 'best' head node. -// This should only be called if something is broken and needs fixing -func (cs *ChainStore) SetHead(ts *types.TipSet) error { +func (cs *ChainStore) UnmarkBlockAsValidated(ctx context.Context, blkid cid.Cid) error { + key := blockValidationCacheKeyPrefix.Instance(blkid.String()) + + if err := cs.metadataDs.Delete(ctx, key); err != nil { + return xerrors.Errorf("removing from valid block cache: %w", err) + } + + return nil +} + +func (cs *ChainStore) SetGenesis(ctx context.Context, b *types.BlockHeader) error { + ts, err := types.NewTipSet([]*types.BlockHeader{b}) + if err != nil { + return err + } + + if err := cs.PutTipSet(ctx, ts); err != nil { + return err + } + + return cs.metadataDs.Put(ctx, dstore.NewKey("0"), b.Cid().Bytes()) +} + +func (cs *ChainStore) PutTipSet(ctx context.Context, ts *types.TipSet) error { + if err := cs.PersistTipsets(ctx, []*types.TipSet{ts}); err != nil { + return xerrors.Errorf("failed to persist tipset: %w", err) + } + + expanded, err := cs.expandTipset(ctx, ts.Blocks()[0]) + if err != nil { + return xerrors.Errorf("errored while expanding tipset: %w", err) + } + + if expanded.Key() != ts.Key() { + log.Debugf("expanded %s into %s\n", ts.Cids(), expanded.Cids()) + + tsBlk, err := expanded.Key().ToStorageBlock() + if err != nil { + return xerrors.Errorf("failed to get tipset key block: %w", err) + } + + if err = cs.chainLocalBlockstore.Put(ctx, tsBlk); err != nil { + return xerrors.Errorf("failed to put tipset key block: %w", err) + } + } + + if err := cs.MaybeTakeHeavierTipSet(ctx, expanded); err != nil { + return xerrors.Errorf("MaybeTakeHeavierTipSet failed in PutTipSet: %w", err) + } + return nil +} + +// MaybeTakeHeavierTipSet evaluates the incoming tipset and locks it in our +// internal state as our new head, if and only if it is heavier than the current +// head and does not exceed the maximum fork length. +func (cs *ChainStore) MaybeTakeHeavierTipSet(ctx context.Context, ts *types.TipSet) error { + 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() + + if ts.Equals(cs.heaviest) { + return nil + } + + w, err := cs.weight(ctx, cs.StateBlockstore(), ts) + if err != nil { + return err + } + heaviestW, err := cs.weight(ctx, cs.StateBlockstore(), cs.heaviest) + if err != nil { + return err + } + + heavier := w.GreaterThan(heaviestW) + if w.Equals(heaviestW) && !ts.Equals(cs.heaviest) { + log.Errorw("weight draw", "currTs", cs.heaviest, "ts", ts) + heavier = breakWeightTie(ts, cs.heaviest) + } + + if heavier { + // TODO: don't do this for initial sync. Now that we don't have a + // difference between 'bootstrap sync' and 'caught up' sync, we need + // some other heuristic. + + exceeds, err := cs.exceedsForkLength(ctx, cs.heaviest, ts) + if err != nil { + return err + } + if exceeds { + return nil + } + + return cs.takeHeaviestTipSet(ctx, ts) + } + + return nil +} + +// Check if the two tipsets have a fork length above `ForkLengthThreshold`. +// `synced` is the head of the chain we are currently synced to and `external` +// is the incoming tipset potentially belonging to a forked chain. It assumes +// the external chain has already been validated and available in the ChainStore. +// The "fast forward" case is covered in this logic as a valid fork of length 0. +// +// FIXME: We may want to replace some of the logic in `syncFork()` with this. +// +// `syncFork()` counts the length on both sides of the fork at the moment (we +// need to settle on that) but here we just enforce it on the `synced` side. +func (cs *ChainStore) exceedsForkLength(ctx context.Context, synced, external *types.TipSet) (bool, error) { + if synced == nil || external == nil { + // FIXME: If `cs.heaviest` is nil we should just bypass the entire + // `MaybeTakeHeavierTipSet` logic (instead of each of the called + // functions having to handle the nil case on their own). + return false, nil + } + + var err error + // `forkLength`: number of tipsets we need to walk back from the our `synced` + // chain to the common ancestor with the new `external` head in order to + // adopt the fork. + for forkLength := 0; forkLength < int(build.ForkLengthThreshold); forkLength++ { + // First walk back as many tipsets in the external chain to match the + // `synced` height to compare them. If we go past the `synced` height + // the subsequent match will fail but it will still be useful to get + // closer to the `synced` head parent's height in the next loop. + for external.Height() > synced.Height() { + if external.Height() == 0 { + // We reached the genesis of the external chain without a match; + // this is considered a fork outside the allowed limit (of "infinite" + // length). + return true, nil + } + external, err = cs.LoadTipSet(ctx, external.Parents()) + if err != nil { + return false, xerrors.Errorf("failed to load parent tipset in external chain: %w", err) + } + } + + // Now check if we arrived at the common ancestor. + if synced.Equals(external) { + return false, nil + } + + // Now check to see if we've walked back to the checkpoint. + if synced.Equals(cs.checkpoint) { + return true, nil + } + + // If we didn't, go back *one* tipset on the `synced` side (incrementing + // the `forkLength`). + if synced.Height() == 0 { + // Same check as the `external` side, if we reach the start (genesis) + // there is no common ancestor. + return true, nil + } + synced, err = cs.LoadTipSet(ctx, synced.Parents()) + if err != nil { + return false, xerrors.Errorf("failed to load parent tipset in synced chain: %w", err) + } + } + + // We traversed the fork length allowed without finding a common ancestor. + return true, nil +} + +// ForceHeadSilent forces a chain head tipset without triggering a reorg +// operation. +// +// CAUTION: Use it only for testing, such as to teleport the chain to a +// particular tipset to carry out a benchmark, verification, etc. on a chain +// segment. +func (cs *ChainStore) ForceHeadSilent(ctx context.Context, ts *types.TipSet) error { + log.Warnf("(!!!) forcing a new head silently; new head: %s", ts) + cs.heaviestLk.Lock() defer cs.heaviestLk.Unlock() + if err := cs.removeCheckpoint(ctx); err != nil { + return err + } + cs.heaviest = ts + + err := cs.writeHead(ctx, ts) + if err != nil { + err = xerrors.Errorf("failed to write chain head: %s", err) + } + return err +} + +type reorg struct { + old *types.TipSet + new *types.TipSet +} + +const reorgChBuf = 32 + +func (cs *ChainStore) reorgWorker(ctx context.Context, initialNotifees []ReorgNotifee) chan<- reorg { + out := make(chan reorg, reorgChBuf) + notifees := make([]ReorgNotifee, len(initialNotifees)) + copy(notifees, initialNotifees) + + cs.wg.Add(1) + go func() { + defer cs.wg.Done() + defer log.Warn("reorgWorker quit") + + for { + select { + case n := <-cs.reorgNotifeeCh: + notifees = append(notifees, n) + + case r := <-out: + revert, apply, err := cs.ReorgOps(ctx, r.old, r.new) + if err != nil { + log.Error("computing reorg ops failed: ", err) + continue + } + + cs.journal.RecordEvent(cs.evtTypes[evtTypeHeadChange], func() interface{} { + return HeadChangeEvt{ + From: r.old.Key(), + FromHeight: r.old.Height(), + To: r.new.Key(), + ToHeight: r.new.Height(), + RevertCount: len(revert), + ApplyCount: len(apply), + } + }) + + // reverse the apply array + for i := len(apply)/2 - 1; i >= 0; i-- { + opp := len(apply) - 1 - i + apply[i], apply[opp] = apply[opp], apply[i] + } + + var toremove map[int]struct{} + for i, hcf := range notifees { + err := hcf(revert, apply) + + switch err { + case nil: + + case ErrNotifeeDone: + if toremove == nil { + toremove = make(map[int]struct{}) + } + toremove[i] = struct{}{} + + default: + log.Error("head change func errored (BAD): ", err) + } + } + + if len(toremove) > 0 { + newNotifees := make([]ReorgNotifee, 0, len(notifees)-len(toremove)) + for i, hcf := range notifees { + _, remove := toremove[i] + if remove { + continue + } + newNotifees = append(newNotifees, hcf) + } + notifees = newNotifees + } + + case <-ctx.Done(): + return + } + } + }() + return out +} + +// takeHeaviestTipSet actually sets the incoming tipset as our head both in +// memory and in the ChainStore. It also sends a notification to deliver to +// ReorgNotifees. +func (cs *ChainStore) takeHeaviestTipSet(ctx context.Context, ts *types.TipSet) error { + _, span := trace.StartSpan(ctx, "takeHeaviestTipSet") + defer span.End() + span.AddAttributes(trace.BoolAttribute("newHead", true)) + + log.Infof("New heaviest tipset! %s (height=%d)", ts.Cids(), ts.Height()) + prevHeaviest := cs.heaviest + cs.heaviest = ts + + if err := cs.writeHead(ctx, ts); err != nil { + log.Errorf("failed to write chain head: %s", err) + return err + } + + if prevHeaviest != nil { // buf + if len(cs.reorgCh) > 0 { + log.Warnf("Reorg channel running behind, %d reorgs buffered", len(cs.reorgCh)) + } + cs.reorgCh <- reorg{ + old: prevHeaviest, + new: ts, + } + } else { + log.Warnf("no previous heaviest tipset found, using %s", ts.Cids()) + } + + return nil +} + +// FlushValidationCache removes all results of block validation from the +// chain metadata store. Usually the first step after a new chain import. +func (cs *ChainStore) FlushValidationCache(ctx context.Context) error { + return FlushValidationCache(ctx, cs.metadataDs) +} + +func FlushValidationCache(ctx context.Context, ds dstore.Batching) error { + log.Infof("clearing block validation cache...") + + dsWalk, err := ds.Query(ctx, query.Query{ + // Potential TODO: the validation cache is not a namespace on its own + // but is rather constructed as prefixed-key `foo:bar` via .Instance(), which + // in turn does not work with the filter, which can match only on `foo/bar` + // + // If this is addressed (blockcache goes into its own sub-namespace) then + // strings.HasPrefix(...) below can be skipped + // + // Prefix: blockValidationCacheKeyPrefix.String() + KeysOnly: true, + }) + if err != nil { + return xerrors.Errorf("failed to initialize key listing query: %w", err) + } + + allKeys, err := dsWalk.Rest() + if err != nil { + return xerrors.Errorf("failed to run key listing query: %w", err) + } + + batch, err := ds.Batch(ctx) + if err != nil { + return xerrors.Errorf("failed to open a DS batch: %w", err) + } + + delCnt := 0 + for _, k := range allKeys { + if strings.HasPrefix(k.Key, blockValidationCacheKeyPrefix.String()) { + delCnt++ + batch.Delete(ctx, dstore.RawKey(k.Key)) // nolint:errcheck + } + } + + if err := batch.Commit(ctx); err != nil { + return xerrors.Errorf("failed to commit the DS batch: %w", err) + } + + log.Infof("%d block validation entries cleared.", delCnt) + + return nil +} + +// SetHead sets the chainstores current 'best' head node. +// This should only be called if something is broken and needs fixing. +// +// This function will bypass and remove any checkpoints. +func (cs *ChainStore) SetHead(ctx context.Context, ts *types.TipSet) error { + cs.heaviestLk.Lock() + defer cs.heaviestLk.Unlock() + if err := cs.removeCheckpoint(ctx); err != nil { + return err + } return cs.takeHeaviestTipSet(context.TODO(), ts) } -func (cs *ChainStore) Contains(ts *types.TipSet) (bool, error) { +// RemoveCheckpoint removes the current checkpoint. +func (cs *ChainStore) RemoveCheckpoint(ctx context.Context) error { + cs.heaviestLk.Lock() + defer cs.heaviestLk.Unlock() + return cs.removeCheckpoint(ctx) +} + +func (cs *ChainStore) removeCheckpoint(ctx context.Context) error { + if err := cs.metadataDs.Delete(ctx, checkpointKey); err != nil { + return err + } + cs.checkpoint = nil + return nil +} + +// SetCheckpoint will set a checkpoint past which the chainstore will not allow forks. +// +// NOTE: Checkpoints cannot be set beyond ForkLengthThreshold epochs in the past. +func (cs *ChainStore) SetCheckpoint(ctx context.Context, ts *types.TipSet) error { + tskBytes, err := json.Marshal(ts.Key()) + if err != nil { + return err + } + + cs.heaviestLk.Lock() + defer cs.heaviestLk.Unlock() + + if ts.Height() > cs.heaviest.Height() { + return xerrors.Errorf("cannot set a checkpoint in the future") + } + + // Otherwise, this operation could get _very_ expensive. + if cs.heaviest.Height()-ts.Height() > build.ForkLengthThreshold { + return xerrors.Errorf("cannot set a checkpoint before the fork threshold") + } + + if !ts.Equals(cs.heaviest) { + anc, err := cs.IsAncestorOf(ctx, ts, cs.heaviest) + if err != nil { + return xerrors.Errorf("cannot determine whether checkpoint tipset is in main-chain: %w", err) + } + + if !anc { + return xerrors.Errorf("cannot mark tipset as checkpoint, since it isn't in the main-chain: %w", err) + } + } + err = cs.metadataDs.Put(ctx, checkpointKey, tskBytes) + if err != nil { + return err + } + + cs.checkpoint = ts + return nil +} + +func (cs *ChainStore) GetCheckpoint() *types.TipSet { + cs.heaviestLk.RLock() + chkpt := cs.checkpoint + cs.heaviestLk.RUnlock() + return chkpt +} + +// Contains returns whether our BlockStore has all blocks in the supplied TipSet. +func (cs *ChainStore) Contains(ctx context.Context, ts *types.TipSet) (bool, error) { for _, c := range ts.Cids() { - has, err := cs.bs.Has(c) + has, err := cs.chainBlockstore.Has(ctx, c) if err != nil { return false, err } @@ -352,29 +811,41 @@ func (cs *ChainStore) Contains(ts *types.TipSet) (bool, error) { return true, nil } -func (cs *ChainStore) GetBlock(c cid.Cid) (*types.BlockHeader, error) { - sb, err := cs.bs.Get(c) - if err != nil { - return nil, err - } - - return types.DecodeBlock(sb.RawData()) +// GetBlock fetches a BlockHeader with the supplied CID. It returns +// blockstore.ErrNotFound if the block was not found in the BlockStore. +func (cs *ChainStore) GetBlock(ctx context.Context, c cid.Cid) (*types.BlockHeader, error) { + var blk *types.BlockHeader + err := cs.chainLocalBlockstore.View(ctx, c, func(b []byte) (err error) { + blk, err = types.DecodeBlock(b) + return err + }) + return blk, err } -func (cs *ChainStore) LoadTipSet(tsk types.TipSetKey) (*types.TipSet, error) { - v, ok := cs.tsCache.Get(tsk) - if ok { - return v.(*types.TipSet), nil +func (cs *ChainStore) LoadTipSet(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error) { + if ts, ok := cs.tsCache.Get(tsk); ok { + return ts, nil } - var blks []*types.BlockHeader - for _, c := range tsk.Cids() { - b, err := cs.GetBlock(c) - if err != nil { - return nil, xerrors.Errorf("get block %s: %w", c, err) - } + // Fetch tipset block headers from blockstore in parallel + var eg errgroup.Group + cids := tsk.Cids() + blks := make([]*types.BlockHeader, len(cids)) + for i, c := range cids { + i, c := i, c + eg.Go(func() error { + b, err := cs.GetBlock(ctx, c) + if err != nil { + return xerrors.Errorf("get block %s: %w", c, err) + } - blks = append(blks, b) + blks[i] = b + return nil + }) + } + err := eg.Wait() + if err != nil { + return nil, err } ts, err := types.NewTipSet(blks) @@ -387,15 +858,15 @@ func (cs *ChainStore) LoadTipSet(tsk types.TipSetKey) (*types.TipSet, error) { return ts, nil } -// returns true if 'a' is an ancestor of 'b' -func (cs *ChainStore) IsAncestorOf(a, b *types.TipSet) (bool, error) { +// IsAncestorOf returns true if 'a' is an ancestor of 'b' +func (cs *ChainStore) IsAncestorOf(ctx context.Context, a, b *types.TipSet) (bool, error) { if b.Height() <= a.Height() { return false, nil } cur := b for !a.Equals(cur) && cur.Height() > a.Height() { - next, err := cs.LoadTipSet(b.Parents()) + next, err := cs.LoadTipSet(ctx, cur.Parents()) if err != nil { return false, err } @@ -406,16 +877,28 @@ func (cs *ChainStore) IsAncestorOf(a, b *types.TipSet) (bool, error) { return cur.Equals(a), nil } -func (cs *ChainStore) NearestCommonAncestor(a, b *types.TipSet) (*types.TipSet, error) { - l, _, err := cs.ReorgOps(a, b) +func (cs *ChainStore) NearestCommonAncestor(ctx context.Context, a, b *types.TipSet) (*types.TipSet, error) { + l, _, err := cs.ReorgOps(ctx, a, b) if err != nil { return nil, err } - return cs.LoadTipSet(l[len(l)-1].Parents()) + return cs.LoadTipSet(ctx, l[len(l)-1].Parents()) } -func (cs *ChainStore) ReorgOps(a, b *types.TipSet) ([]*types.TipSet, []*types.TipSet, error) { +// ReorgOps takes two tipsets (which can be at different heights), and walks +// their corresponding chains backwards one step at a time until we find +// a common ancestor. It then returns the respective chain segments that fork +// from the identified ancestor, in reverse order, where the first element of +// each slice is the supplied tipset, and the last element is the common +// ancestor. +// +// If an error happens along the way, we return the error with nil slices. +func (cs *ChainStore) ReorgOps(ctx context.Context, a, b *types.TipSet) ([]*types.TipSet, []*types.TipSet, error) { + return ReorgOps(ctx, cs.LoadTipSet, a, b) +} + +func ReorgOps(ctx context.Context, lts func(ctx context.Context, _ types.TipSetKey) (*types.TipSet, error), a, b *types.TipSet) ([]*types.TipSet, []*types.TipSet, error) { left := a right := b @@ -423,7 +906,7 @@ func (cs *ChainStore) ReorgOps(a, b *types.TipSet) ([]*types.TipSet, []*types.Ti for !left.Equals(right) { if left.Height() > right.Height() { leftChain = append(leftChain, left) - par, err := cs.LoadTipSet(left.Parents()) + par, err := lts(ctx, left.Parents()) if err != nil { return nil, nil, err } @@ -431,7 +914,7 @@ func (cs *ChainStore) ReorgOps(a, b *types.TipSet) ([]*types.TipSet, []*types.Ti left = par } else { rightChain = append(rightChain, right) - par, err := cs.LoadTipSet(right.Parents()) + par, err := lts(ctx, right.Parents()) if err != nil { log.Infof("failed to fetch right.Parents: %s", err) return nil, nil, err @@ -442,15 +925,18 @@ func (cs *ChainStore) ReorgOps(a, b *types.TipSet) ([]*types.TipSet, []*types.Ti } return leftChain, rightChain, nil + } -func (cs *ChainStore) GetHeaviestTipSet() *types.TipSet { - cs.heaviestLk.Lock() - defer cs.heaviestLk.Unlock() - return cs.heaviest +// GetHeaviestTipSet returns the current heaviest tipset known (i.e. our head). +func (cs *ChainStore) GetHeaviestTipSet() (ts *types.TipSet) { + cs.heaviestLk.RLock() + ts = cs.heaviest + cs.heaviestLk.RUnlock() + return } -func (cs *ChainStore) AddToTipSetTracker(b *types.BlockHeader) error { +func (cs *ChainStore) AddToTipSetTracker(ctx context.Context, b *types.BlockHeader) error { cs.tstLk.Lock() defer cs.tstLk.Unlock() @@ -460,16 +946,60 @@ func (cs *ChainStore) AddToTipSetTracker(b *types.BlockHeader) error { log.Debug("tried to add block to tipset tracker that was already there") return nil } + h, err := cs.GetBlock(ctx, oc) + if err == nil && h != nil { + if h.Miner == b.Miner { + log.Warnf("Have multiple blocks from miner %s at height %d in our tipset cache %s-%s", b.Miner, b.Height, b.Cid(), h.Cid()) + } + } + } + // This function is called 5 times per epoch on average + // It is also called with tipsets that are done with initial validation + // so they cannot be from the future. + // We are guaranteed not to use tipsets older than 900 epochs (fork limit) + // This means that we ideally want to keep only most recent 900 epochs in here + // Golang's map iteration starts at a random point in a map. + // With 5 tries per epoch, and 900 entries to keep, on average we will have + // ~136 garbage entires in the `cs.tipsets` map. (solve for 1-(1-x/(900+x))^5 == 0.5) + // Seems good enough to me + + for height := range cs.tipsets { + if height < b.Height-build.Finality { + delete(cs.tipsets, height) + } + break } cs.tipsets[b.Height] = append(tss, b.Cid()) - // TODO: do we want to look for slashable submissions here? might as well... + return nil +} + +func (cs *ChainStore) PersistTipsets(ctx context.Context, tipsets []*types.TipSet) error { + toPersist := make([]*types.BlockHeader, 0, len(tipsets)*int(build.BlocksPerEpoch)) + tsBlks := make([]block.Block, 0, len(tipsets)) + for _, ts := range tipsets { + toPersist = append(toPersist, ts.Blocks()...) + tsBlk, err := ts.Key().ToStorageBlock() + if err != nil { + return xerrors.Errorf("failed to get tipset key block: %w", err) + } + + tsBlks = append(tsBlks, tsBlk) + } + + if err := cs.persistBlockHeaders(ctx, toPersist...); err != nil { + return xerrors.Errorf("failed to persist block headers: %w", err) + } + + if err := cs.chainLocalBlockstore.PutMany(ctx, tsBlks); err != nil { + return xerrors.Errorf("failed to put tipset key blocks: %w", err) + } return nil } -func (cs *ChainStore) PersistBlockHeaders(b ...*types.BlockHeader) error { +func (cs *ChainStore) persistBlockHeaders(ctx context.Context, b ...*types.BlockHeader) error { sbs := make([]block.Block, len(b)) for i, header := range b { @@ -491,34 +1021,13 @@ func (cs *ChainStore) PersistBlockHeaders(b ...*types.BlockHeader) error { end = len(b) } - err = multierr.Append(err, cs.bs.PutMany(sbs[start:end])) + err = multierr.Append(err, cs.chainLocalBlockstore.PutMany(ctx, sbs[start:end])) } return err } -type storable interface { - ToStorageBlock() (block.Block, error) -} - -func PutMessage(bs bstore.Blockstore, m storable) (cid.Cid, error) { - b, err := m.ToStorageBlock() - if err != nil { - return cid.Undef, err - } - - if err := bs.Put(b); err != nil { - return cid.Undef, err - } - - return b.Cid(), nil -} - -func (cs *ChainStore) PutMessage(m storable) (cid.Cid, error) { - return PutMessage(cs.bs, m) -} - -func (cs *ChainStore) expandTipset(b *types.BlockHeader) (*types.TipSet, error) { +func (cs *ChainStore) expandTipset(ctx context.Context, b *types.BlockHeader) (*types.TipSet, error) { // Hold lock for the whole function for now, if it becomes a problem we can // fix pretty easily cs.tstLk.Lock() @@ -531,25 +1040,25 @@ func (cs *ChainStore) expandTipset(b *types.BlockHeader) (*types.TipSet, error) return types.NewTipSet(all) } - inclMiners := map[address.Address]bool{b.Miner: true} + inclMiners := map[address.Address]cid.Cid{b.Miner: b.Cid()} for _, bhc := range tsets { if bhc == b.Cid() { continue } - h, err := cs.GetBlock(bhc) + h, err := cs.GetBlock(ctx, bhc) if err != nil { return nil, xerrors.Errorf("failed to load block (%s) for tipset expansion: %w", bhc, err) } - if inclMiners[h.Miner] { - log.Warnf("Have multiple blocks from miner %s at height %d in our tipset cache", h.Miner, h.Height) + if cid, found := inclMiners[h.Miner]; found { + log.Warnf("Have multiple blocks from miner %s at height %d in our tipset cache %s-%s", h.Miner, h.Height, h.Cid(), cid) continue } if types.CidArrsEqual(h.Parents, b.Parents) { all = append(all, h) - inclMiners[h.Miner] = true + inclMiners[h.Miner] = bhc } } @@ -558,25 +1067,8 @@ func (cs *ChainStore) expandTipset(b *types.BlockHeader) (*types.TipSet, error) return types.NewTipSet(all) } -func (cs *ChainStore) AddBlock(ctx context.Context, b *types.BlockHeader) error { - if err := cs.PersistBlockHeaders(b); err != nil { - return err - } - - ts, err := cs.expandTipset(b) - if err != nil { - return err - } - - if err := cs.MaybeTakeHeavierTipSet(ctx, ts); err != nil { - return xerrors.Errorf("MaybeTakeHeavierTipSet failed: %w", err) - } - - return nil -} - -func (cs *ChainStore) GetGenesis() (*types.BlockHeader, error) { - data, err := cs.ds.Get(dstore.NewKey("0")) +func (cs *ChainStore) GetGenesis(ctx context.Context) (*types.BlockHeader, error) { + data, err := cs.metadataDs.Get(ctx, dstore.NewKey("0")) if err != nil { return nil, err } @@ -586,174 +1078,22 @@ func (cs *ChainStore) GetGenesis() (*types.BlockHeader, error) { return nil, err } - genb, err := cs.bs.Get(c) - if err != nil { - return nil, err - } - - return types.DecodeBlock(genb.RawData()) -} - -func (cs *ChainStore) GetCMessage(c cid.Cid) (types.ChainMsg, error) { - m, err := cs.GetMessage(c) - if err == nil { - return m, nil - } - if err != bstore.ErrNotFound { - log.Warn("GetCMessage: unexpected error getting unsigned message: %s", err) - } - - return cs.GetSignedMessage(c) -} - -func (cs *ChainStore) GetMessage(c cid.Cid) (*types.Message, error) { - sb, err := cs.bs.Get(c) - if err != nil { - log.Errorf("get message get failed: %s: %s", c, err) - return nil, err - } - - return types.DecodeMessage(sb.RawData()) -} - -func (cs *ChainStore) GetSignedMessage(c cid.Cid) (*types.SignedMessage, error) { - sb, err := cs.bs.Get(c) - if err != nil { - log.Errorf("get message get failed: %s: %s", c, err) - return nil, err - } - - return types.DecodeSignedMessage(sb.RawData()) -} - -func (cs *ChainStore) readAMTCids(root cid.Cid) ([]cid.Cid, error) { - ctx := context.TODO() - bs := cbor.NewCborStore(cs.bs) - a, err := amt.LoadAMT(ctx, bs, root) - if err != nil { - return nil, xerrors.Errorf("amt load: %w", err) - } - - var cids []cid.Cid - for i := uint64(0); i < a.Count; i++ { - var c cbg.CborCid - if err := a.Get(ctx, i, &c); err != nil { - return nil, xerrors.Errorf("failed to load cid from amt: %w", err) - } - - cids = append(cids, cid.Cid(c)) - } - - return cids, nil -} - -func (cs *ChainStore) MessagesForTipset(ts *types.TipSet) ([]types.ChainMsg, error) { - applied := make(map[address.Address]uint64) - balances := make(map[address.Address]types.BigInt) - - cst := cbor.NewCborStore(cs.bs) - st, err := state.LoadStateTree(cst, ts.Blocks()[0].ParentStateRoot) - if err != nil { - return nil, xerrors.Errorf("failed to load state tree") - } - - preloadAddr := func(a address.Address) error { - if _, ok := applied[a]; !ok { - act, err := st.GetActor(a) - if err != nil { - return err - } - - applied[a] = act.Nonce - balances[a] = act.Balance - } - return nil - } - - var out []types.ChainMsg - for _, b := range ts.Blocks() { - bms, sms, err := cs.MessagesForBlock(b) - if err != nil { - return nil, xerrors.Errorf("failed to get messages for block: %w", err) - } - - cmsgs := make([]types.ChainMsg, 0, len(bms)+len(sms)) - for _, m := range bms { - cmsgs = append(cmsgs, m) - } - for _, sm := range sms { - cmsgs = append(cmsgs, sm) - } - - for _, cm := range cmsgs { - m := cm.VMMessage() - if err := preloadAddr(m.From); err != nil { - return nil, err - } - - if applied[m.From] != m.Nonce { - continue - } - applied[m.From]++ - - if balances[m.From].LessThan(m.RequiredFunds()) { - continue - } - balances[m.From] = types.BigSub(balances[m.From], m.RequiredFunds()) - - out = append(out, cm) - } - } - - return out, nil -} - -type mmCids struct { - bls []cid.Cid - secpk []cid.Cid -} - -func (cs *ChainStore) readMsgMetaCids(mmc cid.Cid) ([]cid.Cid, []cid.Cid, error) { - o, ok := cs.mmCache.Get(mmc) - if ok { - mmcids := o.(*mmCids) - return mmcids.bls, mmcids.secpk, nil - } - - cst := cbor.NewCborStore(cs.bs) - var msgmeta types.MsgMeta - if err := cst.Get(context.TODO(), mmc, &msgmeta); err != nil { - return nil, nil, xerrors.Errorf("failed to load msgmeta (%s): %w", mmc, err) - } - - blscids, err := cs.readAMTCids(msgmeta.BlsMessages) - if err != nil { - return nil, nil, xerrors.Errorf("loading bls message cids for block: %w", err) - } - - secpkcids, err := cs.readAMTCids(msgmeta.SecpkMessages) - if err != nil { - return nil, nil, xerrors.Errorf("loading secpk message cids for block: %w", err) - } - - cs.mmCache.Add(mmc, &mmCids{ - bls: blscids, - secpk: secpkcids, - }) - - return blscids, secpkcids, nil + return cs.GetBlock(ctx, c) } +// GetPath returns the sequence of atomic head change operations that +// need to be applied in order to switch the head of the chain from the `from` +// tipset to the `to` tipset. func (cs *ChainStore) GetPath(ctx context.Context, from types.TipSetKey, to types.TipSetKey) ([]*api.HeadChange, error) { - fts, err := cs.LoadTipSet(from) + fts, err := cs.LoadTipSet(ctx, from) if err != nil { return nil, xerrors.Errorf("loading from tipset %s: %w", from, err) } - tts, err := cs.LoadTipSet(to) + tts, err := cs.LoadTipSet(ctx, to) if err != nil { return nil, xerrors.Errorf("loading to tipset %s: %w", to, err) } - revert, apply, err := cs.ReorgOps(fts, tts) + revert, apply, err := cs.ReorgOps(ctx, fts, tts) if err != nil { return nil, xerrors.Errorf("error getting tipset branches: %w", err) } @@ -768,110 +1108,37 @@ func (cs *ChainStore) GetPath(ctx context.Context, from types.TipSetKey, to type return path, nil } -func (cs *ChainStore) MessagesForBlock(b *types.BlockHeader) ([]*types.Message, []*types.SignedMessage, error) { - blscids, secpkcids, err := cs.readMsgMetaCids(b.Messages) - if err != nil { - return nil, nil, err - } - - blsmsgs, err := cs.LoadMessagesFromCids(blscids) - if err != nil { - return nil, nil, xerrors.Errorf("loading bls messages for block: %w", err) - } - - secpkmsgs, err := cs.LoadSignedMessagesFromCids(secpkcids) - if err != nil { - return nil, nil, xerrors.Errorf("loading secpk messages for block: %w", err) - } - - return blsmsgs, secpkmsgs, nil +// ChainBlockstore returns the chain blockstore. Currently the chain and state +// // stores are both backed by the same physical store, albeit with different +// // caching policies, but in the future they will segregate. +func (cs *ChainStore) ChainBlockstore() bstore.Blockstore { + return cs.chainBlockstore } -func (cs *ChainStore) GetParentReceipt(b *types.BlockHeader, i int) (*types.MessageReceipt, error) { - ctx := context.TODO() - bs := cbor.NewCborStore(cs.bs) - a, err := amt.LoadAMT(ctx, bs, b.ParentMessageReceipts) - if err != nil { - return nil, xerrors.Errorf("amt load: %w", err) - } - - var r types.MessageReceipt - if err := a.Get(ctx, uint64(i), &r); err != nil { - return nil, err - } - - return &r, nil +// StateBlockstore returns the state blockstore. Currently the chain and state +// stores are both backed by the same physical store, albeit with different +// caching policies, but in the future they will segregate. +func (cs *ChainStore) StateBlockstore() bstore.Blockstore { + return cs.stateBlockstore } -func (cs *ChainStore) LoadMessagesFromCids(cids []cid.Cid) ([]*types.Message, error) { - msgs := make([]*types.Message, 0, len(cids)) - for i, c := range cids { - m, err := cs.GetMessage(c) - if err != nil { - return nil, xerrors.Errorf("failed to get message: (%s):%d: %w", err, c, i) - } - - msgs = append(msgs, m) - } - - return msgs, nil +func (cs *ChainStore) ChainLocalBlockstore() bstore.Blockstore { + return cs.chainLocalBlockstore } -func (cs *ChainStore) LoadSignedMessagesFromCids(cids []cid.Cid) ([]*types.SignedMessage, error) { - msgs := make([]*types.SignedMessage, 0, len(cids)) - for i, c := range cids { - m, err := cs.GetSignedMessage(c) - if err != nil { - return nil, xerrors.Errorf("failed to get message: (%s):%d: %w", err, c, i) - } - - msgs = append(msgs, m) - } - - return msgs, nil +func ActorStore(ctx context.Context, bs bstore.Blockstore) adt.Store { + return adt.WrapStore(ctx, cbor.NewCborStore(bs)) } -func (cs *ChainStore) Blockstore() bstore.Blockstore { - return cs.bs +func (cs *ChainStore) ActorStore(ctx context.Context) adt.Store { + return ActorStore(ctx, cs.stateBlockstore) } -func ActorStore(ctx context.Context, bs blockstore.Blockstore) adt.Store { - return &astore{ - cst: cbor.NewCborStore(bs), - ctx: ctx, - } -} - -type astore struct { - cst cbor.IpldStore - ctx context.Context -} - -func (a *astore) Context() context.Context { - return a.ctx -} - -func (a *astore) Get(ctx context.Context, c cid.Cid, out interface{}) error { - return a.cst.Get(ctx, c, out) -} - -func (a *astore) Put(ctx context.Context, v interface{}) (cid.Cid, error) { - return a.cst.Put(ctx, v) -} - -func (cs *ChainStore) Store(ctx context.Context) adt.Store { - return ActorStore(ctx, cs.bs) -} - -func (cs *ChainStore) VMSys() runtime.Syscalls { - return cs.vmcalls -} - -func (cs *ChainStore) TryFillTipSet(ts *types.TipSet) (*FullTipSet, error) { +func (cs *ChainStore) TryFillTipSet(ctx context.Context, ts *types.TipSet) (*FullTipSet, error) { var out []*types.FullBlock for _, b := range ts.Blocks() { - bmsgs, smsgs, err := cs.MessagesForBlock(b) + bmsgs, smsgs, err := cs.MessagesForBlock(ctx, b) if err != nil { // TODO: check for 'not found' errors, and only return nil if this // is actually a 'not found' error @@ -889,217 +1156,106 @@ func (cs *ChainStore) TryFillTipSet(ts *types.TipSet) (*FullTipSet, error) { return NewFullTipSet(out), nil } -func DrawRandomness(rbase []byte, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { - h := blake2b.New256() - if err := binary.Write(h, binary.BigEndian, int64(pers)); err != nil { - return nil, xerrors.Errorf("deriving randomness: %w", err) - } - VRFDigest := blake2b.Sum256(rbase) - h.Write(VRFDigest[:]) - if err := binary.Write(h, binary.BigEndian, round); err != nil { - return nil, xerrors.Errorf("deriving randomness: %w", err) - } - h.Write(entropy) - - return h.Sum(nil), nil -} - -func (cs *ChainStore) GetRandomness(ctx context.Context, blks []cid.Cid, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) (out []byte, err error) { - _, span := trace.StartSpan(ctx, "store.GetRandomness") - defer span.End() - span.AddAttributes(trace.Int64Attribute("round", int64(round))) - - //defer func() { - //log.Infof("getRand %v %d %d %x -> %x", blks, pers, round, entropy, out) - //}() - for { - nts, err := cs.LoadTipSet(types.NewTipSetKey(blks...)) - if err != nil { - return nil, err - } - - mtb := nts.MinTicketBlock() - - // if at (or just past -- for null epochs) appropriate epoch - // or at genesis (works for negative epochs) - if nts.Height() <= round || mtb.Height == 0 { - return DrawRandomness(nts.MinTicketBlock().Ticket.VRFProof, pers, round, entropy) - } - - blks = mtb.Parents - } -} - // GetTipsetByHeight returns the tipset on the chain behind 'ts' at the given // height. In the case that the given height is a null round, the 'prev' flag // selects the tipset before the null round if true, and the tipset following // the null round if false. func (cs *ChainStore) GetTipsetByHeight(ctx context.Context, h abi.ChainEpoch, ts *types.TipSet, prev bool) (*types.TipSet, error) { + if h < 0 { + return nil, xerrors.Errorf("height %d is negative", h) + } + if ts == nil { ts = cs.GetHeaviestTipSet() } if h > ts.Height() { - return nil, xerrors.Errorf("looking for tipset with height less than start point") + return nil, xerrors.Errorf("looking for tipset with height greater than start point") } if h == ts.Height() { return ts, nil } - if ts.Height()-h > build.ForkLengthThreshold { - log.Warnf("expensive call to GetTipsetByHeight, seeking %d levels", ts.Height()-h) - } - - for { - pts, err := cs.LoadTipSet(ts.Parents()) - if err != nil { - return nil, err - } - - if h > pts.Height() { - if prev { - return pts, nil - } - return ts, nil - } - if h == pts.Height() { - return pts, nil - } - - ts = pts - } -} - -func recurseLinks(bs blockstore.Blockstore, root cid.Cid, in []cid.Cid) ([]cid.Cid, error) { - if root.Prefix().Codec != cid.DagCBOR { - return in, nil - } - - data, err := bs.Get(root) + lbts, err := cs.cindex.GetTipsetByHeight(ctx, ts, h) if err != nil { - return nil, xerrors.Errorf("recurse links get (%s) failed: %w", root, err) + return nil, err } - top, err := cbg.ScanForLinks(bytes.NewReader(data.RawData())) - if err != nil { - return nil, xerrors.Errorf("scanning for links failed: %w", err) - } - - in = append(in, top...) - for _, c := range top { - var err error - in, err = recurseLinks(bs, c, in) + if lbts.Height() < h { + log.Warnf("chain index returned the wrong tipset at height %d, using slow retrieval", h) + lbts, err = cs.cindex.GetTipsetByHeightWithoutCache(ctx, ts, h) if err != nil { return nil, err } } - return in, nil + if lbts.Height() == h || !prev { + return lbts, nil + } + + return cs.LoadTipSet(ctx, lbts.Parents()) } -func (cs *ChainStore) Export(ctx context.Context, ts *types.TipSet, w io.Writer) error { - if ts == nil { - ts = cs.GetHeaviestTipSet() - } - - seen := cid.NewSet() - - h := &car.CarHeader{ - Roots: ts.Cids(), - Version: 1, - } - - if err := car.WriteHeader(h, w); err != nil { - return xerrors.Errorf("failed to write car header: %s", err) - } - - blocksToWalk := ts.Cids() - - walkChain := func(blk cid.Cid) error { - if !seen.Visit(blk) { - return nil - } - - data, err := cs.bs.Get(blk) - if err != nil { - return xerrors.Errorf("getting block: %w", err) - } - - if err := carutil.LdWrite(w, blk.Bytes(), data.RawData()); err != nil { - return xerrors.Errorf("failed to write block to car output: %w", err) - } - - var b types.BlockHeader - if err := b.UnmarshalCBOR(bytes.NewBuffer(data.RawData())); err != nil { - return xerrors.Errorf("unmarshaling block header (cid=%s): %w", blk, err) - } - - for _, p := range b.Parents { - blocksToWalk = append(blocksToWalk, p) - } - - cids, err := recurseLinks(cs.bs, b.Messages, []cid.Cid{b.Messages}) - if err != nil { - return xerrors.Errorf("recursing messages failed: %w", err) - } - - out := cids - - if b.Height == 0 { - cids, err := recurseLinks(cs.bs, b.ParentStateRoot, []cid.Cid{b.ParentStateRoot}) - if err != nil { - return xerrors.Errorf("recursing genesis state failed: %w", err) - } - - out = append(out, cids...) - } - - for _, c := range out { - if seen.Visit(c) { - if c.Prefix().Codec != cid.DagCBOR { - continue - } - data, err := cs.bs.Get(c) - if err != nil { - return xerrors.Errorf("writing object to car (get %s): %w", c, err) - } - - if err := carutil.LdWrite(w, c.Bytes(), data.RawData()); err != nil { - return xerrors.Errorf("failed to write out car object: %w", err) - } - } - } - - return nil - } - - for len(blocksToWalk) > 0 { - next := blocksToWalk[0] - blocksToWalk = blocksToWalk[1:] - if err := walkChain(next); err != nil { - return xerrors.Errorf("walk chain failed: %w", err) - } - } - - return nil -} - -func (cs *ChainStore) Import(r io.Reader) (*types.TipSet, error) { - header, err := car.LoadCar(cs.Blockstore(), r) +func (cs *ChainStore) GetTipSetByCid(ctx context.Context, c cid.Cid) (*types.TipSet, error) { + blk, err := cs.chainBlockstore.Get(ctx, c) if err != nil { - return nil, xerrors.Errorf("loadcar failed: %w", err) + return nil, xerrors.Errorf("cannot find tipset with cid %s: %w", c, err) } - root, err := cs.LoadTipSet(types.NewTipSetKey(header.Roots...)) + tsk := new(types.TipSetKey) + if err := tsk.UnmarshalCBOR(bytes.NewReader(blk.RawData())); err != nil { + return nil, xerrors.Errorf("cannot unmarshal block into tipset key: %w", err) + } + + ts, err := cs.GetTipSetFromKey(ctx, *tsk) if err != nil { - return nil, xerrors.Errorf("failed to load root tipset from chainfile: %w", err) + return nil, xerrors.Errorf("cannot get tipset from key: %w", err) } - - return root, nil + return ts, nil } -func (cs *ChainStore) GetLatestBeaconEntry(ts *types.TipSet) (*types.BeaconEntry, error) { +func (cs *ChainStore) Weight(ctx context.Context, hts *types.TipSet) (types.BigInt, error) { // todo remove + return cs.weight(ctx, cs.StateBlockstore(), hts) +} + +// StoreEvents marks this ChainStore as storing events. +func (cs *ChainStore) StoreEvents(store bool) { + cs.storeEvents = store +} + +// IsStoringEvents indicates if this ChainStore is storing events. +func (cs *ChainStore) IsStoringEvents() bool { + return cs.storeEvents +} + +// true if ts1 wins according to the filecoin tie-break rule +func breakWeightTie(ts1, ts2 *types.TipSet) bool { + s := len(ts1.Blocks()) + if s > len(ts2.Blocks()) { + s = len(ts2.Blocks()) + } + + // blocks are already sorted by ticket + for i := 0; i < s; i++ { + if ts1.Blocks()[i].Ticket.Less(ts2.Blocks()[i].Ticket) { + log.Infof("weight tie broken in favour of %s", ts1.Key()) + return true + } + } + + log.Infof("weight tie left unbroken, default to %s", ts2.Key()) + return false +} + +func (cs *ChainStore) GetTipSetFromKey(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error) { + if tsk.IsEmpty() { + return cs.GetHeaviestTipSet(), nil + } + return cs.LoadTipSet(ctx, tsk) +} + +func (cs *ChainStore) GetLatestBeaconEntry(ctx context.Context, ts *types.TipSet) (*types.BeaconEntry, error) { cur := ts for i := 0; i < 20; i++ { cbe := cur.Blocks()[0].BeaconEntries @@ -1111,7 +1267,7 @@ func (cs *ChainStore) GetLatestBeaconEntry(ts *types.TipSet) (*types.BeaconEntry return nil, xerrors.Errorf("made it back to genesis block without finding beacon entry") } - next, err := cs.LoadTipSet(cur.Parents()) + next, err := cs.LoadTipSet(ctx, cur.Parents()) if err != nil { return nil, xerrors.Errorf("failed to load parents when searching back for latest beacon entry: %w", err) } @@ -1124,31 +1280,5 @@ func (cs *ChainStore) GetLatestBeaconEntry(ts *types.TipSet) (*types.BeaconEntry }, nil } - return nil, xerrors.Errorf("found NO beacon entries in the 20 blocks prior to given tipset") -} - -type chainRand struct { - cs *ChainStore - blks []cid.Cid - bh abi.ChainEpoch -} - -func NewChainRand(cs *ChainStore, blks []cid.Cid, bheight abi.ChainEpoch) vm.Rand { - return &chainRand{ - cs: cs, - blks: blks, - bh: bheight, - } -} - -func (cr *chainRand) GetRandomness(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { - return cr.cs.GetRandomness(ctx, cr.blks, pers, round, entropy) -} - -func (cs *ChainStore) GetTipSetFromKey(tsk types.TipSetKey) (*types.TipSet, error) { - if tsk.IsEmpty() { - return cs.GetHeaviestTipSet(), nil - } else { - return cs.LoadTipSet(tsk) - } + return nil, xerrors.Errorf("found NO beacon entries in the 20 latest tipsets") } diff --git a/chain/store/store_test.go b/chain/store/store_test.go index 495b91b61..cea0fdc2a 100644 --- a/chain/store/store_test.go +++ b/chain/store/store_test.go @@ -1,35 +1,39 @@ +// stm: #unit package store_test import ( "bytes" "context" + "io" "testing" - datastore "github.com/ipfs/go-datastore" - blockstore "github.com/ipfs/go-ipfs-blockstore" + "github.com/ipfs/go-datastore" + "github.com/stretchr/testify/require" - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/abi/big" - "github.com/filecoin-project/specs-actors/actors/builtin/miner" - "github.com/filecoin-project/specs-actors/actors/builtin/power" - "github.com/filecoin-project/specs-actors/actors/builtin/verifreg" - "github.com/filecoin-project/specs-actors/actors/crypto" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/chain/actors/policy" + "github.com/filecoin-project/lotus/chain/consensus" + "github.com/filecoin-project/lotus/chain/consensus/filcns" "github.com/filecoin-project/lotus/chain/gen" + "github.com/filecoin-project/lotus/chain/index" + "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/node/repo" ) func init() { - miner.SupportedProofTypes = map[abi.RegisteredProof]struct{}{ - abi.RegisteredProof_StackedDRG2KiBSeal: {}, - } - power.ConsensusMinerMinPower = big.NewInt(2048) - verifreg.MinVerifiedDealSize = big.NewInt(256) + policy.SetSupportedProofTypes(abi.RegisteredSealProof_StackedDrg2KiBV1) + policy.SetConsensusMinerMinPower(abi.NewStoragePower(2048)) + policy.SetMinVerifiedDealSize(abi.NewStoragePower(256)) } func BenchmarkGetRandomness(b *testing.B) { + //stm: @CHAIN_GEN_NEXT_TIPSET_001 + //stm: @CHAIN_STATE_GET_RANDOMNESS_FROM_TICKETS_001 cg, err := gen.NewGenerator() if err != nil { b.Fatal(err) @@ -55,24 +59,31 @@ func BenchmarkGetRandomness(b *testing.B) { b.Fatal(err) } - bds, err := lr.Datastore("/blocks") + bs, err := lr.Blockstore(context.TODO(), repo.UniversalBlockstore) if err != nil { b.Fatal(err) } - mds, err := lr.Datastore("/metadata") + defer func() { + if c, ok := bs.(io.Closer); ok { + if err := c.Close(); err != nil { + b.Logf("WARN: failed to close blockstore: %s", err) + } + } + }() + + mds, err := lr.Datastore(context.Background(), "/metadata") if err != nil { b.Fatal(err) } - bs := blockstore.NewBlockstore(bds) - - cs := store.NewChainStore(bs, mds, nil) + cs := store.NewChainStore(bs, bs, mds, filcns.Weight, nil) + defer cs.Close() //nolint:errcheck b.ResetTimer() for i := 0; i < b.N; i++ { - _, err := cs.GetRandomness(context.TODO(), last.Cids(), crypto.DomainSeparationTag_SealRandomness, 500, nil) + _, err := cg.StateManager().GetRandomnessFromTickets(context.TODO(), crypto.DomainSeparationTag_SealRandomness, 500, nil, last.Key()) if err != nil { b.Fatal(err) } @@ -80,6 +91,8 @@ func BenchmarkGetRandomness(b *testing.B) { } func TestChainExportImport(t *testing.T) { + //stm: @CHAIN_GEN_NEXT_TIPSET_001 + //stm: @CHAIN_STORE_IMPORT_001 cg, err := gen.NewGenerator() if err != nil { t.Fatal(err) @@ -96,14 +109,15 @@ func TestChainExportImport(t *testing.T) { } buf := new(bytes.Buffer) - if err := cg.ChainStore().Export(context.TODO(), last, buf); err != nil { + if err := cg.ChainStore().Export(context.TODO(), last, 0, false, buf); err != nil { t.Fatal(err) } - nbs := blockstore.NewBlockstore(datastore.NewMapDatastore()) - cs := store.NewChainStore(nbs, datastore.NewMapDatastore(), nil) + nbs := blockstore.NewMemorySync() + cs := store.NewChainStore(nbs, nbs, datastore.NewMapDatastore(), filcns.Weight, nil) + defer cs.Close() //nolint:errcheck - root, err := cs.Import(buf) + root, err := cs.Import(context.TODO(), buf) if err != nil { t.Fatal(err) } @@ -112,3 +126,115 @@ func TestChainExportImport(t *testing.T) { t.Fatal("imported chain differed from exported chain") } } + +// Test to check if tipset key cids are being stored on snapshot +func TestChainImportTipsetKeyCid(t *testing.T) { + + ctx := context.Background() + cg, err := gen.NewGenerator() + require.NoError(t, err) + + buf := new(bytes.Buffer) + var last *types.TipSet + var tsKeys []types.TipSetKey + for i := 0; i < 10; i++ { + ts, err := cg.NextTipSet() + require.NoError(t, err) + last = ts.TipSet.TipSet() + tsKeys = append(tsKeys, last.Key()) + } + + if err := cg.ChainStore().Export(ctx, last, last.Height(), false, buf); err != nil { + t.Fatal(err) + } + + nbs := blockstore.NewMemorySync() + cs := store.NewChainStore(nbs, nbs, datastore.NewMapDatastore(), filcns.Weight, nil) + defer cs.Close() //nolint:errcheck + + root, err := cs.Import(ctx, buf) + require.NoError(t, err) + + require.Truef(t, root.Equals(last), "imported chain differed from exported chain") + + err = cs.SetHead(ctx, last) + require.NoError(t, err) + + for _, tsKey := range tsKeys { + _, err := cs.LoadTipSet(ctx, tsKey) + require.NoError(t, err) + + tsCid, err := tsKey.Cid() + require.NoError(t, err) + _, err = cs.ChainLocalBlockstore().Get(ctx, tsCid) + require.NoError(t, err) + + } +} + +func TestChainExportImportFull(t *testing.T) { + //stm: @CHAIN_GEN_NEXT_TIPSET_001 + //stm: @CHAIN_STORE_IMPORT_001, @CHAIN_STORE_EXPORT_001, @CHAIN_STORE_SET_HEAD_001 + //stm: @CHAIN_STORE_GET_TIPSET_BY_HEIGHT_001 + cg, err := gen.NewGenerator() + if err != nil { + t.Fatal(err) + } + + var last *types.TipSet + for i := 0; i < 100; i++ { + ts, err := cg.NextTipSet() + if err != nil { + t.Fatal(err) + } + + last = ts.TipSet.TipSet() + } + + buf := new(bytes.Buffer) + if err := cg.ChainStore().Export(context.TODO(), last, last.Height(), false, buf); err != nil { + t.Fatal(err) + } + + nbs := blockstore.NewMemorySync() + ds := datastore.NewMapDatastore() + cs := store.NewChainStore(nbs, nbs, ds, filcns.Weight, nil) + defer cs.Close() //nolint:errcheck + + root, err := cs.Import(context.TODO(), buf) + if err != nil { + t.Fatal(err) + } + + err = cs.SetHead(context.Background(), last) + if err != nil { + t.Fatal(err) + } + + if !root.Equals(last) { + t.Fatal("imported chain differed from exported chain") + } + + sm, err := stmgr.NewStateManager(cs, consensus.NewTipSetExecutor(filcns.RewardFunc), nil, filcns.DefaultUpgradeSchedule(), cg.BeaconSchedule(), ds, index.DummyMsgIndex) + if err != nil { + t.Fatal(err) + } + + for i := 0; i < 100; i++ { + ts, err := cs.GetTipsetByHeight(context.TODO(), abi.ChainEpoch(i), nil, false) + if err != nil { + t.Fatal(err) + } + + st, err := sm.ParentState(ts) + if err != nil { + t.Fatal(err) + } + + // touches a bunch of actors + _, err = sm.GetCirculatingSupply(context.TODO(), abi.ChainEpoch(i), st) + if err != nil { + t.Fatal(err) + } + } +} diff --git a/chain/store/weight.go b/chain/store/weight.go deleted file mode 100644 index c92f1a74d..000000000 --- a/chain/store/weight.go +++ /dev/null @@ -1,108 +0,0 @@ -package store - -import ( - "context" - "math/big" - - "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/lotus/chain/state" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/chain/vm" - big2 "github.com/filecoin-project/specs-actors/actors/abi/big" - "github.com/filecoin-project/specs-actors/actors/builtin" - "github.com/filecoin-project/specs-actors/actors/builtin/power" - cbor "github.com/ipfs/go-ipld-cbor" - "golang.org/x/xerrors" -) - -var zero = types.NewInt(0) - -func (cs *ChainStore) Weight(ctx context.Context, ts *types.TipSet) (types.BigInt, error) { - if ts == nil { - return types.NewInt(0), nil - } - // >>> w[r] <<< + wFunction(totalPowerAtTipset(ts)) * 2^8 + (wFunction(totalPowerAtTipset(ts)) * len(ts.blocks) * wRatio_num * 2^8) / (e * wRatio_den) - - var out = new(big.Int).Set(ts.Blocks()[0].ParentWeight.Int) - - // >>> wFunction(totalPowerAtTipset(ts)) * 2^8 <<< + (wFunction(totalPowerAtTipset(ts)) * len(ts.blocks) * wRatio_num * 2^8) / (e * wRatio_den) - - tpow := big2.Zero() - { - cst := cbor.NewCborStore(cs.Blockstore()) - state, err := state.LoadStateTree(cst, ts.ParentState()) - if err != nil { - return types.NewInt(0), xerrors.Errorf("load state tree: %w", err) - } - - act, err := state.GetActor(builtin.StoragePowerActorAddr) - if err != nil { - return types.NewInt(0), xerrors.Errorf("get power actor: %w", err) - } - - var st power.State - if err := cst.Get(ctx, act.Head, &st); err != nil { - return types.NewInt(0), xerrors.Errorf("get power actor head: %w", err) - } - tpow = st.TotalQualityAdjPower // TODO: REVIEW: Is this correct? - } - - log2P := int64(0) - if tpow.GreaterThan(zero) { - log2P = int64(tpow.BitLen() - 1) - } else { - // Not really expect to be here ... - return types.EmptyInt, xerrors.Errorf("All power in the net is gone. You network might be disconnected, or the net is dead!") - } - - out.Add(out, big.NewInt(log2P<<8)) - - // (wFunction(totalPowerAtTipset(ts)) * len(ts.blocks) * wRatio_num * 2^8) / (e * wRatio_den) - - eWeight := big.NewInt((log2P * int64(len(ts.Blocks())) * build.WRatioNum) << 8) - eWeight.Div(eWeight, big.NewInt(int64(build.BlocksPerEpoch*build.WRatioDen))) - out.Add(out, eWeight) - - return types.BigInt{Int: out}, nil -} - -// todo: dedupe with state manager -func (cs *ChainStore) call(ctx context.Context, msg *types.Message, ts *types.TipSet) (*types.MessageReceipt, error) { - bstate := ts.ParentState() - - r := NewChainRand(cs, ts.Cids(), ts.Height()) - - vmi, err := vm.NewVM(bstate, ts.Height(), r, cs.bs, cs.vmcalls) - if err != nil { - return nil, xerrors.Errorf("failed to set up vm: %w", err) - } - - if msg.GasLimit == 0 { - msg.GasLimit = 10000000000 - } - if msg.GasPrice == types.EmptyInt { - msg.GasPrice = types.NewInt(0) - } - if msg.Value == types.EmptyInt { - msg.Value = types.NewInt(0) - } - - fromActor, err := vmi.StateTree().GetActor(msg.From) - if err != nil { - return nil, xerrors.Errorf("call raw get actor: %s", err) - } - - msg.Nonce = fromActor.Nonce - - // TODO: maybe just use the invoker directly? - // TODO: use signed message length for secp messages - ret, err := vmi.ApplyMessage(ctx, msg) - if err != nil { - return nil, xerrors.Errorf("apply message failed: %w", err) - } - - if ret.ActorErr != nil { - log.Warnf("chain call failed: %s", ret.ActorErr) - } - return &ret.MessageReceipt, nil -} diff --git a/chain/sub/incoming.go b/chain/sub/incoming.go index 2ef9c2718..64185c4cc 100644 --- a/chain/sub/incoming.go +++ b/chain/sub/incoming.go @@ -3,43 +3,51 @@ package sub import ( "bytes" "context" - "fmt" + "encoding/binary" "sync" "time" - "golang.org/x/xerrors" - - address "github.com/filecoin-project/go-address" - amt "github.com/filecoin-project/go-amt-ipld/v2" - miner "github.com/filecoin-project/specs-actors/actors/builtin/miner" - lru "github.com/hashicorp/golang-lru" + lru "github.com/hashicorp/golang-lru/v2" + blocks "github.com/ipfs/go-block-format" + bserv "github.com/ipfs/go-blockservice" "github.com/ipfs/go-cid" - dstore "github.com/ipfs/go-datastore" - bstore "github.com/ipfs/go-ipfs-blockstore" - cbor "github.com/ipfs/go-ipld-cbor" logging "github.com/ipfs/go-log/v2" - connmgr "github.com/libp2p/go-libp2p-core/connmgr" - "github.com/libp2p/go-libp2p-core/peer" + "github.com/ipni/go-libipni/announce/message" pubsub "github.com/libp2p/go-libp2p-pubsub" - cbg "github.com/whyrusleeping/cbor-gen" + "github.com/libp2p/go-libp2p/core/connmgr" + "github.com/libp2p/go-libp2p/core/peer" "go.opencensus.io/stats" "go.opencensus.io/tag" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain" + "github.com/filecoin-project/lotus/chain/consensus" "github.com/filecoin-project/lotus/chain/messagepool" - "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/sub/ratelimit" "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/lib/bufbstore" - "github.com/filecoin-project/lotus/lib/sigs" "github.com/filecoin-project/lotus/metrics" + "github.com/filecoin-project/lotus/node/impl/client" + "github.com/filecoin-project/lotus/node/impl/full" ) var log = logging.Logger("sub") -func HandleIncomingBlocks(ctx context.Context, bsub *pubsub.Subscription, s *chain.Syncer, cmgr connmgr.ConnManager) { +var msgCidPrefix = cid.Prefix{ + Version: 1, + Codec: cid.DagCBOR, + MhType: client.DefaultHashFunction, + MhLength: 32, +} + +func HandleIncomingBlocks(ctx context.Context, bsub *pubsub.Subscription, s *chain.Syncer, bs bserv.BlockService, cmgr connmgr.ConnManager) { + // Timeout after (block time + propagation delay). This is useless at + // this point. + timeout := time.Duration(build.BlockDelaySecs+build.PropagationDelaySecs) * time.Second + for { msg, err := bsub.Next(ctx) if err != nil { @@ -57,30 +65,41 @@ func HandleIncomingBlocks(ctx context.Context, bsub *pubsub.Subscription, s *cha return } - //nolint:golint - src := peer.ID(msg.GetFrom()) + src := msg.GetFrom() go func() { - log.Infof("New block over pubsub: %s", blk.Cid()) + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() - start := time.Now() + // NOTE: we could also share a single session between + // all requests but that may have other consequences. + ses := bserv.NewSession(ctx, bs) + + start := build.Clock.Now() log.Debug("about to fetch messages for block from pubsub") - bmsgs, err := s.Bsync.FetchMessagesByCids(context.TODO(), blk.BlsMessages) + bmsgs, err := FetchMessagesByCids(ctx, ses, blk.BlsMessages) if err != nil { - log.Errorf("failed to fetch all bls messages for block received over pubusb: %s; source: %s", err, src) + log.Errorf("failed to fetch all bls messages for block received over pubsub: %s; source: %s", err, src) return } - smsgs, err := s.Bsync.FetchSignedMessagesByCids(context.TODO(), blk.SecpkMessages) + smsgs, err := FetchSignedMessagesByCids(ctx, ses, blk.SecpkMessages) if err != nil { - log.Errorf("failed to fetch all secpk messages for block received over pubusb: %s; source: %s", err, src) + log.Errorf("failed to fetch all secpk messages for block received over pubsub: %s; source: %s", err, src) return } - took := time.Since(start) - log.Infow("new block over pubsub", "cid", blk.Header.Cid(), "source", msg.GetFrom(), "msgfetch", took) - if delay := time.Now().Unix() - int64(blk.Header.Timestamp); delay > 5 { - log.Warnf("Received block with large delay %d from miner %s", delay, blk.Header.Miner) + took := build.Clock.Since(start) + log.Debugw("new block over pubsub", "cid", blk.Header.Cid(), "source", msg.GetFrom(), "msgfetch", took) + if took > 3*time.Second { + log.Warnw("Slow msg fetch", "cid", blk.Header.Cid(), "source", msg.GetFrom(), "msgfetch", took) + } + if delay := build.Clock.Now().Unix() - int64(blk.Header.Timestamp); delay > 5 { + _ = stats.RecordWithTags(ctx, + []tag.Mutator{tag.Insert(metrics.MinerID, blk.Header.Miner.String())}, + metrics.BlockDelay.M(delay), + ) + log.Warnw("received block with large delay from miner", "block", blk.Cid(), "delay", delay, "miner", blk.Header.Miner) } if s.InformNewBlock(msg.ReceivedFrom, &types.FullBlock{ @@ -94,8 +113,111 @@ func HandleIncomingBlocks(ctx context.Context, bsub *pubsub.Subscription, s *cha } } +func FetchMessagesByCids( + ctx context.Context, + bserv bserv.BlockGetter, + cids []cid.Cid, +) ([]*types.Message, error) { + out := make([]*types.Message, len(cids)) + + err := fetchCids(ctx, bserv, cids, func(i int, b blocks.Block) error { + msg, err := types.DecodeMessage(b.RawData()) + if err != nil { + return err + } + + out[i] = msg + return nil + }) + if err != nil { + return nil, err + } + return out, nil +} + +// FIXME: Duplicate of above. +func FetchSignedMessagesByCids( + ctx context.Context, + bserv bserv.BlockGetter, + cids []cid.Cid, +) ([]*types.SignedMessage, error) { + out := make([]*types.SignedMessage, len(cids)) + + err := fetchCids(ctx, bserv, cids, func(i int, b blocks.Block) error { + smsg, err := types.DecodeSignedMessage(b.RawData()) + if err != nil { + return err + } + + out[i] = smsg + return nil + }) + if err != nil { + return nil, err + } + return out, nil +} + +// Fetch `cids` from the block service, apply `cb` on each of them. Used +// +// by the fetch message functions above. +// +// We check that each block is received only once and we do not received +// +// blocks we did not request. +func fetchCids( + ctx context.Context, + bserv bserv.BlockGetter, + cids []cid.Cid, + cb func(int, blocks.Block) error, +) error { + + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + cidIndex := make(map[cid.Cid]int) + for i, c := range cids { + if c.Prefix() != msgCidPrefix { + return xerrors.Errorf("invalid msg CID: %s", c) + } + cidIndex[c] = i + } + if len(cids) != len(cidIndex) { + return xerrors.Errorf("duplicate CIDs in fetchCids input") + } + + for block := range bserv.GetBlocks(ctx, cids) { + ix, ok := cidIndex[block.Cid()] + if !ok { + // Ignore duplicate/unexpected blocks. This shouldn't + // happen, but we can be safe. + log.Errorw("received duplicate/unexpected block when syncing", "cid", block.Cid()) + continue + } + + // Record that we've received the block. + delete(cidIndex, block.Cid()) + + if err := cb(ix, block); err != nil { + return err + } + } + + if len(cidIndex) > 0 { + err := ctx.Err() + if err == nil { + err = xerrors.Errorf("failed to fetch %d messages for unknown reasons", len(cidIndex)) + } + return err + } + + return nil +} + type BlockValidator struct { - peers *lru.TwoQueueCache + self peer.ID + + peers *lru.TwoQueueCache[peer.ID, int] killThresh int @@ -104,236 +226,76 @@ type BlockValidator struct { blacklist func(peer.ID) // necessary for block validation - chain *store.ChainStore - stmgr *stmgr.StateManager - - mx sync.Mutex - keycache map[string]address.Address + chain *store.ChainStore + consensus consensus.Consensus } -func NewBlockValidator(chain *store.ChainStore, stmgr *stmgr.StateManager, blacklist func(peer.ID)) *BlockValidator { - p, _ := lru.New2Q(4096) +func NewBlockValidator(self peer.ID, chain *store.ChainStore, cns consensus.Consensus, blacklist func(peer.ID)) *BlockValidator { + p, _ := lru.New2Q[peer.ID, int](4096) return &BlockValidator{ + self: self, peers: p, killThresh: 10, blacklist: blacklist, recvBlocks: newBlockReceiptCache(), chain: chain, - stmgr: stmgr, - keycache: make(map[string]address.Address), + consensus: cns, } } func (bv *BlockValidator) flagPeer(p peer.ID) { - v, ok := bv.peers.Get(p) + val, ok := bv.peers.Get(p) if !ok { - bv.peers.Add(p, int(1)) + bv.peers.Add(p, 1) return } - val := v.(int) - if val >= bv.killThresh { log.Warnf("blacklisting peer %s", p) bv.blacklist(p) return } - bv.peers.Add(p, v.(int)+1) + bv.peers.Add(p, val+1) } -func (bv *BlockValidator) Validate(ctx context.Context, pid peer.ID, msg *pubsub.Message) pubsub.ValidationResult { - // track validation time - begin := time.Now() +func (bv *BlockValidator) Validate(ctx context.Context, pid peer.ID, msg *pubsub.Message) (res pubsub.ValidationResult) { defer func() { - end := time.Now() - log.Infof("block validation time: %s", end.Sub(begin)) + if rerr := recover(); rerr != nil { + err := xerrors.Errorf("validate block: %s", rerr) + recordFailure(ctx, metrics.BlockValidationFailure, err.Error()) + bv.flagPeer(pid) + res = pubsub.ValidationReject + return + } }() - stats.Record(ctx, metrics.BlockReceived.M(1)) + var what string + res, what = consensus.ValidateBlockPubsub(ctx, bv.consensus, pid == bv.self, msg) + if res == pubsub.ValidationAccept { + // it's a good block! make sure we've only seen it once + if count := bv.recvBlocks.add(msg.ValidatorData.(*types.BlockMsg).Cid()); count > 0 { + if pid == bv.self { + log.Warnf("local block has been seen %d times; ignoring", count) + } - recordFailure := func(what string) { - ctx, _ = tag.New(ctx, tag.Insert(metrics.FailureType, what)) - stats.Record(ctx, metrics.BlockValidationFailure.M(1)) - bv.flagPeer(pid) - } - - // make sure the block can be decoded - blk, err := types.DecodeBlockMsg(msg.GetData()) - if err != nil { - log.Error("got invalid block over pubsub: ", err) - recordFailure("invalid") - return pubsub.ValidationReject - } - - // check the message limit constraints - if len(blk.BlsMessages)+len(blk.SecpkMessages) > build.BlockMessageLimit { - log.Warnf("received block with too many messages over pubsub") - recordFailure("too_many_messages") - return pubsub.ValidationReject - } - - // make sure we have a signature - if blk.Header.BlockSig == nil { - log.Warnf("received block without a signature over pubsub") - recordFailure("missing_signature") - return pubsub.ValidationReject - } - - // validate the block meta: the Message CID in the header must match the included messages - err = bv.validateMsgMeta(ctx, blk) - if err != nil { - log.Warnf("error validating message metadata: %s", err) - recordFailure("invalid_block_meta") - return pubsub.ValidationReject - } - - // we want to ensure that it is a block from a known miner; we reject blocks from unknown miners - // to prevent spam attacks. - // the logic works as follows: we lookup the miner in the chain for its key. - // if we can find it then it's a known miner and we can validate the signature. - // if we can't find it, we check whether we are (near) synced in the chain. - // if we are not synced we cannot validate the block and we must ignore it. - // if we are synced and the miner is unknown, then the block is rejcected. - key, err := bv.getMinerWorkerKey(ctx, blk) - if err != nil { - if bv.isChainNearSynced() { - log.Warnf("received block message from unknown miner over pubsub; rejecting message") - recordFailure("unknown_miner") - return pubsub.ValidationReject - } else { - log.Warnf("cannot validate block message; unknown miner in unsynced chain") + // TODO: once these changes propagate to the network, we can consider + // dropping peers who send us the same block multiple times return pubsub.ValidationIgnore } + } else { + recordFailure(ctx, metrics.BlockValidationFailure, what) } - err = sigs.CheckBlockSignature(blk.Header, ctx, key) - if err != nil { - log.Errorf("block signature verification failed: %s", err) - recordFailure("signature_verification_failed") - return pubsub.ValidationReject - } - - // it's a good block! make sure we've only seen it once - if bv.recvBlocks.add(blk.Header.Cid()) > 0 { - // TODO: once these changes propagate to the network, we can consider - // dropping peers who send us the same block multiple times - return pubsub.ValidationIgnore - } - - // all good, accept the block - msg.ValidatorData = blk - stats.Record(ctx, metrics.BlockValidationSuccess.M(1)) - return pubsub.ValidationAccept -} - -func (bv *BlockValidator) isChainNearSynced() bool { - ts := bv.chain.GetHeaviestTipSet() - timestamp := ts.MinTimestamp() - now := time.Now().UnixNano() - cutoff := uint64(now) - uint64(6*time.Hour) - return timestamp > cutoff -} - -func (bv *BlockValidator) validateMsgMeta(ctx context.Context, msg *types.BlockMsg) error { - var bcids, scids []cbg.CBORMarshaler - for _, m := range msg.BlsMessages { - c := cbg.CborCid(m) - bcids = append(bcids, &c) - } - - for _, m := range msg.SecpkMessages { - c := cbg.CborCid(m) - scids = append(scids, &c) - } - - // TODO there has to be a simpler way to do this without the blockstore dance - bs := cbor.NewCborStore(bstore.NewBlockstore(dstore.NewMapDatastore())) - - bmroot, err := amt.FromArray(ctx, bs, bcids) - if err != nil { - return err - } - - smroot, err := amt.FromArray(ctx, bs, scids) - if err != nil { - return err - } - - mrcid, err := bs.Put(ctx, &types.MsgMeta{ - BlsMessages: bmroot, - SecpkMessages: smroot, - }) - - if err != nil { - return err - } - - if msg.Header.Messages != mrcid { - return fmt.Errorf("messages didn't match root cid in header") - } - - return nil -} - -func (bv *BlockValidator) getMinerWorkerKey(ctx context.Context, msg *types.BlockMsg) (address.Address, error) { - addr := msg.Header.Miner - - bv.mx.Lock() - key, ok := bv.keycache[addr.String()] - bv.mx.Unlock() - if ok { - return key, nil - } - - // TODO I have a feeling all this can be simplified by cleverer DI to use the API - ts := bv.chain.GetHeaviestTipSet() - st, _, err := bv.stmgr.TipSetState(ctx, ts) - if err != nil { - return address.Undef, err - } - buf := bufbstore.NewBufferedBstore(bv.chain.Blockstore()) - cst := cbor.NewCborStore(buf) - state, err := state.LoadStateTree(cst, st) - if err != nil { - return address.Undef, err - } - act, err := state.GetActor(addr) - if err != nil { - return address.Undef, err - } - - blk, err := bv.chain.Blockstore().Get(act.Head) - if err != nil { - return address.Undef, err - } - aso := blk.RawData() - - var mst miner.State - err = mst.UnmarshalCBOR(bytes.NewReader(aso)) - if err != nil { - return address.Undef, err - } - - worker := mst.Info.Worker - key, err = bv.stmgr.ResolveToKeyAddress(ctx, worker, ts) - if err != nil { - return address.Undef, err - } - - bv.mx.Lock() - bv.keycache[addr.String()] = key - bv.mx.Unlock() - - return key, nil + return res } type blockReceiptCache struct { - blocks *lru.TwoQueueCache + blocks *lru.TwoQueueCache[cid.Cid, int] } func newBlockReceiptCache() *blockReceiptCache { - c, _ := lru.New2Q(8192) + c, _ := lru.New2Q[cid.Cid, int](8192) return &blockReceiptCache{ blocks: c, @@ -343,23 +305,34 @@ func newBlockReceiptCache() *blockReceiptCache { func (brc *blockReceiptCache) add(bcid cid.Cid) int { val, ok := brc.blocks.Get(bcid) if !ok { - brc.blocks.Add(bcid, int(1)) + brc.blocks.Add(bcid, 1) return 0 } - brc.blocks.Add(bcid, val.(int)+1) - return val.(int) + brc.blocks.Add(bcid, val+1) + return val } type MessageValidator struct { + self peer.ID mpool *messagepool.MessagePool } -func NewMessageValidator(mp *messagepool.MessagePool) *MessageValidator { - return &MessageValidator{mp} +func NewMessageValidator(self peer.ID, mp *messagepool.MessagePool) *MessageValidator { + return &MessageValidator{self: self, mpool: mp} } func (mv *MessageValidator) Validate(ctx context.Context, pid peer.ID, msg *pubsub.Message) pubsub.ValidationResult { + if pid == mv.self { + return mv.validateLocalMessage(ctx, msg) + } + + start := time.Now() + defer func() { + ms := time.Now().Sub(start).Microseconds() + stats.Record(ctx, metrics.MessageValidationDuration.M(float64(ms)/1000)) + }() + stats.Record(ctx, metrics.MessageReceived.M(1)) m, err := types.DecodeSignedMessage(msg.Message.GetData()) if err != nil { @@ -369,20 +342,91 @@ func (mv *MessageValidator) Validate(ctx context.Context, pid peer.ID, msg *pubs return pubsub.ValidationReject } - if err := mv.mpool.Add(m); err != nil { + if err := mv.mpool.Add(ctx, m); err != nil { log.Debugf("failed to add message from network to message pool (From: %s, To: %s, Nonce: %d, Value: %s): %s", m.Message.From, m.Message.To, m.Message.Nonce, types.FIL(m.Message.Value), err) ctx, _ = tag.New( ctx, - tag.Insert(metrics.FailureType, "add"), + tag.Upsert(metrics.Local, "false"), ) - stats.Record(ctx, metrics.MessageValidationFailure.M(1)) + recordFailure(ctx, metrics.MessageValidationFailure, "add") switch { - case xerrors.Is(err, messagepool.ErrBroadcastAnyway): + case xerrors.Is(err, messagepool.ErrSoftValidationFailure): + fallthrough + case xerrors.Is(err, messagepool.ErrRBFTooLowPremium): + fallthrough + case xerrors.Is(err, messagepool.ErrTooManyPendingMessages): + fallthrough + case xerrors.Is(err, messagepool.ErrNonceGap): + fallthrough + case xerrors.Is(err, messagepool.ErrGasFeeCapTooLow): + fallthrough + case xerrors.Is(err, messagepool.ErrNonceTooLow): return pubsub.ValidationIgnore default: return pubsub.ValidationReject } } + + ctx, _ = tag.New( + ctx, + tag.Upsert(metrics.MsgValid, "true"), + ) + + stats.Record(ctx, metrics.MessageValidationSuccess.M(1)) + return pubsub.ValidationAccept +} + +func (mv *MessageValidator) validateLocalMessage(ctx context.Context, msg *pubsub.Message) pubsub.ValidationResult { + ctx, _ = tag.New( + ctx, + tag.Upsert(metrics.Local, "true"), + ) + + start := time.Now() + defer func() { + ms := time.Now().Sub(start).Microseconds() + stats.Record(ctx, metrics.MessageValidationDuration.M(float64(ms)/1000)) + }() + + // do some lightweight validation + stats.Record(ctx, metrics.MessagePublished.M(1)) + + m, err := types.DecodeSignedMessage(msg.Message.GetData()) + if err != nil { + log.Warnf("failed to decode local message: %s", err) + recordFailure(ctx, metrics.MessageValidationFailure, "decode") + return pubsub.ValidationIgnore + } + + if m.Size() > messagepool.MaxMessageSize { + log.Warnf("local message is too large! (%dB)", m.Size()) + recordFailure(ctx, metrics.MessageValidationFailure, "oversize") + return pubsub.ValidationIgnore + } + + if m.Message.To == address.Undef { + log.Warn("local message has invalid destination address") + recordFailure(ctx, metrics.MessageValidationFailure, "undef-addr") + return pubsub.ValidationIgnore + } + + if !m.Message.Value.LessThan(types.TotalFilecoinInt) { + log.Warnf("local messages has too high value: %s", m.Message.Value) + recordFailure(ctx, metrics.MessageValidationFailure, "value-too-high") + return pubsub.ValidationIgnore + } + + if err := mv.mpool.VerifyMsgSig(m); err != nil { + log.Warnf("signature verification failed for local message: %s", err) + recordFailure(ctx, metrics.MessageValidationFailure, "verify-sig") + return pubsub.ValidationIgnore + } + + ctx, _ = tag.New( + ctx, + tag.Upsert(metrics.MsgValid, "true"), + ) + stats.Record(ctx, metrics.MessageValidationSuccess.M(1)) return pubsub.ValidationAccept } @@ -402,3 +446,173 @@ func HandleIncomingMessages(ctx context.Context, mpool *messagepool.MessagePool, // Do nothing... everything happens in validate } } + +func recordFailure(ctx context.Context, metric *stats.Int64Measure, failureType string) { + ctx, _ = tag.New( + ctx, + tag.Upsert(metrics.FailureType, failureType), + ) + stats.Record(ctx, metric.M(1)) +} + +type peerMsgInfo struct { + peerID peer.ID + lastCid cid.Cid + lastSeqno uint64 + rateLimit *ratelimit.Window + mutex sync.Mutex +} + +type IndexerMessageValidator struct { + self peer.ID + + peerCache *lru.TwoQueueCache[address.Address, *peerMsgInfo] + chainApi full.ChainModuleAPI + stateApi full.StateModuleAPI +} + +func NewIndexerMessageValidator(self peer.ID, chainApi full.ChainModuleAPI, stateApi full.StateModuleAPI) *IndexerMessageValidator { + peerCache, _ := lru.New2Q[address.Address, *peerMsgInfo](8192) + + return &IndexerMessageValidator{ + self: self, + peerCache: peerCache, + chainApi: chainApi, + stateApi: stateApi, + } +} + +func (v *IndexerMessageValidator) Validate(ctx context.Context, pid peer.ID, msg *pubsub.Message) pubsub.ValidationResult { + // This chain-node should not be publishing its own messages. These are + // relayed from market-nodes. If a node appears to be local, reject it. + if pid == v.self { + log.Debug("ignoring indexer message from self") + stats.Record(ctx, metrics.IndexerMessageValidationFailure.M(1)) + return pubsub.ValidationIgnore + } + originPeer := msg.GetFrom() + if originPeer == v.self { + log.Debug("ignoring indexer message originating from self") + stats.Record(ctx, metrics.IndexerMessageValidationFailure.M(1)) + return pubsub.ValidationIgnore + } + + idxrMsg := message.Message{} + err := idxrMsg.UnmarshalCBOR(bytes.NewBuffer(msg.Data)) + if err != nil { + log.Errorw("Could not decode indexer pubsub message", "err", err) + return pubsub.ValidationReject + } + if len(idxrMsg.ExtraData) == 0 { + log.Debugw("ignoring messsage missing miner id", "peer", originPeer) + return pubsub.ValidationIgnore + } + + // Get miner info from lotus + minerAddr, err := address.NewFromBytes(idxrMsg.ExtraData) + if err != nil { + log.Warnw("cannot parse extra data as miner address", "err", err, "extraData", idxrMsg.ExtraData) + return pubsub.ValidationReject + } + + msgCid := idxrMsg.Cid + + var msgInfo *peerMsgInfo + msgInfo, ok := v.peerCache.Get(minerAddr) + if !ok { + msgInfo = &peerMsgInfo{} + } + + // Lock this peer's message info. + msgInfo.mutex.Lock() + defer msgInfo.mutex.Unlock() + + if ok { + // Reject replayed messages. + seqno := binary.BigEndian.Uint64(msg.Message.GetSeqno()) + if seqno <= msgInfo.lastSeqno { + log.Debugf("ignoring replayed indexer message") + return pubsub.ValidationIgnore + } + msgInfo.lastSeqno = seqno + } + + if !ok || originPeer != msgInfo.peerID { + // Check that the miner ID maps to the peer that sent the message. + err = v.authenticateMessage(ctx, minerAddr, originPeer) + if err != nil { + log.Warnw("cannot authenticate messsage", "err", err, "peer", originPeer, "minerID", minerAddr) + stats.Record(ctx, metrics.IndexerMessageValidationFailure.M(1)) + return pubsub.ValidationReject + } + msgInfo.peerID = originPeer + if !ok { + // Add msgInfo to cache only after being authenticated. If two + // messages from the same peer are handled concurrently, there is a + // small chance that one msgInfo could replace the other here when + // the info is first cached. This is OK, so no need to prevent it. + v.peerCache.Add(minerAddr, msgInfo) + } + } + + // See if message needs to be ignored due to rate limiting. + if v.rateLimitPeer(msgInfo, msgCid) { + return pubsub.ValidationIgnore + } + + stats.Record(ctx, metrics.IndexerMessageValidationSuccess.M(1)) + return pubsub.ValidationAccept +} + +func (v *IndexerMessageValidator) rateLimitPeer(msgInfo *peerMsgInfo, msgCid cid.Cid) bool { + const ( + msgLimit = 5 + msgTimeLimit = 10 * time.Second + repeatTimeLimit = 2 * time.Hour + ) + + timeWindow := msgInfo.rateLimit + + // Check overall message rate. + if timeWindow == nil { + timeWindow = ratelimit.NewWindow(msgLimit, msgTimeLimit) + msgInfo.rateLimit = timeWindow + } else if msgInfo.lastCid == msgCid { + // Check if this is a repeat of the previous message data. + if time.Since(timeWindow.Newest()) < repeatTimeLimit { + log.Warnw("ignoring repeated indexer message", "sender", msgInfo.peerID) + return true + } + } + + err := timeWindow.Add() + if err != nil { + log.Warnw("ignoring indexer message", "sender", msgInfo.peerID, "err", err) + return true + } + + msgInfo.lastCid = msgCid + + return false +} + +func (v *IndexerMessageValidator) authenticateMessage(ctx context.Context, minerAddress address.Address, peerID peer.ID) error { + ts, err := v.chainApi.ChainHead(ctx) + if err != nil { + return err + } + + minerInfo, err := v.stateApi.StateMinerInfo(ctx, minerAddress, ts.Key()) + if err != nil { + return err + } + + if minerInfo.PeerId == nil { + return xerrors.New("no peer id for miner") + } + if *minerInfo.PeerId != peerID { + return xerrors.New("miner id does not map to peer that sent message") + } + + return nil +} diff --git a/chain/sub/incoming_test.go b/chain/sub/incoming_test.go new file mode 100644 index 000000000..f54e09049 --- /dev/null +++ b/chain/sub/incoming_test.go @@ -0,0 +1,136 @@ +// stm: #unit +package sub + +import ( + "bytes" + "context" + "testing" + + "github.com/golang/mock/gomock" + blocks "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" + "github.com/ipni/go-libipni/announce/message" + pubsub "github.com/libp2p/go-libp2p-pubsub" + pb "github.com/libp2p/go-libp2p-pubsub/pb" + "github.com/libp2p/go-libp2p/core/peer" + + "github.com/filecoin-project/go-address" + + "github.com/filecoin-project/lotus/api/mocks" + "github.com/filecoin-project/lotus/chain/types" +) + +type getter struct { + msgs []*types.Message +} + +func (g *getter) GetBlock(ctx context.Context, c cid.Cid) (blocks.Block, error) { panic("NYI") } + +func (g *getter) GetBlocks(ctx context.Context, ks []cid.Cid) <-chan blocks.Block { + ch := make(chan blocks.Block, len(g.msgs)) + for _, m := range g.msgs { + by, err := m.Serialize() + if err != nil { + panic(err) + } + b, err := blocks.NewBlockWithCid(by, m.Cid()) + if err != nil { + panic(err) + } + ch <- b + } + close(ch) + return ch +} + +func TestFetchCidsWithDedup(t *testing.T) { + msgs := []*types.Message{} + for i := 0; i < 10; i++ { + msgs = append(msgs, &types.Message{ + From: address.TestAddress, + To: address.TestAddress, + + Nonce: uint64(i), + }) + } + cids := []cid.Cid{} + for _, m := range msgs { + cids = append(cids, m.Cid()) + } + g := &getter{msgs} + + //stm: @CHAIN_INCOMING_FETCH_MESSAGES_BY_CID_001 + // the cids have a duplicate + res, err := FetchMessagesByCids(context.TODO(), g, append(cids, cids[0])) + + t.Logf("err: %+v", err) + t.Logf("res: %+v", res) + if err == nil { + t.Errorf("there should be an error") + } + if err == nil && (res[0] == nil || res[len(res)-1] == nil) { + t.Fatalf("there is a nil message: first %p, last %p", res[0], res[len(res)-1]) + } +} + +func TestIndexerMessageValidator_Validate(t *testing.T) { + validCid, err := cid.Decode("QmbpDgg5kRLDgMxS8vPKNFXEcA6D5MC4CkuUdSWDVtHPGK") + if err != nil { + t.Fatal(err) + } + tests := []struct { + name string + selfPID string + senderPID string + extraData []byte + wantValidation pubsub.ValidationResult + }{ + { + name: "invalid extra data is rejected", + selfPID: "12D3KooWQiCbqEStCkdqUvr69gQsrp9urYJZUCkzsQXia7mbqbFW", + senderPID: "12D3KooWE8yt84RVwW3sFcd6WMjbUdWrZer2YtT4dmtj3dHdahSZ", + extraData: []byte("f0127896"), // note, casting encoded address to byte is invalid. + wantValidation: pubsub.ValidationReject, + }, + { + name: "same sender and receiver is ignored", + selfPID: "12D3KooWQiCbqEStCkdqUvr69gQsrp9urYJZUCkzsQXia7mbqbFW", + senderPID: "12D3KooWQiCbqEStCkdqUvr69gQsrp9urYJZUCkzsQXia7mbqbFW", + wantValidation: pubsub.ValidationIgnore, + }, + } + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + mc := gomock.NewController(t) + node := mocks.NewMockFullNode(mc) + subject := NewIndexerMessageValidator(peer.ID(tc.selfPID), node, node) + message := message.Message{ + Cid: validCid, + Addrs: nil, + ExtraData: tc.extraData, + } + buf := bytes.NewBuffer(nil) + if err := message.MarshalCBOR(buf); err != nil { + t.Fatal(err) + } + + topic := "topic" + pbm := &pb.Message{ + Data: buf.Bytes(), + Topic: &topic, + From: nil, + Seqno: nil, + } + validate := subject.Validate(context.Background(), peer.ID(tc.senderPID), &pubsub.Message{ + Message: pbm, + ReceivedFrom: peer.ID(tc.senderPID), + ValidatorData: nil, + }) + + if validate != tc.wantValidation { + t.Fatalf("expected %v but got %v", tc.wantValidation, validate) + } + }) + } +} diff --git a/chain/sub/ratelimit/queue.go b/chain/sub/ratelimit/queue.go new file mode 100644 index 000000000..49f9bef66 --- /dev/null +++ b/chain/sub/ratelimit/queue.go @@ -0,0 +1,89 @@ +package ratelimit + +import "errors" + +var ErrRateLimitExceeded = errors.New("rate limit exceeded") + +type queue struct { + buf []int64 + count int + head int + tail int +} + +// cap returns the queue capacity +func (q *queue) cap() int { + return len(q.buf) +} + +// len returns the number of items in the queue +func (q *queue) len() int { + return q.count +} + +// push adds an element to the end of the queue. +func (q *queue) push(elem int64) error { + if q.count == len(q.buf) { + return ErrRateLimitExceeded + } + + q.buf[q.tail] = elem + // Calculate new tail position. + q.tail = q.next(q.tail) + q.count++ + return nil +} + +// pop removes and returns the element from the front of the queue. +func (q *queue) pop() int64 { + if q.count <= 0 { + panic("pop from empty queue") + } + ret := q.buf[q.head] + + // Calculate new head position. + q.head = q.next(q.head) + q.count-- + + return ret +} + +// front returns the element at the front of the queue. This is the element +// that would be returned by pop(). This call panics if the queue is empty. +func (q *queue) front() int64 { + if q.count <= 0 { + panic("front() called when empty") + } + return q.buf[q.head] +} + +// back returns the element at the back of the queue. This call panics if the +// queue is empty. +func (q *queue) back() int64 { + if q.count <= 0 { + panic("back() called when empty") + } + return q.buf[q.prev(q.tail)] +} + +// prev returns the previous buffer position wrapping around buffer. +func (q *queue) prev(i int) int { + if i == 0 { + return len(q.buf) - 1 + } + return (i - 1) % len(q.buf) +} + +// next returns the next buffer position wrapping around buffer. +func (q *queue) next(i int) int { + return (i + 1) % len(q.buf) +} + +// truncate pops values that are less than or equal the specified threshold. +func (q *queue) truncate(threshold int64) { + for q.count != 0 && q.buf[q.head] <= threshold { + // pop() without returning a value + q.head = q.next(q.head) + q.count-- + } +} diff --git a/chain/sub/ratelimit/window.go b/chain/sub/ratelimit/window.go new file mode 100644 index 000000000..0756e8998 --- /dev/null +++ b/chain/sub/ratelimit/window.go @@ -0,0 +1,70 @@ +package ratelimit + +import "time" + +// Window is a time windows for counting events within a span of time. The +// windows slides forward in time so that it spans from the most recent event +// to size time in the past. +type Window struct { + q *queue + size int64 +} + +// NewWindow creates a new Window that limits the number of events to maximum +// count of events within a duration of time. The capacity sets the maximum +// number of events, and size sets the span of time over which the events are +// counted. +func NewWindow(capacity int, size time.Duration) *Window { + return &Window{ + q: &queue{ + buf: make([]int64, capacity), + }, + size: int64(size), + } +} + +// Add attempts to append a new timestamp into the current window. Previously +// added values that are not not within `size` difference from the value being +// added are first removed. Add fails if adding the value would cause the +// window to exceed capacity. +func (w *Window) Add() error { + now := time.Now().UnixNano() + if w.Len() != 0 { + w.q.truncate(now - w.size) + } + return w.q.push(now) +} + +// Cap returns the maximum number of items the window can hold. +func (w *Window) Cap() int { + return w.q.cap() +} + +// Len returns the number of elements currently in the window. +func (w *Window) Len() int { + return w.q.len() +} + +// Span returns the distance from the first to the last item in the window. +func (w *Window) Span() time.Duration { + if w.q.len() < 2 { + return 0 + } + return time.Duration(w.q.back() - w.q.front()) +} + +// Oldest returns the oldest timestamp in the window. +func (w *Window) Oldest() time.Time { + if w.q.len() == 0 { + return time.Time{} + } + return time.Unix(0, w.q.front()) +} + +// Newest returns the newest timestamp in the window. +func (w *Window) Newest() time.Time { + if w.q.len() == 0 { + return time.Time{} + } + return time.Unix(0, w.q.back()) +} diff --git a/chain/sub/ratelimit/window_test.go b/chain/sub/ratelimit/window_test.go new file mode 100644 index 000000000..c86b65ef7 --- /dev/null +++ b/chain/sub/ratelimit/window_test.go @@ -0,0 +1,61 @@ +package ratelimit + +import ( + "testing" + "time" +) + +func TestWindow(t *testing.T) { + const ( + maxEvents = 3 + timeLimit = 100 * time.Millisecond + ) + w := NewWindow(maxEvents, timeLimit) + if w.Len() != 0 { + t.Fatal("q.Len() =", w.Len(), "expect 0") + } + if w.Cap() != maxEvents { + t.Fatal("q.Cap() =", w.Cap(), "expect 3") + } + if !w.Newest().IsZero() { + t.Fatal("expected newest to be zero time with empty window") + } + if !w.Oldest().IsZero() { + t.Fatal("expected oldest to be zero time with empty window") + } + if w.Span() != 0 { + t.Fatal("expected span to be zero time with empty window") + } + + var err error + for i := 0; i < maxEvents; i++ { + err = w.Add() + if err != nil { + t.Fatalf("cannot add event %d", i) + } + } + if w.Len() != maxEvents { + t.Fatalf("q.Len() is %d, expected %d", w.Len(), maxEvents) + } + if err = w.Add(); err != ErrRateLimitExceeded { + t.Fatalf("add event %d within time limit should have failed with err: %s", maxEvents+1, ErrRateLimitExceeded) + } + + time.Sleep(timeLimit) + if err = w.Add(); err != nil { + t.Fatalf("cannot add event after time limit: %s", err) + } + + prev := w.Newest() + time.Sleep(timeLimit) + err = w.Add() + if err != nil { + t.Fatalf("cannot add event") + } + if w.Newest().Before(prev) { + t.Fatal("newest is before previous value") + } + if w.Oldest().Before(prev) { + t.Fatal("oldest is before previous value") + } +} diff --git a/chain/sync.go b/chain/sync.go index 23b790a06..7830a9771 100644 --- a/chain/sync.go +++ b/chain/sync.go @@ -5,65 +5,93 @@ import ( "context" "errors" "fmt" - "os" "sort" - "strings" "sync" "time" "github.com/Gurpartap/async" "github.com/hashicorp/go-multierror" + blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" - dstore "github.com/ipfs/go-datastore" - bstore "github.com/ipfs/go-ipfs-blockstore" cbor "github.com/ipfs/go-ipld-cbor" + ipld "github.com/ipfs/go-ipld-format" logging "github.com/ipfs/go-log/v2" - "github.com/libp2p/go-libp2p-core/connmgr" - "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p/core/connmgr" + "github.com/libp2p/go-libp2p/core/peer" cbg "github.com/whyrusleeping/cbor-gen" - "github.com/whyrusleeping/pubsub" "go.opencensus.io/stats" "go.opencensus.io/trace" "golang.org/x/xerrors" - bls "github.com/filecoin-project/filecoin-ffi" - "github.com/filecoin-project/go-address" - amt "github.com/filecoin-project/go-amt-ipld/v2" - "github.com/filecoin-project/sector-storage/ffiwrapper" - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/builtin" - "github.com/filecoin-project/specs-actors/actors/builtin/power" - "github.com/filecoin-project/specs-actors/actors/crypto" - "github.com/filecoin-project/specs-actors/actors/util/adt" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/pubsub" + + "github.com/filecoin-project/lotus/chain/consensus" + "github.com/filecoin-project/lotus/node/modules/dtypes" + + // named msgarray here to make it clear that these are the types used by + // messages, regardless of specs-actors version. + blockadt "github.com/filecoin-project/specs-actors/actors/util/adt" "github.com/filecoin-project/lotus/api" + bstore "github.com/filecoin-project/lotus/blockstore" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/beacon" - "github.com/filecoin-project/lotus/chain/blocksync" - "github.com/filecoin-project/lotus/chain/gen" - "github.com/filecoin-project/lotus/chain/state" + "github.com/filecoin-project/lotus/chain/exchange" "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/lib/sigs" "github.com/filecoin-project/lotus/metrics" ) -var log = logging.Logger("chain") +var ( + // LocalIncoming is the _local_ pubsub (unrelated to libp2p pubsub) topic + // where the Syncer publishes candidate chain heads to be synced. + LocalIncoming = "incoming" -var LocalIncoming = "incoming" + log = logging.Logger("chain") + concurrentSyncRequests = exchange.ShufflePeersPrefix + syncRequestBatchSize = 8 + syncRequestRetries = 5 +) + +// Syncer is in charge of running the chain synchronization logic. As such, it +// is tasked with these functions, amongst others: +// +// - Fast-forwards the chain as it learns of new TipSets from the network via +// the SyncManager. +// - Applies the fork choice rule to select the correct side when confronted +// with a fork in the network. +// - Requests block headers and messages from other peers when not available +// in our BlockStore. +// - Tracks blocks marked as bad in a cache. +// - Keeps the BlockStore and ChainStore consistent with our view of the world, +// the latter of which in turn informs other components when a reorg has been +// committed. +// +// The Syncer does not run workers itself. It's mainly concerned with +// ensuring a consistent state of chain consensus. The reactive and network- +// interfacing processes are part of other components, such as the SyncManager +// (which owns the sync scheduler and sync workers), ChainExchange, the HELLO +// protocol, and the gossipsub block propagation layer. +// +// {hint/concept} The fork-choice rule as it currently stands is: "pick the +// chain with the heaviest weight, so long as it hasn’t deviated one finality +// threshold from our head (900 epochs, parameter determined by spec-actors)". type Syncer struct { // The interface for accessing and putting tipsets into local storage store *store.ChainStore // handle to the random beacon for verification - beacon beacon.RandomBeacon + beacon beacon.Schedule // the state manager handles making state queries sm *stmgr.StateManager + consensus consensus.Consensus + // The known Genesis tipset Genesis *types.TipSet @@ -71,11 +99,11 @@ type Syncer struct { bad *BadBlockCache // handle to the block sync service - Bsync *blocksync.BlockSync + Exchange exchange.Client self peer.ID - syncmgr *SyncManager + syncmgr SyncManager connmgr connmgr.ConnManager @@ -83,57 +111,108 @@ type Syncer struct { receiptTracker *blockReceiptTracker - verifier ffiwrapper.Verifier + tickerCtxCancel context.CancelFunc + + ds dtypes.MetadataDS } -func NewSyncer(sm *stmgr.StateManager, bsync *blocksync.BlockSync, connmgr connmgr.ConnManager, self peer.ID, beacon beacon.RandomBeacon, verifier ffiwrapper.Verifier) (*Syncer, error) { - gen, err := sm.ChainStore().GetGenesis() +type SyncManagerCtor func(syncFn SyncFunc) SyncManager + +type Genesis *types.TipSet + +func LoadGenesis(ctx context.Context, sm *stmgr.StateManager) (Genesis, error) { + gen, err := sm.ChainStore().GetGenesis(ctx) if err != nil { return nil, xerrors.Errorf("getting genesis block: %w", err) } - gent, err := types.NewTipSet([]*types.BlockHeader{gen}) - if err != nil { - return nil, err - } + return types.NewTipSet([]*types.BlockHeader{gen}) +} + +// NewSyncer creates a new Syncer object. +func NewSyncer(ds dtypes.MetadataDS, + sm *stmgr.StateManager, + exchange exchange.Client, + syncMgrCtor SyncManagerCtor, + connmgr connmgr.ConnManager, + self peer.ID, + beacon beacon.Schedule, + gent Genesis, + consensus consensus.Consensus) (*Syncer, error) { s := &Syncer{ + ds: ds, beacon: beacon, bad: NewBadBlockCache(), Genesis: gent, - Bsync: bsync, + consensus: consensus, + Exchange: exchange, store: sm.ChainStore(), sm: sm, self: self, receiptTracker: newBlockReceiptTracker(), connmgr: connmgr, - verifier: verifier, incoming: pubsub.New(50), } - s.syncmgr = NewSyncManager(s.Sync) + s.syncmgr = syncMgrCtor(s.Sync) return s, nil } func (syncer *Syncer) Start() { + tickerCtx, tickerCtxCancel := context.WithCancel(context.Background()) syncer.syncmgr.Start() + + syncer.tickerCtxCancel = tickerCtxCancel + + go syncer.runMetricsTricker(tickerCtx) +} + +func (syncer *Syncer) runMetricsTricker(tickerCtx context.Context) { + genesisTime := time.Unix(int64(syncer.Genesis.MinTimestamp()), 0) + ticker := build.Clock.Ticker(time.Duration(build.BlockDelaySecs) * time.Second) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + sinceGenesis := build.Clock.Now().Sub(genesisTime) + expectedHeight := int64(sinceGenesis.Seconds()) / int64(build.BlockDelaySecs) + + stats.Record(tickerCtx, metrics.ChainNodeHeightExpected.M(expectedHeight)) + case <-tickerCtx.Done(): + return + } + } } func (syncer *Syncer) Stop() { syncer.syncmgr.Stop() + syncer.tickerCtxCancel() } // InformNewHead informs the syncer about a new potential tipset // This should be called when connecting to new peers, and additionally // when receiving new blocks from the network func (syncer *Syncer) InformNewHead(from peer.ID, fts *store.FullTipSet) bool { + defer func() { + if err := recover(); err != nil { + log.Errorf("panic in InformNewHead: %s", err) + } + }() + ctx := context.Background() if fts == nil { log.Errorf("got nil tipset in InformNewHead") return false } + if !syncer.consensus.IsEpochInConsensusRange(fts.TipSet().Height()) { + log.Infof("received block outside of consensus range at height %d", fts.TipSet().Height()) + return false + } + for _, b := range fts.Blocks { if reason, ok := syncer.bad.Has(b.Cid()); ok { log.Warnf("InformNewHead called on block marked as bad: %s (reason: %s)", b.Cid(), reason) @@ -147,35 +226,24 @@ func (syncer *Syncer) InformNewHead(from peer.ID, fts *store.FullTipSet) bool { syncer.incoming.Pub(fts.TipSet().Blocks(), LocalIncoming) - if from == syncer.self { - // TODO: this is kindof a hack... - log.Debug("got block from ourselves") - - if err := syncer.Sync(ctx, fts.TipSet()); err != nil { - log.Errorf("failed to sync our own block %s: %+v", fts.TipSet().Cids(), err) - return false - } - - return true - } - // TODO: IMPORTANT(GARBAGE) this needs to be put in the 'temporary' side of // the blockstore - if err := syncer.store.PersistBlockHeaders(fts.TipSet().Blocks()...); err != nil { + if err := syncer.store.PersistTipsets(ctx, []*types.TipSet{fts.TipSet()}); err != nil { log.Warn("failed to persist incoming block header: ", err) return false } - syncer.Bsync.AddPeer(from) + syncer.Exchange.AddPeer(from) - bestPweight := syncer.store.GetHeaviestTipSet().Blocks()[0].ParentWeight - targetWeight := fts.TipSet().Blocks()[0].ParentWeight + hts := syncer.store.GetHeaviestTipSet() + bestPweight := hts.ParentWeight() + targetWeight := fts.TipSet().ParentWeight() if targetWeight.LessThan(bestPweight) { var miners []string for _, blk := range fts.TipSet().Blocks() { miners = append(miners, blk.Miner.String()) } - log.Infof("incoming tipset from %s does not appear to be better than our best chain, ignoring for now", miners) + log.Debugw("incoming tipset does not appear to be better than our best chain, ignoring for now", "miners", miners, "bestPweight", bestPweight, "bestTS", hts.Cids(), "incomingWeight", targetWeight, "incomingTS", fts.TipSet().Cids()) return false } @@ -183,12 +251,17 @@ func (syncer *Syncer) InformNewHead(from peer.ID, fts *store.FullTipSet) bool { return true } +// IncomingBlocks spawns a goroutine that subscribes to the local eventbus to +// receive new block headers as they arrive from the network, and sends them to +// the returned channel. +// +// These blocks have not necessarily been incorporated to our view of the chain. func (syncer *Syncer) IncomingBlocks(ctx context.Context) (<-chan *types.BlockHeader, error) { sub := syncer.incoming.Sub(LocalIncoming) out := make(chan *types.BlockHeader, 10) go func() { - defer syncer.incoming.Unsub(sub, LocalIncoming) + defer syncer.incoming.Unsub(sub) for { select { @@ -210,52 +283,54 @@ func (syncer *Syncer) IncomingBlocks(ctx context.Context) (<-chan *types.BlockHe return out, nil } +// ValidateMsgMeta performs structural and content hash validation of the +// messages within this block. If validation passes, it stores the messages in +// the underlying IPLD block store. func (syncer *Syncer) ValidateMsgMeta(fblk *types.FullBlock) error { if msgc := len(fblk.BlsMessages) + len(fblk.SecpkMessages); msgc > build.BlockMessageLimit { return xerrors.Errorf("block %s has too many messages (%d)", fblk.Header.Cid(), msgc) } - var bcids, scids []cbg.CBORMarshaler - for _, m := range fblk.BlsMessages { - c := cbg.CborCid(m.Cid()) - bcids = append(bcids, &c) - } - - for _, m := range fblk.SecpkMessages { - c := cbg.CborCid(m.Cid()) - scids = append(scids, &c) - } - // TODO: IMPORTANT(GARBAGE). These message puts and the msgmeta // computation need to go into the 'temporary' side of the blockstore when // we implement that - blockstore := syncer.store.Blockstore() - bs := cbor.NewCborStore(blockstore) - smroot, err := computeMsgMeta(bs, bcids, scids) + // We use a temporary bstore here to avoid writing intermediate pieces + // into the blockstore. + blockstore := bstore.NewMemory() + cst := cbor.NewCborStore(blockstore) + ctx := context.Background() + var bcids, scids []cid.Cid + + for _, m := range fblk.BlsMessages { + c, err := store.PutMessage(ctx, blockstore, m) + if err != nil { + return xerrors.Errorf("putting bls message to blockstore after msgmeta computation: %w", err) + } + bcids = append(bcids, c) + } + + for _, m := range fblk.SecpkMessages { + c, err := store.PutMessage(ctx, blockstore, m) + if err != nil { + return xerrors.Errorf("putting bls message to blockstore after msgmeta computation: %w", err) + } + scids = append(scids, c) + } + + // Compute the root CID of the combined message trie. + smroot, err := computeMsgMeta(cst, bcids, scids) if err != nil { return xerrors.Errorf("validating msgmeta, compute failed: %w", err) } + // Check that the message trie root matches with what's in the block. if fblk.Header.Messages != smroot { return xerrors.Errorf("messages in full block did not match msgmeta root in header (%s != %s)", fblk.Header.Messages, smroot) } - for _, m := range fblk.BlsMessages { - _, err := store.PutMessage(blockstore, m) - if err != nil { - return xerrors.Errorf("putting bls message to blockstore after msgmeta computation: %w", err) - } - } - - for _, m := range fblk.SecpkMessages { - _, err := store.PutMessage(blockstore, m) - if err != nil { - return xerrors.Errorf("putting bls message to blockstore after msgmeta computation: %w", err) - } - } - - return nil + // Finally, flush. + return vm.Copy(context.TODO(), blockstore, syncer.store.ChainBlockstore(), smroot) } func (syncer *Syncer) LocalPeer() peer.ID { @@ -274,21 +349,28 @@ func (syncer *Syncer) InformNewBlock(from peer.ID, blk *types.FullBlock) bool { return syncer.InformNewHead(from, fts) } -func copyBlockstore(from, to bstore.Blockstore) error { - cids, err := from.AllKeysChan(context.TODO()) +func copyBlockstore(ctx context.Context, from, to bstore.Blockstore) error { + ctx, span := trace.StartSpan(ctx, "copyBlockstore") + defer span.End() + + cids, err := from.AllKeysChan(ctx) if err != nil { return err } + // TODO: should probably expose better methods on the blockstore for this operation + var blks []blocks.Block for c := range cids { - b, err := from.Get(c) + b, err := from.Get(ctx, c) if err != nil { return err } - if err := to.Put(b); err != nil { - return err - } + blks = append(blks, b) + } + + if err := to.PutMany(ctx, blks); err != nil { + return err } return nil @@ -303,35 +385,21 @@ func zipTipSetAndMessages(bs cbor.IpldStore, ts *types.TipSet, allbmsgs []*types return nil, fmt.Errorf("msgincl length didnt match tipset size") } + if err := checkMsgMeta(ts, allbmsgs, allsmsgs, bmi, smi); err != nil { + return nil, err + } + fts := &store.FullTipSet{} for bi, b := range ts.Blocks() { + var smsgs []*types.SignedMessage - var smsgCids []cbg.CBORMarshaler for _, m := range smi[bi] { smsgs = append(smsgs, allsmsgs[m]) - c := cbg.CborCid(allsmsgs[m].Cid()) - smsgCids = append(smsgCids, &c) } var bmsgs []*types.Message - var bmsgCids []cbg.CBORMarshaler for _, m := range bmi[bi] { bmsgs = append(bmsgs, allbmsgs[m]) - c := cbg.CborCid(allbmsgs[m].Cid()) - bmsgCids = append(bmsgCids, &c) - } - - if msgc := len(bmsgCids) + len(smsgCids); msgc > build.BlockMessageLimit { - return nil, fmt.Errorf("block %q has too many messages (%d)", b.Cid(), msgc) - } - - mrcid, err := computeMsgMeta(bs, bmsgCids, smsgCids) - if err != nil { - return nil, err - } - - if b.Messages != mrcid { - return nil, fmt.Errorf("messages didnt match message root in header for ts %s", ts.Key()) } fb := &types.FullBlock{ @@ -346,19 +414,39 @@ func zipTipSetAndMessages(bs cbor.IpldStore, ts *types.TipSet, allbmsgs []*types return fts, nil } -func computeMsgMeta(bs cbor.IpldStore, bmsgCids, smsgCids []cbg.CBORMarshaler) (cid.Cid, error) { - ctx := context.TODO() - bmroot, err := amt.FromArray(ctx, bs, bmsgCids) +// computeMsgMeta computes the root CID of the combined arrays of message CIDs +// of both types (BLS and Secpk). +func computeMsgMeta(bs cbor.IpldStore, bmsgCids, smsgCids []cid.Cid) (cid.Cid, error) { + // block headers use adt0 + store := blockadt.WrapStore(context.TODO(), bs) + bmArr := blockadt.MakeEmptyArray(store) + smArr := blockadt.MakeEmptyArray(store) + + for i, m := range bmsgCids { + c := cbg.CborCid(m) + if err := bmArr.Set(uint64(i), &c); err != nil { + return cid.Undef, err + } + } + + for i, m := range smsgCids { + c := cbg.CborCid(m) + if err := smArr.Set(uint64(i), &c); err != nil { + return cid.Undef, err + } + } + + bmroot, err := bmArr.Root() if err != nil { return cid.Undef, err } - smroot, err := amt.FromArray(ctx, bs, smsgCids) + smroot, err := smArr.Root() if err != nil { return cid.Undef, err } - mrcid, err := bs.Put(ctx, &types.MsgMeta{ + mrcid, err := store.Put(store.Context(), &types.MsgMeta{ BlsMessages: bmroot, SecpkMessages: smroot, }) @@ -369,23 +457,33 @@ func computeMsgMeta(bs cbor.IpldStore, bmsgCids, smsgCids []cbg.CBORMarshaler) ( return mrcid, nil } +// FetchTipSet tries to load the provided tipset from the store, and falls back +// to the network (client) by querying the supplied peer if not found +// locally. +// +// {hint/usage} This is used from the HELLO protocol, to fetch the greeting +// peer's heaviest tipset if we don't have it. func (syncer *Syncer) FetchTipSet(ctx context.Context, p peer.ID, tsk types.TipSetKey) (*store.FullTipSet, error) { - if fts, err := syncer.tryLoadFullTipSet(tsk); err == nil { + if fts, err := syncer.tryLoadFullTipSet(ctx, tsk); err == nil { return fts, nil } - return syncer.Bsync.GetFullTipSet(ctx, p, tsk) + // fall back to the network. + return syncer.Exchange.GetFullTipSet(ctx, p, tsk) } -func (syncer *Syncer) tryLoadFullTipSet(tsk types.TipSetKey) (*store.FullTipSet, error) { - ts, err := syncer.store.LoadTipSet(tsk) +// tryLoadFullTipSet queries the tipset in the ChainStore, and returns a full +// representation of it containing FullBlocks. If ALL blocks are not found +// locally, it errors entirely with blockstore.ErrNotFound. +func (syncer *Syncer) tryLoadFullTipSet(ctx context.Context, tsk types.TipSetKey) (*store.FullTipSet, error) { + ts, err := syncer.store.LoadTipSet(ctx, tsk) if err != nil { return nil, err } fts := &store.FullTipSet{} for _, b := range ts.Blocks() { - bmsgs, smsgs, err := syncer.store.MessagesForBlock(b) + bmsgs, smsgs, err := syncer.store.MessagesForBlock(ctx, b) if err != nil { return nil, err } @@ -401,6 +499,12 @@ func (syncer *Syncer) tryLoadFullTipSet(tsk types.TipSetKey) (*store.FullTipSet, return fts, nil } +// Sync tries to advance our view of the chain to `maybeHead`. It does nothing +// if our current head is heavier than the requested tipset, or if we're already +// at the requested head, or if the head is the genesis. +// +// Most of the heavy-lifting logic happens in syncer#collectChain. Refer to the +// godocs on that method for a more detailed view. func (syncer *Syncer) Sync(ctx context.Context, maybeHead *types.TipSet) error { ctx, span := trace.StartSpan(ctx, "chain.Sync") defer span.End() @@ -412,15 +516,16 @@ func (syncer *Syncer) Sync(ctx context.Context, maybeHead *types.TipSet) error { ) } - if syncer.store.GetHeaviestTipSet().ParentWeight().GreaterThan(maybeHead.ParentWeight()) { + hts := syncer.store.GetHeaviestTipSet() + + if hts.ParentWeight().GreaterThan(maybeHead.ParentWeight()) { + return nil + } + if syncer.Genesis.Equals(maybeHead) || hts.Equals(maybeHead) { return nil } - if syncer.Genesis.Equals(maybeHead) || syncer.store.GetHeaviestTipSet().Equals(maybeHead) { - return nil - } - - if err := syncer.collectChain(ctx, maybeHead); err != nil { + if err := syncer.collectChain(ctx, maybeHead, hts, false); err != nil { span.AddAttributes(trace.StringAttribute("col_error", err.Error())) span.SetStatus(trace.Status{ Code: 13, @@ -429,6 +534,8 @@ func (syncer *Syncer) Sync(ctx context.Context, maybeHead *types.TipSet) error { return xerrors.Errorf("collectChain failed: %w", err) } + // At this point we have accepted and synced to the new `maybeHead` + // (`StageSyncComplete`). if err := syncer.store.PutTipSet(ctx, maybeHead); err != nil { span.AddAttributes(trace.StringAttribute("put_error", err.Error())) span.SetStatus(trace.Status{ @@ -451,10 +558,10 @@ func (syncer *Syncer) Sync(ctx context.Context, maybeHead *types.TipSet) error { } func isPermanent(err error) bool { - return !errors.Is(err, ErrTemporal) + return !errors.Is(err, consensus.ErrTemporal) } -func (syncer *Syncer) ValidateTipSet(ctx context.Context, fts *store.FullTipSet) error { +func (syncer *Syncer) ValidateTipSet(ctx context.Context, fts *store.FullTipSet, useCache bool) error { ctx, span := trace.StartSpan(ctx, "validateTipSet") defer span.End() @@ -465,497 +572,70 @@ func (syncer *Syncer) ValidateTipSet(ctx context.Context, fts *store.FullTipSet) return nil } + var futures []async.ErrorFuture for _, b := range fts.Blocks { - if err := syncer.ValidateBlock(ctx, b); err != nil { - if isPermanent(err) { - syncer.bad.Add(b.Cid(), err.Error()) + b := b // rebind to a scoped variable + + futures = append(futures, async.Err(func() error { + if err := syncer.ValidateBlock(ctx, b, useCache); err != nil { + if isPermanent(err) { + syncer.bad.Add(b.Cid(), NewBadBlockReason([]cid.Cid{b.Cid()}, err.Error())) + } + return xerrors.Errorf("validating block %s: %w", b.Cid(), err) } - return xerrors.Errorf("validating block %s: %w", b.Cid(), err) - } - if err := syncer.sm.ChainStore().AddToTipSetTracker(b.Header); err != nil { - return xerrors.Errorf("failed to add validated header to tipset tracker: %w", err) - } - } - return nil -} - -func (syncer *Syncer) minerIsValid(ctx context.Context, maddr address.Address, baseTs *types.TipSet) error { - var spast power.State - - _, err := syncer.sm.LoadActorState(ctx, builtin.StoragePowerActorAddr, &spast, baseTs) - if err != nil { - return err - } - - cm, err := adt.AsMap(syncer.store.Store(ctx), spast.Claims) - if err != nil { - return err - } - - var claim power.Claim - exist, err := cm.Get(adt.AddrKey(maddr), &claim) - if err != nil { - return err - } - if !exist { - return xerrors.New("miner isn't valid") - } - return nil -} - -var ErrTemporal = errors.New("temporal error") - -func blockSanityChecks(h *types.BlockHeader) error { - if h.ElectionProof == nil { - return xerrors.Errorf("block cannot have nil election proof") - } - - if h.Ticket == nil { - return xerrors.Errorf("block cannot have nil ticket") - } - - if h.BlockSig == nil { - return xerrors.Errorf("block had nil signature") - } - - if h.BLSAggregate == nil { - return xerrors.Errorf("block had nil bls aggregate signature") - } - - return nil -} - -// Should match up with 'Semantical Validation' in validation.md in the spec -func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock) error { - ctx, span := trace.StartSpan(ctx, "validateBlock") - defer span.End() - if build.InsecurePoStValidation { - log.Warn("insecure test validation is enabled, if you see this outside of a test, it is a severe bug!") - } - - if err := blockSanityChecks(b.Header); err != nil { - return xerrors.Errorf("incoming header failed basic sanity checks: %w", err) - } - - h := b.Header - - baseTs, err := syncer.store.LoadTipSet(types.NewTipSetKey(h.Parents...)) - if err != nil { - return xerrors.Errorf("load parent tipset failed (%s): %w", h.Parents, err) - } - - lbts, err := stmgr.GetLookbackTipSetForRound(ctx, syncer.sm, baseTs, h.Height) - if err != nil { - return xerrors.Errorf("failed to get lookback tipset for block: %w", err) - } - - lbst, _, err := syncer.sm.TipSetState(ctx, lbts) - if err != nil { - return xerrors.Errorf("failed to compute lookback tipset state: %w", err) - } - - prevBeacon, err := syncer.store.GetLatestBeaconEntry(baseTs) - if err != nil { - return xerrors.Errorf("failed to get latest beacon entry: %w", err) - } - - //nulls := h.Height - (baseTs.Height() + 1) - - // fast checks first - - now := uint64(time.Now().Unix()) - if h.Timestamp > now+build.AllowableClockDrift { - return xerrors.Errorf("block was from the future (now=%d, blk=%d): %w", now, h.Timestamp, ErrTemporal) - } - if h.Timestamp > now { - log.Warn("Got block from the future, but within threshold", h.Timestamp, time.Now().Unix()) - } - - if h.Timestamp < baseTs.MinTimestamp()+(build.BlockDelay*uint64(h.Height-baseTs.Height())) { - log.Warn("timestamp funtimes: ", h.Timestamp, baseTs.MinTimestamp(), h.Height, baseTs.Height()) - diff := (baseTs.MinTimestamp() + (build.BlockDelay * uint64(h.Height-baseTs.Height()))) - h.Timestamp - - return xerrors.Errorf("block was generated too soon (h.ts:%d < base.mints:%d + BLOCK_DELAY:%d * deltaH:%d; diff %d)", h.Timestamp, baseTs.MinTimestamp(), build.BlockDelay, h.Height-baseTs.Height(), diff) - } - - msgsCheck := async.Err(func() error { - if err := syncer.checkBlockMessages(ctx, b, baseTs); err != nil { - return xerrors.Errorf("block had invalid messages: %w", err) - } - return nil - }) - - minerCheck := async.Err(func() error { - if err := syncer.minerIsValid(ctx, h.Miner, baseTs); err != nil { - return xerrors.Errorf("minerIsValid failed: %w", err) - } - return nil - }) - - // Stuff that needs stateroot / worker address - stateroot, precp, err := syncer.sm.TipSetState(ctx, baseTs) - if err != nil { - return xerrors.Errorf("get tipsetstate(%d, %s) failed: %w", h.Height, h.Parents, err) - } - - if stateroot != h.ParentStateRoot { - msgs, err := syncer.store.MessagesForTipset(baseTs) - if err != nil { - log.Error("failed to load messages for tipset during tipset state mismatch error: ", err) - } else { - log.Warn("Messages for tipset with mismatching state:") - for i, m := range msgs { - mm := m.VMMessage() - log.Warnf("Message[%d]: from=%s to=%s method=%d params=%x", i, mm.From, mm.To, mm.Method, mm.Params) + if err := syncer.sm.ChainStore().AddToTipSetTracker(ctx, b.Header); err != nil { + return xerrors.Errorf("failed to add validated header to tipset tracker: %w", err) } - } - - return xerrors.Errorf("parent state root did not match computed state (%s != %s)", stateroot, h.ParentStateRoot) - } - - if precp != h.ParentMessageReceipts { - return xerrors.Errorf("parent receipts root did not match computed value (%s != %s)", precp, h.ParentMessageReceipts) - } - - waddr, err := stmgr.GetMinerWorkerRaw(ctx, syncer.sm, lbst, h.Miner) - if err != nil { - return xerrors.Errorf("GetMinerWorkerRaw failed: %w", err) - } - - winnerCheck := async.Err(func() error { - rBeacon := *prevBeacon - if len(h.BeaconEntries) != 0 { - rBeacon = h.BeaconEntries[len(h.BeaconEntries)-1] - } - buf := new(bytes.Buffer) - if err := h.Miner.MarshalCBOR(buf); err != nil { - return xerrors.Errorf("failed to marshal miner address to cbor: %w", err) - } - - //TODO: DST from spec actors when it is there - vrfBase, err := store.DrawRandomness(rBeacon.Data, crypto.DomainSeparationTag_ElectionProofProduction, h.Height, buf.Bytes()) - if err != nil { - return xerrors.Errorf("could not draw randomness: %w", err) - } - - if err := gen.VerifyVRF(ctx, waddr, vrfBase, h.ElectionProof.VRFProof); err != nil { - return xerrors.Errorf("validating block election proof failed: %w", err) - } - - slashed, err := stmgr.GetMinerSlashed(ctx, syncer.sm, baseTs, h.Miner) - if err != nil { - return xerrors.Errorf("failed to check if block miner was slashed: %w", err) - } - - if slashed { - return xerrors.Errorf("received block was from slashed or invalid miner") - } - - mpow, tpow, err := stmgr.GetPowerRaw(ctx, syncer.sm, lbst, h.Miner) - if err != nil { - return xerrors.Errorf("failed getting power: %w", err) - } - - if !types.IsTicketWinner(h.ElectionProof.VRFProof, mpow.QualityAdjPower, tpow.QualityAdjPower) { - return xerrors.Errorf("miner created a block but was not a winner") - } - - return nil - }) - - blockSigCheck := async.Err(func() error { - if err := sigs.CheckBlockSignature(h, ctx, waddr); err != nil { - return xerrors.Errorf("check block signature failed: %w", err) - } - return nil - }) - - beaconValuesCheck := async.Err(func() error { - if os.Getenv("LOTUS_IGNORE_DRAND") == "_yes_" { return nil - } - - if err := beacon.ValidateBlockValues(syncer.beacon, h, *prevBeacon); err != nil { - return xerrors.Errorf("failed to validate blocks random beacon values: %w", err) - } - return nil - }) - - tktsCheck := async.Err(func() error { - buf := new(bytes.Buffer) - if err := h.Miner.MarshalCBOR(buf); err != nil { - return xerrors.Errorf("failed to marshal miner address to cbor: %w", err) - } - - beaconBase := *prevBeacon - if len(h.BeaconEntries) == 0 { - buf.Write(baseTs.MinTicket().VRFProof) - } else { - beaconBase = h.BeaconEntries[len(h.BeaconEntries)-1] - } - - vrfBase, err := store.DrawRandomness(beaconBase.Data, crypto.DomainSeparationTag_TicketProduction, h.Height-build.TicketRandomnessLookback, buf.Bytes()) - if err != nil { - return xerrors.Errorf("failed to compute vrf base for ticket: %w", err) - } - - err = gen.VerifyVRF(ctx, waddr, vrfBase, h.Ticket.VRFProof) - if err != nil { - return xerrors.Errorf("validating block tickets failed: %w", err) - } - return nil - }) - - wproofCheck := async.Err(func() error { - if err := syncer.VerifyWinningPoStProof(ctx, h, *prevBeacon, lbst, waddr); err != nil { - return xerrors.Errorf("invalid election post: %w", err) - } - return nil - }) - - await := []async.ErrorFuture{ - minerCheck, - tktsCheck, - blockSigCheck, - beaconValuesCheck, - wproofCheck, - winnerCheck, - msgsCheck, + })) } - - var merr error - for _, fut := range await { - if err := fut.AwaitContext(ctx); err != nil { - merr = multierror.Append(merr, err) - } - } - if merr != nil { - mulErr := merr.(*multierror.Error) - mulErr.ErrorFormat = func(es []error) string { - if len(es) == 1 { - return fmt.Sprintf("1 error occurred:\n\t* %+v\n\n", es[0]) - } - - points := make([]string, len(es)) - for i, err := range es { - points[i] = fmt.Sprintf("* %+v", err) - } - - return fmt.Sprintf( - "%d errors occurred:\n\t%s\n\n", - len(es), strings.Join(points, "\n\t")) - } - } - - return merr -} - -func (syncer *Syncer) VerifyWinningPoStProof(ctx context.Context, h *types.BlockHeader, prevBeacon types.BeaconEntry, lbst cid.Cid, waddr address.Address) error { - if build.InsecurePoStValidation { - if len(h.WinPoStProof) == 0 { - return xerrors.Errorf("[TESTING] No winning post proof given") - } - - if string(h.WinPoStProof[0].ProofBytes) == "valid proof" { - return nil - } - return xerrors.Errorf("[TESTING] winning post was invalid") - } - - buf := new(bytes.Buffer) - if err := h.Miner.MarshalCBOR(buf); err != nil { - return xerrors.Errorf("failed to marshal miner address: %w", err) - } - - rbase := prevBeacon - if len(h.BeaconEntries) > 0 { - rbase = h.BeaconEntries[len(h.BeaconEntries)-1] - } - - rand, err := store.DrawRandomness(rbase.Data, crypto.DomainSeparationTag_WinningPoStChallengeSeed, h.Height, buf.Bytes()) - if err != nil { - return xerrors.Errorf("failed to get randomness for verifying winningPost proof: %w", err) - } - - mid, err := address.IDFromAddress(h.Miner) - if err != nil { - return xerrors.Errorf("failed to get ID from miner address %s: %w", h.Miner, err) - } - - sectors, err := stmgr.GetSectorsForWinningPoSt(ctx, syncer.verifier, syncer.sm, lbst, h.Miner, rand) - if err != nil { - return xerrors.Errorf("getting winning post sector set: %w", err) - } - - ok, err := ffiwrapper.ProofVerifier.VerifyWinningPoSt(ctx, abi.WinningPoStVerifyInfo{ - Randomness: rand, - Proofs: h.WinPoStProof, - ChallengedSectors: sectors, - Prover: abi.ActorID(mid), - }) - if err != nil { - return xerrors.Errorf("failed to verify election post: %w", err) - } - - if !ok { - log.Errorf("invalid winning post (%x; %v)", rand, sectors) - return xerrors.Errorf("winning post was invalid") - } - - return nil -} - -// TODO: We should extract this somewhere else and make the message pool and miner use the same logic -func (syncer *Syncer) checkBlockMessages(ctx context.Context, b *types.FullBlock, baseTs *types.TipSet) error { - { - var sigCids []cid.Cid // this is what we get for people not wanting the marshalcbor method on the cid type - var pubks []bls.PublicKey - - for _, m := range b.BlsMessages { - sigCids = append(sigCids, m.Cid()) - - pubk, err := syncer.sm.GetBlsPublicKey(ctx, m.From, baseTs) - if err != nil { - return xerrors.Errorf("failed to load bls public to validate block: %w", err) - } - - pubks = append(pubks, pubk) - } - - if err := syncer.verifyBlsAggregate(ctx, b.Header.BLSAggregate, sigCids, pubks); err != nil { - return xerrors.Errorf("bls aggregate signature was invalid: %w", err) - } - } - - nonces := make(map[address.Address]uint64) - - stateroot, _, err := syncer.sm.TipSetState(ctx, baseTs) - if err != nil { - return err - } - - cst := cbor.NewCborStore(syncer.store.Blockstore()) - st, err := state.LoadStateTree(cst, stateroot) - if err != nil { - return xerrors.Errorf("failed to load base state tree: %w", err) - } - - checkMsg := func(msg types.ChainMsg) error { - m := msg.VMMessage() - - // Phase 1: syntactic validation, as defined in the spec - minGas := vm.PricelistByEpoch(baseTs.Height()).OnChainMessage(msg.ChainLength()) - if err := m.ValidForBlockInclusion(minGas); err != nil { + for _, f := range futures { + if err := f.AwaitContext(ctx); err != nil { return err } - - // Phase 2: (Partial) semantic validation: - // the sender exists and is an account actor, and the nonces make sense - if _, ok := nonces[m.From]; !ok { - // `GetActor` does not validate that this is an account actor. - act, err := st.GetActor(m.From) - if err != nil { - return xerrors.Errorf("failed to get actor: %w", err) - } - - // redundant check - if !act.IsAccountActor() { - return xerrors.New("Sender must be an account actor") - } - nonces[m.From] = act.Nonce - } - - if nonces[m.From] != m.Nonce { - return xerrors.Errorf("wrong nonce (exp: %d, got: %d)", nonces[m.From], m.Nonce) - } - nonces[m.From]++ - - return nil } - - var blsCids []cbg.CBORMarshaler - - for i, m := range b.BlsMessages { - if err := checkMsg(m); err != nil { - return xerrors.Errorf("block had invalid bls message at index %d: %w", i, err) - } - - c := cbg.CborCid(m.Cid()) - blsCids = append(blsCids, &c) - } - - var secpkCids []cbg.CBORMarshaler - for i, m := range b.SecpkMessages { - if err := checkMsg(m); err != nil { - return xerrors.Errorf("block had invalid secpk message at index %d: %w", i, err) - } - - // `From` being an account actor is only validated inside the `vm.ResolveToKeyAddr` call - // in `StateManager.ResolveToKeyAddress` here (and not in `checkMsg`). - kaddr, err := syncer.sm.ResolveToKeyAddress(ctx, m.Message.From, baseTs) - if err != nil { - return xerrors.Errorf("failed to resolve key addr: %w", err) - } - - if err := sigs.Verify(&m.Signature, kaddr, m.Message.Cid().Bytes()); err != nil { - return xerrors.Errorf("secpk message %s has invalid signature: %w", m.Cid(), err) - } - - c := cbg.CborCid(m.Cid()) - secpkCids = append(secpkCids, &c) - } - - bmroot, err := amt.FromArray(ctx, cst, blsCids) - if err != nil { - return xerrors.Errorf("failed to build amt from bls msg cids: %w", err) - } - - smroot, err := amt.FromArray(ctx, cst, secpkCids) - if err != nil { - return xerrors.Errorf("failed to build amt from bls msg cids: %w", err) - } - - mrcid, err := cst.Put(ctx, &types.MsgMeta{ - BlsMessages: bmroot, - SecpkMessages: smroot, - }) - if err != nil { - return err - } - - if b.Header.Messages != mrcid { - return fmt.Errorf("messages didnt match message root in header") - } - return nil } -func (syncer *Syncer) verifyBlsAggregate(ctx context.Context, sig *crypto.Signature, msgs []cid.Cid, pubks []bls.PublicKey) error { - _, span := trace.StartSpan(ctx, "syncer.verifyBlsAggregate") - defer span.End() - span.AddAttributes( - trace.Int64Attribute("msgCount", int64(len(msgs))), - ) +// ValidateBlock should match up with 'Semantical Validation' in validation.md in the spec +func (syncer *Syncer) ValidateBlock(ctx context.Context, b *types.FullBlock, useCache bool) (err error) { + defer func() { + // b.Cid() could panic for empty blocks that are used in tests. + if rerr := recover(); rerr != nil { + err = xerrors.Errorf("validate block panic: %w", rerr) + return + } + }() - var wg sync.WaitGroup + if useCache { + isValidated, err := syncer.store.IsBlockValidated(ctx, b.Cid()) + if err != nil { + return xerrors.Errorf("check block validation cache %s: %w", b.Cid(), err) + } - digests := make([]bls.Digest, len(msgs)) - for i := 0; i < 10; i++ { - wg.Add(1) - go func(w int) { - defer wg.Done() - for j := 0; (j*10)+w < len(msgs); j++ { - digests[j*10+w] = bls.Hash(bls.Message(msgs[j*10+w].Bytes())) - } - }(i) + if isValidated { + return nil + } } - wg.Wait() - var bsig bls.Signature - copy(bsig[:], sig.Data) - if !bls.Verify(&bsig, digests, pubks) { - return xerrors.New("bls aggregate signature failed to verify") + validationStart := build.Clock.Now() + defer func() { + stats.Record(ctx, metrics.BlockValidationDurationMilliseconds.M(metrics.SinceInMilliseconds(validationStart))) + log.Infow("block validation", "took", time.Since(validationStart), "height", b.Header.Height, "age", time.Since(time.Unix(int64(b.Header.Timestamp), 0))) + }() + + ctx, span := trace.StartSpan(ctx, "validateBlock") + defer span.End() + + if err := syncer.consensus.ValidateBlock(ctx, b); err != nil { + return err + } + + if useCache { + if err := syncer.store.MarkBlockAsValidated(ctx, b.Cid()); err != nil { + return xerrors.Errorf("caching block validation %s: %w", b.Cid(), err) + } } return nil @@ -971,41 +651,70 @@ func extractSyncState(ctx context.Context) *SyncerState { return nil } -func (syncer *Syncer) collectHeaders(ctx context.Context, from *types.TipSet, to *types.TipSet) ([]*types.TipSet, error) { +// collectHeaders collects the headers from the blocks between any two tipsets. +// +// `incoming` is the heaviest/projected/target tipset we have learned about, and +// `known` is usually an anchor tipset we already have in our view of the chain +// (which could be the genesis). +// +// collectHeaders checks if portions of the chain are in our ChainStore; falling +// down to the network to retrieve the missing parts. If during the process, any +// portion we receive is in our denylist (bad list), we short-circuit. +// +// {hint/usage}: This is used by collectChain, which is in turn called from the +// main Sync method (Syncer#Sync), so it's a pretty central method. +// +// {hint/logic}: The logic of this method is as follows: +// +// 1. Check that the from tipset is not linked to a parent block known to be +// bad. +// 2. Check the consistency of beacon entries in the from tipset. We check +// total equality of the BeaconEntries in each block. +// 3. Traverse the chain backwards, for each tipset: +// 3a. Load it from the chainstore; if found, it move on to its parent. +// 3b. Query our peers via client in batches, requesting up to a +// maximum of 500 tipsets every time. +// +// Once we've concluded, if we find a mismatching tipset at the height where the +// anchor tipset should be, we are facing a fork, and we invoke Syncer#syncFork +// to resolve it. Refer to the godocs there. +// +// All throughout the process, we keep checking if the received blocks are in +// the deny list, and short-circuit the process if so. +func (syncer *Syncer) collectHeaders(ctx context.Context, incoming *types.TipSet, known *types.TipSet, ignoreCheckpoint bool) ([]*types.TipSet, error) { ctx, span := trace.StartSpan(ctx, "collectHeaders") defer span.End() ss := extractSyncState(ctx) span.AddAttributes( - trace.Int64Attribute("fromHeight", int64(from.Height())), - trace.Int64Attribute("toHeight", int64(to.Height())), + trace.Int64Attribute("incomingHeight", int64(incoming.Height())), + trace.Int64Attribute("knownHeight", int64(known.Height())), ) - markBad := func(fmts string, args ...interface{}) { - for _, b := range from.Cids() { - syncer.bad.Add(b, fmt.Sprintf(fmts, args...)) - } - } - - for _, pcid := range from.Parents().Cids() { + // Check if the parents of the from block are in the denylist. + // i.e. if a fork of the chain has been requested that we know to be bad. + for _, pcid := range incoming.Parents().Cids() { if reason, ok := syncer.bad.Has(pcid); ok { - markBad("linked to %s", pcid) - return nil, xerrors.Errorf("chain linked to block marked previously as bad (%s, %s) (reason: %s)", from.Cids(), pcid, reason) + newReason := reason.Linked("linked to %s", pcid) + for _, b := range incoming.Cids() { + syncer.bad.Add(b, newReason) + } + return nil, xerrors.Errorf("chain linked to block marked previously as bad (%s, %s) (reason: %s)", incoming.Cids(), pcid, reason) } } { // ensure consistency of beacon entires - targetBE := from.Blocks()[0].BeaconEntries + targetBE := incoming.Blocks()[0].BeaconEntries sorted := sort.SliceIsSorted(targetBE, func(i, j int) bool { return targetBE[i].Round < targetBE[j].Round }) if !sorted { - syncer.bad.Add(from.Cids()[0], "wrong order of beacon entires") + syncer.bad.Add(incoming.Cids()[0], NewBadBlockReason(incoming.Cids(), "wrong order of beacon entires")) return nil, xerrors.Errorf("wrong order of beacon entires") } - for _, bh := range from.Blocks()[1:] { + for _, bh := range incoming.Blocks()[1:] { if len(targetBE) != len(bh.BeaconEntries) { // cannot mark bad, I think @Kubuxu return nil, xerrors.Errorf("tipset contained different number for beacon entires") @@ -1020,12 +729,14 @@ func (syncer *Syncer) collectHeaders(ctx context.Context, from *types.TipSet, to } } - blockSet := []*types.TipSet{from} + blockSet := []*types.TipSet{incoming} - at := from.Parents() + // Parent of the new (possibly better) tipset that we need to fetch next. + at := incoming.Parents() - // we want to sync all the blocks until the height above the block we have - untilHeight := to.Height() + 1 + // we want to sync all the blocks until the height above our + // best tipset so far + untilHeight := known.Height() + 1 ss.SetHeight(blockSet[len(blockSet)-1].Height()) @@ -1035,16 +746,17 @@ loop: for blockSet[len(blockSet)-1].Height() > untilHeight { for _, bc := range at.Cids() { if reason, ok := syncer.bad.Has(bc); ok { + newReason := reason.Linked("change contained %s", bc) for _, b := range acceptedBlocks { - syncer.bad.Add(b, fmt.Sprintf("chain contained %s", bc)) + syncer.bad.Add(b, newReason) } - return nil, xerrors.Errorf("chain contained block marked previously as bad (%s, %s) (reason: %s)", from.Cids(), bc, reason) + return nil, xerrors.Errorf("chain contained block marked previously as bad (%s, %s) (reason: %s)", incoming.Cids(), bc, reason) } } // If, for some reason, we have a suffix of the chain locally, handle that here - ts, err := syncer.store.LoadTipSet(at) + ts, err := syncer.store.LoadTipSet(ctx, at) if err == nil { acceptedBlocks = append(acceptedBlocks, at.Cids()...) @@ -1052,18 +764,18 @@ loop: at = ts.Parents() continue } - if !xerrors.Is(err, bstore.ErrNotFound) { - log.Warn("loading local tipset: %s", err) + if !ipld.IsNotFound(err) { + log.Warnf("loading local tipset: %s", err) } // NB: GetBlocks validates that the blocks are in-fact the ones we - // requested, and that they are correctly linked to eachother. It does - // not validate any state transitions + // requested, and that they are correctly linked to one another. It does + // not validate any state transitions. window := 500 if gap := int(blockSet[len(blockSet)-1].Height() - untilHeight); gap < window { window = gap } - blks, err := syncer.Bsync.GetBlocks(ctx, at, window) + blks, err := syncer.Exchange.GetBlocks(ctx, at, window) if err != nil { // Most likely our peers aren't fully synced yet, but forwarded // new block message (ideally we'd find better peers) @@ -1077,17 +789,34 @@ loop: } log.Info("Got blocks: ", blks[0].Height(), len(blks)) + // Check that the fetched segment of the chain matches what we already + // have. Since we fetch from the head backwards our reassembled chain + // is sorted in reverse here: we have a child -> parent order, our last + // tipset then should be child of the first tipset retrieved. + // FIXME: The reassembly logic should be part of the `client` + // service, the consumer should not be concerned with the + // `MaxRequestLength` limitation, it should just be able to request + // an segment of arbitrary length. The same burden is put on + // `syncFork()` which needs to be aware this as well. + if blockSet[len(blockSet)-1].IsChildOf(blks[0]) == false { + return nil, xerrors.Errorf("retrieved segments of the chain are not connected at heights %d/%d", + blockSet[len(blockSet)-1].Height(), blks[0].Height()) + // A successful `GetBlocks()` call is guaranteed to fetch at least + // one tipset so the access `blks[0]` is safe. + } + for _, b := range blks { if b.Height() < untilHeight { break loop } for _, bc := range b.Cids() { if reason, ok := syncer.bad.Has(bc); ok { + newReason := reason.Linked("change contained %s", bc) for _, b := range acceptedBlocks { - syncer.bad.Add(b, fmt.Sprintf("chain contained %s", bc)) + syncer.bad.Add(b, newReason) } - return nil, xerrors.Errorf("chain contained block marked previously as bad (%s, %s) (reason: %s)", from.Cids(), bc, reason) + return nil, xerrors.Errorf("chain contained block marked previously as bad (%s, %s) (reason: %s)", incoming.Cids(), bc, reason) } } blockSet = append(blockSet, b) @@ -1099,52 +828,86 @@ loop: at = blks[len(blks)-1].Parents() } - // We have now ascertained that this is *not* a 'fast forward' - if !types.CidArrsEqual(blockSet[len(blockSet)-1].Parents().Cids(), to.Cids()) { - last := blockSet[len(blockSet)-1] - if last.Parents() == to.Parents() { - // common case: receiving a block thats potentially part of the same tipset as our best block - return blockSet, nil - } - - log.Warnf("(fork detected) synced header chain (%s - %d) does not link to our best block (%s - %d)", from.Cids(), from.Height(), to.Cids(), to.Height()) - fork, err := syncer.syncFork(ctx, last, to) - if err != nil { - if xerrors.Is(err, ErrForkTooLong) { - // TODO: we're marking this block bad in the same way that we mark invalid blocks bad. Maybe distinguish? - log.Warn("adding forked chain to our bad tipset cache") - for _, b := range from.Blocks() { - syncer.bad.Add(b.Cid(), "fork past finality") - } - } - return nil, xerrors.Errorf("failed to sync fork: %w", err) - } - - blockSet = append(blockSet, fork...) + base := blockSet[len(blockSet)-1] + if base.Equals(known) { + blockSet = blockSet[:len(blockSet)-1] + base = blockSet[len(blockSet)-1] } + if base.IsChildOf(known) { + // common case: receiving blocks that are building on top of our best tipset + return blockSet, nil + } + + knownParent, err := syncer.store.LoadTipSet(ctx, known.Parents()) + if err != nil { + return nil, xerrors.Errorf("failed to load next local tipset: %w", err) + } + if base.IsChildOf(knownParent) { + // common case: receiving a block thats potentially part of the same tipset as our best block + return blockSet, nil + } + + // We have now ascertained that this is *not* a 'fast forward' + log.Warnf("(fork detected) synced header chain (%s - %d) does not link to our best block (%s - %d)", incoming.Cids(), incoming.Height(), known.Cids(), known.Height()) + fork, err := syncer.syncFork(ctx, base, known, ignoreCheckpoint) + if err != nil { + if xerrors.Is(err, ErrForkTooLong) || xerrors.Is(err, ErrForkCheckpoint) { + // TODO: we're marking this block bad in the same way that we mark invalid blocks bad. Maybe distinguish? + log.Warn("adding forked chain to our bad tipset cache") + for _, b := range incoming.Blocks() { + syncer.bad.Add(b.Cid(), NewBadBlockReason(incoming.Cids(), "fork past finality")) + } + } + return nil, xerrors.Errorf("failed to sync fork: %w", err) + } + + blockSet = append(blockSet, fork...) + return blockSet, nil } var ErrForkTooLong = fmt.Errorf("fork longer than threshold") +var ErrForkCheckpoint = fmt.Errorf("fork would require us to diverge from checkpointed block") -func (syncer *Syncer) syncFork(ctx context.Context, from *types.TipSet, to *types.TipSet) ([]*types.TipSet, error) { - tips, err := syncer.Bsync.GetBlocks(ctx, from.Parents(), int(build.ForkLengthThreshold)) +// syncFork tries to obtain the chain fragment that links a fork into a common +// ancestor in our view of the chain. +// +// If the fork is too long (build.ForkLengthThreshold), or would cause us to diverge from the checkpoint (ErrForkCheckpoint), +// we add the entire subchain to the denylist. Else, we find the common ancestor, and add the missing chain +// fragment until the fork point to the returned []TipSet. +func (syncer *Syncer) syncFork(ctx context.Context, incoming *types.TipSet, known *types.TipSet, ignoreCheckpoint bool) ([]*types.TipSet, error) { + + var chkpt *types.TipSet + if !ignoreCheckpoint { + chkpt = syncer.store.GetCheckpoint() + if known.Equals(chkpt) { + return nil, ErrForkCheckpoint + } + } + + // TODO: Does this mean we always ask for ForkLengthThreshold blocks from the network, even if we just need, like, 2? Yes. + // Would it not be better to ask in smaller chunks, given that an ~ForkLengthThreshold is very rare? + tips, err := syncer.Exchange.GetBlocks(ctx, incoming.Parents(), int(build.ForkLengthThreshold)) if err != nil { return nil, err } - nts, err := syncer.store.LoadTipSet(to.Parents()) + nts, err := syncer.store.LoadTipSet(ctx, known.Parents()) if err != nil { return nil, xerrors.Errorf("failed to load next local tipset: %w", err) } + // Track the fork length on our side of the synced chain to enforce + // `ForkLengthThreshold`. Initialized to 1 because we already walked back + // one tipset from `known` (our synced head). + forkLengthInHead := 1 for cur := 0; cur < len(tips); { if nts.Height() == 0 { if !syncer.Genesis.Equals(nts) { return nil, xerrors.Errorf("somehow synced chain that linked back to a different genesis (bad genesis: %s)", nts.Key()) } - return nil, xerrors.Errorf("synced chain forked at genesis, refusing to sync") + return nil, xerrors.Errorf("synced chain forked at genesis, refusing to sync; incoming: %s", incoming.Cids()) } if nts.Equals(tips[cur]) { @@ -1154,22 +917,35 @@ func (syncer *Syncer) syncFork(ctx context.Context, from *types.TipSet, to *type if nts.Height() < tips[cur].Height() { cur++ } else { - nts, err = syncer.store.LoadTipSet(nts.Parents()) + // Walk back one block in our synced chain to try to meet the fork's + // height. + forkLengthInHead++ + if forkLengthInHead > int(build.ForkLengthThreshold) { + return nil, ErrForkTooLong + } + + // We will be forking away from nts, check that it isn't checkpointed + if nts.Equals(chkpt) { + return nil, ErrForkCheckpoint + } + + nts, err = syncer.store.LoadTipSet(ctx, nts.Parents()) if err != nil { return nil, xerrors.Errorf("loading next local tipset: %w", err) } } } + return nil, ErrForkTooLong } func (syncer *Syncer) syncMessagesAndCheckState(ctx context.Context, headers []*types.TipSet) error { ss := extractSyncState(ctx) - ss.SetHeight(0) + ss.SetHeight(headers[len(headers)-1].Height()) return syncer.iterFullTipsets(ctx, headers, func(ctx context.Context, fts *store.FullTipSet) error { log.Debugw("validating tipset", "height", fts.TipSet().Height(), "size", len(fts.TipSet().Cids())) - if err := syncer.ValidateTipSet(ctx, fts); err != nil { + if err := syncer.ValidateTipSet(ctx, fts, true); err != nil { log.Errorf("failed to validate tipset: %+v", err) return xerrors.Errorf("message processing failed: %w", err) } @@ -1183,14 +959,14 @@ func (syncer *Syncer) syncMessagesAndCheckState(ctx context.Context, headers []* // fills out each of the given tipsets with messages and calls the callback with it func (syncer *Syncer) iterFullTipsets(ctx context.Context, headers []*types.TipSet, cb func(context.Context, *store.FullTipSet) error) error { + ss := extractSyncState(ctx) ctx, span := trace.StartSpan(ctx, "iterFullTipsets") defer span.End() span.AddAttributes(trace.Int64Attribute("num_headers", int64(len(headers)))) - windowSize := 200 for i := len(headers) - 1; i >= 0; { - fts, err := syncer.store.TryFillTipSet(headers[i]) + fts, err := syncer.store.TryFillTipSet(ctx, headers[i]) if err != nil { return err } @@ -1202,39 +978,31 @@ func (syncer *Syncer) iterFullTipsets(ctx context.Context, headers []*types.TipS continue } - batchSize := windowSize + batchSize := concurrentSyncRequests * syncRequestBatchSize if i < batchSize { - batchSize = i + batchSize = i + 1 } - nextI := (i + 1) - batchSize // want to fetch batchSize values, 'i' points to last one we want to fetch, so its 'inclusive' of our request, thus we need to add one to our request start index + ss.SetStage(api.StageFetchingMessages) + startOffset := i + 1 - batchSize + bstout, batchErr := syncer.fetchMessages(ctx, headers[startOffset:startOffset+batchSize], startOffset) + ss.SetStage(api.StageMessages) - var bstout []*blocksync.BSTipSet - for len(bstout) < batchSize { - next := headers[nextI] - - nreq := batchSize - len(bstout) - bstips, err := syncer.Bsync.GetChainMessages(ctx, next, uint64(nreq)) - if err != nil { - return xerrors.Errorf("message processing failed: %w", err) - } - - bstout = append(bstout, bstips...) - nextI += len(bstips) + if batchErr != nil { + return xerrors.Errorf("failed to fetch messages: %w", batchErr) } for bsi := 0; bsi < len(bstout); bsi++ { // temp storage so we don't persist data we dont want to - ds := dstore.NewMapDatastore() - bs := bstore.NewBlockstore(ds) + bs := bstore.NewMemory() blks := cbor.NewCborStore(bs) this := headers[i-bsi] bstip := bstout[len(bstout)-(bsi+1)] - fts, err := zipTipSetAndMessages(blks, this, bstip.BlsMessages, bstip.SecpkMessages, bstip.BlsMsgIncludes, bstip.SecpkMsgIncludes) + fts, err := zipTipSetAndMessages(blks, this, bstip.Bls, bstip.Secpk, bstip.BlsIncludes, bstip.SecpkIncludes) if err != nil { log.Warnw("zipping failed", "error", err, "bsi", bsi, "i", i, - "height", this.Height(), "bstip-height", bstip.Blocks[0].Height, + "height", this.Height(), "next-height", i+batchSize) return xerrors.Errorf("message processing failed: %w", err) } @@ -1243,34 +1011,145 @@ func (syncer *Syncer) iterFullTipsets(ctx context.Context, headers []*types.TipS return err } - if err := persistMessages(bs, bstip); err != nil { + if err := persistMessages(ctx, bs, bstip); err != nil { return err } - if err := copyBlockstore(bs, syncer.store.Blockstore()); err != nil { + if err := copyBlockstore(ctx, bs, syncer.store.ChainBlockstore()); err != nil { return xerrors.Errorf("message processing failed: %w", err) } } + i -= batchSize } return nil } -func persistMessages(bs bstore.Blockstore, bst *blocksync.BSTipSet) error { - for _, m := range bst.BlsMessages { +func checkMsgMeta(ts *types.TipSet, allbmsgs []*types.Message, allsmsgs []*types.SignedMessage, bmi, smi [][]uint64) error { + for bi, b := range ts.Blocks() { + if msgc := len(bmi[bi]) + len(smi[bi]); msgc > build.BlockMessageLimit { + return fmt.Errorf("block %q has too many messages (%d)", b.Cid(), msgc) + } + + var smsgCids []cid.Cid + for _, m := range smi[bi] { + smsgCids = append(smsgCids, allsmsgs[m].Cid()) + } + + var bmsgCids []cid.Cid + for _, m := range bmi[bi] { + bmsgCids = append(bmsgCids, allbmsgs[m].Cid()) + } + + mrcid, err := computeMsgMeta(cbor.NewCborStore(bstore.NewMemory()), bmsgCids, smsgCids) + if err != nil { + return err + } + + if b.Messages != mrcid { + return fmt.Errorf("messages didnt match message root in header for ts %s", ts.Key()) + } + } + + return nil +} + +func (syncer *Syncer) fetchMessages(ctx context.Context, headers []*types.TipSet, startOffset int) ([]*exchange.CompactedMessages, error) { + batchSize := len(headers) + batch := make([]*exchange.CompactedMessages, batchSize) + + var wg sync.WaitGroup + var mx sync.Mutex + var batchErr error + + start := build.Clock.Now() + + for j := 0; j < batchSize; j += syncRequestBatchSize { + wg.Add(1) + go func(j int) { + defer wg.Done() + + nreq := syncRequestBatchSize + if j+nreq > batchSize { + nreq = batchSize - j + } + + failed := false + for offset := 0; !failed && offset < nreq; { + nextI := j + offset + lastI := j + nreq + + var requestErr error + var requestResult []*exchange.CompactedMessages + for retry := 0; requestResult == nil && retry < syncRequestRetries; retry++ { + if retry > 0 { + log.Infof("fetching messages at %d (retry %d)", startOffset+nextI, retry) + } else { + log.Infof("fetching messages at %d", startOffset+nextI) + } + + result, err := syncer.Exchange.GetChainMessages(ctx, headers[nextI:lastI]) + if err != nil { + requestErr = multierror.Append(requestErr, err) + } else { + isGood := true + for index, ts := range headers[nextI:lastI] { + cm := result[index] + if err := checkMsgMeta(ts, cm.Bls, cm.Secpk, cm.BlsIncludes, cm.SecpkIncludes); err != nil { + log.Errorf("fetched messages not as expected: %s", err) + isGood = false + break + } + } + + if isGood { + requestResult = result + } + } + } + + mx.Lock() + if requestResult != nil { + copy(batch[j+offset:], requestResult) + offset += len(requestResult) + } else { + log.Errorf("error fetching messages at %d: %s", nextI, requestErr) + batchErr = multierror.Append(batchErr, requestErr) + failed = true + } + mx.Unlock() + } + }(j) + } + wg.Wait() + + if batchErr != nil { + return nil, batchErr + } + + log.Infof("fetching messages for %d tipsets at %d done; took %s", batchSize, startOffset, build.Clock.Since(start)) + + return batch, nil +} + +func persistMessages(ctx context.Context, bs bstore.Blockstore, bst *exchange.CompactedMessages) error { + _, span := trace.StartSpan(ctx, "persistMessages") + defer span.End() + + for _, m := range bst.Bls { //log.Infof("putting BLS message: %s", m.Cid()) - if _, err := store.PutMessage(bs, m); err != nil { + if _, err := store.PutMessage(ctx, bs, m); err != nil { log.Errorf("failed to persist messages: %+v", err) return xerrors.Errorf("BLS message processing failed: %w", err) } } - for _, m := range bst.SecpkMessages { - if m.Signature.Type != crypto.SigTypeSecp256k1 { + for _, m := range bst.Secpk { + if m.Signature.Type != crypto.SigTypeSecp256k1 && m.Signature.Type != crypto.SigTypeDelegated { return xerrors.Errorf("unknown signature type on message %s: %q", m.Cid(), m.Signature.Type) } //log.Infof("putting secp256k1 message: %s", m.Cid()) - if _, err := store.PutMessage(bs, m); err != nil { + if _, err := store.PutMessage(ctx, bs, m); err != nil { log.Errorf("failed to persist messages: %+v", err) return xerrors.Errorf("secp256k1 message processing failed: %w", err) } @@ -1279,14 +1158,33 @@ func persistMessages(bs bstore.Blockstore, bst *blocksync.BSTipSet) error { return nil } -func (syncer *Syncer) collectChain(ctx context.Context, ts *types.TipSet) error { +// collectChain tries to advance our view of the chain to the purported head. +// +// It goes through various stages: +// +// 1. StageHeaders: we proceed in the sync process by requesting block headers +// from our peers, moving back from their heads, until we reach a tipset +// that we have in common (such a common tipset must exist, thought it may +// simply be the genesis block). +// +// If the common tipset is our head, we treat the sync as a "fast-forward", +// else we must drop part of our chain to connect to the peer's head +// (referred to as "forking"). +// +// 2. StagePersistHeaders: now that we've collected the missing headers, +// augmented by those on the other side of a fork, we persist them to the +// BlockStore. +// +// 3. StageMessages: having acquired the headers and found a common tipset, +// we then move forward, requesting the full blocks, including the messages. +func (syncer *Syncer) collectChain(ctx context.Context, ts *types.TipSet, hts *types.TipSet, ignoreCheckpoint bool) error { ctx, span := trace.StartSpan(ctx, "collectChain") defer span.End() ss := extractSyncState(ctx) - ss.Init(syncer.store.GetHeaviestTipSet(), ts) + ss.Init(hts, ts) - headers, err := syncer.collectHeaders(ctx, ts, syncer.store.GetHeaviestTipSet()) + headers, err := syncer.collectHeaders(ctx, ts, hts, ignoreCheckpoint) if err != nil { ss.Error(err) return err @@ -1295,21 +1193,17 @@ func (syncer *Syncer) collectChain(ctx context.Context, ts *types.TipSet) error span.AddAttributes(trace.Int64Attribute("syncChainLength", int64(len(headers)))) if !headers[0].Equals(ts) { - log.Errorf("collectChain headers[0] should be equal to sync target. Its not: %s != %s", headers[0].Cids(), ts.Cids()) + return xerrors.Errorf("collectChain synced %s, wanted to sync %s", headers[0].Cids(), ts.Cids()) } ss.SetStage(api.StagePersistHeaders) - toPersist := make([]*types.BlockHeader, 0, len(headers)*int(build.BlocksPerEpoch)) - for _, ts := range headers { - toPersist = append(toPersist, ts.Blocks()...) - } - if err := syncer.store.PersistBlockHeaders(toPersist...); err != nil { - err = xerrors.Errorf("failed to persist synced blocks to the chainstore: %w", err) + // Write tipsets from oldest to newest. + if err := syncer.store.PersistTipsets(ctx, headers); err != nil { + err = xerrors.Errorf("failed to persist synced tipset to the chainstore: %w", err) ss.Error(err) return err } - toPersist = nil ss.SetStage(api.StageMessages) @@ -1325,47 +1219,25 @@ func (syncer *Syncer) collectChain(ctx context.Context, ts *types.TipSet) error return nil } -func VerifyElectionPoStVRF(ctx context.Context, evrf []byte, rand []byte, worker address.Address) error { - if err := gen.VerifyVRF(ctx, worker, rand, evrf); err != nil { - return xerrors.Errorf("failed to verify post_randomness vrf: %w", err) - } - - return nil -} - -func (syncer *Syncer) State() []SyncerState { - var out []SyncerState - for _, ss := range syncer.syncmgr.syncStates { - out = append(out, ss.Snapshot()) - } - return out +func (syncer *Syncer) State() []SyncerStateSnapshot { + return syncer.syncmgr.State() } +// MarkBad manually adds a block to the "bad blocks" cache. func (syncer *Syncer) MarkBad(blk cid.Cid) { - syncer.bad.Add(blk, "manually marked bad") + syncer.bad.Add(blk, NewBadBlockReason([]cid.Cid{blk}, "manually marked bad")) +} + +// UnmarkBad manually adds a block to the "bad blocks" cache. +func (syncer *Syncer) UnmarkBad(blk cid.Cid) { + syncer.bad.Remove(blk) +} + +func (syncer *Syncer) UnmarkAllBad() { + syncer.bad.Purge() } func (syncer *Syncer) CheckBadBlockCache(blk cid.Cid) (string, bool) { - return syncer.bad.Has(blk) -} -func (syncer *Syncer) getLatestBeaconEntry(ctx context.Context, ts *types.TipSet) (*types.BeaconEntry, error) { - cur := ts - for i := 0; i < 20; i++ { - cbe := cur.Blocks()[0].BeaconEntries - if len(cbe) > 0 { - return &cbe[len(cbe)-1], nil - } - - if cur.Height() == 0 { - return nil, xerrors.Errorf("made it back to genesis block without finding beacon entry") - } - - next, err := syncer.store.LoadTipSet(cur.Parents()) - if err != nil { - return nil, xerrors.Errorf("failed to load parents when searching back for latest beacon entry: %w", err) - } - cur = next - } - - return nil, xerrors.Errorf("found NO beacon entries in the 20 blocks prior to given tipset") + bbr, ok := syncer.bad.Has(blk) + return bbr.String(), ok } diff --git a/chain/sync_manager.go b/chain/sync_manager.go index e00063961..94017c276 100644 --- a/chain/sync_manager.go +++ b/chain/sync_manager.go @@ -2,112 +2,532 @@ package chain import ( "context" + "os" "sort" + "strconv" + "strings" "sync" + "time" + "github.com/libp2p/go-libp2p/core/peer" + + "github.com/filecoin-project/go-address" + + "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/types" - peer "github.com/libp2p/go-libp2p-core/peer" ) -const BootstrapPeerThreshold = 2 +var ( + BootstrapPeerThreshold = build.BootstrapPeerThreshold -const ( - BSStateInit = 0 - BSStateSelected = 1 - BSStateScheduled = 2 - BSStateComplete = 3 + RecentSyncBufferSize = 10 + MaxSyncWorkers = 5 + SyncWorkerHistory = 3 + + InitialSyncTimeThreshold = 15 * time.Minute + + coalesceTipsets = false ) +func init() { + coalesceTipsets = os.Getenv("LOTUS_SYNC_FORMTS_PEND") == "yes" + + if bootstrapPeerThreshold := os.Getenv("LOTUS_SYNC_BOOTSTRAP_PEERS"); bootstrapPeerThreshold != "" { + threshold, err := strconv.Atoi(bootstrapPeerThreshold) + if err != nil { + log.Errorf("failed to parse 'LOTUS_SYNC_BOOTSTRAP_PEERS' env var: %s", err) + } else { + BootstrapPeerThreshold = threshold + } + } +} + type SyncFunc func(context.Context, *types.TipSet) error -type SyncManager struct { - lk sync.Mutex - peerHeads map[peer.ID]*types.TipSet +// SyncManager manages the chain synchronization process, both at bootstrap time +// and during ongoing operation. +// +// It receives candidate chain heads in the form of tipsets from peers, +// and schedules them onto sync workers, deduplicating processing for +// already-active syncs. +type SyncManager interface { + // Start starts the SyncManager. + Start() - bssLk sync.Mutex - bootstrapState int + // Stop stops the SyncManager. + Stop() - bspThresh int + // SetPeerHead informs the SyncManager that the supplied peer reported the + // supplied tipset. + SetPeerHead(ctx context.Context, p peer.ID, ts *types.TipSet) - incomingTipSets chan *types.TipSet - syncTargets chan *types.TipSet - syncResults chan *syncResult + // State retrieves the state of the sync workers. + State() []SyncerStateSnapshot +} - syncStates []*SyncerState +type syncManager struct { + ctx context.Context + cancel func() + + workq chan peerHead + statusq chan workerStatus + + nextWorker uint64 + pend syncBucketSet + deferred syncBucketSet + heads map[peer.ID]*types.TipSet + recent *syncBuffer + + initialSyncDone bool + + mx sync.Mutex + state map[uint64]*workerState + + history []*workerState + historyI int doSync func(context.Context, *types.TipSet) error - - stop chan struct{} - - // Sync Scheduler fields - activeSyncs map[types.TipSetKey]*types.TipSet - syncQueue syncBucketSet - activeSyncTips syncBucketSet - nextSyncTarget *syncTargetBucket - workerChan chan *types.TipSet } -type syncResult struct { - ts *types.TipSet - success bool +var _ SyncManager = (*syncManager)(nil) + +type peerHead struct { + p peer.ID + ts *types.TipSet } -const syncWorkerCount = 3 +type workerState struct { + id uint64 + ts *types.TipSet + ss *SyncerState + dt time.Duration +} -func NewSyncManager(sync SyncFunc) *SyncManager { - return &SyncManager{ - bspThresh: 1, - peerHeads: make(map[peer.ID]*types.TipSet), - syncTargets: make(chan *types.TipSet), - syncResults: make(chan *syncResult), - syncStates: make([]*SyncerState, syncWorkerCount), - incomingTipSets: make(chan *types.TipSet), - activeSyncs: make(map[types.TipSetKey]*types.TipSet), - doSync: sync, - stop: make(chan struct{}), +type workerStatus struct { + id uint64 + err error +} + +// sync manager interface +func NewSyncManager(sync SyncFunc) SyncManager { + ctx, cancel := context.WithCancel(context.Background()) + return &syncManager{ + ctx: ctx, + cancel: cancel, + + workq: make(chan peerHead), + statusq: make(chan workerStatus), + + heads: make(map[peer.ID]*types.TipSet), + state: make(map[uint64]*workerState), + recent: newSyncBuffer(RecentSyncBufferSize), + history: make([]*workerState, SyncWorkerHistory), + + doSync: sync, } } -func (sm *SyncManager) Start() { - go sm.syncScheduler() - for i := 0; i < syncWorkerCount; i++ { - go sm.syncWorker(i) +func (sm *syncManager) Start() { + go sm.scheduler() +} + +func (sm *syncManager) Stop() { + select { + case <-sm.ctx.Done(): + default: + sm.cancel() } } -func (sm *SyncManager) Stop() { - close(sm.stop) +func (sm *syncManager) SetPeerHead(ctx context.Context, p peer.ID, ts *types.TipSet) { + select { + case sm.workq <- peerHead{p: p, ts: ts}: + case <-sm.ctx.Done(): + case <-ctx.Done(): + } } -func (sm *SyncManager) SetPeerHead(ctx context.Context, p peer.ID, ts *types.TipSet) { - sm.lk.Lock() - defer sm.lk.Unlock() - sm.peerHeads[p] = ts - - if sm.getBootstrapState() == BSStateInit { - spc := sm.syncedPeerCount() - if spc >= sm.bspThresh { - // Its go time! - target, err := sm.selectSyncTarget() - if err != nil { - log.Error("failed to select sync target: ", err) - return - } - sm.setBootstrapState(BSStateSelected) - - sm.incomingTipSets <- target +func (sm *syncManager) State() []SyncerStateSnapshot { + sm.mx.Lock() + workerStates := make([]*workerState, 0, len(sm.state)+len(sm.history)) + for _, ws := range sm.state { + workerStates = append(workerStates, ws) + } + for _, ws := range sm.history { + if ws != nil { + workerStates = append(workerStates, ws) } - log.Infof("sync bootstrap has %d peers", spc) + } + sm.mx.Unlock() + + sort.Slice(workerStates, func(i, j int) bool { + return workerStates[i].id < workerStates[j].id + }) + + result := make([]SyncerStateSnapshot, 0, len(workerStates)) + for _, ws := range workerStates { + result = append(result, ws.ss.Snapshot()) + } + + return result +} + +// sync manager internals +func (sm *syncManager) scheduler() { + ticker := time.NewTicker(time.Minute) + tickerC := ticker.C + for { + select { + case head := <-sm.workq: + sm.handlePeerHead(head) + case status := <-sm.statusq: + sm.handleWorkerStatus(status) + case <-tickerC: + if sm.initialSyncDone { + ticker.Stop() + tickerC = nil + sm.handleInitialSyncDone() + } + case <-sm.ctx.Done(): + return + } + } +} + +func (sm *syncManager) handlePeerHead(head peerHead) { + log.Debugf("new peer head: %s %s", head.p, head.ts) + + // have we started syncing yet? + if sm.nextWorker == 0 { + // track the peer head until we start syncing + sm.heads[head.p] = head.ts + + // not yet; do we have enough peers? + if len(sm.heads) < BootstrapPeerThreshold { + log.Debugw("not tracking enough peers to start sync worker", "have", len(sm.heads), "need", BootstrapPeerThreshold) + // not enough peers; track it and wait + return + } + + // we are ready to start syncing; select the sync target and spawn a worker + target, err := sm.selectInitialSyncTarget() + if err != nil { + log.Errorf("failed to select initial sync target: %s", err) + return + } + + log.Infof("selected initial sync target: %s", target) + sm.spawnWorker(target) return } - sm.incomingTipSets <- ts + // we have started syncing, add peer head to the queue if applicable and maybe spawn a worker + // if there is work to do (possibly in a fork) + target, work, err := sm.addSyncTarget(head.ts) + if err != nil { + log.Warnf("failed to add sync target: %s", err) + return + } + + if work { + log.Infof("selected sync target: %s", target) + sm.spawnWorker(target) + } } +func (sm *syncManager) handleWorkerStatus(status workerStatus) { + log.Debugf("worker %d done; status error: %s", status.id, status.err) + + sm.mx.Lock() + ws := sm.state[status.id] + delete(sm.state, status.id) + + // we track the last few workers for debug purposes + sm.history[sm.historyI] = ws + sm.historyI++ + sm.historyI %= len(sm.history) + sm.mx.Unlock() + + if status.err != nil { + // we failed to sync this target -- log it and try to work on an extended chain + // if there is nothing related to be worked on, we stop working on this chain. + log.Errorf("error during sync in %s: %s", ws.ts, status.err) + } else { + // add to the recently synced buffer + sm.recent.Push(ws.ts) + // if we are still in initial sync and this was fast enough, mark the end of the initial sync + if !sm.initialSyncDone && ws.dt < InitialSyncTimeThreshold { + sm.initialSyncDone = true + } + } + + // we are done with this target, select the next sync target and spawn a worker if there is work + // to do, because of an extension of this chain. + target, work, err := sm.selectSyncTarget(ws.ts) + if err != nil { + log.Warnf("failed to select sync target: %s", err) + return + } + + if work { + log.Infof("selected sync target: %s", target) + sm.spawnWorker(target) + } +} + +func (sm *syncManager) handleInitialSyncDone() { + // we have just finished the initial sync; spawn some additional workers in deferred syncs + // as needed (and up to MaxSyncWorkers) to ramp up chain sync + for len(sm.state) < MaxSyncWorkers { + target, work, err := sm.selectDeferredSyncTarget() + if err != nil { + log.Errorf("error selecting deferred sync target: %s", err) + return + } + + if !work { + return + } + + log.Infof("selected deferred sync target: %s", target) + sm.spawnWorker(target) + } +} + +func (sm *syncManager) spawnWorker(target *types.TipSet) { + id := sm.nextWorker + sm.nextWorker++ + ws := &workerState{ + id: id, + ts: target, + ss: new(SyncerState), + } + ws.ss.data.WorkerID = id + + sm.mx.Lock() + sm.state[id] = ws + sm.mx.Unlock() + + go sm.worker(ws) +} + +func (sm *syncManager) worker(ws *workerState) { + log.Infof("worker %d syncing in %s", ws.id, ws.ts) + + start := build.Clock.Now() + + ctx := context.WithValue(sm.ctx, syncStateKey{}, ws.ss) + err := sm.doSync(ctx, ws.ts) + + ws.dt = build.Clock.Since(start) + log.Infof("worker %d done; took %s", ws.id, ws.dt) + select { + case sm.statusq <- workerStatus{id: ws.id, err: err}: + case <-sm.ctx.Done(): + } +} + +// selects the initial sync target by examining known peer heads; only called once for the initial +// sync. +func (sm *syncManager) selectInitialSyncTarget() (*types.TipSet, error) { + var buckets syncBucketSet + + var peerHeads []*types.TipSet + for _, ts := range sm.heads { + peerHeads = append(peerHeads, ts) + } + // clear the map, we don't use it any longer + sm.heads = nil + + sort.Slice(peerHeads, func(i, j int) bool { + return peerHeads[i].Height() < peerHeads[j].Height() + }) + + for _, ts := range peerHeads { + buckets.Insert(ts) + } + + if len(buckets.buckets) > 1 { + log.Warn("caution, multiple distinct chains seen during head selections") + // TODO: we *could* refuse to sync here without user intervention. + // For now, just select the best cluster + } + + return buckets.Heaviest(), nil +} + +// adds a tipset to the potential sync targets; returns true if there is a a tipset to work on. +// this could be either a restart, eg because there is no currently scheduled sync work or a worker +// failed or a potential fork. +func (sm *syncManager) addSyncTarget(ts *types.TipSet) (*types.TipSet, bool, error) { + // Note: we don't need the state lock here to access the active worker states, as the only + // competing threads that may access it do so through State() which is read only. + + // if we have recently synced this or any heavier tipset we just ignore it; this can happen + // with an empty worker set after we just finished syncing to a target + if sm.recent.Synced(ts) { + return nil, false, nil + } + + // if the worker set is empty, we have finished syncing and were waiting for the next tipset + // in this case, we just return the tipset as work to be done + if len(sm.state) == 0 { + return ts, true, nil + } + + // check if it is related to any active sync; if so insert into the pending sync queue + for _, ws := range sm.state { + if ts.Equals(ws.ts) { + // ignore it, we are already syncing it + return nil, false, nil + } + + if ts.Parents() == ws.ts.Key() { + // schedule for syncing next; it's an extension of an active sync + sm.pend.Insert(ts) + return nil, false, nil + } + } + + // check to see if it is related to any pending sync; if so insert it into the pending sync queue + if sm.pend.RelatedToAny(ts) { + sm.pend.Insert(ts) + return nil, false, nil + } + + // it's not related to any active or pending sync; this could be a fork in which case we + // start a new worker to sync it, if it is *heavier* than any active or pending set; + // if it is not, we ignore it. + for _, ws := range sm.state { + if isHeavier(ws.ts, ts) { + return nil, false, nil + } + } + + pendHeaviest := sm.pend.Heaviest() + if pendHeaviest != nil && isHeavier(pendHeaviest, ts) { + return nil, false, nil + } + + // if we have not finished the initial sync or have too many workers, add it to the deferred queue; + // it will be processed once a worker is freed from syncing a chain (or the initial sync finishes) + if !sm.initialSyncDone || len(sm.state) >= MaxSyncWorkers { + log.Debugf("deferring sync on %s", ts) + sm.deferred.Insert(ts) + return nil, false, nil + } + + // start a new worker, seems heavy enough and unrelated to active or pending syncs + return ts, true, nil +} + +// selects the next sync target after a worker sync has finished; returns true and a target +// TipSet if this chain should continue to sync because there is a heavier related tipset. +func (sm *syncManager) selectSyncTarget(done *types.TipSet) (*types.TipSet, bool, error) { + // we pop the related bucket and if there is any related tipset, we work on the heaviest one next + // if we are not already working on a heavier tipset + related := sm.pend.PopRelated(done) + if related == nil { + return sm.selectDeferredSyncTarget() + } + + heaviest := related.heaviestTipSet() + if isHeavier(done, heaviest) { + return sm.selectDeferredSyncTarget() + } + + for _, ws := range sm.state { + if isHeavier(ws.ts, heaviest) { + return sm.selectDeferredSyncTarget() + } + } + + if sm.recent.Synced(heaviest) { + return sm.selectDeferredSyncTarget() + } + + return heaviest, true, nil +} + +// selects a deferred sync target if there is any; these are sync targets that were not related to +// active syncs and were deferred because there were too many workers running +func (sm *syncManager) selectDeferredSyncTarget() (*types.TipSet, bool, error) { +deferredLoop: + for !sm.deferred.Empty() { + bucket := sm.deferred.Pop() + heaviest := bucket.heaviestTipSet() + + if sm.recent.Synced(heaviest) { + // we have synced it or something heavier recently, skip it + continue deferredLoop + } + + if sm.pend.RelatedToAny(heaviest) { + // this has converged to a pending sync, insert it to the pending queue + sm.pend.Insert(heaviest) + continue deferredLoop + } + + for _, ws := range sm.state { + if ws.ts.Equals(heaviest) || isHeavier(ws.ts, heaviest) { + // we have converged and are already syncing it or we are syncing on something heavier + // ignore it and pop the next deferred bucket + continue deferredLoop + } + + if heaviest.Parents() == ws.ts.Key() { + // we have converged and we are syncing its parent; insert it to the pending queue + sm.pend.Insert(heaviest) + continue deferredLoop + } + + // it's not related to any active or pending sync and this worker is free, so sync it! + return heaviest, true, nil + } + } + + return nil, false, nil +} + +func isHeavier(a, b *types.TipSet) bool { + return a.ParentWeight().GreaterThan(b.ParentWeight()) +} + +// sync buffer -- this is a circular buffer of recently synced tipsets +type syncBuffer struct { + buf []*types.TipSet + next int +} + +func newSyncBuffer(size int) *syncBuffer { + return &syncBuffer{buf: make([]*types.TipSet, size)} +} + +func (sb *syncBuffer) Push(ts *types.TipSet) { + sb.buf[sb.next] = ts + sb.next++ + sb.next %= len(sb.buf) +} + +func (sb *syncBuffer) Synced(ts *types.TipSet) bool { + for _, rts := range sb.buf { + if rts != nil && (rts.Equals(ts) || isHeavier(rts, ts)) { + return true + } + } + + return false +} + +// sync buckets and related utilities type syncBucketSet struct { buckets []*syncTargetBucket } +type syncTargetBucket struct { + tips []*types.TipSet +} + func newSyncTargetBucket(tipsets ...*types.TipSet) *syncTargetBucket { var stb syncTargetBucket for _, ts := range tipsets { @@ -116,6 +536,19 @@ func newSyncTargetBucket(tipsets ...*types.TipSet) *syncTargetBucket { return &stb } +func (sbs *syncBucketSet) String() string { + var bStrings []string + for _, b := range sbs.buckets { + var tsStrings []string + for _, t := range b.tips { + tsStrings = append(tsStrings, t.String()) + } + bStrings = append(bStrings, "["+strings.Join(tsStrings, ",")+"]") + } + + return "{" + strings.Join(bStrings, ";") + "}" +} + func (sbs *syncBucketSet) RelatedToAny(ts *types.TipSet) bool { for _, b := range sbs.buckets { if b.sameChainAs(ts) { @@ -162,13 +595,17 @@ func (sbs *syncBucketSet) removeBucket(toremove *syncTargetBucket) { } func (sbs *syncBucketSet) PopRelated(ts *types.TipSet) *syncTargetBucket { + var bOut *syncTargetBucket for _, b := range sbs.buckets { if b.sameChainAs(ts) { sbs.removeBucket(b) - return b + if bOut == nil { + bOut = &syncTargetBucket{} + } + bOut.tips = append(bOut.tips, b.tips...) } } - return nil + return bOut } func (sbs *syncBucketSet) Heaviest() *types.TipSet { @@ -187,11 +624,6 @@ func (sbs *syncBucketSet) Empty() bool { return len(sbs.buckets) == 0 } -type syncTargetBucket struct { - tips []*types.TipSet - count int -} - func (stb *syncTargetBucket) sameChainAs(ts *types.TipSet) bool { for _, t := range stb.tips { if ts.Equals(t) { @@ -208,12 +640,38 @@ func (stb *syncTargetBucket) sameChainAs(ts *types.TipSet) bool { } func (stb *syncTargetBucket) add(ts *types.TipSet) { - stb.count++ - - for _, t := range stb.tips { + for i, t := range stb.tips { if t.Equals(ts) { return } + if coalesceTipsets && t.Height() == ts.Height() && + types.CidArrsEqual(t.Blocks()[0].Parents, ts.Blocks()[0].Parents) { + miners := make(map[address.Address]struct{}) + newTs := []*types.BlockHeader{} + for _, b := range t.Blocks() { + _, have := miners[b.Miner] + if !have { + newTs = append(newTs, b) + miners[b.Miner] = struct{}{} + } + } + for _, b := range ts.Blocks() { + _, have := miners[b.Miner] + if !have { + newTs = append(newTs, b) + miners[b.Miner] = struct{}{} + } + } + + ts2, err := types.NewTipSet(newTs) + if err != nil { + log.Warnf("error while trying to recombine a tipset in a bucket: %+v", err) + continue + } + stb.tips[i] = ts2 + return + } + } stb.tips = append(stb.tips, ts) @@ -232,194 +690,3 @@ func (stb *syncTargetBucket) heaviestTipSet() *types.TipSet { } return best } - -func (sm *SyncManager) selectSyncTarget() (*types.TipSet, error) { - var buckets syncBucketSet - - var peerHeads []*types.TipSet - for _, ts := range sm.peerHeads { - peerHeads = append(peerHeads, ts) - } - sort.Slice(peerHeads, func(i, j int) bool { - return peerHeads[i].Height() < peerHeads[j].Height() - }) - - for _, ts := range peerHeads { - buckets.Insert(ts) - } - - if len(buckets.buckets) > 1 { - log.Warn("caution, multiple distinct chains seen during head selections") - // TODO: we *could* refuse to sync here without user intervention. - // For now, just select the best cluster - } - - return buckets.Heaviest(), nil -} - -func (sm *SyncManager) syncScheduler() { - - for { - select { - case ts, ok := <-sm.incomingTipSets: - if !ok { - log.Info("shutting down sync scheduler") - return - } - - sm.scheduleIncoming(ts) - case res := <-sm.syncResults: - sm.scheduleProcessResult(res) - case sm.workerChan <- sm.nextSyncTarget.heaviestTipSet(): - sm.scheduleWorkSent() - case <-sm.stop: - log.Info("sync scheduler shutting down") - return - } - } -} - -func (sm *SyncManager) scheduleIncoming(ts *types.TipSet) { - log.Info("scheduling incoming tipset sync: ", ts.Cids()) - if sm.getBootstrapState() == BSStateSelected { - sm.setBootstrapState(BSStateScheduled) - sm.syncTargets <- ts - return - } - - var relatedToActiveSync bool - for _, acts := range sm.activeSyncs { - if ts.Equals(acts) { - break - } - - if ts.Parents() == acts.Key() { - // sync this next, after that sync process finishes - relatedToActiveSync = true - } - } - - if !relatedToActiveSync && sm.activeSyncTips.RelatedToAny(ts) { - relatedToActiveSync = true - } - - // if this is related to an active sync process, immediately bucket it - // we don't want to start a parallel sync process that duplicates work - if relatedToActiveSync { - sm.activeSyncTips.Insert(ts) - return - } - - if sm.getBootstrapState() == BSStateScheduled { - sm.syncQueue.Insert(ts) - return - } - - if sm.nextSyncTarget != nil && sm.nextSyncTarget.sameChainAs(ts) { - sm.nextSyncTarget.add(ts) - } else { - sm.syncQueue.Insert(ts) - - if sm.nextSyncTarget == nil { - sm.nextSyncTarget = sm.syncQueue.Pop() - sm.workerChan = sm.syncTargets - } - } -} - -func (sm *SyncManager) scheduleProcessResult(res *syncResult) { - if res.success && sm.getBootstrapState() != BSStateComplete { - sm.setBootstrapState(BSStateComplete) - } - delete(sm.activeSyncs, res.ts.Key()) - relbucket := sm.activeSyncTips.PopRelated(res.ts) - if relbucket != nil { - if res.success { - if sm.nextSyncTarget == nil { - sm.nextSyncTarget = relbucket - sm.workerChan = sm.syncTargets - } else { - sm.syncQueue.buckets = append(sm.syncQueue.buckets, relbucket) - } - return - } else { - // TODO: this is the case where we try to sync a chain, and - // fail, and we have more blocks on top of that chain that - // have come in since. The question is, should we try to - // sync these? or just drop them? - } - } - - if sm.nextSyncTarget == nil && !sm.syncQueue.Empty() { - next := sm.syncQueue.Pop() - if next != nil { - sm.nextSyncTarget = next - sm.workerChan = sm.syncTargets - } - } -} - -func (sm *SyncManager) scheduleWorkSent() { - hts := sm.nextSyncTarget.heaviestTipSet() - sm.activeSyncs[hts.Key()] = hts - - if !sm.syncQueue.Empty() { - sm.nextSyncTarget = sm.syncQueue.Pop() - } else { - sm.nextSyncTarget = nil - sm.workerChan = nil - } -} - -func (sm *SyncManager) syncWorker(id int) { - ss := &SyncerState{} - sm.syncStates[id] = ss - for { - select { - case ts, ok := <-sm.syncTargets: - if !ok { - log.Info("sync manager worker shutting down") - return - } - - ctx := context.WithValue(context.TODO(), syncStateKey{}, ss) - err := sm.doSync(ctx, ts) - if err != nil { - log.Errorf("sync error: %+v", err) - } - - sm.syncResults <- &syncResult{ - ts: ts, - success: err == nil, - } - } - } -} - -func (sm *SyncManager) syncedPeerCount() int { - var count int - for _, ts := range sm.peerHeads { - if ts.Height() > 0 { - count++ - } - } - return count -} - -func (sm *SyncManager) getBootstrapState() int { - sm.bssLk.Lock() - defer sm.bssLk.Unlock() - return sm.bootstrapState -} - -func (sm *SyncManager) setBootstrapState(v int) { - sm.bssLk.Lock() - defer sm.bssLk.Unlock() - sm.bootstrapState = v -} - -func (sm *SyncManager) IsBootstrapped() bool { - sm.bssLk.Lock() - defer sm.bssLk.Unlock() - return sm.bootstrapState == BSStateComplete -} diff --git a/chain/sync_manager_test.go b/chain/sync_manager_test.go index ca2ced856..737845ae2 100644 --- a/chain/sync_manager_test.go +++ b/chain/sync_manager_test.go @@ -1,3 +1,4 @@ +// stm: #unit package chain import ( @@ -6,10 +7,16 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types/mock" ) +func init() { + BootstrapPeerThreshold = 1 +} + var genTs = mock.TipSet(mock.MkBlock(nil, 0, 0)) type syncOp struct { @@ -17,7 +24,7 @@ type syncOp struct { done func() } -func runSyncMgrTest(t *testing.T, tname string, thresh int, tf func(*testing.T, *SyncManager, chan *syncOp)) { +func runSyncMgrTest(t *testing.T, tname string, thresh int, tf func(*testing.T, *syncManager, chan *syncOp)) { syncTargets := make(chan *syncOp) sm := NewSyncManager(func(ctx context.Context, ts *types.TipSet) error { ch := make(chan struct{}) @@ -27,8 +34,13 @@ func runSyncMgrTest(t *testing.T, tname string, thresh int, tf func(*testing.T, } <-ch return nil - }) - sm.bspThresh = thresh + }).(*syncManager) + + oldBootstrapPeerThreshold := BootstrapPeerThreshold + BootstrapPeerThreshold = thresh + defer func() { + BootstrapPeerThreshold = oldBootstrapPeerThreshold + }() sm.Start() defer sm.Stop() @@ -67,7 +79,92 @@ func assertGetSyncOp(t *testing.T, c chan *syncOp, ts *types.TipSet) { } } +func TestSyncManagerEdgeCase(t *testing.T) { + //stm: @CHAIN_SYNCER_SET_PEER_HEAD_001 + ctx := context.Background() + + a := mock.TipSet(mock.MkBlock(genTs, 1, 1)) + t.Logf("a: %s", a) + b1 := mock.TipSet(mock.MkBlock(a, 1, 2)) + t.Logf("b1: %s", b1) + b2 := mock.TipSet(mock.MkBlock(a, 2, 3)) + t.Logf("b2: %s", b2) + c1 := mock.TipSet(mock.MkBlock(b1, 2, 4)) + t.Logf("c1: %s", c1) + c2 := mock.TipSet(mock.MkBlock(b2, 1, 5)) + t.Logf("c2: %s", c2) + d1 := mock.TipSet(mock.MkBlock(c1, 1, 6)) + t.Logf("d1: %s", d1) + e1 := mock.TipSet(mock.MkBlock(d1, 1, 7)) + t.Logf("e1: %s", e1) + + runSyncMgrTest(t, "edgeCase", 1, func(t *testing.T, sm *syncManager, stc chan *syncOp) { + sm.SetPeerHead(ctx, "peer1", a) + + sm.SetPeerHead(ctx, "peer1", b1) + sm.SetPeerHead(ctx, "peer1", b2) + + assertGetSyncOp(t, stc, a) + + // b1 and b2 are in queue after a; the sync manager should pick the heaviest one which is b2 + bop := <-stc + if !bop.ts.Equals(b2) { + t.Fatalf("Expected tipset %s to sync, but got %s", b2, bop.ts) + } + + sm.SetPeerHead(ctx, "peer2", c2) + sm.SetPeerHead(ctx, "peer2", c1) + sm.SetPeerHead(ctx, "peer3", b2) + sm.SetPeerHead(ctx, "peer1", a) + + bop.done() + + // get the next sync target; it should be c1 as the heaviest tipset but added last (same weight as c2) + bop = <-stc + if bop.ts.Equals(c2) { + // there's a small race and we might get c2 first. + // But we should still end on c1. + bop.done() + bop = <-stc + } + + if !bop.ts.Equals(c1) { + t.Fatalf("Expected tipset %s to sync, but got %s", c1, bop.ts) + } + + sm.SetPeerHead(ctx, "peer4", d1) + sm.SetPeerHead(ctx, "peer5", e1) + bop.done() + + // get the last sync target; it should be e1 + var last *types.TipSet + for i := 0; i < 10; { + select { + case bop = <-stc: + bop.done() + if last == nil || bop.ts.Height() > last.Height() { + last = bop.ts + } + default: + i++ + time.Sleep(10 * time.Millisecond) + } + } + if !last.Equals(e1) { + t.Fatalf("Expected tipset %s to sync, but got %s", e1, last) + } + + sm.mx.Lock() + activeSyncs := len(sm.state) + sm.mx.Unlock() + if activeSyncs != 0 { + t.Errorf("active syncs expected empty but got: %d", activeSyncs) + } + }) +} + func TestSyncManager(t *testing.T) { + //stm: @CHAIN_SYNCER_SET_PEER_HEAD_001 ctx := context.Background() a := mock.TipSet(mock.MkBlock(genTs, 1, 1)) @@ -77,12 +174,12 @@ func TestSyncManager(t *testing.T) { c3 := mock.TipSet(mock.MkBlock(b, 3, 5)) d := mock.TipSet(mock.MkBlock(c1, 4, 5)) - runSyncMgrTest(t, "testBootstrap", 1, func(t *testing.T, sm *SyncManager, stc chan *syncOp) { + runSyncMgrTest(t, "testBootstrap", 1, func(t *testing.T, sm *syncManager, stc chan *syncOp) { sm.SetPeerHead(ctx, "peer1", c1) assertGetSyncOp(t, stc, c1) }) - runSyncMgrTest(t, "testBootstrap", 2, func(t *testing.T, sm *SyncManager, stc chan *syncOp) { + runSyncMgrTest(t, "testBootstrap", 2, func(t *testing.T, sm *syncManager, stc chan *syncOp) { sm.SetPeerHead(ctx, "peer1", c1) assertNoOp(t, stc) @@ -90,7 +187,7 @@ func TestSyncManager(t *testing.T) { assertGetSyncOp(t, stc, c1) }) - runSyncMgrTest(t, "testSyncAfterBootstrap", 1, func(t *testing.T, sm *SyncManager, stc chan *syncOp) { + runSyncMgrTest(t, "testSyncAfterBootstrap", 1, func(t *testing.T, sm *syncManager, stc chan *syncOp) { sm.SetPeerHead(ctx, "peer1", b) assertGetSyncOp(t, stc, b) @@ -101,7 +198,7 @@ func TestSyncManager(t *testing.T) { assertGetSyncOp(t, stc, c2) }) - runSyncMgrTest(t, "testCoalescing", 1, func(t *testing.T, sm *SyncManager, stc chan *syncOp) { + runSyncMgrTest(t, "testCoalescing", 1, func(t *testing.T, sm *syncManager, stc chan *syncOp) { sm.SetPeerHead(ctx, "peer1", a) assertGetSyncOp(t, stc, a) @@ -122,7 +219,7 @@ func TestSyncManager(t *testing.T) { assertGetSyncOp(t, stc, d) }) - runSyncMgrTest(t, "testSyncIncomingTipset", 1, func(t *testing.T, sm *SyncManager, stc chan *syncOp) { + runSyncMgrTest(t, "testSyncIncomingTipset", 1, func(t *testing.T, sm *syncManager, stc chan *syncOp) { sm.SetPeerHead(ctx, "peer1", a) assertGetSyncOp(t, stc, a) @@ -148,3 +245,34 @@ func TestSyncManager(t *testing.T) { op3.done() }) } + +func TestSyncManagerBucketSet(t *testing.T) { + ts1 := mock.TipSet(mock.MkBlock(nil, 0, 0)) + ts2 := mock.TipSet(mock.MkBlock(ts1, 1, 0)) + bucket1 := newSyncTargetBucket(ts1, ts2) + bucketSet := syncBucketSet{buckets: []*syncTargetBucket{bucket1}} + + // inserting a tipset (potential sync target) from an existing chain, should add to an existing bucket + //stm: @CHAIN_SYNCER_ADD_SYNC_TARGET_001 + ts3 := mock.TipSet(mock.MkBlock(ts2, 2, 0)) + bucketSet.Insert(ts3) + require.Equal(t, 1, len(bucketSet.buckets)) + require.Equal(t, 3, len(bucketSet.buckets[0].tips)) + + // inserting a tipset from new chain, should create a new bucket + ts4fork := mock.TipSet(mock.MkBlock(nil, 1, 1)) + bucketSet.Insert(ts4fork) + require.Equal(t, 2, len(bucketSet.buckets)) + require.Equal(t, 3, len(bucketSet.buckets[0].tips)) + require.Equal(t, 1, len(bucketSet.buckets[1].tips)) + + // Pop removes the best bucket (best sync target), e.g. bucket1 + //stm: @CHAIN_SYNCER_SELECT_SYNC_TARGET_001 + popped := bucketSet.Pop() + require.Equal(t, popped, bucket1) + require.Equal(t, 1, len(bucketSet.buckets)) + + // PopRelated removes the bucket containing the given tipset, leaving the set empty + bucketSet.PopRelated(ts4fork) + require.Equal(t, 0, len(bucketSet.buckets)) +} diff --git a/chain/sync_test.go b/chain/sync_test.go index 9c7d47bc6..a86d42f17 100644 --- a/chain/sync_test.go +++ b/chain/sync_test.go @@ -1,3 +1,4 @@ +// stm: #unit package chain_test import ( @@ -7,21 +8,25 @@ import ( "testing" "time" + "github.com/ipfs/go-cid" + ds "github.com/ipfs/go-datastore" logging "github.com/ipfs/go-log/v2" - "github.com/libp2p/go-libp2p-core/peer" + "github.com/libp2p/go-libp2p/core/peer" mocknet "github.com/libp2p/go-libp2p/p2p/net/mock" "github.com/stretchr/testify/require" "github.com/filecoin-project/go-address" - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/abi/big" - "github.com/filecoin-project/specs-actors/actors/builtin/miner" - "github.com/filecoin-project/specs-actors/actors/builtin/power" - "github.com/filecoin-project/specs-actors/actors/builtin/verifreg" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/network" + prooftypes "github.com/filecoin-project/go-state-types/proof" "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/consensus/filcns" "github.com/filecoin-project/lotus/chain/gen" + "github.com/filecoin-project/lotus/chain/gen/slashfilter" + "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" mocktypes "github.com/filecoin-project/lotus/chain/types/mock" @@ -33,12 +38,13 @@ import ( func init() { build.InsecurePoStValidation = true - os.Setenv("TRUST_PARAMS", "1") - miner.SupportedProofTypes = map[abi.RegisteredProof]struct{}{ - abi.RegisteredProof_StackedDRG2KiBSeal: {}, + err := os.Setenv("TRUST_PARAMS", "1") + if err != nil { + panic(err) } - power.ConsensusMinerMinPower = big.NewInt(2048) - verifreg.MinVerifiedDealSize = big.NewInt(256) + policy.SetSupportedProofTypes(abi.RegisteredSealProof_StackedDrg2KiBV1) + policy.SetConsensusMinerMinPower(abi.NewStoragePower(2048)) + policy.SetMinVerifiedDealSize(abi.NewStoragePower(256)) } const source = 0 @@ -76,6 +82,7 @@ type syncTestUtil struct { blocks []*store.FullTipSet nds []api.FullNode + us stmgr.UpgradeSchedule } func prepSyncTest(t testing.TB, h int) *syncTestUtil { @@ -93,8 +100,75 @@ func prepSyncTest(t testing.TB, h int) *syncTestUtil { ctx: ctx, cancel: cancel, - mn: mocknet.New(ctx), + mn: mocknet.New(), g: g, + us: filcns.DefaultUpgradeSchedule(), + } + + tu.addSourceNode(h) + + //tu.checkHeight("source", source, h) + + // separate logs + fmt.Println("\x1b[31m///////////////////////////////////////////////////\x1b[39b") + + return tu +} + +func prepSyncTestWithV5Height(t testing.TB, h int, v5height abi.ChainEpoch) *syncTestUtil { + logging.SetLogLevel("*", "INFO") + + sched := stmgr.UpgradeSchedule{{ + // prepare for upgrade. + Network: network.Version9, + Height: 1, + Migration: filcns.UpgradeActorsV2, + }, { + Network: network.Version10, + Height: 2, + Migration: filcns.UpgradeActorsV3, + }, { + Network: network.Version12, + Height: 3, + Migration: filcns.UpgradeActorsV4, + }, { + Network: network.Version13, + Height: v5height, + Migration: filcns.UpgradeActorsV5, + }, { + Network: network.Version14, + Height: v5height + 10, + Migration: filcns.UpgradeActorsV6, + }, { + Network: network.Version15, + Height: v5height + 15, + Migration: filcns.UpgradeActorsV7, + }, { + Network: network.Version16, + Height: v5height + 20, + Migration: filcns.UpgradeActorsV8, + }, { + Network: network.Version17, + Height: v5height + 25, + Migration: filcns.UpgradeActorsV9, + }} + + g, err := gen.NewGeneratorWithUpgradeSchedule(sched) + + if err != nil { + t.Fatalf("%+v", err) + } + + ctx, cancel := context.WithCancel(context.Background()) + + tu := &syncTestUtil{ + t: t, + ctx: ctx, + cancel: cancel, + + mn: mocknet.New(), + g: g, + us: sched, } tu.addSourceNode(h) @@ -102,7 +176,6 @@ func prepSyncTest(t testing.TB, h int) *syncTestUtil { // separate logs fmt.Println("\x1b[31m///////////////////////////////////////////////////\x1b[39b") - return tu } @@ -142,20 +215,21 @@ func (tu *syncTestUtil) pushFtsAndWait(to int, fts *store.FullTipSet, wait bool) } func (tu *syncTestUtil) pushTsExpectErr(to int, fts *store.FullTipSet, experr bool) { + ctx := context.TODO() for _, fb := range fts.Blocks { var b types.BlockMsg // -1 to match block.Height b.Header = fb.Header for _, msg := range fb.SecpkMessages { - c, err := tu.nds[to].(*impl.FullNodeAPI).ChainAPI.Chain.PutMessage(msg) + c, err := tu.nds[to].(*impl.FullNodeAPI).ChainAPI.Chain.PutMessage(ctx, msg) require.NoError(tu.t, err) b.SecpkMessages = append(b.SecpkMessages, c) } for _, msg := range fb.BlsMessages { - c, err := tu.nds[to].(*impl.FullNodeAPI).ChainAPI.Chain.PutMessage(msg) + c, err := tu.nds[to].(*impl.FullNodeAPI).ChainAPI.Chain.PutMessage(ctx, msg) require.NoError(tu.t, err) b.BlsMessages = append(b.BlsMessages, c) @@ -170,7 +244,7 @@ func (tu *syncTestUtil) pushTsExpectErr(to int, fts *store.FullTipSet, experr bo } } -func (tu *syncTestUtil) mineOnBlock(blk *store.FullTipSet, src int, miners []int, wait, fail bool) *store.FullTipSet { +func (tu *syncTestUtil) mineOnBlock(blk *store.FullTipSet, to int, miners []int, wait, fail bool, msgs [][]*types.SignedMessage, nulls abi.ChainEpoch, push bool) *store.FullTipSet { if miners == nil { for i := range tu.g.Miners { miners = append(miners, i) @@ -184,37 +258,33 @@ func (tu *syncTestUtil) mineOnBlock(blk *store.FullTipSet, src int, miners []int fmt.Println("Miner mining block: ", maddrs) - mts, err := tu.g.NextTipSetFromMiners(blk.TipSet(), maddrs) - require.NoError(tu.t, err) - - if fail { - tu.pushTsExpectErr(src, mts.TipSet, true) + var nts *store.FullTipSet + var err error + if msgs != nil { + nts, err = tu.g.NextTipSetFromMinersWithMessagesAndNulls(blk.TipSet(), maddrs, msgs, nulls) + require.NoError(tu.t, err) } else { - tu.pushFtsAndWait(src, mts.TipSet, wait) + mt, err := tu.g.NextTipSetFromMiners(blk.TipSet(), maddrs, nulls) + require.NoError(tu.t, err) + nts = mt.TipSet } - return mts.TipSet + if push { + if fail { + tu.pushTsExpectErr(to, nts, true) + } else { + tu.pushFtsAndWait(to, nts, wait) + } + } + + return nts } func (tu *syncTestUtil) mineNewBlock(src int, miners []int) { - mts := tu.mineOnBlock(tu.g.CurTipset, src, miners, true, false) + mts := tu.mineOnBlock(tu.g.CurTipset, src, miners, true, false, nil, 0, true) tu.g.CurTipset = mts } -func fblkToBlkMsg(fb *types.FullBlock) *types.BlockMsg { - out := &types.BlockMsg{ - Header: fb.Header, - } - - for _, msg := range fb.BlsMessages { - out.BlsMessages = append(out.BlsMessages, msg.Cid()) - } - for _, msg := range fb.SecpkMessages { - out.SecpkMessages = append(out.SecpkMessages, msg.Cid()) - } - return out -} - func (tu *syncTestUtil) addSourceNode(gen int) { if tu.genesis != nil { tu.t.Fatal("source node already exists") @@ -223,25 +293,26 @@ func (tu *syncTestUtil) addSourceNode(gen int) { sourceRepo, genesis, blocks := tu.repoWithChain(tu.t, gen) var out api.FullNode - // TODO: Don't ignore stop - _, err := node.New(tu.ctx, + stop, err := node.New(tu.ctx, node.FullAPI(&out), - node.Online(), + node.Base(), node.Repo(sourceRepo), node.MockHost(tu.mn), node.Test(), node.Override(new(modules.Genesis), modules.LoadGenesis(genesis)), + node.Override(new(stmgr.UpgradeSchedule), tu.us), ) require.NoError(tu.t, err) + tu.t.Cleanup(func() { _ = stop(context.Background()) }) - lastTs := blocks[len(blocks)-1].Blocks - for _, lastB := range lastTs { - cs := out.(*impl.FullNodeAPI).ChainAPI.Chain - require.NoError(tu.t, cs.AddToTipSetTracker(lastB.Header)) - err = cs.AddBlock(tu.ctx, lastB.Header) - require.NoError(tu.t, err) + lastTs := blocks[len(blocks)-1] + cs := out.(*impl.FullNodeAPI).ChainAPI.Chain + for _, lastB := range lastTs.Blocks { + require.NoError(tu.t, cs.AddToTipSetTracker(context.Background(), lastB.Header)) } + err = cs.PutTipSet(tu.ctx, lastTs.TipSet()) + require.NoError(tu.t, err) tu.genesis = genesis tu.blocks = blocks @@ -255,17 +326,19 @@ func (tu *syncTestUtil) addClientNode() int { var out api.FullNode - // TODO: Don't ignore stop - _, err := node.New(tu.ctx, + r := repo.NewMemory(nil) + stop, err := node.New(tu.ctx, node.FullAPI(&out), - node.Online(), - node.Repo(repo.NewMemory(nil)), + node.Base(), + node.Repo(r), node.MockHost(tu.mn), 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()) }) tu.nds = append(tu.nds, out) return len(tu.nds) - 1 @@ -330,6 +403,39 @@ func (tu *syncTestUtil) compareSourceState(with int) { } } +func (tu *syncTestUtil) assertBad(node int, ts *types.TipSet) { + for _, blk := range ts.Cids() { + rsn, err := tu.nds[node].SyncCheckBad(context.TODO(), blk) + require.NoError(tu.t, err) + require.True(tu.t, len(rsn) != 0) + } +} + +func (tu *syncTestUtil) getHead(node int) *types.TipSet { + ts, err := tu.nds[node].ChainHead(context.TODO()) + require.NoError(tu.t, err) + return ts +} + +func (tu *syncTestUtil) checkpointTs(node int, tsk types.TipSetKey) { + require.NoError(tu.t, tu.nds[node].SyncCheckpoint(context.TODO(), tsk)) +} + +func (tu *syncTestUtil) nodeHasTs(node int, tsk types.TipSetKey) bool { + _, err := tu.nds[node].ChainGetTipSet(context.TODO(), tsk) + return err == nil +} + +func (tu *syncTestUtil) waitUntilNodeHasTs(node int, tsk types.TipSetKey) { + for !tu.nodeHasTs(node, tsk) { + // Time to allow for syncing and validation + time.Sleep(10 * time.Millisecond) + } + + // Time to allow for syncing and validation + time.Sleep(2 * time.Second) +} + func (tu *syncTestUtil) waitUntilSync(from, to int) { target, err := tu.nds[from].ChainHead(tu.ctx) if err != nil { @@ -348,17 +454,25 @@ func (tu *syncTestUtil) waitUntilSyncTarget(to int, target *types.TipSet) { tu.t.Fatal(err) } - // TODO: some sort of timeout? - for n := range hc { - for _, c := range n { - if c.Val.Equals(target) { - return + timeout := time.After(5 * time.Second) + + for { + select { + case n := <-hc: + for _, c := range n { + if c.Val.Equals(target) { + return + } } + case <-timeout: + tu.t.Fatal("waitUntilSyncTarget timeout") } } } func TestSyncSimple(t *testing.T) { + //stm: @BLOCKCHAIN_BEACON_VALIDATE_BLOCK_VALUES_01, @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001, @CHAIN_SYNCER_START_001 + //stm: @CHAIN_SYNCER_NEW_PEER_HEAD_001, @CHAIN_SYNCER_VALIDATE_MESSAGE_META_001, @CHAIN_SYNCER_STOP_001 H := 50 tu := prepSyncTest(t, H) @@ -375,6 +489,8 @@ func TestSyncSimple(t *testing.T) { } func TestSyncMining(t *testing.T) { + //stm: @BLOCKCHAIN_BEACON_VALIDATE_BLOCK_VALUES_01, @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001, @CHAIN_SYNCER_START_001 + //stm: @CHAIN_SYNCER_NEW_PEER_HEAD_001, @CHAIN_SYNCER_VALIDATE_MESSAGE_META_001, @CHAIN_SYNCER_STOP_001 H := 50 tu := prepSyncTest(t, H) @@ -397,6 +513,8 @@ func TestSyncMining(t *testing.T) { } func TestSyncBadTimestamp(t *testing.T) { + //stm: @BLOCKCHAIN_BEACON_VALIDATE_BLOCK_VALUES_01, @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001, @CHAIN_SYNCER_START_001 + //stm: @CHAIN_SYNCER_NEW_PEER_HEAD_001, @CHAIN_SYNCER_VALIDATE_MESSAGE_META_001, @CHAIN_SYNCER_STOP_001 H := 50 tu := prepSyncTest(t, H) @@ -408,20 +526,22 @@ func TestSyncBadTimestamp(t *testing.T) { base := tu.g.CurTipset tu.g.Timestamper = func(pts *types.TipSet, tl abi.ChainEpoch) uint64 { - return pts.MinTimestamp() + (build.BlockDelay / 2) + return pts.MinTimestamp() + (build.BlockDelaySecs / 2) } fmt.Println("BASE: ", base.Cids()) tu.printHeads() - a1 := tu.mineOnBlock(base, 0, nil, false, true) + a1 := tu.mineOnBlock(base, 0, nil, false, true, nil, 0, true) tu.g.Timestamper = nil - tu.g.ResyncBankerNonce(a1.TipSet()) + require.NoError(t, tu.g.ResyncBankerNonce(a1.TipSet())) + + tu.nds[0].(*impl.FullNodeAPI).SlashFilter = slashfilter.New(ds.NewMapDatastore()) fmt.Println("After mine bad block!") tu.printHeads() - a2 := tu.mineOnBlock(base, 0, nil, true, false) + a2 := tu.mineOnBlock(base, 0, nil, true, false, nil, 0, true) tu.waitUntilSync(0, client) @@ -433,6 +553,43 @@ func TestSyncBadTimestamp(t *testing.T) { } } +type badWpp struct{} + +func (wpp badWpp) GenerateCandidates(context.Context, abi.PoStRandomness, uint64) ([]uint64, error) { + return []uint64{1}, nil +} + +func (wpp badWpp) ComputeProof(context.Context, []prooftypes.ExtendedSectorInfo, abi.PoStRandomness, abi.ChainEpoch, network.Version) ([]prooftypes.PoStProof, error) { + return []prooftypes.PoStProof{ + { + PoStProof: abi.RegisteredPoStProof_StackedDrgWinning2KiBV1, + ProofBytes: []byte("evil"), + }, + }, nil +} + +func TestSyncBadWinningPoSt(t *testing.T) { + //stm: @BLOCKCHAIN_BEACON_VALIDATE_BLOCK_VALUES_01, @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001, @CHAIN_SYNCER_START_001 + //stm: @CHAIN_SYNCER_NEW_PEER_HEAD_001, @CHAIN_SYNCER_VALIDATE_MESSAGE_META_001, @CHAIN_SYNCER_STOP_001 + H := 15 + tu := prepSyncTest(t, H) + + client := tu.addClientNode() + + require.NoError(t, tu.mn.LinkAll()) + tu.connect(client, 0) + tu.waitUntilSync(0, client) + + base := tu.g.CurTipset + + // both miners now produce invalid winning posts + tu.g.SetWinningPoStProver(tu.g.Miners[0], &badWpp{}) + tu.g.SetWinningPoStProver(tu.g.Miners[1], &badWpp{}) + + // now ensure that new blocks are not accepted + tu.mineOnBlock(base, client, nil, false, true, nil, 0, true) +} + func (tu *syncTestUtil) loadChainToNode(to int) { // utility to simulate incoming blocks without miner process // TODO: should call syncer directly, this won't work correctly in all cases @@ -443,6 +600,9 @@ func (tu *syncTestUtil) loadChainToNode(to int) { } func TestSyncFork(t *testing.T) { + //stm: @BLOCKCHAIN_BEACON_VALIDATE_BLOCK_VALUES_01, @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001, @CHAIN_SYNCER_START_001 + //stm: @CHAIN_SYNCER_SYNC_001, @CHAIN_SYNCER_COLLECT_CHAIN_001, @CHAIN_SYNCER_COLLECT_HEADERS_001, @CHAIN_SYNCER_VALIDATE_TIPSET_001 + //stm: @CHAIN_SYNCER_NEW_PEER_HEAD_001, @CHAIN_SYNCER_VALIDATE_MESSAGE_META_001, @CHAIN_SYNCER_STOP_001 H := 10 tu := prepSyncTest(t, H) @@ -453,15 +613,20 @@ func TestSyncFork(t *testing.T) { tu.loadChainToNode(p1) tu.loadChainToNode(p2) - phead := func() { + printHead := func() { h1, err := tu.nds[1].ChainHead(tu.ctx) require.NoError(tu.t, err) h2, err := tu.nds[2].ChainHead(tu.ctx) require.NoError(tu.t, err) - fmt.Println("Node 1: ", h1.Cids(), h1.Parents(), h1.Height()) - fmt.Println("Node 2: ", h2.Cids(), h1.Parents(), h2.Height()) + w1, err := tu.nds[1].(*impl.FullNodeAPI).ChainAPI.Chain.Weight(tu.ctx, h1) + require.NoError(tu.t, err) + w2, err := tu.nds[2].(*impl.FullNodeAPI).ChainAPI.Chain.Weight(tu.ctx, h2) + require.NoError(tu.t, err) + + fmt.Println("Node 1: ", h1.Cids(), h1.Parents(), h1.Height(), w1) + fmt.Println("Node 2: ", h2.Cids(), h2.Parents(), h2.Height(), w2) //time.Sleep(time.Second * 2) fmt.Println() fmt.Println() @@ -469,26 +634,28 @@ func TestSyncFork(t *testing.T) { fmt.Println() } - phead() + printHead() base := tu.g.CurTipset fmt.Println("Mining base: ", base.TipSet().Cids(), base.TipSet().Height()) // The two nodes fork at this point into 'a' and 'b' - a1 := tu.mineOnBlock(base, p1, []int{0}, true, false) - a := tu.mineOnBlock(a1, p1, []int{0}, true, false) - a = tu.mineOnBlock(a, p1, []int{0}, true, false) + a1 := tu.mineOnBlock(base, p1, []int{0}, true, false, nil, 0, true) + a := tu.mineOnBlock(a1, p1, []int{0}, true, false, nil, 0, true) + a = tu.mineOnBlock(a, p1, []int{0}, true, false, nil, 0, true) - tu.g.ResyncBankerNonce(a1.TipSet()) + require.NoError(t, tu.g.ResyncBankerNonce(a1.TipSet())) // chain B will now be heaviest - b := tu.mineOnBlock(base, p2, []int{1}, true, false) - b = tu.mineOnBlock(b, p2, []int{1}, true, false) - b = tu.mineOnBlock(b, p2, []int{1}, true, false) - b = tu.mineOnBlock(b, p2, []int{1}, true, false) + b := tu.mineOnBlock(base, p2, []int{1}, true, false, nil, 0, true) + b = tu.mineOnBlock(b, p2, []int{1}, true, false, nil, 0, true) + b = tu.mineOnBlock(b, p2, []int{1}, true, false, nil, 0, true) + b = tu.mineOnBlock(b, p2, []int{1}, true, false, nil, 0, true) fmt.Println("A: ", a.Cids(), a.TipSet().Height()) fmt.Println("B: ", b.Cids(), b.TipSet().Height()) + printHead() + // Now for the fun part!! require.NoError(t, tu.mn.LinkAll()) @@ -496,7 +663,272 @@ func TestSyncFork(t *testing.T) { tu.waitUntilSyncTarget(p1, b.TipSet()) tu.waitUntilSyncTarget(p2, b.TipSet()) - phead() + printHead() +} + +// This test crafts a tipset with 2 blocks, A and B. +// A and B both include _different_ messages from sender X with nonce N (where N is the correct nonce for X). +// We can confirm that the state can be correctly computed, and that `MessagesForTipset` behaves as expected. +func TestDuplicateNonce(t *testing.T) { + //stm: @BLOCKCHAIN_BEACON_VALIDATE_BLOCK_VALUES_01, @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001, @CHAIN_SYNCER_START_001 + //stm: @CHAIN_SYNCER_SYNC_001, @CHAIN_SYNCER_COLLECT_CHAIN_001, @CHAIN_SYNCER_COLLECT_HEADERS_001, @CHAIN_SYNCER_VALIDATE_TIPSET_001 + //stm: @CHAIN_SYNCER_NEW_PEER_HEAD_001, @CHAIN_SYNCER_VALIDATE_MESSAGE_META_001, @CHAIN_SYNCER_STOP_001 + H := 10 + tu := prepSyncTest(t, H) + + base := tu.g.CurTipset + + // Get the banker from computed tipset state, not the parent. + st, _, err := tu.g.StateManager().TipSetState(context.TODO(), base.TipSet()) + require.NoError(t, err) + ba, err := tu.g.StateManager().LoadActorRaw(context.TODO(), tu.g.Banker(), st) + require.NoError(t, err) + + // Produce a message from the banker to the rcvr + makeMsg := func(rcvr address.Address) *types.SignedMessage { + msg := types.Message{ + To: rcvr, + From: tu.g.Banker(), + + Nonce: ba.Nonce, + + Value: types.NewInt(1), + + Method: 0, + + GasLimit: 100_000_000, + GasFeeCap: types.NewInt(0), + GasPremium: types.NewInt(0), + } + + sig, err := tu.g.Wallet().WalletSign(context.TODO(), tu.g.Banker(), msg.Cid().Bytes(), api.MsgMeta{}) + require.NoError(t, err) + + return &types.SignedMessage{ + Message: msg, + Signature: *sig, + } + } + + msgs := make([][]*types.SignedMessage, 2) + // Each miner includes a message from the banker with the same nonce, but to different addresses + for k := range msgs { + msgs[k] = []*types.SignedMessage{makeMsg(tu.g.Miners[k])} + } + + ts1 := tu.mineOnBlock(base, 0, []int{0, 1}, true, false, msgs, 0, true) + + tu.waitUntilSyncTarget(0, ts1.TipSet()) + + // mine another tipset + + ts2 := tu.mineOnBlock(ts1, 0, []int{0, 1}, true, false, make([][]*types.SignedMessage, 2), 0, true) + tu.waitUntilSyncTarget(0, ts2.TipSet()) + + var includedMsg cid.Cid + var skippedMsg cid.Cid + //stm: @CHAIN_STATE_SEARCH_MSG_001 + r0, err0 := tu.nds[0].StateSearchMsg(context.TODO(), ts2.TipSet().Key(), msgs[0][0].Cid(), api.LookbackNoLimit, true) + r1, err1 := tu.nds[0].StateSearchMsg(context.TODO(), ts2.TipSet().Key(), msgs[1][0].Cid(), api.LookbackNoLimit, true) + + if err0 == nil { + require.Error(t, err1, "at least one of the StateGetReceipt calls should fail") + require.True(t, r0.Receipt.ExitCode.IsSuccess()) + includedMsg = msgs[0][0].Message.Cid() + skippedMsg = msgs[1][0].Message.Cid() + } else { + require.NoError(t, err1, "both the StateGetReceipt calls should not fail") + require.True(t, r1.Receipt.ExitCode.IsSuccess()) + includedMsg = msgs[1][0].Message.Cid() + skippedMsg = msgs[0][0].Message.Cid() + } + + _, rslts, err := tu.g.StateManager().ExecutionTrace(context.TODO(), ts1.TipSet()) + require.NoError(t, err) + found := false + for _, v := range rslts { + if v.Msg.Cid() == skippedMsg { + t.Fatal("skipped message should not be in exec trace") + } + + if v.Msg.Cid() == includedMsg { + found = true + } + } + + if !found { + t.Fatal("included message should be in exec trace") + } + + mft, err := tu.g.ChainStore().MessagesForTipset(context.TODO(), ts1.TipSet()) + require.NoError(t, err) + require.True(t, len(mft) == 1, "only expecting one message for this tipset") + require.Equal(t, includedMsg, mft[0].VMMessage().Cid(), "messages for tipset didn't contain expected message") +} + +// This test asserts that a block that includes a message with bad nonce can't be synced. A nonce is "bad" if it can't +// be applied on the parent state. +func TestBadNonce(t *testing.T) { + //stm: @BLOCKCHAIN_BEACON_VALIDATE_BLOCK_VALUES_01, @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001, @CHAIN_SYNCER_START_001 + //stm: @CHAIN_SYNCER_SYNC_001, @CHAIN_SYNCER_COLLECT_CHAIN_001, @CHAIN_SYNCER_COLLECT_HEADERS_001 + //stm: @CHAIN_SYNCER_VALIDATE_MESSAGE_META_001, @CHAIN_SYNCER_VALIDATE_TIPSET_001, @CHAIN_SYNCER_STOP_001 + H := 10 + tu := prepSyncTest(t, H) + + base := tu.g.CurTipset + + // Get the banker from computed tipset state, not the parent. + st, _, err := tu.g.StateManager().TipSetState(context.TODO(), base.TipSet()) + require.NoError(t, err) + ba, err := tu.g.StateManager().LoadActorRaw(context.TODO(), tu.g.Banker(), st) + require.NoError(t, err) + + // Produce a message from the banker with a bad nonce + makeBadMsg := func() *types.SignedMessage { + msg := types.Message{ + To: tu.g.Banker(), + From: tu.g.Banker(), + + Nonce: ba.Nonce + 5, + + Value: types.NewInt(1), + + Method: 0, + + GasLimit: 100_000_000, + GasFeeCap: types.NewInt(0), + GasPremium: types.NewInt(0), + } + + sig, err := tu.g.Wallet().WalletSign(context.TODO(), tu.g.Banker(), msg.Cid().Bytes(), api.MsgMeta{}) + require.NoError(t, err) + + return &types.SignedMessage{ + Message: msg, + Signature: *sig, + } + } + + msgs := make([][]*types.SignedMessage, 1) + msgs[0] = []*types.SignedMessage{makeBadMsg()} + + tu.mineOnBlock(base, 0, []int{0}, true, true, msgs, 0, true) +} + +// This test introduces a block that has 2 messages, with the same sender, and same nonce. +// One of the messages uses the sender's robust address, the other uses the ID address. +// Such a block is invalid and should not sync. +func TestMismatchedNoncesRobustID(t *testing.T) { + //stm: @BLOCKCHAIN_BEACON_VALIDATE_BLOCK_VALUES_01, @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001, @CHAIN_SYNCER_START_001 + //stm: @CHAIN_SYNCER_SYNC_001, @CHAIN_SYNCER_COLLECT_CHAIN_001, @CHAIN_SYNCER_COLLECT_HEADERS_001 + //stm: @CHAIN_SYNCER_VALIDATE_MESSAGE_META_001, @CHAIN_SYNCER_VALIDATE_TIPSET_001, @CHAIN_SYNCER_STOP_001 + v5h := abi.ChainEpoch(4) + tu := prepSyncTestWithV5Height(t, int(v5h+5), v5h) + + base := tu.g.CurTipset + + // Get the banker from computed tipset state, not the parent. + st, _, err := tu.g.StateManager().TipSetState(context.TODO(), base.TipSet()) + require.NoError(t, err) + ba, err := tu.g.StateManager().LoadActorRaw(context.TODO(), tu.g.Banker(), st) + require.NoError(t, err) + + // Produce a message from the banker + //stm: @CHAIN_STATE_LOOKUP_ID_001 + makeMsg := func(id bool) *types.SignedMessage { + sender := tu.g.Banker() + if id { + s, err := tu.nds[0].StateLookupID(context.TODO(), sender, base.TipSet().Key()) + require.NoError(t, err) + sender = s + } + + msg := types.Message{ + To: tu.g.Banker(), + From: sender, + + Nonce: ba.Nonce, + + Value: types.NewInt(1), + + Method: 0, + + GasLimit: 100_000_000, + GasFeeCap: types.NewInt(0), + GasPremium: types.NewInt(0), + } + + sig, err := tu.g.Wallet().WalletSign(context.TODO(), tu.g.Banker(), msg.Cid().Bytes(), api.MsgMeta{}) + require.NoError(t, err) + + return &types.SignedMessage{ + Message: msg, + Signature: *sig, + } + } + + msgs := make([][]*types.SignedMessage, 1) + msgs[0] = []*types.SignedMessage{makeMsg(false), makeMsg(true)} + + tu.mineOnBlock(base, 0, []int{0}, true, true, msgs, 0, true) +} + +// This test introduces a block that has 2 messages, with the same sender, and nonces N and N+1 (so both can be included in a block) +// One of the messages uses the sender's robust address, the other uses the ID address. +// Such a block is valid and should sync. +func TestMatchedNoncesRobustID(t *testing.T) { + //stm: @BLOCKCHAIN_BEACON_VALIDATE_BLOCK_VALUES_01, @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001, @CHAIN_SYNCER_START_001 + //stm: @CHAIN_SYNCER_SYNC_001, @CHAIN_SYNCER_COLLECT_CHAIN_001, @CHAIN_SYNCER_COLLECT_HEADERS_001 + //stm: @CHAIN_SYNCER_VALIDATE_MESSAGE_META_001, @CHAIN_SYNCER_VALIDATE_TIPSET_001, @CHAIN_SYNCER_STOP_001 + v5h := abi.ChainEpoch(4) + tu := prepSyncTestWithV5Height(t, int(v5h+5), v5h) + + base := tu.g.CurTipset + + // Get the banker from computed tipset state, not the parent. + st, _, err := tu.g.StateManager().TipSetState(context.TODO(), base.TipSet()) + require.NoError(t, err) + ba, err := tu.g.StateManager().LoadActorRaw(context.TODO(), tu.g.Banker(), st) + require.NoError(t, err) + + // Produce a message from the banker with specified nonce + //stm: @CHAIN_STATE_LOOKUP_ID_001 + makeMsg := func(n uint64, id bool) *types.SignedMessage { + sender := tu.g.Banker() + if id { + s, err := tu.nds[0].StateLookupID(context.TODO(), sender, base.TipSet().Key()) + require.NoError(t, err) + sender = s + } + + msg := types.Message{ + To: tu.g.Banker(), + From: sender, + + Nonce: n, + + Value: types.NewInt(1), + + Method: 0, + + GasLimit: 100_000_000, + GasFeeCap: types.NewInt(0), + GasPremium: types.NewInt(0), + } + + sig, err := tu.g.Wallet().WalletSign(context.TODO(), tu.g.Banker(), msg.Cid().Bytes(), api.MsgMeta{}) + require.NoError(t, err) + + return &types.SignedMessage{ + Message: msg, + Signature: *sig, + } + } + + msgs := make([][]*types.SignedMessage, 1) + msgs[0] = []*types.SignedMessage{makeMsg(ba.Nonce, false), makeMsg(ba.Nonce+1, true)} + + tu.mineOnBlock(base, 0, []int{0}, true, false, msgs, 0, true) } func BenchmarkSyncBasic(b *testing.B) { @@ -520,6 +952,8 @@ func runSyncBenchLength(b *testing.B, l int) { } func TestSyncInputs(t *testing.T) { + //stm: @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001, @CHAIN_SYNCER_VALIDATE_BLOCK_001, + //stm: @CHAIN_SYNCER_START_001, @CHAIN_SYNCER_STOP_001 H := 10 tu := prepSyncTest(t, H) @@ -531,7 +965,7 @@ func TestSyncInputs(t *testing.T) { err := s.ValidateBlock(context.TODO(), &types.FullBlock{ Header: &types.BlockHeader{}, - }) + }, false) if err == nil { t.Fatal("should error on empty block") } @@ -540,8 +974,290 @@ func TestSyncInputs(t *testing.T) { h.ElectionProof = nil - err = s.ValidateBlock(context.TODO(), &types.FullBlock{Header: h}) + err = s.ValidateBlock(context.TODO(), &types.FullBlock{Header: h}, false) if err == nil { t.Fatal("should error on block with nil election proof") } } + +func TestSyncCheckpointHead(t *testing.T) { + //stm: @BLOCKCHAIN_BEACON_VALIDATE_BLOCK_VALUES_01, @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001, @CHAIN_SYNCER_START_001 + //stm: @CHAIN_SYNCER_SYNC_001, @CHAIN_SYNCER_COLLECT_CHAIN_001, @CHAIN_SYNCER_COLLECT_HEADERS_001 + //stm: @CHAIN_SYNCER_VALIDATE_MESSAGE_META_001, @CHAIN_SYNCER_VALIDATE_TIPSET_001, @CHAIN_SYNCER_STOP_001 + H := 10 + tu := prepSyncTest(t, H) + + p1 := tu.addClientNode() + p2 := tu.addClientNode() + + fmt.Println("GENESIS: ", tu.g.Genesis().Cid()) + tu.loadChainToNode(p1) + tu.loadChainToNode(p2) + + base := tu.g.CurTipset + fmt.Println("Mining base: ", base.TipSet().Cids(), base.TipSet().Height()) + + // The two nodes fork at this point into 'a' and 'b' + a1 := tu.mineOnBlock(base, p1, []int{0}, true, false, nil, 0, true) + a := tu.mineOnBlock(a1, p1, []int{0}, true, false, nil, 0, true) + a = tu.mineOnBlock(a, p1, []int{0}, true, false, nil, 0, true) + + tu.waitUntilSyncTarget(p1, a.TipSet()) + //stm: @CHAIN_SYNCER_CHECKPOINT_001 + tu.checkpointTs(p1, a.TipSet().Key()) + + require.NoError(t, tu.g.ResyncBankerNonce(a1.TipSet())) + // chain B will now be heaviest + b := tu.mineOnBlock(base, p2, []int{1}, true, false, nil, 0, true) + b = tu.mineOnBlock(b, p2, []int{1}, true, false, nil, 0, true) + b = tu.mineOnBlock(b, p2, []int{1}, true, false, nil, 0, true) + b = tu.mineOnBlock(b, p2, []int{1}, true, false, nil, 0, true) + + fmt.Println("A: ", a.Cids(), a.TipSet().Height()) + fmt.Println("B: ", b.Cids(), b.TipSet().Height()) + + // Now for the fun part!! p1 should mark p2's head as BAD. + + require.NoError(t, tu.mn.LinkAll()) + tu.connect(p1, p2) + tu.waitUntilNodeHasTs(p1, b.TipSet().Key()) + p1Head := tu.getHead(p1) + require.True(tu.t, p1Head.Equals(a.TipSet())) + //stm: @CHAIN_SYNCER_CHECK_BAD_001 + tu.assertBad(p1, b.TipSet()) + + // Should be able to switch forks. + //stm: @CHAIN_SYNCER_CHECKPOINT_001 + tu.checkpointTs(p1, b.TipSet().Key()) + p1Head = tu.getHead(p1) + require.True(tu.t, p1Head.Equals(b.TipSet())) +} + +func TestSyncCheckpointEarlierThanHead(t *testing.T) { + //stm: @BLOCKCHAIN_BEACON_VALIDATE_BLOCK_VALUES_01, @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001, @CHAIN_SYNCER_START_001 + //stm: @CHAIN_SYNCER_SYNC_001, @CHAIN_SYNCER_COLLECT_CHAIN_001, @CHAIN_SYNCER_COLLECT_HEADERS_001 + //stm: @CHAIN_SYNCER_VALIDATE_MESSAGE_META_001, @CHAIN_SYNCER_VALIDATE_TIPSET_001, @CHAIN_SYNCER_STOP_001 + H := 10 + tu := prepSyncTest(t, H) + + p1 := tu.addClientNode() + p2 := tu.addClientNode() + + fmt.Println("GENESIS: ", tu.g.Genesis().Cid()) + tu.loadChainToNode(p1) + tu.loadChainToNode(p2) + + base := tu.g.CurTipset + fmt.Println("Mining base: ", base.TipSet().Cids(), base.TipSet().Height()) + + // The two nodes fork at this point into 'a' and 'b' + a1 := tu.mineOnBlock(base, p1, []int{0}, true, false, nil, 0, true) + a := tu.mineOnBlock(a1, p1, []int{0}, true, false, nil, 0, true) + a = tu.mineOnBlock(a, p1, []int{0}, true, false, nil, 0, true) + + tu.waitUntilSyncTarget(p1, a.TipSet()) + //stm: @CHAIN_SYNCER_CHECKPOINT_001 + tu.checkpointTs(p1, a1.TipSet().Key()) + + require.NoError(t, tu.g.ResyncBankerNonce(a1.TipSet())) + // chain B will now be heaviest + b := tu.mineOnBlock(base, p2, []int{1}, true, false, nil, 0, true) + b = tu.mineOnBlock(b, p2, []int{1}, true, false, nil, 0, true) + b = tu.mineOnBlock(b, p2, []int{1}, true, false, nil, 0, true) + b = tu.mineOnBlock(b, p2, []int{1}, true, false, nil, 0, true) + + fmt.Println("A: ", a.Cids(), a.TipSet().Height()) + fmt.Println("B: ", b.Cids(), b.TipSet().Height()) + + // Now for the fun part!! p1 should mark p2's head as BAD. + + require.NoError(t, tu.mn.LinkAll()) + tu.connect(p1, p2) + tu.waitUntilNodeHasTs(p1, b.TipSet().Key()) + p1Head := tu.getHead(p1) + require.True(tu.t, p1Head.Equals(a.TipSet())) + //stm: @CHAIN_SYNCER_CHECK_BAD_001 + tu.assertBad(p1, b.TipSet()) + + // Should be able to switch forks. + //stm: @CHAIN_SYNCER_CHECKPOINT_001 + tu.checkpointTs(p1, b.TipSet().Key()) + p1Head = tu.getHead(p1) + require.True(tu.t, p1Head.Equals(b.TipSet())) +} + +func TestInvalidHeight(t *testing.T) { + //stm: @CHAIN_SYNCER_LOAD_GENESIS_001, @CHAIN_SYNCER_FETCH_TIPSET_001, @CHAIN_SYNCER_START_001 + //stm: @CHAIN_SYNCER_VALIDATE_MESSAGE_META_001, @CHAIN_SYNCER_STOP_001 + H := 50 + tu := prepSyncTest(t, H) + + client := tu.addClientNode() + + require.NoError(t, tu.mn.LinkAll()) + tu.connect(client, 0) + tu.waitUntilSync(0, client) + + base := tu.g.CurTipset + + for i := 0; i < 5; i++ { + base = tu.mineOnBlock(base, 0, nil, false, false, nil, 0, false) + } + + tu.mineOnBlock(base, 0, nil, false, true, nil, -1, true) +} + +// TestIncomingBlocks mines new blocks and checks if the incoming channel streams new block headers properly +func TestIncomingBlocks(t *testing.T) { + H := 50 + tu := prepSyncTest(t, H) + + client := tu.addClientNode() + require.NoError(t, tu.mn.LinkAll()) + + clientNode := tu.nds[client] + //stm: @CHAIN_SYNCER_INCOMING_BLOCKS_001 + incoming, err := clientNode.SyncIncomingBlocks(tu.ctx) + require.NoError(tu.t, err) + + tu.connect(client, 0) + tu.waitUntilSync(0, client) + tu.compareSourceState(client) + + timeout := time.After(10 * time.Second) + + for i := 0; i < 5; i++ { + tu.mineNewBlock(0, nil) + tu.waitUntilSync(0, client) + tu.compareSourceState(client) + + // just in case, so we don't get deadlocked + select { + case <-incoming: + case <-timeout: + tu.t.Fatal("TestIncomingBlocks timeout") + } + } +} + +// TestSyncManualBadTS tests manually marking and unmarking blocks in the bad TS cache +func TestSyncManualBadTS(t *testing.T) { + // Test setup: + // - source node is fully synced, + // - client node is unsynced + // - client manually marked source's head and it's parent as bad + H := 50 + tu := prepSyncTest(t, H) + + client := tu.addClientNode() + require.NoError(t, tu.mn.LinkAll()) + + sourceHead, err := tu.nds[source].ChainHead(tu.ctx) + require.NoError(tu.t, err) + + clientHead, err := tu.nds[client].ChainHead(tu.ctx) + require.NoError(tu.t, err) + + require.True(tu.t, !sourceHead.Equals(clientHead), "source and client should be out of sync in test setup") + + //stm: @CHAIN_SYNCER_MARK_BAD_001 + err = tu.nds[client].SyncMarkBad(tu.ctx, sourceHead.Cids()[0]) + require.NoError(tu.t, err) + + sourceHeadParent := sourceHead.Parents().Cids()[0] + err = tu.nds[client].SyncMarkBad(tu.ctx, sourceHeadParent) + require.NoError(tu.t, err) + + //stm: @CHAIN_SYNCER_CHECK_BAD_001 + reason, err := tu.nds[client].SyncCheckBad(tu.ctx, sourceHead.Cids()[0]) + require.NoError(tu.t, err) + require.NotEqual(tu.t, "", reason, "block is not bad after manually marking") + + reason, err = tu.nds[client].SyncCheckBad(tu.ctx, sourceHeadParent) + require.NoError(tu.t, err) + require.NotEqual(tu.t, "", reason, "block is not bad after manually marking") + + // Assertion 1: + // - client shouldn't be synced after timeout, because the source TS is marked bad. + // - bad block is the first block that should be synced, 1sec should be enough + tu.connect(1, 0) + timeout := time.After(1 * time.Second) + <-timeout + + clientHead, err = tu.nds[client].ChainHead(tu.ctx) + require.NoError(tu.t, err) + require.True(tu.t, !sourceHead.Equals(clientHead), "source and client should be out of sync if source head is bad") + + // Assertion 2: + // - after unmarking blocks as bad and reconnecting, source & client should be in sync + //stm: @CHAIN_SYNCER_UNMARK_BAD_001 + err = tu.nds[client].SyncUnmarkBad(tu.ctx, sourceHead.Cids()[0]) + require.NoError(tu.t, err) + + reason, err = tu.nds[client].SyncCheckBad(tu.ctx, sourceHead.Cids()[0]) + require.NoError(tu.t, err) + require.Equal(tu.t, "", reason, "block is still bad after manually unmarking") + + err = tu.nds[client].SyncUnmarkAllBad(tu.ctx) + require.NoError(tu.t, err) + + reason, err = tu.nds[client].SyncCheckBad(tu.ctx, sourceHeadParent) + require.NoError(tu.t, err) + require.Equal(tu.t, "", reason, "block is still bad after manually unmarking") + + tu.disconnect(1, 0) + tu.connect(1, 0) + + tu.waitUntilSync(0, client) + tu.compareSourceState(client) +} + +// TestState tests fetching the sync worker state before, during & after the sync +func TestSyncState(t *testing.T) { + H := 50 + tu := prepSyncTest(t, H) + + client := tu.addClientNode() + require.NoError(t, tu.mn.LinkAll()) + clientNode := tu.nds[client] + sourceHead, err := tu.nds[source].ChainHead(tu.ctx) + require.NoError(tu.t, err) + + // sync state should be empty before the sync + state, err := clientNode.SyncState(tu.ctx) + require.NoError(tu.t, err) + require.Equal(tu.t, len(state.ActiveSyncs), 0) + + tu.connect(client, 0) + + // wait until sync starts, or at most `timeout` seconds + timeout := time.After(5 * time.Second) + activeSyncs := []api.ActiveSync{} + + for len(activeSyncs) == 0 { + //stm: @CHAIN_SYNCER_STATE_001 + state, err = clientNode.SyncState(tu.ctx) + require.NoError(tu.t, err) + activeSyncs = state.ActiveSyncs + + sleep := time.After(100 * time.Millisecond) + select { + case <-sleep: + case <-timeout: + tu.t.Fatal("TestSyncState timeout") + } + } + + // check state during sync + require.Equal(tu.t, len(activeSyncs), 1) + require.True(tu.t, activeSyncs[0].Target.Equals(sourceHead)) + + tu.waitUntilSync(0, client) + tu.compareSourceState(client) + + // check state after sync + state, err = clientNode.SyncState(tu.ctx) + require.NoError(tu.t, err) + require.Equal(tu.t, len(state.ActiveSyncs), 1) + require.Equal(tu.t, state.ActiveSyncs[0].Stage, api.StageSyncComplete) +} diff --git a/chain/syncstate.go b/chain/syncstate.go index b213c7483..527d6be48 100644 --- a/chain/syncstate.go +++ b/chain/syncstate.go @@ -1,42 +1,30 @@ package chain import ( - "fmt" "sync" "time" - "github.com/filecoin-project/specs-actors/actors/abi" + "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" ) -func SyncStageString(v api.SyncStateStage) string { - switch v { - case api.StageHeaders: - return "header sync" - case api.StagePersistHeaders: - return "persisting headers" - case api.StageMessages: - return "message sync" - case api.StageSyncComplete: - return "complete" - case api.StageSyncErrored: - return "error" - default: - return fmt.Sprintf("", v) - } +type SyncerStateSnapshot struct { + WorkerID uint64 + Target *types.TipSet + Base *types.TipSet + Stage api.SyncStateStage + Height abi.ChainEpoch + Message string + Start time.Time + End time.Time } type SyncerState struct { - lk sync.Mutex - Target *types.TipSet - Base *types.TipSet - Stage api.SyncStateStage - Height abi.ChainEpoch - Message string - Start time.Time - End time.Time + lk sync.Mutex + data SyncerStateSnapshot } func (ss *SyncerState) SetStage(v api.SyncStateStage) { @@ -46,9 +34,9 @@ func (ss *SyncerState) SetStage(v api.SyncStateStage) { ss.lk.Lock() defer ss.lk.Unlock() - ss.Stage = v + ss.data.Stage = v if v == api.StageSyncComplete { - ss.End = time.Now() + ss.data.End = build.Clock.Now() } } @@ -59,13 +47,13 @@ func (ss *SyncerState) Init(base, target *types.TipSet) { ss.lk.Lock() defer ss.lk.Unlock() - ss.Target = target - ss.Base = base - ss.Stage = api.StageHeaders - ss.Height = 0 - ss.Message = "" - ss.Start = time.Now() - ss.End = time.Time{} + ss.data.Target = target + ss.data.Base = base + ss.data.Stage = api.StageHeaders + ss.data.Height = 0 + ss.data.Message = "" + ss.data.Start = build.Clock.Now() + ss.data.End = time.Time{} } func (ss *SyncerState) SetHeight(h abi.ChainEpoch) { @@ -75,7 +63,7 @@ func (ss *SyncerState) SetHeight(h abi.ChainEpoch) { ss.lk.Lock() defer ss.lk.Unlock() - ss.Height = h + ss.data.Height = h } func (ss *SyncerState) Error(err error) { @@ -85,21 +73,13 @@ func (ss *SyncerState) Error(err error) { ss.lk.Lock() defer ss.lk.Unlock() - ss.Message = err.Error() - ss.Stage = api.StageSyncErrored - ss.End = time.Now() + ss.data.Message = err.Error() + ss.data.Stage = api.StageSyncErrored + ss.data.End = build.Clock.Now() } -func (ss *SyncerState) Snapshot() SyncerState { +func (ss *SyncerState) Snapshot() SyncerStateSnapshot { ss.lk.Lock() defer ss.lk.Unlock() - return SyncerState{ - Base: ss.Base, - Target: ss.Target, - Stage: ss.Stage, - Height: ss.Height, - Message: ss.Message, - Start: ss.Start, - End: ss.End, - } + return ss.data } diff --git a/chain/types/actor.go b/chain/types/actor.go index 56aa55735..8ba611617 100644 --- a/chain/types/actor.go +++ b/chain/types/actor.go @@ -1,15 +1,17 @@ package types import ( + "errors" + "github.com/ipfs/go-cid" - "github.com/filecoin-project/specs-actors/actors/builtin" - init_ "github.com/filecoin-project/specs-actors/actors/builtin/init" + "github.com/filecoin-project/go-address" ) -var ErrActorNotFound = init_.ErrAddressNotFound +var ErrActorNotFound = errors.New("actor not found") -type Actor struct { +// Actor State for state tree version up to 4 +type ActorV4 struct { // Identifies the type of actor (string coded as a CID), see `chain/actors/actors.go`. Code cid.Cid Head cid.Cid @@ -17,6 +19,33 @@ type Actor struct { Balance BigInt } -func (a *Actor) IsAccountActor() bool { - return a.Code == builtin.AccountActorCodeID +// Actor State for state tree version 5 +type ActorV5 struct { + // Identifies the type of actor (string coded as a CID), see `chain/actors/actors.go`. + Code cid.Cid + Head cid.Cid + Nonce uint64 + Balance BigInt + // Deterministic Address. + Address *address.Address +} + +type Actor = ActorV5 + +func AsActorV4(a *ActorV5) *ActorV4 { + return &ActorV4{ + Code: a.Code, + Head: a.Head, + Nonce: a.Nonce, + Balance: a.Balance, + } +} + +func AsActorV5(a *ActorV4) *ActorV5 { + return &ActorV5{ + Code: a.Code, + Head: a.Head, + Nonce: a.Nonce, + Balance: a.Balance, + } } diff --git a/chain/types/bigint.go b/chain/types/bigint.go index 22ecf833c..72ef52128 100644 --- a/chain/types/bigint.go +++ b/chain/types/bigint.go @@ -4,14 +4,14 @@ import ( "fmt" "math/big" - big2 "github.com/filecoin-project/specs-actors/actors/abi/big" + big2 "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/lotus/build" ) const BigIntMaxSerializedLen = 128 // is this big enough? or too big? -var TotalFilecoinInt = FromFil(build.TotalFilecoin) +var TotalFilecoinInt = FromFil(build.FilBase) var EmptyInt = BigInt{} @@ -47,6 +47,11 @@ func BigDiv(a, b BigInt) BigInt { return BigInt{Int: big.NewInt(0).Div(a.Int, b.Int)} } +func BigDivFloat(num, den BigInt) float64 { + res, _ := new(big.Rat).SetFrac(num.Int, den.Int).Float64() + return res +} + func BigMod(a, b BigInt) BigInt { return BigInt{Int: big.NewInt(0).Mod(a.Int, b.Int)} } @@ -76,7 +81,7 @@ func SizeStr(bi BigInt) string { } f, _ := r.Float64() - return fmt.Sprintf("%.3g %s", f, byteSizeUnits[i]) + return fmt.Sprintf("%.4g %s", f, byteSizeUnits[i]) } var deciUnits = []string{"", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"} diff --git a/chain/types/bigint_test.go b/chain/types/bigint_test.go index 2f632d1d7..348724953 100644 --- a/chain/types/bigint_test.go +++ b/chain/types/bigint_test.go @@ -1,14 +1,20 @@ +// stm: #unit package types import ( "bytes" "math/big" + "math/rand" + "strings" "testing" + "time" + "github.com/docker/go-units" "github.com/stretchr/testify/assert" ) func TestBigIntSerializationRoundTrip(t *testing.T) { + //stm: @CHAIN_TYPES_PARSE_BIGINT_001 testValues := []string{ "0", "1", "10", "-10", "9999", "12345678901234567891234567890123456789012345678901234567890", } @@ -37,8 +43,9 @@ func TestBigIntSerializationRoundTrip(t *testing.T) { } func TestFilRoundTrip(t *testing.T) { + //stm: @TYPES_FIL_PARSE_001 testValues := []string{ - "0", "1", "1.001", "100.10001", "101100", "5000.01", "5000", + "0 FIL", "1 FIL", "1.001 FIL", "100.10001 FIL", "101100 FIL", "5000.01 FIL", "5000 FIL", } for _, v := range testValues { @@ -54,14 +61,17 @@ func TestFilRoundTrip(t *testing.T) { } func TestSizeStr(t *testing.T) { + //stm: @CHAIN_TYPES_SIZE_BIGINT_001 cases := []struct { in uint64 out string }{ {0, "0 B"}, {1, "1 B"}, + {1016, "1016 B"}, {1024, "1 KiB"}, - {2000, "1.95 KiB"}, + {1000 * 1024, "1000 KiB"}, + {2000, "1.953 KiB"}, {5 << 20, "5 MiB"}, {11 << 60, "11 EiB"}, } @@ -71,7 +81,25 @@ func TestSizeStr(t *testing.T) { } } +func TestSizeStrUnitsSymmetry(t *testing.T) { + //stm: @CHAIN_TYPES_SIZE_BIGINT_001 + s := rand.NewSource(time.Now().UnixNano()) + r := rand.New(s) + + for i := 0; i < 10000; i++ { + n := r.Uint64() + l := strings.ReplaceAll(units.BytesSize(float64(n)), " ", "") + r := strings.ReplaceAll(SizeStr(NewInt(n)), " ", "") + + assert.NotContains(t, l, "e+") + assert.NotContains(t, r, "e+") + + assert.Equal(t, l, r, "wrong formatting for %d", n) + } +} + func TestSizeStrBig(t *testing.T) { + //stm: @CHAIN_TYPES_SIZE_BIGINT_001 ZiB := big.NewInt(50000) ZiB = ZiB.Lsh(ZiB, 70) diff --git a/chain/types/blockheader.go b/chain/types/blockheader.go index a5940d9e6..3117e3122 100644 --- a/chain/types/blockheader.go +++ b/chain/types/blockheader.go @@ -4,27 +4,29 @@ import ( "bytes" "math/big" - "github.com/minio/blake2b-simd" - - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/crypto" - block "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" - "github.com/multiformats/go-multihash" - xerrors "golang.org/x/xerrors" + "github.com/minio/blake2b-simd" + "golang.org/x/xerrors" "github.com/filecoin-project/go-address" - - "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/go-state-types/proof" ) type Ticket struct { VRFProof []byte } -type ElectionProof struct { - VRFProof []byte +func (t *Ticket) Quality() float64 { + ticketHash := blake2b.Sum256(t.VRFProof) + ticketNum := BigFromBytes(ticketHash[:]).Int + ticketDenu := big.NewInt(1) + ticketDenu.Lsh(ticketDenu, 256) + tv, _ := new(big.Rat).SetFrac(ticketNum, ticketDenu).Float64() + tq := 1 - tv + return tq } type BeaconEntry struct { @@ -40,48 +42,34 @@ func NewBeaconEntry(round uint64, data []byte) BeaconEntry { } type BlockHeader struct { - Miner address.Address // 0 + Miner address.Address // 0 unique per block/miner - Ticket *Ticket // 1 + Ticket *Ticket // 1 unique per block/miner: should be a valid VRF + ElectionProof *ElectionProof // 2 unique per block/miner: should be a valid VRF + BeaconEntries []BeaconEntry // 3 identical for all blocks in same tipset + WinPoStProof []proof.PoStProof // 4 unique per block/miner + Parents []cid.Cid // 5 identical for all blocks in same tipset + ParentWeight BigInt // 6 identical for all blocks in same tipset + Height abi.ChainEpoch // 7 identical for all blocks in same tipset + ParentStateRoot cid.Cid // 8 identical for all blocks in same tipset + ParentMessageReceipts cid.Cid // 9 identical for all blocks in same tipset + Messages cid.Cid // 10 unique per block + BLSAggregate *crypto.Signature // 11 unique per block: aggrregate of BLS messages from above + Timestamp uint64 // 12 identical for all blocks in same tipset / hard-tied to the value of Height above + BlockSig *crypto.Signature // 13 unique per block/miner: miner signature + ForkSignaling uint64 // 14 currently unused/undefined + ParentBaseFee abi.TokenAmount // 15 identical for all blocks in same tipset: the base fee after executing parent tipset - ElectionProof *ElectionProof // 2 - - BeaconEntries []BeaconEntry // 3 - - WinPoStProof []abi.PoStProof // 4 - - Parents []cid.Cid // 5 - - ParentWeight BigInt // 6 - - Height abi.ChainEpoch // 7 - - ParentStateRoot cid.Cid // 8 - - ParentMessageReceipts cid.Cid // 8 - - Messages cid.Cid // 10 - - BLSAggregate *crypto.Signature // 11 - - Timestamp uint64 // 12 - - BlockSig *crypto.Signature // 13 - - ForkSignaling uint64 // 14 - - // internal - validated bool // true if the signature has been validated + validated bool // internal, true if the signature has been validated } -func (b *BlockHeader) ToStorageBlock() (block.Block, error) { - data, err := b.Serialize() +func (blk *BlockHeader) ToStorageBlock() (block.Block, error) { + data, err := blk.Serialize() if err != nil { return nil, err } - pref := cid.NewPrefixV1(cid.DagCBOR, multihash.BLAKE2B_MIN+31) - c, err := pref.Sum(data) + c, err := abi.CidBuilder.Sum(data) if err != nil { return nil, err } @@ -89,8 +77,8 @@ func (b *BlockHeader) ToStorageBlock() (block.Block, error) { return block.NewBlockWithCid(data, c) } -func (b *BlockHeader) Cid() cid.Cid { - sb, err := b.ToStorageBlock() +func (blk *BlockHeader) Cid() cid.Cid { + sb, err := blk.ToStorageBlock() if err != nil { panic(err) // Not sure i'm entirely comfortable with this one, needs to be checked } @@ -149,13 +137,12 @@ func (mm *MsgMeta) Cid() cid.Cid { } func (mm *MsgMeta) ToStorageBlock() (block.Block, error) { - buf := new(bytes.Buffer) - if err := mm.MarshalCBOR(buf); err != nil { + var buf bytes.Buffer + if err := mm.MarshalCBOR(&buf); err != nil { return nil, xerrors.Errorf("failed to marshal MsgMeta: %w", err) } - pref := cid.NewPrefixV1(cid.DagCBOR, multihash.BLAKE2B_MIN+31) - c, err := pref.Sum(buf.Bytes()) + c, err := abi.CidBuilder.Sum(buf.Bytes()) if err != nil { return nil, err } @@ -182,6 +169,21 @@ func CidArrsEqual(a, b []cid.Cid) bool { return true } +func CidArrsSubset(a, b []cid.Cid) bool { + // order ignoring compare... + s := make(map[cid.Cid]bool) + for _, c := range b { + s[c] = true + } + + for _, c := range a { + if !s[c] { + return false + } + } + return true +} + func CidArrsContains(a []cid.Cid, b cid.Cid) bool { for _, elem := range a { if elem.Equals(b) { @@ -191,36 +193,6 @@ func CidArrsContains(a []cid.Cid, b cid.Cid) bool { return false } -var blocksPerEpoch = NewInt(build.BlocksPerEpoch) - -const sha256bits = 256 - -func IsTicketWinner(vrfTicket []byte, mypow BigInt, totpow BigInt) bool { - /* - Need to check that - (h(vrfout) + 1) / (max(h) + 1) <= e * myPower / totalPower - max(h) == 2^256-1 - which in terms of integer math means: - (h(vrfout) + 1) * totalPower <= e * myPower * 2^256 - in 2^256 space, it is equivalent to: - h(vrfout) * totalPower < e * myPower * 2^256 - - */ - - h := blake2b.Sum256(vrfTicket) - - lhs := BigFromBytes(h[:]).Int - lhs = lhs.Mul(lhs, totpow.Int) - - // rhs = sectorSize * 2^256 - // rhs = sectorSize << 256 - rhs := new(big.Int).Lsh(mypow.Int, sha256bits) - rhs = rhs.Mul(rhs, blocksPerEpoch.Int) - - // h(vrfout) * totalPower < e * sectorSize * 2^256? - return lhs.Cmp(rhs) < 0 -} - func (t *Ticket) Equals(ot *Ticket) bool { return bytes.Equal(t.VRFProof, ot.VRFProof) } diff --git a/chain/types/blockheader_test.go b/chain/types/blockheader_test.go index e18e4028a..b7af86d3c 100644 --- a/chain/types/blockheader_test.go +++ b/chain/types/blockheader_test.go @@ -1,16 +1,20 @@ +// stm: #unit package types import ( "bytes" "encoding/hex" "fmt" - "github.com/filecoin-project/specs-actors/actors/abi" "reflect" "testing" + "github.com/ipfs/go-cid" + "github.com/stretchr/testify/require" + "github.com/filecoin-project/go-address" - "github.com/filecoin-project/specs-actors/actors/crypto" - cid "github.com/ipfs/go-cid" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/crypto" + prooftypes "github.com/filecoin-project/go-state-types/proof" ) func testBlockHeader(t testing.TB) *BlockHeader { @@ -42,10 +46,12 @@ func testBlockHeader(t testing.TB) *BlockHeader { Height: 85919298723, ParentStateRoot: c, BlockSig: &crypto.Signature{Type: crypto.SigTypeBLS, Data: []byte("boo! im a signature")}, + ParentBaseFee: NewInt(3432432843291), } } func TestBlockHeaderSerialization(t *testing.T) { + //stm: @CHAIN_TYPES_BLOCK_HEADER_FROM_CBOR_001, @CHAIN_TYPES_BLOCK_HEADER_TO_CBOR_001 bh := testBlockHeader(t) buf := new(bytes.Buffer) @@ -66,6 +72,7 @@ func TestBlockHeaderSerialization(t *testing.T) { } func TestInteropBH(t *testing.T) { + //stm: @OTHER_IMPLEMENTATION_BLOCK_INTEROP_001 newAddr, err := address.NewSecp256k1Address([]byte("address0")) if err != nil { @@ -77,14 +84,14 @@ func TestInteropBH(t *testing.T) { t.Fatal(err) } - posts := []abi.PoStProof{ - {abi.RegisteredProof_StackedDRG2KiBWinningPoSt, []byte{0x07}}, + posts := []prooftypes.PoStProof{ + {PoStProof: abi.RegisteredPoStProof_StackedDrgWinning2KiBV1, ProofBytes: []byte{0x07}}, } bh := &BlockHeader{ Miner: newAddr, Ticket: &Ticket{[]byte{0x01, 0x02, 0x03}}, - ElectionProof: &ElectionProof{[]byte{0x0a, 0x0b}}, + ElectionProof: &ElectionProof{0, []byte{0x0a, 0x0b}}, BeaconEntries: []BeaconEntry{ { Round: 5, @@ -105,7 +112,8 @@ func TestInteropBH(t *testing.T) { Type: crypto.SigTypeBLS, Data: []byte{0x3}, }, - BLSAggregate: &crypto.Signature{}, + BLSAggregate: &crypto.Signature{}, + ParentBaseFee: NewInt(1000000000), } bhsb, err := bh.SigningBytes() @@ -114,11 +122,8 @@ func TestInteropBH(t *testing.T) { t.Fatal(err) } - // acquired from go-filecoin - gfc := "8f5501d04cb15021bf6bd003073d79e2238d4e61f1ad22814301020381420a0b818205410c818209410781d82a5827000171a0e402202f84fef0d7cc2d7f9f00d22445f7bf7539fdd685fd9f284aa37f3822b57619cc430003e802d82a5827000171a0e402202f84fef0d7cc2d7f9f00d22445f7bf7539fdd685fd9f284aa37f3822b57619ccd82a5827000171a0e402202f84fef0d7cc2d7f9f00d22445f7bf7539fdd685fd9f284aa37f3822b57619ccd82a5827000171a0e402202f84fef0d7cc2d7f9f00d22445f7bf7539fdd685fd9f284aa37f3822b57619cc410001f603" - if gfc != hex.EncodeToString(bhsb) { - t.Fatal("not equal!") - } + gfc := "905501d04cb15021bf6bd003073d79e2238d4e61f1ad2281430102038200420a0b818205410c818200410781d82a5827000171a0e402202f84fef0d7cc2d7f9f00d22445f7bf7539fdd685fd9f284aa37f3822b57619cc430003e802d82a5827000171a0e402202f84fef0d7cc2d7f9f00d22445f7bf7539fdd685fd9f284aa37f3822b57619ccd82a5827000171a0e402202f84fef0d7cc2d7f9f00d22445f7bf7539fdd685fd9f284aa37f3822b57619ccd82a5827000171a0e402202f84fef0d7cc2d7f9f00d22445f7bf7539fdd685fd9f284aa37f3822b57619cc410001f60345003b9aca00" + require.Equal(t, gfc, hex.EncodeToString(bhsb)) } func BenchmarkBlockHeaderMarshal(b *testing.B) { diff --git a/chain/types/blockmsg.go b/chain/types/blockmsg.go index f3114499d..f8f0a08db 100644 --- a/chain/types/blockmsg.go +++ b/chain/types/blockmsg.go @@ -2,6 +2,7 @@ package types import ( "bytes" + "fmt" "github.com/ipfs/go-cid" ) @@ -14,10 +15,13 @@ type BlockMsg struct { func DecodeBlockMsg(b []byte) (*BlockMsg, error) { var bm BlockMsg - if err := bm.UnmarshalCBOR(bytes.NewReader(b)); err != nil { + data := bytes.NewReader(b) + if err := bm.UnmarshalCBOR(data); err != nil { return nil, err } - + if l := data.Len(); l != 0 { + return nil, fmt.Errorf("extraneous data in BlockMsg CBOR encoding: got %d unexpected bytes", l) + } return &bm, nil } diff --git a/chain/types/blockmsg_test.go b/chain/types/blockmsg_test.go new file mode 100644 index 000000000..02a622768 --- /dev/null +++ b/chain/types/blockmsg_test.go @@ -0,0 +1,40 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDecodeBlockMsg(t *testing.T) { + type args struct { + b []byte + } + tests := []struct { + name string + data []byte + want *BlockMsg + wantErr bool + }{ + {"decode empty BlockMsg with extra data at the end", []byte{0x83, 0xf6, 0x80, 0x80, 0x20}, nil, true}, + {"decode valid empty BlockMsg", []byte{0x83, 0xf6, 0x80, 0x80}, new(BlockMsg), false}, + {"decode invalid cbor", []byte{0x83, 0xf6, 0x80}, nil, true}, + } + for _, tt := range tests { + data := tt.data + want := tt.want + wantErr := tt.wantErr + t.Run(tt.name, func(t *testing.T) { + got, err := DecodeBlockMsg(data) + if wantErr { + assert.Errorf(t, err, "DecodeBlockMsg(%x)", data) + return + } + assert.NoErrorf(t, err, "DecodeBlockMsg(%x)", data) + assert.Equalf(t, want, got, "DecodeBlockMsg(%x)", data) + serialized, err := got.Serialize() + assert.NoErrorf(t, err, "DecodeBlockMsg(%x)", data) + assert.Equalf(t, serialized, data, "DecodeBlockMsg(%x)", data) + }) + } +} diff --git a/chain/types/cbor_gen.go b/chain/types/cbor_gen.go index b52171797..90d1a14c5 100644 --- a/chain/types/cbor_gen.go +++ b/chain/types/cbor_gen.go @@ -5,42 +5,52 @@ package types import ( "fmt" "io" + "math" + "sort" + time "time" - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/crypto" - "github.com/filecoin-project/specs-actors/actors/runtime/exitcode" - "github.com/ipfs/go-cid" + cid "github.com/ipfs/go-cid" cbg "github.com/whyrusleeping/cbor-gen" xerrors "golang.org/x/xerrors" + + address "github.com/filecoin-project/go-address" + abi "github.com/filecoin-project/go-state-types/abi" + crypto "github.com/filecoin-project/go-state-types/crypto" + exitcode "github.com/filecoin-project/go-state-types/exitcode" + proof "github.com/filecoin-project/go-state-types/proof" ) var _ = xerrors.Errorf +var _ = cid.Undef +var _ = math.E +var _ = sort.Sort -var lengthBufBlockHeader = []byte{143} +var lengthBufBlockHeader = []byte{144} func (t *BlockHeader) MarshalCBOR(w io.Writer) error { if t == nil { _, err := w.Write(cbg.CborNull) return err } - if _, err := w.Write(lengthBufBlockHeader); err != nil { + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufBlockHeader); err != nil { return err } - scratch := make([]byte, 9) - // t.Miner (address.Address) (struct) - if err := t.Miner.MarshalCBOR(w); err != nil { + if err := t.Miner.MarshalCBOR(cw); err != nil { return err } // t.Ticket (types.Ticket) (struct) - if err := t.Ticket.MarshalCBOR(w); err != nil { + if err := t.Ticket.MarshalCBOR(cw); err != nil { return err } // t.ElectionProof (types.ElectionProof) (struct) - if err := t.ElectionProof.MarshalCBOR(w); err != nil { + if err := t.ElectionProof.MarshalCBOR(cw); err != nil { return err } @@ -49,25 +59,25 @@ func (t *BlockHeader) MarshalCBOR(w io.Writer) error { return xerrors.Errorf("Slice value in field t.BeaconEntries was too long") } - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.BeaconEntries))); err != nil { + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.BeaconEntries))); err != nil { return err } for _, v := range t.BeaconEntries { - if err := v.MarshalCBOR(w); err != nil { + if err := v.MarshalCBOR(cw); err != nil { return err } } - // t.WinPoStProof ([]abi.PoStProof) (slice) + // t.WinPoStProof ([]proof.PoStProof) (slice) if len(t.WinPoStProof) > cbg.MaxLength { return xerrors.Errorf("Slice value in field t.WinPoStProof was too long") } - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.WinPoStProof))); err != nil { + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.WinPoStProof))); err != nil { return err } for _, v := range t.WinPoStProof { - if err := v.MarshalCBOR(w); err != nil { + if err := v.MarshalCBOR(cw); err != nil { return err } } @@ -77,87 +87,98 @@ func (t *BlockHeader) MarshalCBOR(w io.Writer) error { return xerrors.Errorf("Slice value in field t.Parents was too long") } - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.Parents))); err != nil { + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Parents))); err != nil { return err } for _, v := range t.Parents { - if err := cbg.WriteCidBuf(scratch, w, v); err != nil { + if err := cbg.WriteCid(w, v); err != nil { return xerrors.Errorf("failed writing cid field t.Parents: %w", err) } } // t.ParentWeight (big.Int) (struct) - if err := t.ParentWeight.MarshalCBOR(w); err != nil { + if err := t.ParentWeight.MarshalCBOR(cw); err != nil { return err } // t.Height (abi.ChainEpoch) (int64) if t.Height >= 0 { - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Height)); err != nil { + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Height)); err != nil { return err } } else { - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajNegativeInt, uint64(-t.Height-1)); err != nil { + if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.Height-1)); err != nil { return err } } // t.ParentStateRoot (cid.Cid) (struct) - if err := cbg.WriteCidBuf(scratch, w, t.ParentStateRoot); err != nil { + if err := cbg.WriteCid(cw, t.ParentStateRoot); err != nil { return xerrors.Errorf("failed to write cid field t.ParentStateRoot: %w", err) } // t.ParentMessageReceipts (cid.Cid) (struct) - if err := cbg.WriteCidBuf(scratch, w, t.ParentMessageReceipts); err != nil { + if err := cbg.WriteCid(cw, t.ParentMessageReceipts); err != nil { return xerrors.Errorf("failed to write cid field t.ParentMessageReceipts: %w", err) } // t.Messages (cid.Cid) (struct) - if err := cbg.WriteCidBuf(scratch, w, t.Messages); err != nil { + if err := cbg.WriteCid(cw, t.Messages); err != nil { return xerrors.Errorf("failed to write cid field t.Messages: %w", err) } // t.BLSAggregate (crypto.Signature) (struct) - if err := t.BLSAggregate.MarshalCBOR(w); err != nil { + if err := t.BLSAggregate.MarshalCBOR(cw); err != nil { return err } // t.Timestamp (uint64) (uint64) - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Timestamp)); err != nil { + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Timestamp)); err != nil { return err } // t.BlockSig (crypto.Signature) (struct) - if err := t.BlockSig.MarshalCBOR(w); err != nil { + if err := t.BlockSig.MarshalCBOR(cw); err != nil { return err } // t.ForkSignaling (uint64) (uint64) - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.ForkSignaling)); err != nil { + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.ForkSignaling)); err != nil { return err } + // t.ParentBaseFee (big.Int) (struct) + if err := t.ParentBaseFee.MarshalCBOR(cw); err != nil { + return err + } return nil } -func (t *BlockHeader) UnmarshalCBOR(r io.Reader) error { - br := cbg.GetPeeker(r) - scratch := make([]byte, 8) +func (t *BlockHeader) UnmarshalCBOR(r io.Reader) (err error) { + *t = BlockHeader{} - maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() if err != nil { return err } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + if maj != cbg.MajArray { return fmt.Errorf("cbor input should be of type array") } - if extra != 15 { + if extra != 16 { return fmt.Errorf("cbor input had wrong number of fields") } @@ -165,7 +186,7 @@ func (t *BlockHeader) UnmarshalCBOR(r io.Reader) error { { - if err := t.Miner.UnmarshalCBOR(br); err != nil { + if err := t.Miner.UnmarshalCBOR(cr); err != nil { return xerrors.Errorf("unmarshaling t.Miner: %w", err) } @@ -174,18 +195,16 @@ func (t *BlockHeader) UnmarshalCBOR(r io.Reader) error { { - pb, err := br.PeekByte() + b, err := cr.ReadByte() if err != nil { return err } - if pb == cbg.CborNull[0] { - var nbuf [1]byte - if _, err := br.Read(nbuf[:]); err != nil { + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { return err } - } else { t.Ticket = new(Ticket) - if err := t.Ticket.UnmarshalCBOR(br); err != nil { + if err := t.Ticket.UnmarshalCBOR(cr); err != nil { return xerrors.Errorf("unmarshaling t.Ticket pointer: %w", err) } } @@ -195,18 +214,16 @@ func (t *BlockHeader) UnmarshalCBOR(r io.Reader) error { { - pb, err := br.PeekByte() + b, err := cr.ReadByte() if err != nil { return err } - if pb == cbg.CborNull[0] { - var nbuf [1]byte - if _, err := br.Read(nbuf[:]); err != nil { + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { return err } - } else { t.ElectionProof = new(ElectionProof) - if err := t.ElectionProof.UnmarshalCBOR(br); err != nil { + if err := t.ElectionProof.UnmarshalCBOR(cr); err != nil { return xerrors.Errorf("unmarshaling t.ElectionProof pointer: %w", err) } } @@ -214,7 +231,7 @@ func (t *BlockHeader) UnmarshalCBOR(r io.Reader) error { } // t.BeaconEntries ([]types.BeaconEntry) (slice) - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + maj, extra, err = cr.ReadHeader() if err != nil { return err } @@ -234,16 +251,16 @@ func (t *BlockHeader) UnmarshalCBOR(r io.Reader) error { for i := 0; i < int(extra); i++ { var v BeaconEntry - if err := v.UnmarshalCBOR(br); err != nil { + if err := v.UnmarshalCBOR(cr); err != nil { return err } t.BeaconEntries[i] = v } - // t.WinPoStProof ([]abi.PoStProof) (slice) + // t.WinPoStProof ([]proof.PoStProof) (slice) - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + maj, extra, err = cr.ReadHeader() if err != nil { return err } @@ -257,13 +274,13 @@ func (t *BlockHeader) UnmarshalCBOR(r io.Reader) error { } if extra > 0 { - t.WinPoStProof = make([]abi.PoStProof, extra) + t.WinPoStProof = make([]proof.PoStProof, extra) } for i := 0; i < int(extra); i++ { - var v abi.PoStProof - if err := v.UnmarshalCBOR(br); err != nil { + var v proof.PoStProof + if err := v.UnmarshalCBOR(cr); err != nil { return err } @@ -272,7 +289,7 @@ func (t *BlockHeader) UnmarshalCBOR(r io.Reader) error { // t.Parents ([]cid.Cid) (slice) - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + maj, extra, err = cr.ReadHeader() if err != nil { return err } @@ -291,7 +308,7 @@ func (t *BlockHeader) UnmarshalCBOR(r io.Reader) error { for i := 0; i < int(extra); i++ { - c, err := cbg.ReadCid(br) + c, err := cbg.ReadCid(cr) if err != nil { return xerrors.Errorf("reading cid field t.Parents failed: %w", err) } @@ -302,14 +319,14 @@ func (t *BlockHeader) UnmarshalCBOR(r io.Reader) error { { - if err := t.ParentWeight.UnmarshalCBOR(br); err != nil { + if err := t.ParentWeight.UnmarshalCBOR(cr); err != nil { return xerrors.Errorf("unmarshaling t.ParentWeight: %w", err) } } // t.Height (abi.ChainEpoch) (int64) { - maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + maj, extra, err := cr.ReadHeader() var extraI int64 if err != nil { return err @@ -323,7 +340,7 @@ func (t *BlockHeader) UnmarshalCBOR(r io.Reader) error { case cbg.MajNegativeInt: extraI = int64(extra) if extraI < 0 { - return fmt.Errorf("int64 negative oveflow") + return fmt.Errorf("int64 negative overflow") } extraI = -1 - extraI default: @@ -336,7 +353,7 @@ func (t *BlockHeader) UnmarshalCBOR(r io.Reader) error { { - c, err := cbg.ReadCid(br) + c, err := cbg.ReadCid(cr) if err != nil { return xerrors.Errorf("failed to read cid field t.ParentStateRoot: %w", err) } @@ -348,7 +365,7 @@ func (t *BlockHeader) UnmarshalCBOR(r io.Reader) error { { - c, err := cbg.ReadCid(br) + c, err := cbg.ReadCid(cr) if err != nil { return xerrors.Errorf("failed to read cid field t.ParentMessageReceipts: %w", err) } @@ -360,7 +377,7 @@ func (t *BlockHeader) UnmarshalCBOR(r io.Reader) error { { - c, err := cbg.ReadCid(br) + c, err := cbg.ReadCid(cr) if err != nil { return xerrors.Errorf("failed to read cid field t.Messages: %w", err) } @@ -372,18 +389,16 @@ func (t *BlockHeader) UnmarshalCBOR(r io.Reader) error { { - pb, err := br.PeekByte() + b, err := cr.ReadByte() if err != nil { return err } - if pb == cbg.CborNull[0] { - var nbuf [1]byte - if _, err := br.Read(nbuf[:]); err != nil { + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { return err } - } else { t.BLSAggregate = new(crypto.Signature) - if err := t.BLSAggregate.UnmarshalCBOR(br); err != nil { + if err := t.BLSAggregate.UnmarshalCBOR(cr); err != nil { return xerrors.Errorf("unmarshaling t.BLSAggregate pointer: %w", err) } } @@ -393,7 +408,7 @@ func (t *BlockHeader) UnmarshalCBOR(r io.Reader) error { { - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + maj, extra, err = cr.ReadHeader() if err != nil { return err } @@ -407,18 +422,16 @@ func (t *BlockHeader) UnmarshalCBOR(r io.Reader) error { { - pb, err := br.PeekByte() + b, err := cr.ReadByte() if err != nil { return err } - if pb == cbg.CborNull[0] { - var nbuf [1]byte - if _, err := br.Read(nbuf[:]); err != nil { + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { return err } - } else { t.BlockSig = new(crypto.Signature) - if err := t.BlockSig.UnmarshalCBOR(br); err != nil { + if err := t.BlockSig.UnmarshalCBOR(cr); err != nil { return xerrors.Errorf("unmarshaling t.BlockSig pointer: %w", err) } } @@ -428,7 +441,7 @@ func (t *BlockHeader) UnmarshalCBOR(r io.Reader) error { { - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + maj, extra, err = cr.ReadHeader() if err != nil { return err } @@ -437,6 +450,15 @@ func (t *BlockHeader) UnmarshalCBOR(r io.Reader) error { } t.ForkSignaling = uint64(extra) + } + // t.ParentBaseFee (big.Int) (struct) + + { + + if err := t.ParentBaseFee.UnmarshalCBOR(cr); err != nil { + return xerrors.Errorf("unmarshaling t.ParentBaseFee: %w", err) + } + } return nil } @@ -448,35 +470,43 @@ func (t *Ticket) MarshalCBOR(w io.Writer) error { _, err := w.Write(cbg.CborNull) return err } - if _, err := w.Write(lengthBufTicket); err != nil { + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufTicket); err != nil { return err } - scratch := make([]byte, 9) - // t.VRFProof ([]uint8) (slice) if len(t.VRFProof) > cbg.ByteArrayMaxLen { return xerrors.Errorf("Byte array in field t.VRFProof was too long") } - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajByteString, uint64(len(t.VRFProof))); err != nil { + if err := cw.WriteMajorTypeHeader(cbg.MajByteString, uint64(len(t.VRFProof))); err != nil { return err } - if _, err := w.Write(t.VRFProof); err != nil { + if _, err := cw.Write(t.VRFProof[:]); err != nil { return err } return nil } -func (t *Ticket) UnmarshalCBOR(r io.Reader) error { - br := cbg.GetPeeker(r) - scratch := make([]byte, 8) +func (t *Ticket) UnmarshalCBOR(r io.Reader) (err error) { + *t = Ticket{} - maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() if err != nil { return err } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + if maj != cbg.MajArray { return fmt.Errorf("cbor input should be of type array") } @@ -487,7 +517,7 @@ func (t *Ticket) UnmarshalCBOR(r io.Reader) error { // t.VRFProof ([]uint8) (slice) - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + maj, extra, err = cr.ReadHeader() if err != nil { return err } @@ -498,178 +528,83 @@ func (t *Ticket) UnmarshalCBOR(r io.Reader) error { if maj != cbg.MajByteString { return fmt.Errorf("expected byte array") } - t.VRFProof = make([]byte, extra) - if _, err := io.ReadFull(br, t.VRFProof); err != nil { + + if extra > 0 { + t.VRFProof = make([]uint8, extra) + } + + if _, err := io.ReadFull(cr, t.VRFProof[:]); err != nil { return err } return nil } -var lengthBufElectionProof = []byte{129} +var lengthBufElectionProof = []byte{130} func (t *ElectionProof) MarshalCBOR(w io.Writer) error { if t == nil { _, err := w.Write(cbg.CborNull) return err } - if _, err := w.Write(lengthBufElectionProof); err != nil { + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufElectionProof); err != nil { return err } - scratch := make([]byte, 9) + // t.WinCount (int64) (int64) + if t.WinCount >= 0 { + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.WinCount)); err != nil { + return err + } + } else { + if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.WinCount-1)); err != nil { + return err + } + } // t.VRFProof ([]uint8) (slice) if len(t.VRFProof) > cbg.ByteArrayMaxLen { return xerrors.Errorf("Byte array in field t.VRFProof was too long") } - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajByteString, uint64(len(t.VRFProof))); err != nil { + if err := cw.WriteMajorTypeHeader(cbg.MajByteString, uint64(len(t.VRFProof))); err != nil { return err } - if _, err := w.Write(t.VRFProof); err != nil { + if _, err := cw.Write(t.VRFProof[:]); err != nil { return err } return nil } -func (t *ElectionProof) UnmarshalCBOR(r io.Reader) error { - br := cbg.GetPeeker(r) - scratch := make([]byte, 8) +func (t *ElectionProof) UnmarshalCBOR(r io.Reader) (err error) { + *t = ElectionProof{} - maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() if err != nil { return err } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + if maj != cbg.MajArray { return fmt.Errorf("cbor input should be of type array") } - if extra != 1 { + if extra != 2 { return fmt.Errorf("cbor input had wrong number of fields") } - // t.VRFProof ([]uint8) (slice) - - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) - if err != nil { - return err - } - - if extra > cbg.ByteArrayMaxLen { - return fmt.Errorf("t.VRFProof: byte array too large (%d)", extra) - } - if maj != cbg.MajByteString { - return fmt.Errorf("expected byte array") - } - t.VRFProof = make([]byte, extra) - if _, err := io.ReadFull(br, t.VRFProof); err != nil { - return err - } - return nil -} - -var lengthBufMessage = []byte{137} - -func (t *Message) MarshalCBOR(w io.Writer) error { - if t == nil { - _, err := w.Write(cbg.CborNull) - return err - } - if _, err := w.Write(lengthBufMessage); err != nil { - return err - } - - scratch := make([]byte, 9) - - // t.Version (int64) (int64) - if t.Version >= 0 { - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Version)); err != nil { - return err - } - } else { - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajNegativeInt, uint64(-t.Version-1)); err != nil { - return err - } - } - - // t.To (address.Address) (struct) - if err := t.To.MarshalCBOR(w); err != nil { - return err - } - - // t.From (address.Address) (struct) - if err := t.From.MarshalCBOR(w); err != nil { - return err - } - - // t.Nonce (uint64) (uint64) - - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Nonce)); err != nil { - return err - } - - // t.Value (big.Int) (struct) - if err := t.Value.MarshalCBOR(w); err != nil { - return err - } - - // t.GasPrice (big.Int) (struct) - if err := t.GasPrice.MarshalCBOR(w); err != nil { - return err - } - - // t.GasLimit (int64) (int64) - if t.GasLimit >= 0 { - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.GasLimit)); err != nil { - return err - } - } else { - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajNegativeInt, uint64(-t.GasLimit-1)); err != nil { - return err - } - } - - // t.Method (abi.MethodNum) (uint64) - - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Method)); err != nil { - return err - } - - // t.Params ([]uint8) (slice) - if len(t.Params) > cbg.ByteArrayMaxLen { - return xerrors.Errorf("Byte array in field t.Params was too long") - } - - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajByteString, uint64(len(t.Params))); err != nil { - return err - } - - if _, err := w.Write(t.Params); err != nil { - return err - } - return nil -} - -func (t *Message) UnmarshalCBOR(r io.Reader) error { - br := cbg.GetPeeker(r) - scratch := make([]byte, 8) - - maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) - if err != nil { - return err - } - if maj != cbg.MajArray { - return fmt.Errorf("cbor input should be of type array") - } - - if extra != 9 { - return fmt.Errorf("cbor input had wrong number of fields") - } - - // t.Version (int64) (int64) + // t.WinCount (int64) (int64) { - maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + maj, extra, err := cr.ReadHeader() var extraI int64 if err != nil { return err @@ -683,20 +618,164 @@ func (t *Message) UnmarshalCBOR(r io.Reader) error { case cbg.MajNegativeInt: extraI = int64(extra) if extraI < 0 { - return fmt.Errorf("int64 negative oveflow") + return fmt.Errorf("int64 negative overflow") } extraI = -1 - extraI default: return fmt.Errorf("wrong type for int64 field: %d", maj) } - t.Version = int64(extraI) + t.WinCount = int64(extraI) + } + // t.VRFProof ([]uint8) (slice) + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > cbg.ByteArrayMaxLen { + return fmt.Errorf("t.VRFProof: byte array too large (%d)", extra) + } + if maj != cbg.MajByteString { + return fmt.Errorf("expected byte array") + } + + if extra > 0 { + t.VRFProof = make([]uint8, extra) + } + + if _, err := io.ReadFull(cr, t.VRFProof[:]); err != nil { + return err + } + return nil +} + +var lengthBufMessage = []byte{138} + +func (t *Message) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufMessage); err != nil { + return err + } + + // t.Version (uint64) (uint64) + + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Version)); err != nil { + return err + } + + // t.To (address.Address) (struct) + if err := t.To.MarshalCBOR(cw); err != nil { + return err + } + + // t.From (address.Address) (struct) + if err := t.From.MarshalCBOR(cw); err != nil { + return err + } + + // t.Nonce (uint64) (uint64) + + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Nonce)); err != nil { + return err + } + + // t.Value (big.Int) (struct) + if err := t.Value.MarshalCBOR(cw); err != nil { + return err + } + + // t.GasLimit (int64) (int64) + if t.GasLimit >= 0 { + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.GasLimit)); err != nil { + return err + } + } else { + if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.GasLimit-1)); err != nil { + return err + } + } + + // t.GasFeeCap (big.Int) (struct) + if err := t.GasFeeCap.MarshalCBOR(cw); err != nil { + return err + } + + // t.GasPremium (big.Int) (struct) + if err := t.GasPremium.MarshalCBOR(cw); err != nil { + return err + } + + // t.Method (abi.MethodNum) (uint64) + + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Method)); err != nil { + return err + } + + // t.Params ([]uint8) (slice) + if len(t.Params) > cbg.ByteArrayMaxLen { + return xerrors.Errorf("Byte array in field t.Params was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajByteString, uint64(len(t.Params))); err != nil { + return err + } + + if _, err := cw.Write(t.Params[:]); err != nil { + return err + } + return nil +} + +func (t *Message) UnmarshalCBOR(r io.Reader) (err error) { + *t = Message{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != 10 { + return fmt.Errorf("cbor input had wrong number of fields") + } + + // t.Version (uint64) (uint64) + + { + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.Version = uint64(extra) + } // t.To (address.Address) (struct) { - if err := t.To.UnmarshalCBOR(br); err != nil { + if err := t.To.UnmarshalCBOR(cr); err != nil { return xerrors.Errorf("unmarshaling t.To: %w", err) } @@ -705,7 +784,7 @@ func (t *Message) UnmarshalCBOR(r io.Reader) error { { - if err := t.From.UnmarshalCBOR(br); err != nil { + if err := t.From.UnmarshalCBOR(cr); err != nil { return xerrors.Errorf("unmarshaling t.From: %w", err) } @@ -714,7 +793,7 @@ func (t *Message) UnmarshalCBOR(r io.Reader) error { { - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + maj, extra, err = cr.ReadHeader() if err != nil { return err } @@ -728,23 +807,14 @@ func (t *Message) UnmarshalCBOR(r io.Reader) error { { - if err := t.Value.UnmarshalCBOR(br); err != nil { + if err := t.Value.UnmarshalCBOR(cr); err != nil { return xerrors.Errorf("unmarshaling t.Value: %w", err) } - } - // t.GasPrice (big.Int) (struct) - - { - - if err := t.GasPrice.UnmarshalCBOR(br); err != nil { - return xerrors.Errorf("unmarshaling t.GasPrice: %w", err) - } - } // t.GasLimit (int64) (int64) { - maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + maj, extra, err := cr.ReadHeader() var extraI int64 if err != nil { return err @@ -758,7 +828,7 @@ func (t *Message) UnmarshalCBOR(r io.Reader) error { case cbg.MajNegativeInt: extraI = int64(extra) if extraI < 0 { - return fmt.Errorf("int64 negative oveflow") + return fmt.Errorf("int64 negative overflow") } extraI = -1 - extraI default: @@ -767,11 +837,29 @@ func (t *Message) UnmarshalCBOR(r io.Reader) error { t.GasLimit = int64(extraI) } + // t.GasFeeCap (big.Int) (struct) + + { + + if err := t.GasFeeCap.UnmarshalCBOR(cr); err != nil { + return xerrors.Errorf("unmarshaling t.GasFeeCap: %w", err) + } + + } + // t.GasPremium (big.Int) (struct) + + { + + if err := t.GasPremium.UnmarshalCBOR(cr); err != nil { + return xerrors.Errorf("unmarshaling t.GasPremium: %w", err) + } + + } // t.Method (abi.MethodNum) (uint64) { - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + maj, extra, err = cr.ReadHeader() if err != nil { return err } @@ -783,7 +871,7 @@ func (t *Message) UnmarshalCBOR(r io.Reader) error { } // t.Params ([]uint8) (slice) - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + maj, extra, err = cr.ReadHeader() if err != nil { return err } @@ -794,8 +882,12 @@ func (t *Message) UnmarshalCBOR(r io.Reader) error { if maj != cbg.MajByteString { return fmt.Errorf("expected byte array") } - t.Params = make([]byte, extra) - if _, err := io.ReadFull(br, t.Params); err != nil { + + if extra > 0 { + t.Params = make([]uint8, extra) + } + + if _, err := io.ReadFull(cr, t.Params[:]); err != nil { return err } return nil @@ -808,30 +900,40 @@ func (t *SignedMessage) MarshalCBOR(w io.Writer) error { _, err := w.Write(cbg.CborNull) return err } - if _, err := w.Write(lengthBufSignedMessage); err != nil { + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufSignedMessage); err != nil { return err } // t.Message (types.Message) (struct) - if err := t.Message.MarshalCBOR(w); err != nil { + if err := t.Message.MarshalCBOR(cw); err != nil { return err } // t.Signature (crypto.Signature) (struct) - if err := t.Signature.MarshalCBOR(w); err != nil { + if err := t.Signature.MarshalCBOR(cw); err != nil { return err } return nil } -func (t *SignedMessage) UnmarshalCBOR(r io.Reader) error { - br := cbg.GetPeeker(r) - scratch := make([]byte, 8) +func (t *SignedMessage) UnmarshalCBOR(r io.Reader) (err error) { + *t = SignedMessage{} - maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() if err != nil { return err } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + if maj != cbg.MajArray { return fmt.Errorf("cbor input should be of type array") } @@ -844,7 +946,7 @@ func (t *SignedMessage) UnmarshalCBOR(r io.Reader) error { { - if err := t.Message.UnmarshalCBOR(br); err != nil { + if err := t.Message.UnmarshalCBOR(cr); err != nil { return xerrors.Errorf("unmarshaling t.Message: %w", err) } @@ -853,7 +955,7 @@ func (t *SignedMessage) UnmarshalCBOR(r io.Reader) error { { - if err := t.Signature.UnmarshalCBOR(br); err != nil { + if err := t.Signature.UnmarshalCBOR(cr); err != nil { return xerrors.Errorf("unmarshaling t.Signature: %w", err) } @@ -868,35 +970,43 @@ func (t *MsgMeta) MarshalCBOR(w io.Writer) error { _, err := w.Write(cbg.CborNull) return err } - if _, err := w.Write(lengthBufMsgMeta); err != nil { + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufMsgMeta); err != nil { return err } - scratch := make([]byte, 9) - // t.BlsMessages (cid.Cid) (struct) - if err := cbg.WriteCidBuf(scratch, w, t.BlsMessages); err != nil { + if err := cbg.WriteCid(cw, t.BlsMessages); err != nil { return xerrors.Errorf("failed to write cid field t.BlsMessages: %w", err) } // t.SecpkMessages (cid.Cid) (struct) - if err := cbg.WriteCidBuf(scratch, w, t.SecpkMessages); err != nil { + if err := cbg.WriteCid(cw, t.SecpkMessages); err != nil { return xerrors.Errorf("failed to write cid field t.SecpkMessages: %w", err) } return nil } -func (t *MsgMeta) UnmarshalCBOR(r io.Reader) error { - br := cbg.GetPeeker(r) - scratch := make([]byte, 8) +func (t *MsgMeta) UnmarshalCBOR(r io.Reader) (err error) { + *t = MsgMeta{} - maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() if err != nil { return err } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + if maj != cbg.MajArray { return fmt.Errorf("cbor input should be of type array") } @@ -909,7 +1019,7 @@ func (t *MsgMeta) UnmarshalCBOR(r io.Reader) error { { - c, err := cbg.ReadCid(br) + c, err := cbg.ReadCid(cr) if err != nil { return xerrors.Errorf("failed to read cid field t.BlsMessages: %w", err) } @@ -921,7 +1031,7 @@ func (t *MsgMeta) UnmarshalCBOR(r io.Reader) error { { - c, err := cbg.ReadCid(br) + c, err := cbg.ReadCid(cr) if err != nil { return xerrors.Errorf("failed to read cid field t.SecpkMessages: %w", err) } @@ -932,52 +1042,60 @@ func (t *MsgMeta) UnmarshalCBOR(r io.Reader) error { return nil } -var lengthBufActor = []byte{132} +var lengthBufActorV4 = []byte{132} -func (t *Actor) MarshalCBOR(w io.Writer) error { +func (t *ActorV4) MarshalCBOR(w io.Writer) error { if t == nil { _, err := w.Write(cbg.CborNull) return err } - if _, err := w.Write(lengthBufActor); err != nil { + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufActorV4); err != nil { return err } - scratch := make([]byte, 9) - // t.Code (cid.Cid) (struct) - if err := cbg.WriteCidBuf(scratch, w, t.Code); err != nil { + if err := cbg.WriteCid(cw, t.Code); err != nil { return xerrors.Errorf("failed to write cid field t.Code: %w", err) } // t.Head (cid.Cid) (struct) - if err := cbg.WriteCidBuf(scratch, w, t.Head); err != nil { + if err := cbg.WriteCid(cw, t.Head); err != nil { return xerrors.Errorf("failed to write cid field t.Head: %w", err) } // t.Nonce (uint64) (uint64) - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Nonce)); err != nil { + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Nonce)); err != nil { return err } // t.Balance (big.Int) (struct) - if err := t.Balance.MarshalCBOR(w); err != nil { + if err := t.Balance.MarshalCBOR(cw); err != nil { return err } return nil } -func (t *Actor) UnmarshalCBOR(r io.Reader) error { - br := cbg.GetPeeker(r) - scratch := make([]byte, 8) +func (t *ActorV4) UnmarshalCBOR(r io.Reader) (err error) { + *t = ActorV4{} - maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() if err != nil { return err } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + if maj != cbg.MajArray { return fmt.Errorf("cbor input should be of type array") } @@ -990,7 +1108,7 @@ func (t *Actor) UnmarshalCBOR(r io.Reader) error { { - c, err := cbg.ReadCid(br) + c, err := cbg.ReadCid(cr) if err != nil { return xerrors.Errorf("failed to read cid field t.Code: %w", err) } @@ -1002,7 +1120,7 @@ func (t *Actor) UnmarshalCBOR(r io.Reader) error { { - c, err := cbg.ReadCid(br) + c, err := cbg.ReadCid(cr) if err != nil { return xerrors.Errorf("failed to read cid field t.Head: %w", err) } @@ -1014,7 +1132,7 @@ func (t *Actor) UnmarshalCBOR(r io.Reader) error { { - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + maj, extra, err = cr.ReadHeader() if err != nil { return err } @@ -1028,7 +1146,7 @@ func (t *Actor) UnmarshalCBOR(r io.Reader) error { { - if err := t.Balance.UnmarshalCBOR(br); err != nil { + if err := t.Balance.UnmarshalCBOR(cr); err != nil { return xerrors.Errorf("unmarshaling t.Balance: %w", err) } @@ -1036,138 +1154,138 @@ func (t *Actor) UnmarshalCBOR(r io.Reader) error { return nil } -var lengthBufMessageReceipt = []byte{131} +var lengthBufActorV5 = []byte{133} -func (t *MessageReceipt) MarshalCBOR(w io.Writer) error { +func (t *ActorV5) MarshalCBOR(w io.Writer) error { if t == nil { _, err := w.Write(cbg.CborNull) return err } - if _, err := w.Write(lengthBufMessageReceipt); err != nil { + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufActorV5); err != nil { return err } - scratch := make([]byte, 9) + // t.Code (cid.Cid) (struct) - // t.ExitCode (exitcode.ExitCode) (int64) - if t.ExitCode >= 0 { - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.ExitCode)); err != nil { - return err - } - } else { - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajNegativeInt, uint64(-t.ExitCode-1)); err != nil { - return err - } + if err := cbg.WriteCid(cw, t.Code); err != nil { + return xerrors.Errorf("failed to write cid field t.Code: %w", err) } - // t.Return ([]uint8) (slice) - if len(t.Return) > cbg.ByteArrayMaxLen { - return xerrors.Errorf("Byte array in field t.Return was too long") + // t.Head (cid.Cid) (struct) + + if err := cbg.WriteCid(cw, t.Head); err != nil { + return xerrors.Errorf("failed to write cid field t.Head: %w", err) } - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajByteString, uint64(len(t.Return))); err != nil { + // t.Nonce (uint64) (uint64) + + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Nonce)); err != nil { return err } - if _, err := w.Write(t.Return); err != nil { + // t.Balance (big.Int) (struct) + if err := t.Balance.MarshalCBOR(cw); err != nil { return err } - // t.GasUsed (int64) (int64) - if t.GasUsed >= 0 { - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.GasUsed)); err != nil { - return err - } - } else { - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajNegativeInt, uint64(-t.GasUsed-1)); err != nil { - return err - } + // t.Address (address.Address) (struct) + if err := t.Address.MarshalCBOR(cw); err != nil { + return err } return nil } -func (t *MessageReceipt) UnmarshalCBOR(r io.Reader) error { - br := cbg.GetPeeker(r) - scratch := make([]byte, 8) +func (t *ActorV5) UnmarshalCBOR(r io.Reader) (err error) { + *t = ActorV5{} - maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() if err != nil { return err } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + if maj != cbg.MajArray { return fmt.Errorf("cbor input should be of type array") } - if extra != 3 { + if extra != 5 { return fmt.Errorf("cbor input had wrong number of fields") } - // t.ExitCode (exitcode.ExitCode) (int64) + // t.Code (cid.Cid) (struct) + { - maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) - var extraI int64 + + c, err := cbg.ReadCid(cr) + if err != nil { + return xerrors.Errorf("failed to read cid field t.Code: %w", err) + } + + t.Code = c + + } + // t.Head (cid.Cid) (struct) + + { + + c, err := cbg.ReadCid(cr) + if err != nil { + return xerrors.Errorf("failed to read cid field t.Head: %w", err) + } + + t.Head = c + + } + // t.Nonce (uint64) (uint64) + + { + + maj, extra, err = cr.ReadHeader() if err != nil { return err } - switch maj { - case cbg.MajUnsignedInt: - extraI = int64(extra) - if extraI < 0 { - return fmt.Errorf("int64 positive overflow") - } - case cbg.MajNegativeInt: - extraI = int64(extra) - if extraI < 0 { - return fmt.Errorf("int64 negative oveflow") - } - extraI = -1 - extraI - default: - return fmt.Errorf("wrong type for int64 field: %d", maj) + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.Nonce = uint64(extra) + + } + // t.Balance (big.Int) (struct) + + { + + if err := t.Balance.UnmarshalCBOR(cr); err != nil { + return xerrors.Errorf("unmarshaling t.Balance: %w", err) } - t.ExitCode = exitcode.ExitCode(extraI) } - // t.Return ([]uint8) (slice) + // t.Address (address.Address) (struct) - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) - if err != nil { - return err - } - - if extra > cbg.ByteArrayMaxLen { - return fmt.Errorf("t.Return: byte array too large (%d)", extra) - } - if maj != cbg.MajByteString { - return fmt.Errorf("expected byte array") - } - t.Return = make([]byte, extra) - if _, err := io.ReadFull(br, t.Return); err != nil { - return err - } - // t.GasUsed (int64) (int64) { - maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) - var extraI int64 + + b, err := cr.ReadByte() if err != nil { return err } - switch maj { - case cbg.MajUnsignedInt: - extraI = int64(extra) - if extraI < 0 { - return fmt.Errorf("int64 positive overflow") + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { + return err } - case cbg.MajNegativeInt: - extraI = int64(extra) - if extraI < 0 { - return fmt.Errorf("int64 negative oveflow") + t.Address = new(address.Address) + if err := t.Address.UnmarshalCBOR(cr); err != nil { + return xerrors.Errorf("unmarshaling t.Address pointer: %w", err) } - extraI = -1 - extraI - default: - return fmt.Errorf("wrong type for int64 field: %d", maj) } - t.GasUsed = int64(extraI) } return nil } @@ -1179,14 +1297,15 @@ func (t *BlockMsg) MarshalCBOR(w io.Writer) error { _, err := w.Write(cbg.CborNull) return err } - if _, err := w.Write(lengthBufBlockMsg); err != nil { + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufBlockMsg); err != nil { return err } - scratch := make([]byte, 9) - // t.Header (types.BlockHeader) (struct) - if err := t.Header.MarshalCBOR(w); err != nil { + if err := t.Header.MarshalCBOR(cw); err != nil { return err } @@ -1195,11 +1314,11 @@ func (t *BlockMsg) MarshalCBOR(w io.Writer) error { return xerrors.Errorf("Slice value in field t.BlsMessages was too long") } - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.BlsMessages))); err != nil { + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.BlsMessages))); err != nil { return err } for _, v := range t.BlsMessages { - if err := cbg.WriteCidBuf(scratch, w, v); err != nil { + if err := cbg.WriteCid(w, v); err != nil { return xerrors.Errorf("failed writing cid field t.BlsMessages: %w", err) } } @@ -1209,25 +1328,32 @@ func (t *BlockMsg) MarshalCBOR(w io.Writer) error { return xerrors.Errorf("Slice value in field t.SecpkMessages was too long") } - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.SecpkMessages))); err != nil { + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.SecpkMessages))); err != nil { return err } for _, v := range t.SecpkMessages { - if err := cbg.WriteCidBuf(scratch, w, v); err != nil { + if err := cbg.WriteCid(w, v); err != nil { return xerrors.Errorf("failed writing cid field t.SecpkMessages: %w", err) } } return nil } -func (t *BlockMsg) UnmarshalCBOR(r io.Reader) error { - br := cbg.GetPeeker(r) - scratch := make([]byte, 8) +func (t *BlockMsg) UnmarshalCBOR(r io.Reader) (err error) { + *t = BlockMsg{} - maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() if err != nil { return err } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + if maj != cbg.MajArray { return fmt.Errorf("cbor input should be of type array") } @@ -1240,18 +1366,16 @@ func (t *BlockMsg) UnmarshalCBOR(r io.Reader) error { { - pb, err := br.PeekByte() + b, err := cr.ReadByte() if err != nil { return err } - if pb == cbg.CborNull[0] { - var nbuf [1]byte - if _, err := br.Read(nbuf[:]); err != nil { + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { return err } - } else { t.Header = new(BlockHeader) - if err := t.Header.UnmarshalCBOR(br); err != nil { + if err := t.Header.UnmarshalCBOR(cr); err != nil { return xerrors.Errorf("unmarshaling t.Header pointer: %w", err) } } @@ -1259,7 +1383,7 @@ func (t *BlockMsg) UnmarshalCBOR(r io.Reader) error { } // t.BlsMessages ([]cid.Cid) (slice) - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + maj, extra, err = cr.ReadHeader() if err != nil { return err } @@ -1278,7 +1402,7 @@ func (t *BlockMsg) UnmarshalCBOR(r io.Reader) error { for i := 0; i < int(extra); i++ { - c, err := cbg.ReadCid(br) + c, err := cbg.ReadCid(cr) if err != nil { return xerrors.Errorf("reading cid field t.BlsMessages failed: %w", err) } @@ -1287,7 +1411,7 @@ func (t *BlockMsg) UnmarshalCBOR(r io.Reader) error { // t.SecpkMessages ([]cid.Cid) (slice) - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + maj, extra, err = cr.ReadHeader() if err != nil { return err } @@ -1306,7 +1430,7 @@ func (t *BlockMsg) UnmarshalCBOR(r io.Reader) error { for i := 0; i < int(extra); i++ { - c, err := cbg.ReadCid(br) + c, err := cbg.ReadCid(cr) if err != nil { return xerrors.Errorf("reading cid field t.SecpkMessages failed: %w", err) } @@ -1323,22 +1447,23 @@ func (t *ExpTipSet) MarshalCBOR(w io.Writer) error { _, err := w.Write(cbg.CborNull) return err } - if _, err := w.Write(lengthBufExpTipSet); err != nil { + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufExpTipSet); err != nil { return err } - scratch := make([]byte, 9) - // t.Cids ([]cid.Cid) (slice) if len(t.Cids) > cbg.MaxLength { return xerrors.Errorf("Slice value in field t.Cids was too long") } - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.Cids))); err != nil { + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Cids))); err != nil { return err } for _, v := range t.Cids { - if err := cbg.WriteCidBuf(scratch, w, v); err != nil { + if err := cbg.WriteCid(w, v); err != nil { return xerrors.Errorf("failed writing cid field t.Cids: %w", err) } } @@ -1348,36 +1473,43 @@ func (t *ExpTipSet) MarshalCBOR(w io.Writer) error { return xerrors.Errorf("Slice value in field t.Blocks was too long") } - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(t.Blocks))); err != nil { + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Blocks))); err != nil { return err } for _, v := range t.Blocks { - if err := v.MarshalCBOR(w); err != nil { + if err := v.MarshalCBOR(cw); err != nil { return err } } // t.Height (abi.ChainEpoch) (int64) if t.Height >= 0 { - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Height)); err != nil { + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Height)); err != nil { return err } } else { - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajNegativeInt, uint64(-t.Height-1)); err != nil { + if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.Height-1)); err != nil { return err } } return nil } -func (t *ExpTipSet) UnmarshalCBOR(r io.Reader) error { - br := cbg.GetPeeker(r) - scratch := make([]byte, 8) +func (t *ExpTipSet) UnmarshalCBOR(r io.Reader) (err error) { + *t = ExpTipSet{} - maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() if err != nil { return err } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + if maj != cbg.MajArray { return fmt.Errorf("cbor input should be of type array") } @@ -1388,7 +1520,7 @@ func (t *ExpTipSet) UnmarshalCBOR(r io.Reader) error { // t.Cids ([]cid.Cid) (slice) - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + maj, extra, err = cr.ReadHeader() if err != nil { return err } @@ -1407,7 +1539,7 @@ func (t *ExpTipSet) UnmarshalCBOR(r io.Reader) error { for i := 0; i < int(extra); i++ { - c, err := cbg.ReadCid(br) + c, err := cbg.ReadCid(cr) if err != nil { return xerrors.Errorf("reading cid field t.Cids failed: %w", err) } @@ -1416,7 +1548,7 @@ func (t *ExpTipSet) UnmarshalCBOR(r io.Reader) error { // t.Blocks ([]*types.BlockHeader) (slice) - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + maj, extra, err = cr.ReadHeader() if err != nil { return err } @@ -1436,7 +1568,7 @@ func (t *ExpTipSet) UnmarshalCBOR(r io.Reader) error { for i := 0; i < int(extra); i++ { var v BlockHeader - if err := v.UnmarshalCBOR(br); err != nil { + if err := v.UnmarshalCBOR(cr); err != nil { return err } @@ -1445,7 +1577,7 @@ func (t *ExpTipSet) UnmarshalCBOR(r io.Reader) error { // t.Height (abi.ChainEpoch) (int64) { - maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + maj, extra, err := cr.ReadHeader() var extraI int64 if err != nil { return err @@ -1459,7 +1591,7 @@ func (t *ExpTipSet) UnmarshalCBOR(r io.Reader) error { case cbg.MajNegativeInt: extraI = int64(extra) if extraI < 0 { - return fmt.Errorf("int64 negative oveflow") + return fmt.Errorf("int64 negative overflow") } extraI = -1 - extraI default: @@ -1478,15 +1610,16 @@ func (t *BeaconEntry) MarshalCBOR(w io.Writer) error { _, err := w.Write(cbg.CborNull) return err } - if _, err := w.Write(lengthBufBeaconEntry); err != nil { + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufBeaconEntry); err != nil { return err } - scratch := make([]byte, 9) - // t.Round (uint64) (uint64) - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajUnsignedInt, uint64(t.Round)); err != nil { + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Round)); err != nil { return err } @@ -1495,24 +1628,31 @@ func (t *BeaconEntry) MarshalCBOR(w io.Writer) error { return xerrors.Errorf("Byte array in field t.Data was too long") } - if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajByteString, uint64(len(t.Data))); err != nil { + if err := cw.WriteMajorTypeHeader(cbg.MajByteString, uint64(len(t.Data))); err != nil { return err } - if _, err := w.Write(t.Data); err != nil { + if _, err := cw.Write(t.Data[:]); err != nil { return err } return nil } -func (t *BeaconEntry) UnmarshalCBOR(r io.Reader) error { - br := cbg.GetPeeker(r) - scratch := make([]byte, 8) +func (t *BeaconEntry) UnmarshalCBOR(r io.Reader) (err error) { + *t = BeaconEntry{} - maj, extra, err := cbg.CborReadHeaderBuf(br, scratch) + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() if err != nil { return err } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + if maj != cbg.MajArray { return fmt.Errorf("cbor input should be of type array") } @@ -1525,7 +1665,7 @@ func (t *BeaconEntry) UnmarshalCBOR(r io.Reader) error { { - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + maj, extra, err = cr.ReadHeader() if err != nil { return err } @@ -1537,7 +1677,7 @@ func (t *BeaconEntry) UnmarshalCBOR(r io.Reader) error { } // t.Data ([]uint8) (slice) - maj, extra, err = cbg.CborReadHeaderBuf(br, scratch) + maj, extra, err = cr.ReadHeader() if err != nil { return err } @@ -1548,9 +1688,1051 @@ func (t *BeaconEntry) UnmarshalCBOR(r io.Reader) error { if maj != cbg.MajByteString { return fmt.Errorf("expected byte array") } - t.Data = make([]byte, extra) - if _, err := io.ReadFull(br, t.Data); err != nil { + + if extra > 0 { + t.Data = make([]uint8, extra) + } + + if _, err := io.ReadFull(cr, t.Data[:]); err != nil { return err } return nil } + +var lengthBufStateRoot = []byte{131} + +func (t *StateRoot) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufStateRoot); err != nil { + return err + } + + // t.Version (types.StateTreeVersion) (uint64) + + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Version)); err != nil { + return err + } + + // t.Actors (cid.Cid) (struct) + + if err := cbg.WriteCid(cw, t.Actors); err != nil { + return xerrors.Errorf("failed to write cid field t.Actors: %w", err) + } + + // t.Info (cid.Cid) (struct) + + if err := cbg.WriteCid(cw, t.Info); err != nil { + return xerrors.Errorf("failed to write cid field t.Info: %w", err) + } + + return nil +} + +func (t *StateRoot) UnmarshalCBOR(r io.Reader) (err error) { + *t = StateRoot{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != 3 { + return fmt.Errorf("cbor input had wrong number of fields") + } + + // t.Version (types.StateTreeVersion) (uint64) + + { + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.Version = StateTreeVersion(extra) + + } + // t.Actors (cid.Cid) (struct) + + { + + c, err := cbg.ReadCid(cr) + if err != nil { + return xerrors.Errorf("failed to read cid field t.Actors: %w", err) + } + + t.Actors = c + + } + // t.Info (cid.Cid) (struct) + + { + + c, err := cbg.ReadCid(cr) + if err != nil { + return xerrors.Errorf("failed to read cid field t.Info: %w", err) + } + + t.Info = c + + } + return nil +} + +var lengthBufStateInfo0 = []byte{128} + +func (t *StateInfo0) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufStateInfo0); err != nil { + return err + } + return nil +} + +func (t *StateInfo0) UnmarshalCBOR(r io.Reader) (err error) { + *t = StateInfo0{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != 0 { + return fmt.Errorf("cbor input had wrong number of fields") + } + + return nil +} + +var lengthBufEvent = []byte{130} + +func (t *Event) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufEvent); err != nil { + return err + } + + // t.Emitter (abi.ActorID) (uint64) + + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Emitter)); err != nil { + return err + } + + // t.Entries ([]types.EventEntry) (slice) + if len(t.Entries) > cbg.MaxLength { + return xerrors.Errorf("Slice value in field t.Entries was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Entries))); err != nil { + return err + } + for _, v := range t.Entries { + if err := v.MarshalCBOR(cw); err != nil { + return err + } + } + return nil +} + +func (t *Event) UnmarshalCBOR(r io.Reader) (err error) { + *t = Event{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != 2 { + return fmt.Errorf("cbor input had wrong number of fields") + } + + // t.Emitter (abi.ActorID) (uint64) + + { + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.Emitter = abi.ActorID(extra) + + } + // t.Entries ([]types.EventEntry) (slice) + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > cbg.MaxLength { + return fmt.Errorf("t.Entries: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.Entries = make([]EventEntry, extra) + } + + for i := 0; i < int(extra); i++ { + + var v EventEntry + if err := v.UnmarshalCBOR(cr); err != nil { + return err + } + + t.Entries[i] = v + } + + return nil +} + +var lengthBufEventEntry = []byte{132} + +func (t *EventEntry) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufEventEntry); err != nil { + return err + } + + // t.Flags (uint8) (uint8) + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Flags)); err != nil { + return err + } + + // t.Key (string) (string) + if len(t.Key) > cbg.MaxLength { + return xerrors.Errorf("Value in field t.Key was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Key))); err != nil { + return err + } + if _, err := io.WriteString(w, string(t.Key)); err != nil { + return err + } + + // t.Codec (uint64) (uint64) + + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Codec)); err != nil { + return err + } + + // t.Value ([]uint8) (slice) + if len(t.Value) > cbg.ByteArrayMaxLen { + return xerrors.Errorf("Byte array in field t.Value was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajByteString, uint64(len(t.Value))); err != nil { + return err + } + + if _, err := cw.Write(t.Value[:]); err != nil { + return err + } + return nil +} + +func (t *EventEntry) UnmarshalCBOR(r io.Reader) (err error) { + *t = EventEntry{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != 4 { + return fmt.Errorf("cbor input had wrong number of fields") + } + + // t.Flags (uint8) (uint8) + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint8 field") + } + if extra > math.MaxUint8 { + return fmt.Errorf("integer in input was too large for uint8 field") + } + t.Flags = uint8(extra) + // t.Key (string) (string) + + { + sval, err := cbg.ReadString(cr) + if err != nil { + return err + } + + t.Key = string(sval) + } + // t.Codec (uint64) (uint64) + + { + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.Codec = uint64(extra) + + } + // t.Value ([]uint8) (slice) + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > cbg.ByteArrayMaxLen { + return fmt.Errorf("t.Value: byte array too large (%d)", extra) + } + if maj != cbg.MajByteString { + return fmt.Errorf("expected byte array") + } + + if extra > 0 { + t.Value = make([]uint8, extra) + } + + if _, err := io.ReadFull(cr, t.Value[:]); err != nil { + return err + } + return nil +} + +var lengthBufGasTrace = []byte{133} + +func (t *GasTrace) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufGasTrace); err != nil { + return err + } + + // t.Name (string) (string) + if len(t.Name) > cbg.MaxLength { + return xerrors.Errorf("Value in field t.Name was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.Name))); err != nil { + return err + } + if _, err := io.WriteString(w, string(t.Name)); err != nil { + return err + } + + // t.TotalGas (int64) (int64) + if t.TotalGas >= 0 { + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.TotalGas)); err != nil { + return err + } + } else { + if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.TotalGas-1)); err != nil { + return err + } + } + + // t.ComputeGas (int64) (int64) + if t.ComputeGas >= 0 { + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.ComputeGas)); err != nil { + return err + } + } else { + if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.ComputeGas-1)); err != nil { + return err + } + } + + // t.StorageGas (int64) (int64) + if t.StorageGas >= 0 { + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.StorageGas)); err != nil { + return err + } + } else { + if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.StorageGas-1)); err != nil { + return err + } + } + + // t.TimeTaken (time.Duration) (int64) + if t.TimeTaken >= 0 { + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.TimeTaken)); err != nil { + return err + } + } else { + if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.TimeTaken-1)); err != nil { + return err + } + } + return nil +} + +func (t *GasTrace) UnmarshalCBOR(r io.Reader) (err error) { + *t = GasTrace{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != 5 { + return fmt.Errorf("cbor input had wrong number of fields") + } + + // t.Name (string) (string) + + { + sval, err := cbg.ReadString(cr) + if err != nil { + return err + } + + t.Name = string(sval) + } + // t.TotalGas (int64) (int64) + { + maj, extra, err := cr.ReadHeader() + var extraI int64 + if err != nil { + return err + } + switch maj { + case cbg.MajUnsignedInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 positive overflow") + } + case cbg.MajNegativeInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 negative overflow") + } + extraI = -1 - extraI + default: + return fmt.Errorf("wrong type for int64 field: %d", maj) + } + + t.TotalGas = int64(extraI) + } + // t.ComputeGas (int64) (int64) + { + maj, extra, err := cr.ReadHeader() + var extraI int64 + if err != nil { + return err + } + switch maj { + case cbg.MajUnsignedInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 positive overflow") + } + case cbg.MajNegativeInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 negative overflow") + } + extraI = -1 - extraI + default: + return fmt.Errorf("wrong type for int64 field: %d", maj) + } + + t.ComputeGas = int64(extraI) + } + // t.StorageGas (int64) (int64) + { + maj, extra, err := cr.ReadHeader() + var extraI int64 + if err != nil { + return err + } + switch maj { + case cbg.MajUnsignedInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 positive overflow") + } + case cbg.MajNegativeInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 negative overflow") + } + extraI = -1 - extraI + default: + return fmt.Errorf("wrong type for int64 field: %d", maj) + } + + t.StorageGas = int64(extraI) + } + // t.TimeTaken (time.Duration) (int64) + { + maj, extra, err := cr.ReadHeader() + var extraI int64 + if err != nil { + return err + } + switch maj { + case cbg.MajUnsignedInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 positive overflow") + } + case cbg.MajNegativeInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 negative overflow") + } + extraI = -1 - extraI + default: + return fmt.Errorf("wrong type for int64 field: %d", maj) + } + + t.TimeTaken = time.Duration(extraI) + } + return nil +} + +var lengthBufMessageTrace = []byte{134} + +func (t *MessageTrace) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufMessageTrace); err != nil { + return err + } + + // t.From (address.Address) (struct) + if err := t.From.MarshalCBOR(cw); err != nil { + return err + } + + // t.To (address.Address) (struct) + if err := t.To.MarshalCBOR(cw); err != nil { + return err + } + + // t.Value (big.Int) (struct) + if err := t.Value.MarshalCBOR(cw); err != nil { + return err + } + + // t.Method (abi.MethodNum) (uint64) + + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Method)); err != nil { + return err + } + + // t.Params ([]uint8) (slice) + if len(t.Params) > cbg.ByteArrayMaxLen { + return xerrors.Errorf("Byte array in field t.Params was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajByteString, uint64(len(t.Params))); err != nil { + return err + } + + if _, err := cw.Write(t.Params[:]); err != nil { + return err + } + + // t.ParamsCodec (uint64) (uint64) + + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.ParamsCodec)); err != nil { + return err + } + + return nil +} + +func (t *MessageTrace) UnmarshalCBOR(r io.Reader) (err error) { + *t = MessageTrace{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != 6 { + return fmt.Errorf("cbor input had wrong number of fields") + } + + // t.From (address.Address) (struct) + + { + + if err := t.From.UnmarshalCBOR(cr); err != nil { + return xerrors.Errorf("unmarshaling t.From: %w", err) + } + + } + // t.To (address.Address) (struct) + + { + + if err := t.To.UnmarshalCBOR(cr); err != nil { + return xerrors.Errorf("unmarshaling t.To: %w", err) + } + + } + // t.Value (big.Int) (struct) + + { + + if err := t.Value.UnmarshalCBOR(cr); err != nil { + return xerrors.Errorf("unmarshaling t.Value: %w", err) + } + + } + // t.Method (abi.MethodNum) (uint64) + + { + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.Method = abi.MethodNum(extra) + + } + // t.Params ([]uint8) (slice) + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > cbg.ByteArrayMaxLen { + return fmt.Errorf("t.Params: byte array too large (%d)", extra) + } + if maj != cbg.MajByteString { + return fmt.Errorf("expected byte array") + } + + if extra > 0 { + t.Params = make([]uint8, extra) + } + + if _, err := io.ReadFull(cr, t.Params[:]); err != nil { + return err + } + // t.ParamsCodec (uint64) (uint64) + + { + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.ParamsCodec = uint64(extra) + + } + return nil +} + +var lengthBufReturnTrace = []byte{131} + +func (t *ReturnTrace) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufReturnTrace); err != nil { + return err + } + + // t.ExitCode (exitcode.ExitCode) (int64) + if t.ExitCode >= 0 { + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.ExitCode)); err != nil { + return err + } + } else { + if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.ExitCode-1)); err != nil { + return err + } + } + + // t.Return ([]uint8) (slice) + if len(t.Return) > cbg.ByteArrayMaxLen { + return xerrors.Errorf("Byte array in field t.Return was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajByteString, uint64(len(t.Return))); err != nil { + return err + } + + if _, err := cw.Write(t.Return[:]); err != nil { + return err + } + + // t.ReturnCodec (uint64) (uint64) + + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.ReturnCodec)); err != nil { + return err + } + + return nil +} + +func (t *ReturnTrace) UnmarshalCBOR(r io.Reader) (err error) { + *t = ReturnTrace{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != 3 { + return fmt.Errorf("cbor input had wrong number of fields") + } + + // t.ExitCode (exitcode.ExitCode) (int64) + { + maj, extra, err := cr.ReadHeader() + var extraI int64 + if err != nil { + return err + } + switch maj { + case cbg.MajUnsignedInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 positive overflow") + } + case cbg.MajNegativeInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 negative overflow") + } + extraI = -1 - extraI + default: + return fmt.Errorf("wrong type for int64 field: %d", maj) + } + + t.ExitCode = exitcode.ExitCode(extraI) + } + // t.Return ([]uint8) (slice) + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > cbg.ByteArrayMaxLen { + return fmt.Errorf("t.Return: byte array too large (%d)", extra) + } + if maj != cbg.MajByteString { + return fmt.Errorf("expected byte array") + } + + if extra > 0 { + t.Return = make([]uint8, extra) + } + + if _, err := io.ReadFull(cr, t.Return[:]); err != nil { + return err + } + // t.ReturnCodec (uint64) (uint64) + + { + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.ReturnCodec = uint64(extra) + + } + return nil +} + +var lengthBufExecutionTrace = []byte{132} + +func (t *ExecutionTrace) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufExecutionTrace); err != nil { + return err + } + + // t.Msg (types.MessageTrace) (struct) + if err := t.Msg.MarshalCBOR(cw); err != nil { + return err + } + + // t.MsgRct (types.ReturnTrace) (struct) + if err := t.MsgRct.MarshalCBOR(cw); err != nil { + return err + } + + // t.GasCharges ([]*types.GasTrace) (slice) + if len(t.GasCharges) > 1000000000 { + return xerrors.Errorf("Slice value in field t.GasCharges was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.GasCharges))); err != nil { + return err + } + for _, v := range t.GasCharges { + if err := v.MarshalCBOR(cw); err != nil { + return err + } + } + + // t.Subcalls ([]types.ExecutionTrace) (slice) + if len(t.Subcalls) > 1000000000 { + return xerrors.Errorf("Slice value in field t.Subcalls was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Subcalls))); err != nil { + return err + } + for _, v := range t.Subcalls { + if err := v.MarshalCBOR(cw); err != nil { + return err + } + } + return nil +} + +func (t *ExecutionTrace) UnmarshalCBOR(r io.Reader) (err error) { + *t = ExecutionTrace{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != 4 { + return fmt.Errorf("cbor input had wrong number of fields") + } + + // t.Msg (types.MessageTrace) (struct) + + { + + if err := t.Msg.UnmarshalCBOR(cr); err != nil { + return xerrors.Errorf("unmarshaling t.Msg: %w", err) + } + + } + // t.MsgRct (types.ReturnTrace) (struct) + + { + + if err := t.MsgRct.UnmarshalCBOR(cr); err != nil { + return xerrors.Errorf("unmarshaling t.MsgRct: %w", err) + } + + } + // t.GasCharges ([]*types.GasTrace) (slice) + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > 1000000000 { + return fmt.Errorf("t.GasCharges: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.GasCharges = make([]*GasTrace, extra) + } + + for i := 0; i < int(extra); i++ { + + var v GasTrace + if err := v.UnmarshalCBOR(cr); err != nil { + return err + } + + t.GasCharges[i] = &v + } + + // t.Subcalls ([]types.ExecutionTrace) (slice) + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > 1000000000 { + return fmt.Errorf("t.Subcalls: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.Subcalls = make([]ExecutionTrace, extra) + } + + for i := 0; i < int(extra); i++ { + + var v ExecutionTrace + if err := v.UnmarshalCBOR(cr); err != nil { + return err + } + + t.Subcalls[i] = v + } + + return nil +} diff --git a/chain/types/electionproof.go b/chain/types/electionproof.go new file mode 100644 index 000000000..f3168becb --- /dev/null +++ b/chain/types/electionproof.go @@ -0,0 +1,207 @@ +package types + +import ( + "math/big" + + "github.com/minio/blake2b-simd" + + "github.com/filecoin-project/lotus/build" +) + +type ElectionProof struct { + WinCount int64 + VRFProof []byte +} + +const precision = 256 + +var ( + expNumCoef []*big.Int + expDenoCoef []*big.Int +) + +func init() { + parse := func(coefs []string) []*big.Int { + out := make([]*big.Int, len(coefs)) + for i, coef := range coefs { + c, ok := new(big.Int).SetString(coef, 10) + if !ok { + panic("could not parse exp paramemter") + } + // << 256 (Q.0 to Q.256), >> 128 to transform integer params to coefficients + c = c.Lsh(c, precision-128) + out[i] = c + } + return out + } + + // parameters are in integer format, + // coefficients are *2^-128 of that + num := []string{ + "-648770010757830093818553637600", + "67469480939593786226847644286976", + "-3197587544499098424029388939001856", + "89244641121992890118377641805348864", + "-1579656163641440567800982336819953664", + "17685496037279256458459817590917169152", + "-115682590513835356866803355398940131328", + "340282366920938463463374607431768211456", + } + expNumCoef = parse(num) + + deno := []string{ + "1225524182432722209606361", + "114095592300906098243859450", + "5665570424063336070530214243", + "194450132448609991765137938448", + "5068267641632683791026134915072", + "104716890604972796896895427629056", + "1748338658439454459487681798864896", + "23704654329841312470660182937960448", + "259380097567996910282699886670381056", + "2250336698853390384720606936038375424", + "14978272436876548034486263159246028800", + "72144088983913131323343765784380833792", + "224599776407103106596571252037123047424", + "340282366920938463463374607431768211456", + } + expDenoCoef = parse(deno) +} + +// expneg accepts x in Q.256 format and computes e^-x. +// It is most precise within [0, 1.725) range, where error is less than 3.4e-30. +// Over the [0, 5) range its error is less than 4.6e-15. +// Output is in Q.256 format. +func expneg(x *big.Int) *big.Int { + // exp is approximated by rational function + // polynomials of the rational function are evaluated using Horner's method + num := polyval(expNumCoef, x) // Q.256 + deno := polyval(expDenoCoef, x) // Q.256 + + num = num.Lsh(num, precision) // Q.512 + return num.Div(num, deno) // Q.512 / Q.256 => Q.256 +} + +// polyval evaluates a polynomial given by coefficients `p` in Q.256 format +// at point `x` in Q.256 format. Output is in Q.256. +// Coefficients should be ordered from the highest order coefficient to the lowest. +func polyval(p []*big.Int, x *big.Int) *big.Int { + // evaluation using Horner's method + res := new(big.Int).Set(p[0]) // Q.256 + tmp := new(big.Int) // big.Int.Mul doesn't like when input is reused as output + for _, c := range p[1:] { + tmp = tmp.Mul(res, x) // Q.256 * Q.256 => Q.512 + res = res.Rsh(tmp, precision) // Q.512 >> 256 => Q.256 + res = res.Add(res, c) + } + + return res +} + +// computes lambda in Q.256 +func lambda(power, totalPower *big.Int) *big.Int { + blocksPerEpoch := NewInt(build.BlocksPerEpoch) + lam := new(big.Int).Mul(power, blocksPerEpoch.Int) // Q.0 + lam = lam.Lsh(lam, precision) // Q.256 + lam = lam.Div(lam /* Q.256 */, totalPower /* Q.0 */) // Q.256 + return lam +} + +var MaxWinCount = 3 * int64(build.BlocksPerEpoch) + +type poiss struct { + lam *big.Int + pmf *big.Int + icdf *big.Int + + tmp *big.Int // temporary variable for optimization + + k uint64 +} + +// newPoiss starts poisson inverted CDF +// lambda is in Q.256 format +// returns (instance, `1-poisscdf(0, lambda)`) +// CDF value returend is reused when calling `next` +func newPoiss(lambda *big.Int) (*poiss, *big.Int) { + + // pmf(k) = (lambda^k)*(e^lambda) / k! + // k = 0 here, so it simplifies to just e^-lambda + elam := expneg(lambda) // Q.256 + pmf := new(big.Int).Set(elam) + + // icdf(k) = 1 - ∑ᵏᵢ₌₀ pmf(i) + // icdf(0) = 1 - pmf(0) + icdf := big.NewInt(1) + icdf = icdf.Lsh(icdf, precision) // Q.256 + icdf = icdf.Sub(icdf, pmf) // Q.256 + + k := uint64(0) + + p := &poiss{ + lam: lambda, + pmf: pmf, + + tmp: elam, + icdf: icdf, + + k: k, + } + + return p, icdf +} + +// next computes `k++, 1-poisscdf(k, lam)` +// return is in Q.256 format +func (p *poiss) next() *big.Int { + // incrementally compute next pmf and icdf + + // pmf(k) = (lambda^k)*(e^lambda) / k! + // so pmf(k) = pmf(k-1) * lambda / k + p.k++ + p.tmp.SetUint64(p.k) // Q.0 + + // calculate pmf for k + p.pmf = p.pmf.Div(p.pmf, p.tmp) // Q.256 / Q.0 => Q.256 + // we are using `tmp` as target for multiplication as using an input as output + // for Int.Mul causes allocations + p.tmp = p.tmp.Mul(p.pmf, p.lam) // Q.256 * Q.256 => Q.512 + p.pmf = p.pmf.Rsh(p.tmp, precision) // Q.512 >> 256 => Q.256 + + // calculate output + // icdf(k) = icdf(k-1) - pmf(k) + p.icdf = p.icdf.Sub(p.icdf, p.pmf) // Q.256 + return p.icdf +} + +// ComputeWinCount uses VRFProof to compute number of wins +// The algorithm is based on Algorand's Sortition with Binomial distribution +// replaced by Poisson distribution. +func (ep *ElectionProof) ComputeWinCount(power BigInt, totalPower BigInt) int64 { + h := blake2b.Sum256(ep.VRFProof) + + lhs := BigFromBytes(h[:]).Int // 256bits, assume Q.256 so [0, 1) + + // We are calculating upside-down CDF of Poisson distribution with + // rate λ=power*E/totalPower + // Steps: + // 1. calculate λ=power*E/totalPower + // 2. calculate elam = exp(-λ) + // 3. Check how many times we win: + // j = 0 + // pmf = elam + // rhs = 1 - pmf + // for h(vrf) < rhs: j++; pmf = pmf * lam / j; rhs = rhs - pmf + + lam := lambda(power.Int, totalPower.Int) // Q.256 + + p, rhs := newPoiss(lam) + + var j int64 + for lhs.Cmp(rhs) < 0 && j < MaxWinCount { + rhs = p.next() + j++ + } + + return j +} diff --git a/chain/types/electionproof_test.go b/chain/types/electionproof_test.go new file mode 100644 index 000000000..3a06460fe --- /dev/null +++ b/chain/types/electionproof_test.go @@ -0,0 +1,158 @@ +// stm: #unit +package types + +import ( + "bytes" + "fmt" + "math/big" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/xorcare/golden" +) + +func TestPoissonFunction(t *testing.T) { + //stm: @CHAIN_TYPES_POISSON_001 + tests := []struct { + lambdaBase uint64 + lambdaShift uint + }{ + {10, 10}, // 0.0097 + {209714, 20}, // 0.19999885 + {1036915, 20}, // 0.9888792038 + {1706, 10}, // 1.6660 + {2, 0}, // 2 + {5242879, 20}, //4.9999990 + {5, 0}, // 5 + } + + for _, test := range tests { + test := test + t.Run(fmt.Sprintf("lam-%d-%d", test.lambdaBase, test.lambdaShift), func(t *testing.T) { + b := &bytes.Buffer{} + b.WriteString("icdf\n") + + lam := new(big.Int).SetUint64(test.lambdaBase) + lam = lam.Lsh(lam, precision-test.lambdaShift) + p, icdf := newPoiss(lam) + + b.WriteString(icdf.String()) + b.WriteRune('\n') + + for i := 0; i < 15; i++ { + b.WriteString(p.next().String()) + b.WriteRune('\n') + } + golden.Assert(t, []byte(b.String())) + }) + } +} + +func TestLambdaFunction(t *testing.T) { + //stm: @CHAIN_TYPES_LAMBDA_001 + tests := []struct { + power string + totalPower string + target float64 + }{ + {"10", "100", .1 * 5.}, + {"1024", "2048", 0.5 * 5.}, + {"2000000000000000", "100000000000000000", 0.02 * 5.}, + } + + for _, test := range tests { + test := test + t.Run(fmt.Sprintf("%s-%s", test.power, test.totalPower), func(t *testing.T) { + pow, ok := new(big.Int).SetString(test.power, 10) + assert.True(t, ok) + total, ok := new(big.Int).SetString(test.totalPower, 10) + assert.True(t, ok) + lam := lambda(pow, total) + assert.Equal(t, test.target, q256ToF(lam)) + golden.Assert(t, []byte(lam.String())) + }) + } +} + +func TestExpFunction(t *testing.T) { + //stm: @CHAIN_TYPES_NEGATIVE_EXP_001 + const N = 256 + + step := big.NewInt(5) + step = step.Lsh(step, 256) // Q.256 + step = step.Div(step, big.NewInt(N-1)) + + x := big.NewInt(0) + b := &bytes.Buffer{} + + b.WriteString("x, y\n") + for i := 0; i < N; i++ { + y := expneg(x) + fmt.Fprintf(b, "%s,%s\n", x, y) + x = x.Add(x, step) + } + + golden.Assert(t, b.Bytes()) +} + +func q256ToF(x *big.Int) float64 { + deno := big.NewInt(1) + deno = deno.Lsh(deno, 256) + rat := new(big.Rat).SetFrac(x, deno) + f, _ := rat.Float64() + return f +} + +func TestElectionLam(t *testing.T) { + //stm: @CHAIN_TYPES_LAMBDA_001 + p := big.NewInt(64) + tot := big.NewInt(128) + lam := lambda(p, tot) + target := 64. * 5. / 128. + if q256ToF(lam) != target { + t.Fatalf("wrong lambda: %f, should be: %f", q256ToF(lam), target) + } +} + +var Res int64 + +func BenchmarkWinCounts(b *testing.B) { + totalPower := NewInt(100) + power := NewInt(100) + ep := &ElectionProof{VRFProof: nil} + var res int64 + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + ep.VRFProof = []byte{byte(i), byte(i >> 8), byte(i >> 16), byte(i >> 24), byte(i >> 32)} + j := ep.ComputeWinCount(power, totalPower) + res += j + } + Res += res +} + +func TestWinCounts(t *testing.T) { + //stm: @TYPES_ELECTION_PROOF_COMPUTE_WIN_COUNT_001 + totalPower := NewInt(100) + power := NewInt(20) + + count := uint64(1000000) + total := uint64(0) + ep := &ElectionProof{VRFProof: make([]byte, 5)} + for i := uint64(0); i < count; i++ { + w := i + count + ep.VRFProof[0] = byte(w) + ep.VRFProof[1] = byte(w >> 8) + ep.VRFProof[2] = byte(w >> 16) + ep.VRFProof[3] = byte(w >> 24) + ep.VRFProof[4] = byte(w >> 32) + + total += uint64(ep.ComputeWinCount(power, totalPower)) + } + // We have 1/5 of the power, so we expect to win 1 block per epoch on average. Plus or minus + // 1%. + avgWins := float64(total) / float64(count) + assert.GreaterOrEqual(t, avgWins, 1.0-0.01) + assert.LessOrEqual(t, avgWins, 1.0+0.01) +} diff --git a/chain/types/ethtypes/eth_transactions.go b/chain/types/ethtypes/eth_transactions.go new file mode 100644 index 000000000..6c13c5bf6 --- /dev/null +++ b/chain/types/ethtypes/eth_transactions.go @@ -0,0 +1,656 @@ +package ethtypes + +import ( + "bytes" + "encoding/binary" + "fmt" + mathbig "math/big" + + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/crypto/sha3" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + gocrypto "github.com/filecoin-project/go-crypto" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + builtintypes "github.com/filecoin-project/go-state-types/builtin" + typescrypto "github.com/filecoin-project/go-state-types/crypto" + + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/types" +) + +const Eip1559TxType = 2 + +type EthTx struct { + ChainID EthUint64 `json:"chainId"` + Nonce EthUint64 `json:"nonce"` + Hash EthHash `json:"hash"` + BlockHash *EthHash `json:"blockHash"` + BlockNumber *EthUint64 `json:"blockNumber"` + TransactionIndex *EthUint64 `json:"transactionIndex"` + From EthAddress `json:"from"` + To *EthAddress `json:"to"` + Value EthBigInt `json:"value"` + Type EthUint64 `json:"type"` + Input EthBytes `json:"input"` + Gas EthUint64 `json:"gas"` + MaxFeePerGas EthBigInt `json:"maxFeePerGas"` + MaxPriorityFeePerGas EthBigInt `json:"maxPriorityFeePerGas"` + AccessList []EthHash `json:"accessList"` + V EthBigInt `json:"v"` + R EthBigInt `json:"r"` + S EthBigInt `json:"s"` +} + +type EthTxArgs struct { + ChainID int `json:"chainId"` + Nonce int `json:"nonce"` + To *EthAddress `json:"to"` + Value big.Int `json:"value"` + MaxFeePerGas big.Int `json:"maxFeePerGas"` + MaxPriorityFeePerGas big.Int `json:"maxPriorityFeePerGas"` + GasLimit int `json:"gasLimit"` + Input []byte `json:"input"` + V big.Int `json:"v"` + R big.Int `json:"r"` + S big.Int `json:"s"` +} + +// EthTxFromSignedEthMessage does NOT populate: +// - BlockHash +// - BlockNumber +// - TransactionIndex +// - From +// - Hash +func EthTxFromSignedEthMessage(smsg *types.SignedMessage) (EthTx, error) { + if smsg.Signature.Type != typescrypto.SigTypeDelegated { + return EthTx{}, xerrors.Errorf("signature is not delegated type, is type: %d", smsg.Signature.Type) + } + + txArgs, err := EthTxArgsFromUnsignedEthMessage(&smsg.Message) + if err != nil { + return EthTx{}, xerrors.Errorf("failed to convert the unsigned message: %w", err) + } + + r, s, v, err := RecoverSignature(smsg.Signature) + if err != nil { + return EthTx{}, xerrors.Errorf("failed to recover signature: %w", err) + } + + return EthTx{ + Nonce: EthUint64(txArgs.Nonce), + ChainID: EthUint64(txArgs.ChainID), + To: txArgs.To, + Value: EthBigInt(txArgs.Value), + Type: Eip1559TxType, + Gas: EthUint64(txArgs.GasLimit), + MaxFeePerGas: EthBigInt(txArgs.MaxFeePerGas), + MaxPriorityFeePerGas: EthBigInt(txArgs.MaxPriorityFeePerGas), + AccessList: []EthHash{}, + V: v, + R: r, + S: s, + Input: txArgs.Input, + }, nil +} + +func EthTxArgsFromUnsignedEthMessage(msg *types.Message) (EthTxArgs, error) { + var ( + to *EthAddress + params []byte + err error + ) + + if msg.Version != 0 { + return EthTxArgs{}, xerrors.Errorf("unsupported msg version: %d", msg.Version) + } + + if len(msg.Params) > 0 { + paramsReader := bytes.NewReader(msg.Params) + params, err = cbg.ReadByteArray(paramsReader, uint64(len(msg.Params))) + if err != nil { + return EthTxArgs{}, xerrors.Errorf("failed to read params byte array: %w", err) + } + if paramsReader.Len() != 0 { + return EthTxArgs{}, xerrors.Errorf("extra data found in params") + } + if len(params) == 0 { + return EthTxArgs{}, xerrors.Errorf("non-empty params encode empty byte array") + } + } + + if msg.To == builtintypes.EthereumAddressManagerActorAddr { + if msg.Method != builtintypes.MethodsEAM.CreateExternal { + return EthTxArgs{}, fmt.Errorf("unsupported EAM method") + } + } else if msg.Method == builtintypes.MethodsEVM.InvokeContract { + addr, err := EthAddressFromFilecoinAddress(msg.To) + if err != nil { + return EthTxArgs{}, err + } + to = &addr + } else { + return EthTxArgs{}, + xerrors.Errorf("invalid methodnum %d: only allowed method is InvokeContract(%d)", + msg.Method, builtintypes.MethodsEVM.InvokeContract) + } + + return EthTxArgs{ + ChainID: build.Eip155ChainId, + Nonce: int(msg.Nonce), + To: to, + Value: msg.Value, + Input: params, + MaxFeePerGas: msg.GasFeeCap, + MaxPriorityFeePerGas: msg.GasPremium, + GasLimit: int(msg.GasLimit), + }, nil +} + +func (tx *EthTxArgs) ToUnsignedMessage(from address.Address) (*types.Message, error) { + if tx.ChainID != build.Eip155ChainId { + return nil, xerrors.Errorf("unsupported chain id: %d", tx.ChainID) + } + + var err error + var params []byte + if len(tx.Input) > 0 { + buf := new(bytes.Buffer) + if err = cbg.WriteByteArray(buf, tx.Input); err != nil { + return nil, xerrors.Errorf("failed to write input args: %w", err) + } + params = buf.Bytes() + } + + var to address.Address + var method abi.MethodNum + // nil indicates the EAM, only CreateExternal is allowed + if tx.To == nil { + method = builtintypes.MethodsEAM.CreateExternal + to = builtintypes.EthereumAddressManagerActorAddr + } else { + method = builtintypes.MethodsEVM.InvokeContract + to, err = tx.To.ToFilecoinAddress() + if err != nil { + return nil, xerrors.Errorf("failed to convert To into filecoin addr: %w", err) + } + } + + return &types.Message{ + Version: 0, + To: to, + From: from, + Nonce: uint64(tx.Nonce), + Value: tx.Value, + GasLimit: int64(tx.GasLimit), + GasFeeCap: tx.MaxFeePerGas, + GasPremium: tx.MaxPriorityFeePerGas, + Method: method, + Params: params, + }, nil +} + +func (tx *EthTxArgs) ToSignedMessage() (*types.SignedMessage, error) { + from, err := tx.Sender() + if err != nil { + return nil, xerrors.Errorf("failed to calculate sender: %w", err) + } + + unsignedMsg, err := tx.ToUnsignedMessage(from) + if err != nil { + return nil, xerrors.Errorf("failed to convert to unsigned msg: %w", err) + } + + siggy, err := tx.Signature() + if err != nil { + return nil, xerrors.Errorf("failed to calculate signature: %w", err) + } + + return &types.SignedMessage{ + Message: *unsignedMsg, + Signature: *siggy, + }, nil +} + +func (tx *EthTxArgs) HashedOriginalRlpMsg() ([]byte, error) { + msg, err := tx.ToRlpUnsignedMsg() + if err != nil { + return nil, err + } + + hasher := sha3.NewLegacyKeccak256() + hasher.Write(msg) + hash := hasher.Sum(nil) + return hash, nil +} + +func (tx *EthTxArgs) ToRlpUnsignedMsg() ([]byte, error) { + packed, err := tx.packTxFields() + if err != nil { + return nil, err + } + + encoded, err := EncodeRLP(packed) + if err != nil { + return nil, err + } + return append([]byte{0x02}, encoded...), nil +} + +func (tx *EthTx) ToEthTxArgs() EthTxArgs { + return EthTxArgs{ + ChainID: int(tx.ChainID), + Nonce: int(tx.Nonce), + To: tx.To, + Value: big.Int(tx.Value), + MaxFeePerGas: big.Int(tx.MaxFeePerGas), + MaxPriorityFeePerGas: big.Int(tx.MaxPriorityFeePerGas), + GasLimit: int(tx.Gas), + Input: tx.Input, + V: big.Int(tx.V), + R: big.Int(tx.R), + S: big.Int(tx.S), + } +} + +func (tx *EthTx) TxHash() (EthHash, error) { + ethTxArgs := tx.ToEthTxArgs() + return (ðTxArgs).TxHash() +} + +func (tx *EthTxArgs) TxHash() (EthHash, error) { + rlp, err := tx.ToRlpSignedMsg() + if err != nil { + return EmptyEthHash, err + } + + return EthHashFromTxBytes(rlp), nil +} + +func (tx *EthTxArgs) ToRlpSignedMsg() ([]byte, error) { + packed1, err := tx.packTxFields() + if err != nil { + return nil, err + } + + packed2, err := tx.packSigFields() + if err != nil { + return nil, err + } + + encoded, err := EncodeRLP(append(packed1, packed2...)) + if err != nil { + return nil, err + } + return append([]byte{0x02}, encoded...), nil +} + +func (tx *EthTxArgs) packTxFields() ([]interface{}, error) { + chainId, err := formatInt(tx.ChainID) + if err != nil { + return nil, err + } + + nonce, err := formatInt(tx.Nonce) + if err != nil { + return nil, err + } + + maxPriorityFeePerGas, err := formatBigInt(tx.MaxPriorityFeePerGas) + if err != nil { + return nil, err + } + + maxFeePerGas, err := formatBigInt(tx.MaxFeePerGas) + if err != nil { + return nil, err + } + + gasLimit, err := formatInt(tx.GasLimit) + if err != nil { + return nil, err + } + + value, err := formatBigInt(tx.Value) + if err != nil { + return nil, err + } + + res := []interface{}{ + chainId, + nonce, + maxPriorityFeePerGas, + maxFeePerGas, + gasLimit, + formatEthAddr(tx.To), + value, + tx.Input, + []interface{}{}, // access list + } + return res, nil +} + +func (tx *EthTxArgs) packSigFields() ([]interface{}, error) { + r, err := formatBigInt(tx.R) + if err != nil { + return nil, err + } + + s, err := formatBigInt(tx.S) + if err != nil { + return nil, err + } + + v, err := formatBigInt(tx.V) + if err != nil { + return nil, err + } + + res := []interface{}{v, r, s} + return res, nil +} + +func (tx *EthTxArgs) Signature() (*typescrypto.Signature, error) { + r := tx.R.Int.Bytes() + s := tx.S.Int.Bytes() + v := tx.V.Int.Bytes() + + sig := append([]byte{}, padLeadingZeros(r, 32)...) + sig = append(sig, padLeadingZeros(s, 32)...) + if len(v) == 0 { + sig = append(sig, 0) + } else { + sig = append(sig, v[0]) + } + + if len(sig) != 65 { + return nil, fmt.Errorf("signature is not 65 bytes") + } + return &typescrypto.Signature{ + Type: typescrypto.SigTypeDelegated, Data: sig, + }, nil +} + +func (tx *EthTxArgs) Sender() (address.Address, error) { + msg, err := tx.ToRlpUnsignedMsg() + if err != nil { + return address.Undef, err + } + + hasher := sha3.NewLegacyKeccak256() + hasher.Write(msg) + hash := hasher.Sum(nil) + + sig, err := tx.Signature() + if err != nil { + return address.Undef, err + } + + pubk, err := gocrypto.EcRecover(hash, sig.Data) + if err != nil { + return address.Undef, err + } + + ethAddr, err := EthAddressFromPubKey(pubk) + if err != nil { + return address.Undef, err + } + + ea, err := CastEthAddress(ethAddr) + if err != nil { + return address.Undef, err + } + + return ea.ToFilecoinAddress() +} + +func RecoverSignature(sig typescrypto.Signature) (r, s, v EthBigInt, err error) { + if sig.Type != typescrypto.SigTypeDelegated { + return EthBigIntZero, EthBigIntZero, EthBigIntZero, fmt.Errorf("RecoverSignature only supports Delegated signature") + } + + if len(sig.Data) != 65 { + return EthBigIntZero, EthBigIntZero, EthBigIntZero, fmt.Errorf("signature should be 65 bytes long, but got %d bytes", len(sig.Data)) + } + + r_, err := parseBigInt(sig.Data[0:32]) + if err != nil { + return EthBigIntZero, EthBigIntZero, EthBigIntZero, fmt.Errorf("cannot parse r into EthBigInt") + } + + s_, err := parseBigInt(sig.Data[32:64]) + if err != nil { + return EthBigIntZero, EthBigIntZero, EthBigIntZero, fmt.Errorf("cannot parse s into EthBigInt") + } + + v_, err := parseBigInt([]byte{sig.Data[64]}) + if err != nil { + return EthBigIntZero, EthBigIntZero, EthBigIntZero, fmt.Errorf("cannot parse v into EthBigInt") + } + + return EthBigInt(r_), EthBigInt(s_), EthBigInt(v_), nil +} + +func parseEip1559Tx(data []byte) (*EthTxArgs, error) { + if data[0] != 2 { + return nil, fmt.Errorf("not an EIP-1559 transaction: first byte is not 2") + } + + d, err := DecodeRLP(data[1:]) + if err != nil { + return nil, err + } + decoded, ok := d.([]interface{}) + if !ok { + return nil, fmt.Errorf("not an EIP-1559 transaction: decoded data is not a list") + } + + if len(decoded) != 12 { + return nil, fmt.Errorf("not an EIP-1559 transaction: should have 12 elements in the rlp list") + } + + chainId, err := parseInt(decoded[0]) + if err != nil { + return nil, err + } + + nonce, err := parseInt(decoded[1]) + if err != nil { + return nil, err + } + + maxPriorityFeePerGas, err := parseBigInt(decoded[2]) + if err != nil { + return nil, err + } + + maxFeePerGas, err := parseBigInt(decoded[3]) + if err != nil { + return nil, err + } + + gasLimit, err := parseInt(decoded[4]) + if err != nil { + return nil, err + } + + to, err := parseEthAddr(decoded[5]) + if err != nil { + return nil, err + } + + value, err := parseBigInt(decoded[6]) + if err != nil { + return nil, err + } + + input, err := parseBytes(decoded[7]) + if err != nil { + return nil, err + } + + accessList, ok := decoded[8].([]interface{}) + if !ok || (ok && len(accessList) != 0) { + return nil, fmt.Errorf("access list should be an empty list") + } + + r, err := parseBigInt(decoded[10]) + if err != nil { + return nil, err + } + + s, err := parseBigInt(decoded[11]) + if err != nil { + return nil, err + } + + v, err := parseBigInt(decoded[9]) + if err != nil { + return nil, err + } + + // EIP-1559 and EIP-2930 transactions only support 0 or 1 for v + // Legacy and EIP-155 transactions support other values + // https://github.com/ethers-io/ethers.js/blob/56fabe987bb8c1e4891fdf1e5d3fe8a4c0471751/packages/transactions/src.ts/index.ts#L333 + if !v.Equals(big.NewInt(0)) && !v.Equals(big.NewInt(1)) { + return nil, fmt.Errorf("EIP-1559 transactions only support 0 or 1 for v") + } + + args := EthTxArgs{ + ChainID: chainId, + Nonce: nonce, + To: to, + MaxPriorityFeePerGas: maxPriorityFeePerGas, + MaxFeePerGas: maxFeePerGas, + GasLimit: gasLimit, + Value: value, + Input: input, + V: v, + R: r, + S: s, + } + return &args, nil +} + +func ParseEthTxArgs(data []byte) (*EthTxArgs, error) { + if len(data) == 0 { + return nil, fmt.Errorf("empty data") + } + + if data[0] > 0x7f { + // legacy transaction + return nil, fmt.Errorf("legacy transaction is not supported") + } + + if data[0] == 1 { + // EIP-2930 + return nil, fmt.Errorf("EIP-2930 transaction is not supported") + } + + if data[0] == Eip1559TxType { + // EIP-1559 + return parseEip1559Tx(data) + } + + return nil, fmt.Errorf("unsupported transaction type") +} + +func padLeadingZeros(data []byte, length int) []byte { + if len(data) >= length { + return data + } + zeros := make([]byte, length-len(data)) + return append(zeros, data...) +} + +func removeLeadingZeros(data []byte) []byte { + firstNonZeroIndex := len(data) + for i, b := range data { + if b > 0 { + firstNonZeroIndex = i + break + } + } + return data[firstNonZeroIndex:] +} + +func formatInt(val int) ([]byte, error) { + buf := new(bytes.Buffer) + err := binary.Write(buf, binary.BigEndian, int64(val)) + if err != nil { + return nil, err + } + return removeLeadingZeros(buf.Bytes()), nil +} + +func formatEthAddr(addr *EthAddress) []byte { + if addr == nil { + return nil + } + return addr[:] +} + +func formatBigInt(val big.Int) ([]byte, error) { + b, err := val.Bytes() + if err != nil { + return nil, err + } + return removeLeadingZeros(b), nil +} + +func parseInt(v interface{}) (int, error) { + data, ok := v.([]byte) + if !ok { + return 0, fmt.Errorf("cannot parse interface to int: input is not a byte array") + } + if len(data) == 0 { + return 0, nil + } + if len(data) > 8 { + return 0, fmt.Errorf("cannot parse interface to int: length is more than 8 bytes") + } + var value int64 + r := bytes.NewReader(append(make([]byte, 8-len(data)), data...)) + if err := binary.Read(r, binary.BigEndian, &value); err != nil { + return 0, fmt.Errorf("cannot parse interface to EthUint64: %w", err) + } + return int(value), nil +} + +func parseBigInt(v interface{}) (big.Int, error) { + data, ok := v.([]byte) + if !ok { + return big.Zero(), fmt.Errorf("cannot parse interface to big.Int: input is not a byte array") + } + if len(data) == 0 { + return big.Zero(), nil + } + var b mathbig.Int + b.SetBytes(data) + return big.NewFromGo(&b), nil +} + +func parseBytes(v interface{}) ([]byte, error) { + val, ok := v.([]byte) + if !ok { + return nil, fmt.Errorf("cannot parse interface into bytes: input is not a byte array") + } + return val, nil +} + +func parseEthAddr(v interface{}) (*EthAddress, error) { + b, err := parseBytes(v) + if err != nil { + return nil, err + } + if len(b) == 0 { + return nil, nil + } + addr, err := CastEthAddress(b) + if err != nil { + return nil, err + } + return &addr, nil +} diff --git a/chain/types/ethtypes/eth_transactions_test.go b/chain/types/ethtypes/eth_transactions_test.go new file mode 100644 index 000000000..68abc55dd --- /dev/null +++ b/chain/types/ethtypes/eth_transactions_test.go @@ -0,0 +1,251 @@ +package ethtypes + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + "golang.org/x/crypto/sha3" + + "github.com/filecoin-project/go-address" + gocrypto "github.com/filecoin-project/go-crypto" + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtintypes "github.com/filecoin-project/go-state-types/builtin" + "github.com/filecoin-project/go-state-types/builtin/v10/evm" + init10 "github.com/filecoin-project/go-state-types/builtin/v10/init" + crypto1 "github.com/filecoin-project/go-state-types/crypto" + + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/lib/sigs" + _ "github.com/filecoin-project/lotus/lib/sigs/delegated" +) + +type TxTestcase struct { + TxJSON string + NosigTx string + Input EthBytes + Output EthTxArgs +} + +func TestTxArgs(t *testing.T) { + testcases, err := prepareTxTestcases() + require.Nil(t, err) + require.NotEmpty(t, testcases) + + for i, tc := range testcases { + comment := fmt.Sprintf("case %d: \n%s\n%s", i, tc.TxJSON, hex.EncodeToString(tc.Input)) + + // parse txargs + txArgs, err := ParseEthTxArgs(tc.Input) + require.NoError(t, err, comment) + + msgRecovered, err := txArgs.ToRlpUnsignedMsg() + require.NoError(t, err, comment) + require.Equal(t, tc.NosigTx, "0x"+hex.EncodeToString(msgRecovered), comment) + + // verify signatures + from, err := txArgs.Sender() + require.NoError(t, err, comment) + + smsg, err := txArgs.ToSignedMessage() + require.NoError(t, err, comment) + + err = sigs.Verify(&smsg.Signature, from, msgRecovered) + require.NoError(t, err, comment) + + // verify data + require.Equal(t, tc.Output.ChainID, txArgs.ChainID, comment) + require.Equal(t, tc.Output.Nonce, txArgs.Nonce, comment) + require.Equal(t, tc.Output.To, txArgs.To, comment) + } +} + +func TestSignatures(t *testing.T) { + testcases := []struct { + RawTx string + ExpectedR string + ExpectedS string + ExpectedV string + ExpectErr bool + }{ + { + "0x02f8598401df5e76028301d69083086a5e835532dd808080c080a0457e33227ac7ceee2ef121755e26b872b6fb04221993f9939349bb7b0a3e1595a02d8ef379e1d2a9e30fa61c92623cc9ed72d80cf6a48cfea341cb916bcc0a81bc", + `"0x457e33227ac7ceee2ef121755e26b872b6fb04221993f9939349bb7b0a3e1595"`, + `"0x2d8ef379e1d2a9e30fa61c92623cc9ed72d80cf6a48cfea341cb916bcc0a81bc"`, + `"0x0"`, + false, + }, + { + "0x02f8598401df5e76038301d69083086a5e835532dd808080c001a012a232866dcb0671eb0ddc01fb9c01d6ef384ec892bb29691ed0d2d293052ddfa052a6ae38c6139930db21a00eee2a4caced9a6500991b823d64ec664d003bc4b1", + `"0x12a232866dcb0671eb0ddc01fb9c01d6ef384ec892bb29691ed0d2d293052ddf"`, + `"0x52a6ae38c6139930db21a00eee2a4caced9a6500991b823d64ec664d003bc4b1"`, + `"0x1"`, + false, + }, + { + "0x00", + `""`, + `""`, + `""`, + true, + }, + } + + for _, tc := range testcases { + tx, err := ParseEthTxArgs(mustDecodeHex(tc.RawTx)) + if tc.ExpectErr { + require.Error(t, err) + continue + } + require.Nil(t, err) + + sig, err := tx.Signature() + require.Nil(t, err) + + r, s, v, err := RecoverSignature(*sig) + require.Nil(t, err) + + marshaledR, err := r.MarshalJSON() + require.Nil(t, err) + + marshaledS, err := s.MarshalJSON() + require.Nil(t, err) + + marshaledV, err := v.MarshalJSON() + require.Nil(t, err) + + require.Equal(t, tc.ExpectedR, string(marshaledR)) + require.Equal(t, tc.ExpectedS, string(marshaledS)) + require.Equal(t, tc.ExpectedV, string(marshaledV)) + } +} + +func TestTransformParams(t *testing.T) { + constructorParams, err := actors.SerializeParams(&evm.ConstructorParams{ + Initcode: mustDecodeHex("0x1122334455"), + }) + require.Nil(t, err) + + evmActorCid, ok := actors.GetActorCodeID(actorstypes.Version10, "reward") + require.True(t, ok) + + params, err := actors.SerializeParams(&init10.ExecParams{ + CodeCID: evmActorCid, + ConstructorParams: constructorParams, + }) + require.Nil(t, err) + + var exec init10.ExecParams + reader := bytes.NewReader(params) + err1 := exec.UnmarshalCBOR(reader) + require.Nil(t, err1) + + var evmParams evm.ConstructorParams + reader1 := bytes.NewReader(exec.ConstructorParams) + err1 = evmParams.UnmarshalCBOR(reader1) + require.Nil(t, err1) + + require.Equal(t, mustDecodeHex("0x1122334455"), evmParams.Initcode) +} + +func TestEcRecover(t *testing.T) { + rHex := "0x479ff7fa64cf8bf641eb81635d1e8a698530d2f219951d234539e6d074819529" + sHex := "0x4b6146d27be50cdbb2853ba9a42f207af8d730272f1ebe9c9a78aeef1d6aa924" + fromHex := "0x3947D223fc5415f43ea099866AB62B1d4D33814D" + v := byte(0) + + msgHex := "0x02f1030185012a05f2008504a817c800825208942b87d1cb599bc2a606db9a0169fcec96af04ad3a880de0b6b3a764000080c0" + pubKeyHex := "0x048362749392a0e192eff600d21155236c5a0648d300a8e0e44d8617712c7c96384c75825dc5c7595df2a5005fd8a0f7c809119fb9ab36403ed712244fc329348e" + + msg := mustDecodeHex(msgHex) + pubKey := mustDecodeHex(pubKeyHex) + r := mustDecodeHex(rHex) + s := mustDecodeHex(sHex) + from := mustDecodeHex(fromHex) + + sig := append(r, s...) + sig = append(sig, v) + require.Equal(t, 65, len(sig)) + + sha := sha3.NewLegacyKeccak256() + sha.Write(msg) + h := sha.Sum(nil) + + pubk, err := gocrypto.EcRecover(h, sig) + require.Nil(t, err) + require.Equal(t, pubKey, pubk) + + sha.Reset() + sha.Write(pubk[1:]) + h = sha.Sum(nil) + h = h[len(h)-20:] + + require.Equal(t, from, h) +} + +func TestDelegatedSigner(t *testing.T) { + rHex := "0xcf1fa52fae9154ba21d67aeca9b42adfe186eb9e426c441051a8473efd190848" + sHex := "0x0e6c8c79ffaf35fb8f136c8cf6c5656f1f3befad21f2644321aa6dba58d68737" + v := byte(0) + + msgHex := "0x02f08401df5e76038502540be400843b9aca008398968094ff000000000000000000000000000000000003f2832dc6c080c0" + pubKeyHex := "0x04cfecc0520d906cbfea387759246e89d85e2998843e56ad1c41de247ce10b3e4c453aa73c8de13c178d94461b6fa3f8b6f74406ce43d2fbab6992d0b283394242" + + msg := mustDecodeHex(msgHex) + pubk := mustDecodeHex(pubKeyHex) + r := mustDecodeHex(rHex) + s := mustDecodeHex(sHex) + + addrHash, err := EthAddressFromPubKey(pubk) + require.NoError(t, err) + + from, err := address.NewDelegatedAddress(builtintypes.EthereumAddressManagerActorID, addrHash) + require.NoError(t, err) + + sig := append(r, s...) + sig = append(sig, v) + require.Equal(t, 65, len(sig)) + + signature := &crypto1.Signature{ + Type: crypto1.SigTypeDelegated, + Data: sig, + } + + err = sigs.Verify(signature, from, msg) + require.NoError(t, err) +} + +func prepareTxTestcases() ([]TxTestcase, error) { + tcstr := `[{"input":"0x02f86282013a8080808094ff000000000000000000000000000000000003ec8080c080a0f411a73e33523b40c1a916e79e67746bd01a4a4fb4ecfa87b441375a215ddfb4a0551692c1553574fab4c227ca70cb1c121dc3a2ef82179a9c984bd7acc0880a38","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02df82013a8080808094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86382013a81c880808094ff000000000000000000000000000000000003ec8080c001a0ed75a56e365c88479bf3f60251a2dd47ae181f1a3d95724581a3f648487b4396a046628bb9734edf4b4c455f2bbd351e43c466f315272cd1927f2c55d9b52e058b","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e082013a81c880808094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86282013a8080808094ff000000000000000000000000000000000003ec8080c080a0f411a73e33523b40c1a916e79e67746bd01a4a4fb4ecfa87b441375a215ddfb4a0551692c1553574fab4c227ca70cb1c121dc3a2ef82179a9c984bd7acc0880a38","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02df82013a8080808094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86382013a81c880808094ff000000000000000000000000000000000003ec8080c001a0ed75a56e365c88479bf3f60251a2dd47ae181f1a3d95724581a3f648487b4396a046628bb9734edf4b4c455f2bbd351e43c466f315272cd1927f2c55d9b52e058b","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e082013a81c880808094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f88682013a8080808094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0706d871013403cf8b965dfa7f2be5a4d185d746da45b21d5a67c667c26d255d6a02e68a14f386aa325ce8e82d30405107d53103d038cf20e40af961ef3a3963608","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84382013a8080808094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88782013a81c880808094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0df137d0a6733354b2f2419a4ea5fe77d333deca28b2fe091d76190b51c2bae73a0232cbf9c29b8840cbf104ff77360fbf3ca4acda29b5e230636e19ac253ad92de","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84482013a81c880808094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86482013a808082ea608094ff000000000000000000000000000000000003ec8080c001a03a2880cc65e88d5320067f502a0ffda72111d01f0ebeeea9fbeb812e457aa0f9a020c08483b104dbfbbbffffedc3acdbe8245ca6daf97c0dbab843d747e587d625","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a808082ea608094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86582013a81c88082ea608094ff000000000000000000000000000000000003ec8080c001a03427daf1639de6bf1b948abeab765b0a6a9170cc6a16d263c71c859f78916b03a01bbbb824b9953b5eb9f3098b4358a7ebb78f3358866eed997de66350ae4c9475","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c88082ea608094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86482013a808082ea608094ff000000000000000000000000000000000003ec8080c001a03a2880cc65e88d5320067f502a0ffda72111d01f0ebeeea9fbeb812e457aa0f9a020c08483b104dbfbbbffffedc3acdbe8245ca6daf97c0dbab843d747e587d625","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a808082ea608094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86582013a81c88082ea608094ff000000000000000000000000000000000003ec8080c001a03427daf1639de6bf1b948abeab765b0a6a9170cc6a16d263c71c859f78916b03a01bbbb824b9953b5eb9f3098b4358a7ebb78f3358866eed997de66350ae4c9475","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c88082ea608094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f88882013a808082ea608094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0b9ebc36653a4800816f71ceacf93a1ee601a136916a3476ea9073a9a55ff026aa0647665249b12e8d1d1773b91844588ed70f65c91bc088ccb259ec0f0a24330d5","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84582013a808082ea608094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88982013a81c88082ea608094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0122dd8468dbd34e111e1a5ea1997199be633aa3bc9c1a7ee27dc3a8eda39c29da07cb99cd28ac67f55e507a8b8ef5b931c56cacf79273a4a2969a004a4b4a2864a","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84682013a81c88082ea608094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86482013a8082ea60808094ff000000000000000000000000000000000003ec8080c080a0c1d020df63cb6db76e3a27a60ba0500a3cdd30f9f47b08733009dc8d610ea29ba05cbafb4c223417526ded0b02b8eb66a73535386d0e62da0e20f3641b532aa406","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a8082ea60808094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86582013a81c882ea60808094ff000000000000000000000000000000000003ec8080c080a090e30d32c6cd3f1ba2109b6a9f1c9fffc50b96a934192edf98adc086299e410ba057db0c136436de2e907942bdaad8e0113cf576f250b336ab652ef094c260dae6","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c882ea60808094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86482013a8082ea60808094ff000000000000000000000000000000000003ec8080c080a0c1d020df63cb6db76e3a27a60ba0500a3cdd30f9f47b08733009dc8d610ea29ba05cbafb4c223417526ded0b02b8eb66a73535386d0e62da0e20f3641b532aa406","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a8082ea60808094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86582013a81c882ea60808094ff000000000000000000000000000000000003ec8080c080a090e30d32c6cd3f1ba2109b6a9f1c9fffc50b96a934192edf98adc086299e410ba057db0c136436de2e907942bdaad8e0113cf576f250b336ab652ef094c260dae6","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c882ea60808094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f88882013a8082ea60808094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a016e3f30a612fc802bb64b765325ecf78f2769b879a9acf62f07669f9723335d6a0781bb3444a73819f28233f1eebf8c3a4de288842fd73c2e05a7a7b0c288d5b25","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84582013a8082ea60808094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88982013a81c882ea60808094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0b652a447bdcdd1906ed86406ee543ee06023e4f762784c1d3aaf4c3bd85c6a17a0368ae9995e15258f14b74f937e97140a659d052d341674be0c24452257b56b30","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84682013a81c882ea60808094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86682013a8082ea6082ea608094ff000000000000000000000000000000000003ec8080c001a0b1411f337b69609a256c0e76c57ccf4af87e977c98fd2a889f29281bf623cab4a049bec0fb4773aed870bae9c1cdf1ee398c498f0b436dcd19cae588b4ecd8bdf2","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a8082ea6082ea608094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86782013a81c882ea6082ea608094ff000000000000000000000000000000000003ec8080c080a00b845fec9c96bf593c3501753764e14867d3f5d4bd02051e49329b6810d6513ea070d046e5b38c18c542594b328f02345a8f34ab05fd00db33974f914f7ae31c63","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c882ea6082ea608094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86682013a8082ea6082ea608094ff000000000000000000000000000000000003ec8080c001a0b1411f337b69609a256c0e76c57ccf4af87e977c98fd2a889f29281bf623cab4a049bec0fb4773aed870bae9c1cdf1ee398c498f0b436dcd19cae588b4ecd8bdf2","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a8082ea6082ea608094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86782013a81c882ea6082ea608094ff000000000000000000000000000000000003ec8080c080a00b845fec9c96bf593c3501753764e14867d3f5d4bd02051e49329b6810d6513ea070d046e5b38c18c542594b328f02345a8f34ab05fd00db33974f914f7ae31c63","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c882ea6082ea608094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f88a82013a8082ea6082ea608094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a02d8215d8408d2f4b83a2e68f4aad6fe5dee97d7ef6a43b02ec413ead2215ac80a0641a43cebd6905e3e324c0dd06585d5ffc9b971b519045999c48e31db7aa7f9d","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84782013a8082ea6082ea608094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88a82013a81c882ea6082ea608094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0da68784e191ce0806527d389f84b5d15bed3908e1c2cc0d8f0cea7a29eb0dba39f231a0b438b7d0f0f57292c68dc174d4ee6df7add933ab4e0b3789f597a7d3b","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84882013a81c882ea6082ea608094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86482013a80808082ea6094ff000000000000000000000000000000000003ec8080c080a04c97162e2d2ab508116a23c522fd816ecd9cb091d4c288afe45c37ee3a8dde34a06ebf67ff15b74d65c276340aaebde8e6ebb8da0d3bbab43deffac8eb1e6a0630","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a80808082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86582013a81c8808082ea6094ff000000000000000000000000000000000003ec8080c080a0d503d409e667c2876ab9e420854cecce4c0092985855234be07f270bfcf3ed4aa07a40deecc8a4448d4dc0e2014b4b23ac5721409c62bffa05aee6938d8447f72d","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c8808082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86482013a80808082ea6094ff000000000000000000000000000000000003ec8080c080a04c97162e2d2ab508116a23c522fd816ecd9cb091d4c288afe45c37ee3a8dde34a06ebf67ff15b74d65c276340aaebde8e6ebb8da0d3bbab43deffac8eb1e6a0630","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a80808082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86582013a81c8808082ea6094ff000000000000000000000000000000000003ec8080c080a0d503d409e667c2876ab9e420854cecce4c0092985855234be07f270bfcf3ed4aa07a40deecc8a4448d4dc0e2014b4b23ac5721409c62bffa05aee6938d8447f72d","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c8808082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f88882013a80808082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a059aecc1d365ee0dc56a577d162f04c0912a5c5b62f889cff1acc706ac17a4489a017209b3ec43a10a40c5863a2b7a1ee823380ad42697a5f7d5f537c230583a4c7","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84582013a80808082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88982013a81c8808082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0dc1eb40f93e311f3f9a94d8a695db2bbb38973ce097121875885e4bc54f18152a0075da0bd405bb4f5c69034daaf8f40052b941fae5b9f3b8df218d80fb4d7ea99","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84682013a81c8808082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86682013a808082ea6082ea6094ff000000000000000000000000000000000003ec8080c080a03d392fd5e83c64554907a55204572aaeec6ffab25f2c73655c6a22344fa02a14a03b9ae94b7dc21108db6dda65125ecaff844f8f43f483bed35f32f6d5d530fe9f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a808082ea6082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86782013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec8080c001a0405e8a430ef6ad4c3403150776af08c255b6f6fbe278d194f88517733c816caca0364203b5bca7953dd863d4cf90c0a77b499ef4a3d5831c4fdf33926c31709c4f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86682013a808082ea6082ea6094ff000000000000000000000000000000000003ec8080c080a03d392fd5e83c64554907a55204572aaeec6ffab25f2c73655c6a22344fa02a14a03b9ae94b7dc21108db6dda65125ecaff844f8f43f483bed35f32f6d5d530fe9f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a808082ea6082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86782013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec8080c001a0405e8a430ef6ad4c3403150776af08c255b6f6fbe278d194f88517733c816caca0364203b5bca7953dd863d4cf90c0a77b499ef4a3d5831c4fdf33926c31709c4f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f88a82013a808082ea6082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a083cf6701aee00872946b6550c059f028f72e3052acb8cc9c25b830ace860e046a03fd969d73e995d43896659f94d3956a17da18451050349e7db6f7881f8c057d3","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84782013a808082ea6082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88b82013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0c5a545f2d94e719068d9a43b01879bcb46b56e236dd378dd26ef3b8e4ec8314aa04024b9936960b9b156405e4f3e0b6562518df8778324a927381e380b23f47fb8","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84882013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86682013a8082ea608082ea6094ff000000000000000000000000000000000003ec8080c080a0aa406ec7f4901a1777e44b975ff41603b9d46257efdc1ca904a3e7890f2b020ea03bda5c785182cfa2d9f9b7a54f194cd08b9d0f913069a4514ff21e8fa0ef3850","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a8082ea608082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86782013a81c882ea608082ea6094ff000000000000000000000000000000000003ec8080c080a089fc465c24b4bad898cf900f585eddab6d40189e8d19746da76597f86fbadf51a005732ffa2ebac36646afab9105540b543f74a5c91b441834a2b1930815c2ccc8","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c882ea608082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86682013a8082ea608082ea6094ff000000000000000000000000000000000003ec8080c080a0aa406ec7f4901a1777e44b975ff41603b9d46257efdc1ca904a3e7890f2b020ea03bda5c785182cfa2d9f9b7a54f194cd08b9d0f913069a4514ff21e8fa0ef3850","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a8082ea608082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86782013a81c882ea608082ea6094ff000000000000000000000000000000000003ec8080c080a089fc465c24b4bad898cf900f585eddab6d40189e8d19746da76597f86fbadf51a005732ffa2ebac36646afab9105540b543f74a5c91b441834a2b1930815c2ccc8","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c882ea608082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f88a82013a8082ea608082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a09d9a8ee802486b826348a76346987b3e7331d70ef0c0257ff976ceebef1141a2a07d97d14ed877c16bd932f08a67c374e773ee3337d512ff8241c8d78566a04d46","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84782013a8082ea608082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88b82013a81c882ea608082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a024ad1ec1578f51beb2b574507bda7691a486cdbc9c22add01ad4c1f686beb567a048445e0fe8945b8052e5e87139690c0615a11c52503b226cf23610c999eada40","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84882013a81c882ea608082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86882013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec8080c080a06b382fcbe48de85615ff6e2dcc0c84021beb4abc527878accd36c9c77af84ba8a06a07d34a6896b270538525cb14b0856ceb442714fa85e4c9ee36dedf638935f9","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e582013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86982013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec8080c080a0ba2586cfb3323fd0f9d7bb38bf9948758a52f156bda66f7100b789760894ad89a01e4bd2ff4eff2c391915141250313ab845401d5e2f71c23691d20a0b3c68cbd9","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e682013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86882013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec8080c080a06b382fcbe48de85615ff6e2dcc0c84021beb4abc527878accd36c9c77af84ba8a06a07d34a6896b270538525cb14b0856ceb442714fa85e4c9ee36dedf638935f9","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e582013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f86982013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec8080c080a0ba2586cfb3323fd0f9d7bb38bf9948758a52f156bda66f7100b789760894ad89a01e4bd2ff4eff2c391915141250313ab845401d5e2f71c23691d20a0b3c68cbd9","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e682013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec8080c0"},{"input":"0x02f88c82013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0f36ff02ab3e90d2de77cdb24423dc39ca5c959429db62cb5c9ed4f0c9e04703aa0476bf841b0602af44039801d4e68648971f63fc2152002b127be6d914d4fc5ca","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84982013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88d82013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a08267ae8838a8a5d9c2a761c182b5759184b7672b761278d499c1514fb6e8a495a023aa268f67da7728767e114fdec4d141bf649e0ad931117b5b325834dbf72803","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"0\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84a82013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec80a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86282013a8080808094ff000000000000000000000000000000000003ec6480c080a011ec4af7fc663080460b70ae8829f47e9cfa1814c616750d359459cbbba55563a0446e4ec9ea504d13dcbef44238e442caad366dbae1ae9408d39c6d902a5577b0","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02df82013a8080808094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86382013a81c880808094ff000000000000000000000000000000000003ec6480c001a0b80bc30bef46b3f824d1460685db875ff070f7798c3148c1fc49c01d6acc550ca0437efe7721563800e6a56ac54877a72c7860cd5e17ef4675afe989822ae87759","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e082013a81c880808094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86282013a8080808094ff000000000000000000000000000000000003ec6480c080a011ec4af7fc663080460b70ae8829f47e9cfa1814c616750d359459cbbba55563a0446e4ec9ea504d13dcbef44238e442caad366dbae1ae9408d39c6d902a5577b0","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02df82013a8080808094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86382013a81c880808094ff000000000000000000000000000000000003ec6480c001a0b80bc30bef46b3f824d1460685db875ff070f7798c3148c1fc49c01d6acc550ca0437efe7721563800e6a56ac54877a72c7860cd5e17ef4675afe989822ae87759","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e082013a81c880808094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f88682013a8080808094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a06ab9d5988105d28dd090e509c8caabaa7773fc08ec5ef3dfeae532e01938ff69a078bca296df26dd2497a49110e138a49a67a6e232a35524b041d04a10fc583651","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84382013a8080808094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88782013a81c880808094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a031d51b866a02a9966250d312ed6cb4e083f9131ad8f6bb5814074375093d7536a03f8f819c4011dd54348930b6f98f365de8060b487ada38a62a5617aab6cc6e09","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84482013a81c880808094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86482013a808082ea608094ff000000000000000000000000000000000003ec6480c001a05bda5ad44c8f9a7516226488cf2d4f53188b40352f35ea7cece8076acda26dbba015373b3b78c88b74c7cca32fd02696a248bb9bea22a09c7a4a17b9e3b629b896","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a808082ea608094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86582013a81c88082ea608094ff000000000000000000000000000000000003ec6480c080a00d92624cc3335c903077e318204929b4a8c9cd96d94690b0191f8a3bb24e937aa02f1d0315ececf46900154791a732eb8fee9efd0dc998a4e6b892d07ad657a815","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c88082ea608094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86482013a808082ea608094ff000000000000000000000000000000000003ec6480c001a05bda5ad44c8f9a7516226488cf2d4f53188b40352f35ea7cece8076acda26dbba015373b3b78c88b74c7cca32fd02696a248bb9bea22a09c7a4a17b9e3b629b896","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a808082ea608094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86582013a81c88082ea608094ff000000000000000000000000000000000003ec6480c080a00d92624cc3335c903077e318204929b4a8c9cd96d94690b0191f8a3bb24e937aa02f1d0315ececf46900154791a732eb8fee9efd0dc998a4e6b892d07ad657a815","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c88082ea608094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f88882013a808082ea608094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0def168136c0532ec148a9e200e3cc1b22f90c7bbc5d9ef25ac0c5d342e8f3784a022f94642dfc81ba321b3e09879888332fa7c25b623bead7686e3e493c0911b55","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84582013a808082ea608094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88982013a81c88082ea608094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0626f43b80260f84cde2c67538c5cfbd328ce85b0f934e8568769e51709b100a7a0283fff5dbfde72b72e2b74c464b1add985d72750be3f4e16ae8ffb4747a40ff2","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84682013a81c88082ea608094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86482013a8082ea60808094ff000000000000000000000000000000000003ec6480c080a051b109080002dab4aae47139eb92ddea8951ef5ac6dfc3d7fa07621047dbc680a0334aa47a2888a6cc52b8cf3c3635192b66c692416e954822c1c93c3896ff1ead","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a8082ea60808094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86582013a81c882ea60808094ff000000000000000000000000000000000003ec6480c080a009e179e3bad2da6fb5e205e52fd8d1c462007162aabde5a4d6b052dd4fc4f23ca063922c31438835adf2e4424e2e7d5d2702ec65de2e24a72b491ff0004a53865d","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c882ea60808094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86482013a8082ea60808094ff000000000000000000000000000000000003ec6480c080a051b109080002dab4aae47139eb92ddea8951ef5ac6dfc3d7fa07621047dbc680a0334aa47a2888a6cc52b8cf3c3635192b66c692416e954822c1c93c3896ff1ead","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a8082ea60808094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86582013a81c882ea60808094ff000000000000000000000000000000000003ec6480c080a009e179e3bad2da6fb5e205e52fd8d1c462007162aabde5a4d6b052dd4fc4f23ca063922c31438835adf2e4424e2e7d5d2702ec65de2e24a72b491ff0004a53865d","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c882ea60808094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f88882013a8082ea60808094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0d3bfebc6597304c6a06491f68d2ac149fc233d28e81af48dd5b1f83e6ff951d2a06668da06d86aba341971dabb58016ca7764cd4b4c1634e3f829dcc8ef8bca4f6","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84582013a8082ea60808094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88982013a81c882ea60808094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0d45b9fd9a2a3fdf79805cf73b70348037cc69927209a5e3728fe62cbe9543f03a02f5f8477666487ee5148a65ce59f400beac7c208369162b2d555411314d358fb","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84682013a81c882ea60808094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86682013a8082ea6082ea608094ff000000000000000000000000000000000003ec6480c001a02a6a910f7b5f83fda937006021b9c074f4544d5bb37b9b5a1b7045095f461836a038572b25418528bce7e6a3a480cf9fc90a33d9c63b392c2dbc8faf72a1e4ab8f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a8082ea6082ea608094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86782013a81c882ea6082ea608094ff000000000000000000000000000000000003ec6480c080a07a6dd661b5da27c809cce22aa186c158fe3b07a484a9395fd9a7a31a2b90636fa02b86f82b661264e27c3fda085b59740d3059335bff91693291afcf93c7ca627c","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c882ea6082ea608094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86682013a8082ea6082ea608094ff000000000000000000000000000000000003ec6480c001a02a6a910f7b5f83fda937006021b9c074f4544d5bb37b9b5a1b7045095f461836a038572b25418528bce7e6a3a480cf9fc90a33d9c63b392c2dbc8faf72a1e4ab8f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a8082ea6082ea608094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86782013a81c882ea6082ea608094ff000000000000000000000000000000000003ec6480c080a07a6dd661b5da27c809cce22aa186c158fe3b07a484a9395fd9a7a31a2b90636fa02b86f82b661264e27c3fda085b59740d3059335bff91693291afcf93c7ca627c","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c882ea6082ea608094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f88a82013a8082ea6082ea608094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a08c13c10490bc20cb1e55dc54ececb37a6c9cc8d013dbe513feacbb0416f09feba045c4e038759a0901820091e043db326b1bf9a8a1cd046ac72629969497c6a86f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84782013a8082ea6082ea608094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88b82013a81c882ea6082ea608094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0b904edf8eb9b6beb9cde9e1fae538e12f8d40e9124ace0cba2eee8cbbe77aa10a0788a0bd9a6fb98e7230f5db89be2f5067d1a227ba277b9cb155fb5859c57aae6","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84882013a81c882ea6082ea608094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86482013a80808082ea6094ff000000000000000000000000000000000003ec6480c080a08d10a7a81c561391fe88bcb2c1dfbf4f7140fb7884fec0558606e76ffc4eaa91a049fa2a95e0f07a4376df9c6f2e1563ad443ce8369d44c6e1ce8ee521805b3623","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a80808082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86582013a81c8808082ea6094ff000000000000000000000000000000000003ec6480c001a00de6dc2841a25e5ea2dc1e054d69638ec519a9953666930060797cd110cde122a07fd1dcb6319eca7c681cef006efb3f7dcd74ff98a79ce05917d5d1fa7a175b6f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c8808082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86482013a80808082ea6094ff000000000000000000000000000000000003ec6480c080a08d10a7a81c561391fe88bcb2c1dfbf4f7140fb7884fec0558606e76ffc4eaa91a049fa2a95e0f07a4376df9c6f2e1563ad443ce8369d44c6e1ce8ee521805b3623","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e182013a80808082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86582013a81c8808082ea6094ff000000000000000000000000000000000003ec6480c001a00de6dc2841a25e5ea2dc1e054d69638ec519a9953666930060797cd110cde122a07fd1dcb6319eca7c681cef006efb3f7dcd74ff98a79ce05917d5d1fa7a175b6f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e282013a81c8808082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f88882013a80808082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a04c43dab94dd746973a1f7f051cc520cc01e93e9c6c55147cef34e5fdc0b182a2a06d148cc6ec017f9aeb6442a17d72e388ffc835950e19abd0c06057520f893542","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84582013a80808082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88982013a81c8808082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a025b50c1db31c0ae7aaa73374659201b54b71488efecbb6985dc50015abde7e36a04dd8cf68920de7232ab8d1fb28ab94ac05466c1f9d9a3a658f2054fce7868e2c","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84682013a81c8808082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86682013a808082ea6082ea6094ff000000000000000000000000000000000003ec6480c080a0415ad0a93225eaec617206ec835e362d5e75fd0e1903747c1806270ec2684c7da0487ec1479cdb2affa891ff56413818ec169651c906ab932594b6e5bbb79d4998","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a808082ea6082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86782013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec6480c001a0a46ac278c400ef099ad23ac4ccb066a37db8bb5c4d65e0a347152a499ae9eb92a07505f9c67f0897cbe6f848c9a2164c3c234dab2fea7a4dd6f4436be34080e2ff","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86682013a808082ea6082ea6094ff000000000000000000000000000000000003ec6480c080a0415ad0a93225eaec617206ec835e362d5e75fd0e1903747c1806270ec2684c7da0487ec1479cdb2affa891ff56413818ec169651c906ab932594b6e5bbb79d4998","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a808082ea6082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86782013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec6480c001a0a46ac278c400ef099ad23ac4ccb066a37db8bb5c4d65e0a347152a499ae9eb92a07505f9c67f0897cbe6f848c9a2164c3c234dab2fea7a4dd6f4436be34080e2ff","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f88a82013a808082ea6082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0a43aba5078d2da3ecc1ec0c67191f8cf58f29f5b4db7f8d4765ea691ddbd4195a0110e568c803db5ea587b406f452cf49ddf6b6f24d41207973d6c785ffaed1454","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84782013a808082ea6082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88b82013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a00caeadf2fcba95f0deab5ee4899348ecac4a18eeb09317d6f8156b891626d219a0549c5376aba320889c2f7b61fd4a51aec5f9a1d9ed9b26cef0a3bee52fac4989","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84882013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86682013a8082ea608082ea6094ff000000000000000000000000000000000003ec6480c001a07b5568d8a3ec3c7e126f570955db304e31d3f3d7b0c4fd103b6d064a2f6f5e23a030a1b17f299352ae193b8dbce2adda473ccb04e00670f416877762971697606f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a8082ea608082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86782013a81c882ea608082ea6094ff000000000000000000000000000000000003ec6480c080a07bb69d01062f9d6ecb011ad344bbe08d4eca2f6b192dde45015def4c2e6096e0a03a3df52d753e3293d2fd544f72e62ceae00ea6dcab7229685d7b1873d873d203","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c882ea608082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86682013a8082ea608082ea6094ff000000000000000000000000000000000003ec6480c001a07b5568d8a3ec3c7e126f570955db304e31d3f3d7b0c4fd103b6d064a2f6f5e23a030a1b17f299352ae193b8dbce2adda473ccb04e00670f416877762971697606f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e382013a8082ea608082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86782013a81c882ea608082ea6094ff000000000000000000000000000000000003ec6480c080a07bb69d01062f9d6ecb011ad344bbe08d4eca2f6b192dde45015def4c2e6096e0a03a3df52d753e3293d2fd544f72e62ceae00ea6dcab7229685d7b1873d873d203","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e482013a81c882ea608082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f88a82013a8082ea608082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0621255015626b35acf19629ce318999336441537920f9f3ff1bfd44e54d8abd3a03b3426f8fa963debdfa6b44561772bdebc9524c7f63abd0d947b678f5e966502","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84782013a8082ea608082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88b82013a81c882ea608082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0b73c3ba53fc5a0f7fab636cc2b826c3873cda5d0be9dd2100fdceae7899f3310a0491905f676063924cf847fdf2e488be4606ce351748e5c88d49ed50c8d595c94","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84882013a81c882ea608082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86882013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec6480c001a0e60702e3f5c5f56e3d1bc2907015ec889d0557ea14e81f137056471fef0fdb9da066e601e6e55c2e37e2042401b352e81841d492d0fe4f05bfe81bba29c9e6ce1f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e582013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86982013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec6480c001a085a947fb201d0b50272e7bb7a056adc9ee6f5904634ed91dbde0d650641b7de3a03635c731769302e955d41f794a63262d5d4d37d117c9db89a6b6bce927b71f42","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e682013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86882013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec6480c001a0e60702e3f5c5f56e3d1bc2907015ec889d0557ea14e81f137056471fef0fdb9da066e601e6e55c2e37e2042401b352e81841d492d0fe4f05bfe81bba29c9e6ce1f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e582013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f86982013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec6480c001a085a947fb201d0b50272e7bb7a056adc9ee6f5904634ed91dbde0d650641b7de3a03635c731769302e955d41f794a63262d5d4d37d117c9db89a6b6bce927b71f42","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e682013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec6480c0"},{"input":"0x02f88c82013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0d67e28d31489af5129c4832af814a01e0baa5e5ba6245fe2d3304693ceea48e0a03bc06f1c6dd01a14826c67aa35258c0bbf7c516a9bb21e9190eaa8d3768f49bb","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84982013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88d82013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0a5368984aca4bc1e3d7ebc7ae4ead5e09ffd3b4b4712d039c19fdac948e5952ea065953ace0a29210440d6a0f05d6b43f482950b463b3be6b23fc63452c94b9446","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"100\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84a82013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec64a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86a82013a8080808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a086da25ab078729b08cf48da02eb1c1e05fe0f4e5d7b332262b68f4db3dc9b72fa04102c03c7d9f11a6fdb77d6a36d3f07e09b1ceaab0bf4ef1fdc604bcd726f83b","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e782013a8080808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86b82013a81c880808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0cde92f395919b3205b4260867b11597f9ecf363bc1be9bbd8b5400d3381d64b3a01b9555cfa22ee8615c3033235ebad605d0bef616d08876de26719866fcc4d41e","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e882013a81c880808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86a82013a8080808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a086da25ab078729b08cf48da02eb1c1e05fe0f4e5d7b332262b68f4db3dc9b72fa04102c03c7d9f11a6fdb77d6a36d3f07e09b1ceaab0bf4ef1fdc604bcd726f83b","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e782013a8080808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86b82013a81c880808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0cde92f395919b3205b4260867b11597f9ecf363bc1be9bbd8b5400d3381d64b3a01b9555cfa22ee8615c3033235ebad605d0bef616d08876de26719866fcc4d41e","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02e882013a81c880808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f88e82013a8080808094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a03dd64e48a1ae228665b3f180367997ee96bc60ee226615c900e3d86634044328a00f6cdb24633e75fa65f6b93fce9b084c1f30dd03dde97d01f25c6f10f34d5d9d","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84b82013a8080808094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f88f82013a81c880808094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a07475efeb8dd5bf4ba7efb31ab67a9077401ed71f4e8dd13e7058ce5cfeb5a0f2a01046e93a5258bf320bc392173a49b6fef15976be4c1210f2e367af223ad8c026","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84c82013a81c880808094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86c82013a808082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0ca84441c7ba097a7afa5ef9ad7ef70ba58ddfffc06c5d015b5c8553f1632d103a057fee6d92055c9c031a1efa667f3ee554804c4f34a195b6dfc781e1592c20444","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e982013a808082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86d82013a81c88082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a04055dfcd6e0b7264d3474ba13f76659384e5f365ebc6ba271641481b12bf410ca01ef7d04dc33fdf0c3137e31d8c822ad68bbd4f89ada52db9705bb66813d11583","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ea82013a81c88082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86c82013a808082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0ca84441c7ba097a7afa5ef9ad7ef70ba58ddfffc06c5d015b5c8553f1632d103a057fee6d92055c9c031a1efa667f3ee554804c4f34a195b6dfc781e1592c20444","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e982013a808082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86d82013a81c88082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a04055dfcd6e0b7264d3474ba13f76659384e5f365ebc6ba271641481b12bf410ca01ef7d04dc33fdf0c3137e31d8c822ad68bbd4f89ada52db9705bb66813d11583","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ea82013a81c88082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f89082013a808082ea608094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a02080212bb64a798e1e138e4991ab830cf04d37ffeedf6fde7eba0eb7d972b350a02aff43f9e5ca8d6cea6e918391188fa37bdb91b864eadec705f7c69c4a61bc5a","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84d82013a808082ea608094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f89182013a81c88082ea608094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0e41c052d72950a563b8ed7fb15855beabea43ff5b038bd6a3ccc6416e3498619a0568bbd7cbff31a47e1d0b9712f382c52e74b7b28cbcb8458974d82a8d54ddc57","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84e82013a81c88082ea608094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86c82013a8082ea60808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a057c342304f133ff8832d3d16a43571afe905dc9b10afc24c6e99225cca6d8817a00e2155d1904751ce0d2ba01e6475aeae254c02966773f5bc7650e37252a01a92","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e982013a8082ea60808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86d82013a81c882ea60808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0fc2a550a7798085cae28028abbe4829be29e5f3a40af221086831d0e17ca3c83a01ce21f5934b9ca566958e09e89c99fd9ed2dc4acae209a6fb81fd3a6c9879a99","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ea82013a81c882ea60808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86c82013a8082ea60808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a057c342304f133ff8832d3d16a43571afe905dc9b10afc24c6e99225cca6d8817a00e2155d1904751ce0d2ba01e6475aeae254c02966773f5bc7650e37252a01a92","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e982013a8082ea60808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86d82013a81c882ea60808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0fc2a550a7798085cae28028abbe4829be29e5f3a40af221086831d0e17ca3c83a01ce21f5934b9ca566958e09e89c99fd9ed2dc4acae209a6fb81fd3a6c9879a99","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ea82013a81c882ea60808094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f89082013a8082ea60808094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0fa33b63666310ca1c72fc5d82639c5b8e2a7638910be7bee23ada9f139c6b891a02012cad8e991beea7dcf0b6e9346b0228699698e183e2fadfc5b9b880601af9b","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84d82013a8082ea60808094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f89182013a81c882ea60808094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0bc6ae4e92e7a20d5ff61258653dffda636cee0fd97dd156eac7a1f231f1f2785a0323055e0e0bed496b3fec30be292338d0956ecf8baeeb34458230821589aa7fb","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84e82013a81c882ea60808094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86e82013a8082ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a0bd2889395392859a83a33bfe549c09d172e1f289de29d4bc9d0a3d25ea8aa71ba075fe92140a08d8e680061852438623c9cd10e211955577d1a3b56e49e960e4e7","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02eb82013a8082ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86f82013a81c882ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a05553c929ae32692a9f742371ffcfc8c8d2b77f31a7795460297cb78c29e357e8a043e42ca4ed7eb1b8e3546de2364522735d79a2e2ff5d16f7f96d165c5815c80c","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ec82013a81c882ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86e82013a8082ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a0bd2889395392859a83a33bfe549c09d172e1f289de29d4bc9d0a3d25ea8aa71ba075fe92140a08d8e680061852438623c9cd10e211955577d1a3b56e49e960e4e7","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02eb82013a8082ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86f82013a81c882ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a05553c929ae32692a9f742371ffcfc8c8d2b77f31a7795460297cb78c29e357e8a043e42ca4ed7eb1b8e3546de2364522735d79a2e2ff5d16f7f96d165c5815c80c","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ec82013a81c882ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f89282013a8082ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a055f63a6bef8e23dc437ff4ac9349a59fcde2f72d1879de50b0d3686ff648749da04cf8034df06cf6f15f31bb55979b40eeacbd28fb1d745e608acdc088e22beb66","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84f82013a8082ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f89382013a81c882ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0c4a0253448dad999692c1bf3cfb5de9e95a2e96da4e1f64133ada452a825fe9aa0757b576ceb7a2c494819960ac59e9d3a4e3da384f23c0e88ada758dc265eae94","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":0,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f85082013a81c882ea6082ea608094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86c82013a80808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a02632c4d8a443afb8d39f91d036fd4915ca3ad2f253b8f93211b4b3ee15566519a009bdc00c8eaaf22f3d7d04b53dbc777fd027a780fb4ddaf01002724ddf2879dd","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e982013a80808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86d82013a81c8808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a08bda02c15ca37d35d9ad2e2f7731d24dd039f5c6c6f7eaad739daadac6db33e5a044c01e493e10929e4021c69d9df886b211eb349a865df9f0796846ad1cdf23e8","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ea82013a81c8808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86c82013a80808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a02632c4d8a443afb8d39f91d036fd4915ca3ad2f253b8f93211b4b3ee15566519a009bdc00c8eaaf22f3d7d04b53dbc777fd027a780fb4ddaf01002724ddf2879dd","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02e982013a80808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86d82013a81c8808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a08bda02c15ca37d35d9ad2e2f7731d24dd039f5c6c6f7eaad739daadac6db33e5a044c01e493e10929e4021c69d9df886b211eb349a865df9f0796846ad1cdf23e8","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ea82013a81c8808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f89082013a80808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0ed0db75f41b2b8b89768ce5ad08716aff149dc1d5a2e593140d8964eb2da3229a02e5248cca9b5af340d73271cad4d690f7efa11c9278824aca528eb15d28aec4d","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84d82013a80808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f89182013a81c8808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a07108fbbabc45826dbdc8e4cf831240fb39ead7bd4b8ec5d8de64d04e2885e554a04dae4fb4bdbabb9d8f923d579e75ee980da1b4fac5773ec68f395af240f037f0","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84e82013a81c8808082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86e82013a808082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0130b6723050095faa2e7abc69c2f785e73d333c65fae6cf2835518f970c627d5a00b90bd4f2ded1da0163ab5e81ad76d51aef005d663137347fc550313e1c8b6fc","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02eb82013a808082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86f82013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a0993a50431e82d10d632466d45f8aaffea9a56efa59d529dfd497d3c2a06aabeba0070d3132c6ce1e4ff70b0721d1f4c03ab566b8e2af29d33148033fb3009dc29d","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ec82013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86e82013a808082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0130b6723050095faa2e7abc69c2f785e73d333c65fae6cf2835518f970c627d5a00b90bd4f2ded1da0163ab5e81ad76d51aef005d663137347fc550313e1c8b6fc","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02eb82013a808082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86f82013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a0993a50431e82d10d632466d45f8aaffea9a56efa59d529dfd497d3c2a06aabeba0070d3132c6ce1e4ff70b0721d1f4c03ab566b8e2af29d33148033fb3009dc29d","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ec82013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f89282013a808082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a09c9d3b0d7b58bfe81a6881b9db184e0ade03c1ad11aa8f1566e2f24f50f85525a06c10cf91f4dbc24d0f78ef09a8e2310d349a034cec7e86e807d7a48ea26161e1","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84f82013a808082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f89382013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a0f8423b51e513618c6a4bdd2696479d91c760e11ea24657dd27fa6eb9b7da8c0ea07e9456113fb034718d1b4f4e09ade1ce78251a8c86f298b152850bc5925156cb","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"0\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f85082013a81c88082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f86e82013a8082ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0d09b373d45c1bfc1c5d9b5198e69f974d4df456245e2f7a5edd486f3dd2795a9a011396197a670e7b0c4613b7ebf8aee53382930c7bd25c35dda15acae78ec0e2c","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02eb82013a8082ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86f82013a81c882ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a0131f5af3ece9a0b723d0c812dbcfc6cb458acf5e0846cc506215fc04d6af66d5a078d0bf7a40cc1ddcebbc4e86fb9a04bfc94f3da94b4a74476883b7b1729f8a44","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ec82013a81c882ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86e82013a8082ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0d09b373d45c1bfc1c5d9b5198e69f974d4df456245e2f7a5edd486f3dd2795a9a011396197a670e7b0c4613b7ebf8aee53382930c7bd25c35dda15acae78ec0e2c","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02eb82013a8082ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f86f82013a81c882ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c080a0131f5af3ece9a0b723d0c812dbcfc6cb458acf5e0846cc506215fc04d6af66d5a078d0bf7a40cc1ddcebbc4e86fb9a04bfc94f3da94b4a74476883b7b1729f8a44","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ec82013a81c882ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f89282013a8082ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0c286f4ee350eab70273cf9a952537534446a0f39e9bfea7340eabc04396a0e3da01e1302ae987a69836ec2c9266e6fe623db5fcdc566e37084c0c57630c4de8ee6","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f84f82013a8082ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f89382013a81c882ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c080a09dee3fa88e365133a18035618af718a045e1a957f10f50c632f23923fd337b9ba06bbbd59489849803f8c61138932ac1a8361edb4c80789d030542829c0a2b5b7f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"0\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f85082013a81c882ea608082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f87082013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0c1cb1e2b41e48fecd59d72039147c76993653f061f9ea156b53c377673eef7f1a01822506f755206b60209a12ed3c84446f4fcb4ad602fa7ab7ee4ff2acde19ed6","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02ed82013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f87182013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a09817043ad22797d2f26ca46697db5f586c38336a171dce2d22d659889e9e9eb5a0369a5d6169586d9c831b6e017aa29fd49eac0636a136bfa5bafb95390fa95b8f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":null,\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ee82013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f87082013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a0c1cb1e2b41e48fecd59d72039147c76993653f061f9ea156b53c377673eef7f1a01822506f755206b60209a12ed3c84446f4fcb4ad602fa7ab7ee4ff2acde19ed6","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02ed82013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f87182013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c001a09817043ad22797d2f26ca46697db5f586c38336a171dce2d22d659889e9e9eb5a0369a5d6169586d9c831b6e017aa29fd49eac0636a136bfa5bafb95390fa95b8f","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02ee82013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a764000080c0"},{"input":"0x02f89482013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a039357ad40087d17551ca2b94723f0394185a993671db02172a7de70c24054852a046c84070dfadd244b358690e5b89c75f3988b21b6614e6e3af2f8ca302d6c42a","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":0,\"type\":2,\"chainId\":314}","nosigTx":"0x02f85182013a8082ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"},{"input":"0x02f89582013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c001a0c991c81705a4c53a9255e72beb8243638c68f10c63b082755972bbbe15245d12a014f6852ae34c92882559e6810d4372109930a23b522368fdef2c85ce04e27839","output":"{\"to\":\"0xff000000000000000000000000000000000003EC\",\"value\":\"1000000000000000000\",\"gasLimit\":60000,\"maxFeePerGas\":\"60000\",\"maxPriorityFeePerGas\":\"60000\",\"data\":\"0xf8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064\",\"nonce\":200,\"type\":2,\"chainId\":314}","nosigTx":"0x02f85282013a81c882ea6082ea6082ea6094ff000000000000000000000000000000000003ec880de0b6b3a7640000a4f8b2cb4f000000000000000000000000ff00000000000000000000000000000000000064c0"}]` + + testcases := []struct { + Input EthBytes `json:"input"` + Output string `json:"output"` + NosigTx string `json:"nosigTx"` + }{} + + err := json.Unmarshal([]byte(tcstr), &testcases) + if err != nil { + return nil, err + } + + res := []TxTestcase{} + for _, tc := range testcases { + tx := EthTxArgs{} + err := json.Unmarshal([]byte(tc.Output), &tx) + if err != nil { + return nil, err + } + res = append(res, TxTestcase{ + Input: tc.Input, + Output: tx, + TxJSON: tc.Output, + NosigTx: tc.NosigTx, + }) + } + + return res, err +} diff --git a/chain/types/ethtypes/eth_types.go b/chain/types/ethtypes/eth_types.go new file mode 100644 index 000000000..64f67f662 --- /dev/null +++ b/chain/types/ethtypes/eth_types.go @@ -0,0 +1,841 @@ +package ethtypes + +import ( + "bytes" + "encoding/binary" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + mathbig "math/big" + "strconv" + "strings" + + "github.com/ipfs/go-cid" + "github.com/multiformats/go-multihash" + "github.com/multiformats/go-varint" + "golang.org/x/crypto/sha3" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/big" + builtintypes "github.com/filecoin-project/go-state-types/builtin" + + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/lib/must" +) + +var ErrInvalidAddress = errors.New("invalid Filecoin Eth address") + +type EthUint64 uint64 + +func (e EthUint64) MarshalJSON() ([]byte, error) { + return json.Marshal(e.Hex()) +} + +// UnmarshalJSON should be able to parse these types of input: +// 1. a JSON string containing a hex-encoded uint64 starting with 0x +// 2. a JSON string containing an uint64 in decimal +// 3. a string containing an uint64 in decimal +func (e *EthUint64) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err == nil { + base := 10 + if strings.HasPrefix(s, "0x") { + base = 16 + } + parsedInt, err := strconv.ParseUint(strings.Replace(s, "0x", "", -1), base, 64) + if err != nil { + return err + } + eint := EthUint64(parsedInt) + *e = eint + return nil + } else if eint, err := strconv.ParseUint(string(b), 10, 64); err == nil { + *e = EthUint64(eint) + return nil + } + return fmt.Errorf("cannot interpret %s as a hex-encoded uint64, or a number", string(b)) +} + +func EthUint64FromHex(s string) (EthUint64, error) { + parsedInt, err := strconv.ParseUint(strings.Replace(s, "0x", "", -1), 16, 64) + if err != nil { + return EthUint64(0), err + } + return EthUint64(parsedInt), nil +} + +// Parse a uint64 from big-endian encoded bytes. +func EthUint64FromBytes(b []byte) (EthUint64, error) { + if len(b) != 32 { + return 0, xerrors.Errorf("eth int must be 32 bytes long") + } + var zeros [32 - 8]byte + if !bytes.Equal(b[:len(zeros)], zeros[:]) { + return 0, xerrors.Errorf("eth int overflows 64 bits") + } + return EthUint64(binary.BigEndian.Uint64(b[len(zeros):])), nil +} + +func (e EthUint64) Hex() string { + if e == 0 { + return "0x0" + } + return fmt.Sprintf("0x%x", e) +} + +// EthBigInt represents a large integer whose zero value serializes to "0x0". +type EthBigInt big.Int + +var EthBigIntZero = EthBigInt{Int: big.Zero().Int} + +func (e EthBigInt) String() string { + if e.Int == nil || e.Int.BitLen() == 0 { + return "0x0" + } + return fmt.Sprintf("0x%x", e.Int) +} + +func (e EthBigInt) MarshalJSON() ([]byte, error) { + return json.Marshal(e.String()) +} + +func (e *EthBigInt) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + replaced := strings.Replace(s, "0x", "", -1) + if len(replaced)%2 == 1 { + replaced = "0" + replaced + } + + i := new(mathbig.Int) + i.SetString(replaced, 16) + + *e = EthBigInt(big.NewFromGo(i)) + return nil +} + +// EthBytes represent arbitrary bytes. A nil or empty slice serializes to "0x". +type EthBytes []byte + +func (e EthBytes) String() string { + if len(e) == 0 { + return "0x" + } + return "0x" + hex.EncodeToString(e) +} + +func (e EthBytes) MarshalJSON() ([]byte, error) { + return json.Marshal(e.String()) +} + +func (e *EthBytes) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + s = strings.Replace(s, "0x", "", -1) + if len(s)%2 == 1 { + s = "0" + s + } + + decoded, err := hex.DecodeString(s) + if err != nil { + return err + } + + *e = decoded + return nil +} + +type EthBlock struct { + Hash EthHash `json:"hash"` + ParentHash EthHash `json:"parentHash"` + Sha3Uncles EthHash `json:"sha3Uncles"` + Miner EthAddress `json:"miner"` + StateRoot EthHash `json:"stateRoot"` + TransactionsRoot EthHash `json:"transactionsRoot"` + ReceiptsRoot EthHash `json:"receiptsRoot"` + LogsBloom EthBytes `json:"logsBloom"` + Difficulty EthUint64 `json:"difficulty"` + TotalDifficulty EthUint64 `json:"totalDifficulty"` + Number EthUint64 `json:"number"` + GasLimit EthUint64 `json:"gasLimit"` + GasUsed EthUint64 `json:"gasUsed"` + Timestamp EthUint64 `json:"timestamp"` + Extradata EthBytes `json:"extraData"` + MixHash EthHash `json:"mixHash"` + Nonce EthNonce `json:"nonce"` + BaseFeePerGas EthBigInt `json:"baseFeePerGas"` + Size EthUint64 `json:"size"` + // can be []EthTx or []string depending on query params + Transactions []interface{} `json:"transactions"` + Uncles []EthHash `json:"uncles"` +} + +const EthBloomSize = 2048 + +var ( + EmptyEthBloom = [EthBloomSize / 8]byte{} + FullEthBloom = [EthBloomSize / 8]byte{} + EmptyEthHash = EthHash{} + EmptyUncleHash = must.One(ParseEthHash("0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347")) // Keccak-256 of an RLP of an empty array + EmptyRootHash = must.One(ParseEthHash("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")) // Keccak-256 hash of the RLP of null + EmptyEthInt = EthUint64(0) + EmptyEthNonce = [8]byte{0, 0, 0, 0, 0, 0, 0, 0} +) + +func init() { + for i := range FullEthBloom { + FullEthBloom[i] = 0xff + } +} + +func NewEthBlock(hasTransactions bool) EthBlock { + b := EthBlock{ + Sha3Uncles: EmptyUncleHash, // Sha3Uncles set to a hardcoded value which is used by some clients to determine if has no uncles. + StateRoot: EmptyEthHash, + TransactionsRoot: EmptyRootHash, // TransactionsRoot set to a hardcoded value which is used by some clients to determine if has no transactions. + ReceiptsRoot: EmptyEthHash, + Difficulty: EmptyEthInt, + LogsBloom: FullEthBloom[:], + Extradata: []byte{}, + MixHash: EmptyEthHash, + Nonce: EmptyEthNonce, + GasLimit: EthUint64(build.BlockGasLimit), // TODO we map Ethereum blocks to Filecoin tipsets; this is inconsistent. + Uncles: []EthHash{}, + Transactions: []interface{}{}, + } + if hasTransactions { + b.TransactionsRoot = EmptyEthHash + } + + return b +} + +type EthCall struct { + From *EthAddress `json:"from"` + To *EthAddress `json:"to"` + Gas EthUint64 `json:"gas"` + GasPrice EthBigInt `json:"gasPrice"` + Value EthBigInt `json:"value"` + Data EthBytes `json:"data"` +} + +func (c *EthCall) UnmarshalJSON(b []byte) error { + type TempEthCall EthCall + var params TempEthCall + + if err := json.Unmarshal(b, ¶ms); err != nil { + return err + } + *c = EthCall(params) + return nil +} + +type EthSyncingResult struct { + DoneSync bool + StartingBlock EthUint64 + CurrentBlock EthUint64 + HighestBlock EthUint64 +} + +func (sr EthSyncingResult) MarshalJSON() ([]byte, error) { + if sr.DoneSync { + // when done syncing, the json response should be '"result": false' + return []byte("false"), nil + } + + // need to do an anonymous struct to avoid infinite recursion + return json.Marshal(&struct { + StartingBlock EthUint64 `json:"startingblock"` + CurrentBlock EthUint64 `json:"currentblock"` + HighestBlock EthUint64 `json:"highestblock"` + }{ + StartingBlock: sr.StartingBlock, + CurrentBlock: sr.CurrentBlock, + HighestBlock: sr.HighestBlock}) +} + +const ( + EthAddressLength = 20 + EthHashLength = 32 +) + +type EthNonce [8]byte + +func (n EthNonce) String() string { + return "0x" + hex.EncodeToString(n[:]) +} + +func (n EthNonce) MarshalJSON() ([]byte, error) { + return json.Marshal(n.String()) +} + +func (n *EthNonce) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + s = strings.Replace(s, "0x", "", -1) + if len(s)%2 == 1 { + s = "0" + s + } + + decoded, err := hex.DecodeString(s) + if err != nil { + return err + } + copy(n[:], decoded[:8]) + return nil +} + +type EthAddress [EthAddressLength]byte + +// EthAddressFromPubKey returns the Ethereum address corresponding to an +// uncompressed secp256k1 public key. +func EthAddressFromPubKey(pubk []byte) ([]byte, error) { + // if we get an uncompressed public key (that's what we get from the library, + // but putting this check here for defensiveness), strip the prefix + const pubKeyLen = 65 + if len(pubk) != pubKeyLen { + return nil, fmt.Errorf("public key should have %d in length, but got %d", pubKeyLen, len(pubk)) + } + if pubk[0] != 0x04 { + return nil, fmt.Errorf("expected first byte of secp256k1 to be 0x04 (uncompressed)") + } + pubk = pubk[1:] + + // Calculate the Ethereum address based on the keccak hash of the pubkey. + hasher := sha3.NewLegacyKeccak256() + hasher.Write(pubk) + ethAddr := hasher.Sum(nil)[12:] + return ethAddr, nil +} + +var maskedIDPrefix = [20 - 8]byte{0xff} + +func IsEthAddress(addr address.Address) bool { + if addr.Protocol() != address.Delegated { + return false + } + payload := addr.Payload() + namespace, offset, err := varint.FromUvarint(payload) + if err != nil { + return false + } + + payload = payload[offset:] + + return namespace == builtintypes.EthereumAddressManagerActorID && len(payload) == 20 && !bytes.HasPrefix(payload, maskedIDPrefix[:]) +} + +func EthAddressFromFilecoinAddress(addr address.Address) (EthAddress, error) { + switch addr.Protocol() { + case address.ID: + id, err := address.IDFromAddress(addr) + if err != nil { + return EthAddress{}, err + } + var ethaddr EthAddress + ethaddr[0] = 0xff + binary.BigEndian.PutUint64(ethaddr[12:], id) + return ethaddr, nil + case address.Delegated: + payload := addr.Payload() + namespace, n, err := varint.FromUvarint(payload) + if err != nil { + return EthAddress{}, xerrors.Errorf("invalid delegated address namespace in: %s", addr) + } + payload = payload[n:] + if namespace != builtintypes.EthereumAddressManagerActorID { + return EthAddress{}, ErrInvalidAddress + } + ethAddr, err := CastEthAddress(payload) + if err != nil { + return EthAddress{}, err + } + if ethAddr.IsMaskedID() { + return EthAddress{}, xerrors.Errorf("f410f addresses cannot embed masked-ID payloads: %s", ethAddr) + } + return ethAddr, nil + } + return EthAddress{}, ErrInvalidAddress +} + +// ParseEthAddress parses an Ethereum address from a hex string. +func ParseEthAddress(s string) (EthAddress, error) { + b, err := decodeHexString(s, EthAddressLength) + if err != nil { + return EthAddress{}, err + } + var h EthAddress + copy(h[EthAddressLength-len(b):], b) + return h, nil +} + +// CastEthAddress interprets bytes as an EthAddress, performing some basic checks. +func CastEthAddress(b []byte) (EthAddress, error) { + var a EthAddress + if len(b) != EthAddressLength { + return EthAddress{}, xerrors.Errorf("cannot parse bytes into an EthAddress: incorrect input length") + } + copy(a[:], b[:]) + return a, nil +} + +func (ea EthAddress) String() string { + return "0x" + hex.EncodeToString(ea[:]) +} + +func (ea EthAddress) MarshalJSON() ([]byte, error) { + return json.Marshal(ea.String()) +} + +func (ea *EthAddress) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + addr, err := ParseEthAddress(s) + if err != nil { + return err + } + copy(ea[:], addr[:]) + return nil +} + +func (ea EthAddress) IsMaskedID() bool { + return bytes.HasPrefix(ea[:], maskedIDPrefix[:]) +} + +func (ea EthAddress) ToFilecoinAddress() (address.Address, error) { + if ea.IsMaskedID() { + // This is a masked ID address. + id := binary.BigEndian.Uint64(ea[12:]) + return address.NewIDAddress(id) + } + + // Otherwise, translate the address into an address controlled by the + // Ethereum Address Manager. + addr, err := address.NewDelegatedAddress(builtintypes.EthereumAddressManagerActorID, ea[:]) + if err != nil { + return address.Undef, fmt.Errorf("failed to translate supplied address (%s) into a "+ + "Filecoin f4 address: %w", hex.EncodeToString(ea[:]), err) + } + return addr, nil +} + +type EthHash [EthHashLength]byte + +func (h EthHash) MarshalJSON() ([]byte, error) { + return json.Marshal(h.String()) +} + +func (h *EthHash) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + hash, err := ParseEthHash(s) + if err != nil { + return err + } + copy(h[:], hash[:]) + return nil +} + +func (h EthHash) String() string { + return "0x" + hex.EncodeToString(h[:]) +} + +// Should ONLY be used for blocks and Filecoin messages. Eth transactions expect a different hashing scheme. +func (h EthHash) ToCid() cid.Cid { + // err is always nil + mh, _ := multihash.EncodeName(h[:], "blake2b-256") + + return cid.NewCidV1(cid.DagCBOR, mh) +} + +func decodeHexString(s string, expectedLen int) ([]byte, error) { + s = handleHexStringPrefix(s) + if len(s) != expectedLen*2 { + return nil, xerrors.Errorf("expected hex string length sans prefix %d, got %d", expectedLen*2, len(s)) + } + b, err := hex.DecodeString(s) + if err != nil { + return nil, xerrors.Errorf("cannot parse hex value: %w", err) + } + return b, nil +} + +func DecodeHexString(s string) ([]byte, error) { + s = handleHexStringPrefix(s) + b, err := hex.DecodeString(s) + if err != nil { + return nil, xerrors.Errorf("cannot parse hex value: %w", err) + } + return b, nil +} + +func DecodeHexStringTrimSpace(s string) ([]byte, error) { + return DecodeHexString(strings.TrimSpace(s)) +} + +func handleHexStringPrefix(s string) string { + // Strip the leading 0x or 0X prefix since hex.DecodeString does not support it. + if strings.HasPrefix(s, "0x") || strings.HasPrefix(s, "0X") { + s = s[2:] + } + // Sometimes clients will omit a leading zero in a byte; pad so we can decode correctly. + if len(s)%2 == 1 { + s = "0" + s + } + return s +} + +func EthHashFromCid(c cid.Cid) (EthHash, error) { + return ParseEthHash(c.Hash().HexString()[8:]) +} + +func ParseEthHash(s string) (EthHash, error) { + b, err := decodeHexString(s, EthHashLength) + if err != nil { + return EthHash{}, err + } + var h EthHash + copy(h[EthHashLength-len(b):], b) + return h, nil +} + +func EthHashFromTxBytes(b []byte) EthHash { + hasher := sha3.NewLegacyKeccak256() + hasher.Write(b) + hash := hasher.Sum(nil) + + var ethHash EthHash + copy(ethHash[:], hash) + return ethHash +} + +func EthBloomSet(f EthBytes, data []byte) { + hasher := sha3.NewLegacyKeccak256() + hasher.Write(data) + hash := hasher.Sum(nil) + + for i := 0; i < 3; i++ { + n := binary.BigEndian.Uint16(hash[i*2:]) % EthBloomSize + f[(EthBloomSize/8)-(n/8)-1] |= 1 << (n % 8) + } +} + +type EthFeeHistory struct { + OldestBlock EthUint64 `json:"oldestBlock"` + BaseFeePerGas []EthBigInt `json:"baseFeePerGas"` + GasUsedRatio []float64 `json:"gasUsedRatio"` + Reward *[][]EthBigInt `json:"reward,omitempty"` +} + +type EthFilterID EthHash + +func (h EthFilterID) MarshalJSON() ([]byte, error) { + return (EthHash)(h).MarshalJSON() +} + +func (h *EthFilterID) UnmarshalJSON(b []byte) error { + return (*EthHash)(h).UnmarshalJSON(b) +} + +func (h EthFilterID) String() string { + return (EthHash)(h).String() +} + +// An opaque identifier generated by the Lotus node to refer to an active subscription. +type EthSubscriptionID EthHash + +func (h EthSubscriptionID) MarshalJSON() ([]byte, error) { + return (EthHash)(h).MarshalJSON() +} + +func (h *EthSubscriptionID) UnmarshalJSON(b []byte) error { + return (*EthHash)(h).UnmarshalJSON(b) +} + +func (h EthSubscriptionID) String() string { + return (EthHash)(h).String() +} + +type EthFilterSpec struct { + // Interpreted as an epoch (in hex) or one of "latest" for last mined block, "earliest" for first, + // "pending" for not yet committed messages. + // Optional, default: "latest". + FromBlock *string `json:"fromBlock,omitempty"` + + // Interpreted as an epoch (in hex) or one of "latest" for last mined block, "earliest" for first, + // "pending" for not yet committed messages. + // Optional, default: "latest". + ToBlock *string `json:"toBlock,omitempty"` + + // Actor address or a list of addresses from which event logs should originate. + // Optional, default nil. + // The JSON decoding must treat a string as equivalent to an array with one value, for example + // "0x8888f1f195afa192cfee86069858" must be decoded as [ "0x8888f1f195afa192cfee86069858" ] + Address EthAddressList `json:"address"` + + // List of topics to be matched. + // Optional, default: empty list + Topics EthTopicSpec `json:"topics"` + + // Restricts event logs returned to those emitted from messages contained in this tipset. + // If BlockHash is present in in the filter criteria, then neither FromBlock nor ToBlock are allowed. + // Added in EIP-234 + BlockHash *EthHash `json:"blockHash,omitempty"` +} + +// EthAddressSpec represents a list of addresses. +// The JSON decoding must treat a string as equivalent to an array with one value, for example +// "0x8888f1f195afa192cfee86069858" must be decoded as [ "0x8888f1f195afa192cfee86069858" ] +type EthAddressList []EthAddress + +func (e *EthAddressList) UnmarshalJSON(b []byte) error { + if bytes.Equal(b, []byte{'n', 'u', 'l', 'l'}) { + return nil + } + if len(b) > 0 && b[0] == '[' { + var addrs []EthAddress + err := json.Unmarshal(b, &addrs) + if err != nil { + return err + } + *e = addrs + return nil + } + var addr EthAddress + err := json.Unmarshal(b, &addr) + if err != nil { + return err + } + *e = []EthAddress{addr} + return nil +} + +// TopicSpec represents a specification for matching by topic. An empty spec means all topics +// will be matched. Otherwise topics are matched conjunctively in the first dimension of the +// slice and disjunctively in the second dimension. Topics are matched in order. +// An event log with topics [A, B] will be matched by the following topic specs: +// [] "all" +// [[A]] "A in first position (and anything after)" +// [nil, [B] ] "anything in first position AND B in second position (and anything after)" +// [[A], [B]] "A in first position AND B in second position (and anything after)" +// [[A, B], [A, B]] "(A OR B) in first position AND (A OR B) in second position (and anything after)" +// +// The JSON decoding must treat string values as equivalent to arrays with one value, for example +// { "A", [ "B", "C" ] } must be decoded as [ [ A ], [ B, C ] ] +type EthTopicSpec []EthHashList + +// EthHashList represents a list of EthHashes. +// The JSON decoding treats string values as equivalent to arrays with one value. +type EthHashList []EthHash + +func (e *EthHashList) UnmarshalJSON(b []byte) error { + if bytes.Equal(b, []byte{'n', 'u', 'l', 'l'}) { + return nil + } + if len(b) > 0 && b[0] == '[' { + var hashes []EthHash + err := json.Unmarshal(b, &hashes) + if err != nil { + return err + } + *e = hashes + return nil + } + var hash EthHash + err := json.Unmarshal(b, &hash) + if err != nil { + return err + } + *e = []EthHash{hash} + return nil +} + +// FilterResult represents the response from executing a filter: a list of block hashes, a list of transaction hashes +// or a list of logs +// This is a union type. Only one field will be populated. +// The JSON encoding must produce an array of the populated field. +type EthFilterResult struct { + Results []interface{} +} + +func (h EthFilterResult) MarshalJSON() ([]byte, error) { + if h.Results != nil { + return json.Marshal(h.Results) + } + return []byte{'[', ']'}, nil +} + +func (h *EthFilterResult) UnmarshalJSON(b []byte) error { + if bytes.Equal(b, []byte{'n', 'u', 'l', 'l'}) { + return nil + } + err := json.Unmarshal(b, &h.Results) + return err +} + +// EthLog represents the results of an event filter execution. +type EthLog struct { + // Address is the address of the actor that produced the event log. + Address EthAddress `json:"address"` + + // Data is the value of the event log, excluding topics + Data EthBytes `json:"data"` + + // List of topics associated with the event log. + Topics []EthHash `json:"topics"` + + // Following fields are derived from the transaction containing the log + + // Indicates whether the log was removed due to a chain reorganization. + Removed bool `json:"removed"` + + // LogIndex is the index of the event log in the sequence of events produced by the message execution. + // (this is the index in the events AMT on the message receipt) + LogIndex EthUint64 `json:"logIndex"` + + // TransactionIndex is the index in the tipset of the transaction that produced the event log. + // The index corresponds to the sequence of messages produced by ChainGetParentMessages + TransactionIndex EthUint64 `json:"transactionIndex"` + + // TransactionHash is the hash of the RLP message that produced the event log. + TransactionHash EthHash `json:"transactionHash"` + + // BlockHash is the hash of the tipset containing the message that produced the log. + BlockHash EthHash `json:"blockHash"` + + // BlockNumber is the epoch of the tipset containing the message. + BlockNumber EthUint64 `json:"blockNumber"` +} + +// EthSubscribeParams handles raw jsonrpc params for eth_subscribe +type EthSubscribeParams struct { + EventType string + Params *EthSubscriptionParams +} + +func (e *EthSubscribeParams) UnmarshalJSON(b []byte) error { + var params []json.RawMessage + err := json.Unmarshal(b, ¶ms) + if err != nil { + return err + } + switch len(params) { + case 2: + err = json.Unmarshal(params[1], &e.Params) + if err != nil { + return err + } + fallthrough + case 1: + err = json.Unmarshal(params[0], &e.EventType) + if err != nil { + return err + } + default: + return xerrors.Errorf("expected 1 or 2 params, got %d", len(params)) + } + return nil +} + +func (e EthSubscribeParams) MarshalJSON() ([]byte, error) { + if e.Params != nil { + return json.Marshal([]interface{}{e.EventType, e.Params}) + } + return json.Marshal([]interface{}{e.EventType}) +} + +type EthSubscriptionParams struct { + // List of topics to be matched. + // Optional, default: empty list + Topics EthTopicSpec `json:"topics,omitempty"` + + // Actor address or a list of addresses from which event logs should originate. + // Optional, default nil. + // The JSON decoding must treat a string as equivalent to an array with one value, for example + // "0x8888f1f195afa192cfee86069858" must be decoded as [ "0x8888f1f195afa192cfee86069858" ] + Address EthAddressList `json:"address"` +} + +type EthSubscriptionResponse struct { + // The persistent identifier for the subscription which can be used to unsubscribe. + SubscriptionID EthSubscriptionID `json:"subscription"` + + // The object matching the subscription. This may be a Block (tipset), a Transaction (message) or an EthLog + Result interface{} `json:"result"` +} + +func GetContractEthAddressFromCode(sender EthAddress, salt [32]byte, initcode []byte) (EthAddress, error) { + hasher := sha3.NewLegacyKeccak256() + hasher.Write(initcode) + inithash := hasher.Sum(nil) + + hasher.Reset() + hasher.Write([]byte{0xff}) + hasher.Write(sender[:]) + hasher.Write(salt[:]) + hasher.Write(inithash) + + ethAddr, err := CastEthAddress(hasher.Sum(nil)[12:]) + if err != nil { + return [20]byte{}, err + } + + return ethAddr, nil +} + +// EthFeeHistoryParams handles raw jsonrpc params for eth_feeHistory +type EthFeeHistoryParams struct { + BlkCount EthUint64 + NewestBlkNum string + RewardPercentiles *[]float64 +} + +func (e *EthFeeHistoryParams) UnmarshalJSON(b []byte) error { + var params []json.RawMessage + err := json.Unmarshal(b, ¶ms) + if err != nil { + return err + } + switch len(params) { + case 3: + err = json.Unmarshal(params[2], &e.RewardPercentiles) + if err != nil { + return err + } + fallthrough + case 2: + err = json.Unmarshal(params[1], &e.NewestBlkNum) + if err != nil { + return err + } + err = json.Unmarshal(params[0], &e.BlkCount) + if err != nil { + return err + } + default: + return xerrors.Errorf("expected 2 or 3 params, got %d", len(params)) + } + return nil +} + +func (e EthFeeHistoryParams) MarshalJSON() ([]byte, error) { + if e.RewardPercentiles != nil { + return json.Marshal([]interface{}{e.BlkCount, e.NewestBlkNum, e.RewardPercentiles}) + } + return json.Marshal([]interface{}{e.BlkCount, e.NewestBlkNum}) +} diff --git a/chain/types/ethtypes/eth_types_test.go b/chain/types/ethtypes/eth_types_test.go new file mode 100644 index 000000000..4a73184c2 --- /dev/null +++ b/chain/types/ethtypes/eth_types_test.go @@ -0,0 +1,447 @@ +package ethtypes + +import ( + "encoding/json" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/builtin" +) + +type TestCase struct { + Input interface{} + Output interface{} +} + +func TestEthIntMarshalJSON(t *testing.T) { + // https://ethereum.org/en/developers/docs/apis/json-rpc/#quantities-encoding + testcases := []TestCase{ + {EthUint64(0), []byte("\"0x0\"")}, + {EthUint64(65), []byte("\"0x41\"")}, + {EthUint64(1024), []byte("\"0x400\"")}, + } + + for _, tc := range testcases { + j, err := tc.Input.(EthUint64).MarshalJSON() + require.Nil(t, err) + require.Equal(t, j, tc.Output) + } +} + +func TestEthIntUnmarshalJSON(t *testing.T) { + testcases := []TestCase{ + {[]byte("\"0x0\""), EthUint64(0)}, + {[]byte("\"0x41\""), EthUint64(65)}, + {[]byte("\"0x400\""), EthUint64(1024)}, + {[]byte("\"0\""), EthUint64(0)}, + {[]byte("\"41\""), EthUint64(41)}, + {[]byte("\"400\""), EthUint64(400)}, + {[]byte("0"), EthUint64(0)}, + {[]byte("100"), EthUint64(100)}, + {[]byte("1024"), EthUint64(1024)}, + } + + for _, tc := range testcases { + var i EthUint64 + err := i.UnmarshalJSON(tc.Input.([]byte)) + require.Nil(t, err) + require.Equal(t, tc.Output, i) + } +} + +func TestEthBigIntMarshalJSON(t *testing.T) { + testcases := []TestCase{ + {EthBigInt(big.NewInt(0)), []byte("\"0x0\"")}, + {EthBigInt(big.NewInt(65)), []byte("\"0x41\"")}, + {EthBigInt(big.NewInt(1024)), []byte("\"0x400\"")}, + {EthBigInt(big.Int{}), []byte("\"0x0\"")}, + } + for _, tc := range testcases { + j, err := tc.Input.(EthBigInt).MarshalJSON() + require.Nil(t, err) + require.Equal(t, j, tc.Output) + } +} + +func TestEthBigIntUnmarshalJSON(t *testing.T) { + testcases := []TestCase{ + {[]byte("\"0x0\""), EthBigInt(big.MustFromString("0"))}, + {[]byte("\"0x41\""), EthBigInt(big.MustFromString("65"))}, + {[]byte("\"0x400\""), EthBigInt(big.MustFromString("1024"))}, + {[]byte("\"0xff1000000000000000000000000\""), EthBigInt(big.MustFromString("323330131220712761719252861321216"))}, + } + + for _, tc := range testcases { + var i EthBigInt + err := i.UnmarshalJSON(tc.Input.([]byte)) + require.Nil(t, err) + require.Equal(t, i, tc.Output) + } +} + +func TestEthHash(t *testing.T) { + testcases := []string{ + `"0x013dbb9442ca9667baccc6230fcd5c1c4b2d4d2870f4bd20681d4d47cfd15184"`, + `"0xab8653edf9f51785664a643b47605a7ba3d917b5339a0724e7642c114d0e4738"`, + } + + for _, hash := range testcases { + var h EthHash + err := h.UnmarshalJSON([]byte(hash)) + + require.Nil(t, err) + require.Equal(t, h.String(), strings.Replace(hash, `"`, "", -1)) + + c := h.ToCid() + h1, err := EthHashFromCid(c) + require.Nil(t, err) + require.Equal(t, h, h1) + + jm, err := json.Marshal(h) + require.NoError(t, err) + require.Equal(t, hash, string(jm)) + } +} + +func TestEthFilterID(t *testing.T) { + testcases := []string{ + `"0x013dbb9442ca9667baccc6230fcd5c1c4b2d4d2870f4bd20681d4d47cfd15184"`, + `"0xab8653edf9f51785664a643b47605a7ba3d917b5339a0724e7642c114d0e4738"`, + } + + for _, hash := range testcases { + var h EthFilterID + err := h.UnmarshalJSON([]byte(hash)) + + require.Nil(t, err) + require.Equal(t, h.String(), strings.Replace(hash, `"`, "", -1)) + + jm, err := json.Marshal(h) + require.NoError(t, err) + require.Equal(t, hash, string(jm)) + } +} + +func TestEthSubscriptionID(t *testing.T) { + testcases := []string{ + `"0x013dbb9442ca9667baccc6230fcd5c1c4b2d4d2870f4bd20681d4d47cfd15184"`, + `"0xab8653edf9f51785664a643b47605a7ba3d917b5339a0724e7642c114d0e4738"`, + } + + for _, hash := range testcases { + var h EthSubscriptionID + err := h.UnmarshalJSON([]byte(hash)) + + require.Nil(t, err) + require.Equal(t, h.String(), strings.Replace(hash, `"`, "", -1)) + + jm, err := json.Marshal(h) + require.NoError(t, err) + require.Equal(t, hash, string(jm)) + } +} + +func TestEthAddr(t *testing.T) { + testcases := []string{ + strings.ToLower(`"0xd4c5fb16488Aa48081296299d54b0c648C9333dA"`), + strings.ToLower(`"0x2C2EC67e3e1FeA8e4A39601cB3A3Cd44f5fa830d"`), + strings.ToLower(`"0x01184F793982104363F9a8a5845743f452dE0586"`), + } + + for _, addr := range testcases { + var a EthAddress + err := a.UnmarshalJSON([]byte(addr)) + + require.Nil(t, err) + require.Equal(t, a.String(), strings.Replace(addr, `"`, "", -1)) + } +} + +func TestParseEthAddr(t *testing.T) { + testcases := []uint64{ + 1, 2, 3, 100, 101, + } + for _, id := range testcases { + addr, err := address.NewIDAddress(id) + require.Nil(t, err) + + eaddr, err := EthAddressFromFilecoinAddress(addr) + require.Nil(t, err) + + faddr, err := eaddr.ToFilecoinAddress() + require.Nil(t, err) + + require.Equal(t, addr, faddr) + } +} + +func TestMaskedIDInF4(t *testing.T) { + addr, err := address.NewIDAddress(100) + require.NoError(t, err) + + eaddr, err := EthAddressFromFilecoinAddress(addr) + require.NoError(t, err) + + badaddr, err := address.NewDelegatedAddress(builtin.EthereumAddressManagerActorID, eaddr[:]) + require.NoError(t, err) + + _, err = EthAddressFromFilecoinAddress(badaddr) + require.Error(t, err) +} + +func TestUnmarshalEthCall(t *testing.T) { + data := `{"from":"0x4D6D86b31a112a05A473c4aE84afaF873f632325","to":"0xFe01CC39f5Ae8553D6914DBb9dC27D219fa22D7f","gas":"0x5","gasPrice":"0x6","value":"0x123","data":""}` + + var c EthCall + err := c.UnmarshalJSON([]byte(data)) + require.Nil(t, err) +} + +func TestUnmarshalEthBytes(t *testing.T) { + testcases := []string{ + `"0x00"`, + strings.ToLower(`"0xd4c5fb16488Aa48081296299d54b0c648C9333dA"`), + strings.ToLower(`"0x2C2EC67e3e1FeA8e4A39601cB3A3Cd44f5fa830d"`), + strings.ToLower(`"0x01184F793982104363F9a8a5845743f452dE0586"`), + } + + for _, tc := range testcases { + var s EthBytes + err := s.UnmarshalJSON([]byte(tc)) + require.Nil(t, err) + + data, err := s.MarshalJSON() + require.Nil(t, err) + require.Equal(t, string(data), tc) + } +} + +func TestEthFilterResultMarshalJSON(t *testing.T) { + hash1, err := ParseEthHash("013dbb9442ca9667baccc6230fcd5c1c4b2d4d2870f4bd20681d4d47cfd15184") + require.NoError(t, err, "eth hash") + + hash2, err := ParseEthHash("ab8653edf9f51785664a643b47605a7ba3d917b5339a0724e7642c114d0e4738") + require.NoError(t, err, "eth hash") + + addr, err := ParseEthAddress("d4c5fb16488Aa48081296299d54b0c648C9333dA") + require.NoError(t, err, "eth address") + + log := EthLog{ + Removed: true, + LogIndex: 5, + TransactionIndex: 45, + TransactionHash: hash1, + BlockHash: hash2, + BlockNumber: 53, + Topics: []EthHash{hash1}, + Data: EthBytes(hash1[:]), + Address: addr, + } + logjson, err := json.Marshal(log) + require.NoError(t, err, "log json") + + testcases := []struct { + res EthFilterResult + want string + }{ + { + res: EthFilterResult{}, + want: "[]", + }, + + { + res: EthFilterResult{ + Results: []any{hash1, hash2}, + }, + want: `["0x013dbb9442ca9667baccc6230fcd5c1c4b2d4d2870f4bd20681d4d47cfd15184","0xab8653edf9f51785664a643b47605a7ba3d917b5339a0724e7642c114d0e4738"]`, + }, + + { + res: EthFilterResult{ + Results: []any{hash1, hash2}, + }, + want: `["0x013dbb9442ca9667baccc6230fcd5c1c4b2d4d2870f4bd20681d4d47cfd15184","0xab8653edf9f51785664a643b47605a7ba3d917b5339a0724e7642c114d0e4738"]`, + }, + + { + res: EthFilterResult{ + Results: []any{log}, + }, + want: `[` + string(logjson) + `]`, + }, + } + + for _, tc := range testcases { + tc := tc + t.Run("", func(t *testing.T) { + data, err := json.Marshal(tc.res) + require.NoError(t, err) + require.Equal(t, tc.want, string(data)) + }) + } +} + +func TestEthFilterSpecUnmarshalJSON(t *testing.T) { + hash1, err := ParseEthHash("013dbb9442ca9667baccc6230fcd5c1c4b2d4d2870f4bd20681d4d47cfd15184") + require.NoError(t, err, "eth hash") + + hash2, err := ParseEthHash("ab8653edf9f51785664a643b47605a7ba3d917b5339a0724e7642c114d0e4738") + require.NoError(t, err, "eth hash") + + addr, err := ParseEthAddress("d4c5fb16488Aa48081296299d54b0c648C9333dA") + require.NoError(t, err, "eth address") + + pstring := func(s string) *string { return &s } + phash := func(h EthHash) *EthHash { return &h } + + testcases := []struct { + input string + want EthFilterSpec + }{ + { + input: `{"fromBlock":"latest"}`, + want: EthFilterSpec{FromBlock: pstring("latest")}, + }, + { + input: `{"toBlock":"pending"}`, + want: EthFilterSpec{ToBlock: pstring("pending")}, + }, + { + input: `{"address":["0xd4c5fb16488Aa48081296299d54b0c648C9333dA"]}`, + want: EthFilterSpec{Address: EthAddressList{addr}}, + }, + { + input: `{"address":"0xd4c5fb16488Aa48081296299d54b0c648C9333dA"}`, + want: EthFilterSpec{Address: EthAddressList{addr}}, + }, + { + input: `{"blockHash":"0x013dbb9442ca9667baccc6230fcd5c1c4b2d4d2870f4bd20681d4d47cfd15184"}`, + want: EthFilterSpec{BlockHash: phash(hash1)}, + }, + { + input: `{"topics":["0x013dbb9442ca9667baccc6230fcd5c1c4b2d4d2870f4bd20681d4d47cfd15184"]}`, + want: EthFilterSpec{ + Topics: EthTopicSpec{ + {hash1}, + }, + }, + }, + { + input: `{"topics":["0x013dbb9442ca9667baccc6230fcd5c1c4b2d4d2870f4bd20681d4d47cfd15184","0xab8653edf9f51785664a643b47605a7ba3d917b5339a0724e7642c114d0e4738"]}`, + want: EthFilterSpec{ + Topics: EthTopicSpec{ + {hash1}, + {hash2}, + }, + }, + }, + { + input: `{"topics":[null, ["0x013dbb9442ca9667baccc6230fcd5c1c4b2d4d2870f4bd20681d4d47cfd15184","0xab8653edf9f51785664a643b47605a7ba3d917b5339a0724e7642c114d0e4738"]]}`, + want: EthFilterSpec{ + Topics: EthTopicSpec{ + nil, + {hash1, hash2}, + }, + }, + }, + { + input: `{"topics":[null, "0x013dbb9442ca9667baccc6230fcd5c1c4b2d4d2870f4bd20681d4d47cfd15184"]}`, + want: EthFilterSpec{ + Topics: EthTopicSpec{ + nil, + {hash1}, + }, + }, + }, + } + + for _, tc := range testcases { + var got EthFilterSpec + err := json.Unmarshal([]byte(tc.input), &got) + require.NoError(t, err) + require.Equal(t, tc.want, got) + } +} + +func TestEthAddressListUnmarshalJSON(t *testing.T) { + addr1, err := ParseEthAddress("d4c5fb16488Aa48081296299d54b0c648C9333dA") + require.NoError(t, err, "eth address") + + addr2, err := ParseEthAddress("abbbfb16488Aa48081296299d54b0c648C9333dA") + require.NoError(t, err, "eth address") + + testcases := []struct { + input string + want EthAddressList + }{ + { + input: `["0xd4c5fb16488Aa48081296299d54b0c648C9333dA"]`, + want: EthAddressList{addr1}, + }, + { + input: `["0xd4c5fb16488Aa48081296299d54b0c648C9333dA","abbbfb16488Aa48081296299d54b0c648C9333dA"]`, + want: EthAddressList{addr1, addr2}, + }, + { + input: `"0xd4c5fb16488Aa48081296299d54b0c648C9333dA"`, + want: EthAddressList{addr1}, + }, + { + input: `[]`, + want: EthAddressList{}, + }, + { + input: `null`, + want: EthAddressList(nil), + }, + } + for _, tc := range testcases { + tc := tc + t.Run("", func(t *testing.T) { + var got EthAddressList + err := json.Unmarshal([]byte(tc.input), &got) + require.NoError(t, err) + require.Equal(t, tc.want, got) + }) + } +} + +func TestEthHashListUnmarshalJSON(t *testing.T) { + hash1, err := ParseEthHash("013dbb9442ca9667baccc6230fcd5c1c4b2d4d2870f4bd20681d4d47cfd15184") + require.NoError(t, err, "eth hash") + + hash2, err := ParseEthHash("ab8653edf9f51785664a643b47605a7ba3d917b5339a0724e7642c114d0e4738") + require.NoError(t, err, "eth hash") + + testcases := []struct { + input string + want *EthHashList + }{ + { + input: `["0x013dbb9442ca9667baccc6230fcd5c1c4b2d4d2870f4bd20681d4d47cfd15184"]`, + want: &EthHashList{hash1}, + }, + { + input: `["0x013dbb9442ca9667baccc6230fcd5c1c4b2d4d2870f4bd20681d4d47cfd15184","0xab8653edf9f51785664a643b47605a7ba3d917b5339a0724e7642c114d0e4738"]`, + want: &EthHashList{hash1, hash2}, + }, + { + input: `"0x013dbb9442ca9667baccc6230fcd5c1c4b2d4d2870f4bd20681d4d47cfd15184"`, + want: &EthHashList{hash1}, + }, + { + input: `null`, + want: nil, + }, + } + for _, tc := range testcases { + var got *EthHashList + err := json.Unmarshal([]byte(tc.input), &got) + require.NoError(t, err) + require.Equal(t, tc.want, got) + } +} diff --git a/chain/types/ethtypes/rlp.go b/chain/types/ethtypes/rlp.go new file mode 100644 index 000000000..049ea6fc4 --- /dev/null +++ b/chain/types/ethtypes/rlp.go @@ -0,0 +1,182 @@ +package ethtypes + +import ( + "bytes" + "encoding/binary" + "fmt" + + "golang.org/x/xerrors" +) + +// maxListElements restricts the amount of RLP list elements we'll read. +// The ETH API only ever reads EIP-1559 transactions, which are bounded by +// 12 elements exactly, so we play it safe and set exactly that limit here. +const maxListElements = 12 + +func EncodeRLP(val interface{}) ([]byte, error) { + return encodeRLP(val) +} + +func encodeRLPListItems(list []interface{}) (result []byte, err error) { + res := []byte{} + for _, elem := range list { + encoded, err := encodeRLP(elem) + if err != nil { + return nil, err + } + res = append(res, encoded...) + } + return res, nil +} + +func encodeLength(length int) (lenInBytes []byte, err error) { + if length == 0 { + return nil, fmt.Errorf("cannot encode length: length should be larger than 0") + } + + buf := new(bytes.Buffer) + err = binary.Write(buf, binary.BigEndian, int64(length)) + if err != nil { + return nil, err + } + + firstNonZeroIndex := len(buf.Bytes()) - 1 + for i, b := range buf.Bytes() { + if b != 0 { + firstNonZeroIndex = i + break + } + } + + res := buf.Bytes()[firstNonZeroIndex:] + return res, nil +} + +func encodeRLP(val interface{}) ([]byte, error) { + switch data := val.(type) { + case []byte: + if len(data) == 1 && data[0] <= 0x7f { + return data, nil + } else if len(data) <= 55 { + prefix := byte(0x80 + len(data)) + return append([]byte{prefix}, data...), nil + } else { + lenInBytes, err := encodeLength(len(data)) + if err != nil { + return nil, err + } + prefix := byte(0xb7 + len(lenInBytes)) + return append( + []byte{prefix}, + append(lenInBytes, data...)..., + ), nil + } + case []interface{}: + encodedList, err := encodeRLPListItems(data) + if err != nil { + return nil, err + } + if len(encodedList) <= 55 { + prefix := byte(0xc0 + len(encodedList)) + return append( + []byte{prefix}, + encodedList..., + ), nil + } + lenInBytes, err := encodeLength(len(encodedList)) + if err != nil { + return nil, err + } + prefix := byte(0xf7 + len(lenInBytes)) + return append( + []byte{prefix}, + append(lenInBytes, encodedList...)..., + ), nil + default: + return nil, fmt.Errorf("input data should either be a list or a byte array") + } +} + +func DecodeRLP(data []byte) (interface{}, error) { + res, consumed, err := decodeRLP(data) + if err != nil { + return nil, err + } + if consumed != len(data) { + return nil, xerrors.Errorf("invalid rlp data: length %d, consumed %d", len(data), consumed) + } + return res, nil +} + +func decodeRLP(data []byte) (res interface{}, consumed int, err error) { + if len(data) == 0 { + return data, 0, xerrors.Errorf("invalid rlp data: data cannot be empty") + } + if data[0] >= 0xf8 { + listLenInBytes := int(data[0]) - 0xf7 + listLen, err := decodeLength(data[1:], listLenInBytes) + if err != nil { + return nil, 0, err + } + if 1+listLenInBytes+listLen > len(data) { + return nil, 0, xerrors.Errorf("invalid rlp data: out of bound while parsing list") + } + result, err := decodeListElems(data[1+listLenInBytes:], listLen) + return result, 1 + listLenInBytes + listLen, err + } else if data[0] >= 0xc0 { + length := int(data[0]) - 0xc0 + result, err := decodeListElems(data[1:], length) + return result, 1 + length, err + } else if data[0] >= 0xb8 { + strLenInBytes := int(data[0]) - 0xb7 + strLen, err := decodeLength(data[1:], strLenInBytes) + if err != nil { + return nil, 0, err + } + totalLen := 1 + strLenInBytes + strLen + if totalLen > len(data) { + return nil, 0, xerrors.Errorf("invalid rlp data: out of bound while parsing string") + } + return data[1+strLenInBytes : totalLen], totalLen, nil + } else if data[0] >= 0x80 { + length := int(data[0]) - 0x80 + if 1+length > len(data) { + return nil, 0, xerrors.Errorf("invalid rlp data: out of bound while parsing string") + } + return data[1 : 1+length], 1 + length, nil + } + return []byte{data[0]}, 1, nil +} + +func decodeLength(data []byte, lenInBytes int) (length int, err error) { + if lenInBytes > len(data) || lenInBytes > 8 { + return 0, xerrors.Errorf("invalid rlp data: out of bound while parsing list length") + } + var decodedLength int64 + r := bytes.NewReader(append(make([]byte, 8-lenInBytes), data[:lenInBytes]...)) + if err := binary.Read(r, binary.BigEndian, &decodedLength); err != nil { + return 0, xerrors.Errorf("invalid rlp data: cannot parse string length: %w", err) + } + if lenInBytes+int(decodedLength) > len(data) { + return 0, xerrors.Errorf("invalid rlp data: out of bound while parsing list") + } + return int(decodedLength), nil +} + +func decodeListElems(data []byte, length int) (res []interface{}, err error) { + totalConsumed := 0 + result := []interface{}{} + + for i := 0; totalConsumed < length && i < maxListElements; i++ { + elem, consumed, err := decodeRLP(data[totalConsumed:]) + if err != nil { + return nil, xerrors.Errorf("invalid rlp data: cannot decode list element: %w", err) + } + totalConsumed += consumed + result = append(result, elem) + } + if totalConsumed != length { + return nil, xerrors.Errorf("invalid rlp data: incorrect list length") + } + return result, nil +} diff --git a/chain/types/ethtypes/rlp_test.go b/chain/types/ethtypes/rlp_test.go new file mode 100644 index 000000000..bdbedff00 --- /dev/null +++ b/chain/types/ethtypes/rlp_test.go @@ -0,0 +1,190 @@ +package ethtypes + +import ( + "encoding/hex" + "fmt" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-address" +) + +func TestEncode(t *testing.T) { + testcases := []TestCase{ + {[]byte(""), mustDecodeHex("0x80")}, + {mustDecodeHex("0x01"), mustDecodeHex("0x01")}, + {mustDecodeHex("0xaa"), mustDecodeHex("0x81aa")}, + {mustDecodeHex("0x0402"), mustDecodeHex("0x820402")}, + { + []interface{}{}, + mustDecodeHex("0xc0"), + }, + { + mustDecodeHex("0xabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd"), + mustDecodeHex("0xb83cabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd"), + }, + { + mustDecodeHex("0xabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd"), + mustDecodeHex("0xb8aaabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd"), + }, + { + []interface{}{ + mustDecodeHex("0xaaaa"), + mustDecodeHex("0xbbbb"), + mustDecodeHex("0xcccc"), + mustDecodeHex("0xdddd"), + }, + mustDecodeHex("0xcc82aaaa82bbbb82cccc82dddd"), + }, + { + []interface{}{ + mustDecodeHex("0xaaaaaaaaaaaaaaaaaaaa"), + mustDecodeHex("0xbbbbbbbbbbbbbbbbbbbb"), + []interface{}{ + mustDecodeHex("0xc1c1c1c1c1c1c1c1c1c1"), + mustDecodeHex("0xc2c2c2c2c2c2c2c2c2c2"), + mustDecodeHex("0xc3c3c3c3c3c3c3c3c3c3"), + }, + mustDecodeHex("0xdddddddddddddddddddd"), + mustDecodeHex("0xeeeeeeeeeeeeeeeeeeee"), + mustDecodeHex("0xffffffffffffffffffff"), + }, + mustDecodeHex("0xf8598aaaaaaaaaaaaaaaaaaaaa8abbbbbbbbbbbbbbbbbbbbe18ac1c1c1c1c1c1c1c1c1c18ac2c2c2c2c2c2c2c2c2c28ac3c3c3c3c3c3c3c3c3c38adddddddddddddddddddd8aeeeeeeeeeeeeeeeeeeee8affffffffffffffffffff"), + }, + } + + for _, tc := range testcases { + result, err := EncodeRLP(tc.Input) + require.Nil(t, err) + + require.Equal(t, tc.Output.([]byte), result) + } +} + +func TestDecodeString(t *testing.T) { + testcases := []TestCase{ + {"0x00", "0x00"}, + {"0x80", "0x"}, + {"0x0f", "0x0f"}, + {"0x81aa", "0xaa"}, + {"0x820400", "0x0400"}, + {"0xb83cabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd", + "0xabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd"}, + } + + for _, tc := range testcases { + input, err := hex.DecodeString(strings.Replace(tc.Input.(string), "0x", "", -1)) + require.Nil(t, err) + + output, err := hex.DecodeString(strings.Replace(tc.Output.(string), "0x", "", -1)) + require.Nil(t, err) + + result, err := DecodeRLP(input) + require.Nil(t, err) + require.Equal(t, output, result.([]byte)) + } +} + +func mustDecodeHex(s string) []byte { + d, err := hex.DecodeString(strings.Replace(s, "0x", "", -1)) + if err != nil { + panic(fmt.Errorf("err must be nil: %w", err)) + } + return d +} + +func TestDecodeList(t *testing.T) { + testcases := []TestCase{ + {"0xc0", []interface{}{}}, + {"0xc100", []interface{}{[]byte{0}}}, + {"0xc3000102", []interface{}{[]byte{0}, []byte{1}, []byte{2}}}, + {"0xc4000181aa", []interface{}{[]byte{0}, []byte{1}, []byte{0xaa}}}, + {"0xc6000181aa81ff", []interface{}{[]byte{0}, []byte{1}, []byte{0xaa}, []byte{0xff}}}, + {"0xf8428aabcdabcdabcdabcdabcd8aabcdabcdabcdabcdabcd8aabcdabcdabcdabcdabcd8aabcdabcdabcdabcdabcd8aabcdabcdabcdabcdabcd8aabcdabcdabcdabcdabcd", + []interface{}{ + mustDecodeHex("0xabcdabcdabcdabcdabcd"), + mustDecodeHex("0xabcdabcdabcdabcdabcd"), + mustDecodeHex("0xabcdabcdabcdabcdabcd"), + mustDecodeHex("0xabcdabcdabcdabcdabcd"), + mustDecodeHex("0xabcdabcdabcdabcdabcd"), + mustDecodeHex("0xabcdabcdabcdabcdabcd"), + }, + }, + {"0xf1030185012a05f2008504a817c800825208942b87d1cb599bc2a606db9a0169fcec96af04ad3a880de0b6b3a764000080c0", + []interface{}{ + []byte{3}, + []byte{1}, + mustDecodeHex("0x012a05f200"), + mustDecodeHex("0x04a817c800"), + mustDecodeHex("0x5208"), + mustDecodeHex("0x2b87d1CB599Bc2a606Db9A0169fcEc96Af04ad3a"), + mustDecodeHex("0x0de0b6b3a7640000"), + []byte{}, + []interface{}{}, + }}, + } + + for _, tc := range testcases { + input, err := hex.DecodeString(strings.Replace(tc.Input.(string), "0x", "", -1)) + require.Nil(t, err) + + result, err := DecodeRLP(input) + require.Nil(t, err) + + fmt.Println(result) + r := result.([]interface{}) + require.Equal(t, len(tc.Output.([]interface{})), len(r)) + + for i, v := range r { + require.Equal(t, tc.Output.([]interface{})[i], v) + } + } +} + +func TestDecodeEncodeTx(t *testing.T) { + testcases := [][]byte{ + mustDecodeHex("0xdc82013a0185012a05f2008504a817c8008080872386f26fc1000000c0"), + mustDecodeHex("0xf85f82013a0185012a05f2008504a817c8008080872386f26fc1000000c001a027fa36fb9623e4d71fcdd7f7dce71eb814c9560dcf3908c1719386e2efd122fba05fb4e4227174eeb0ba84747a4fb883c8d4e0fdb129c4b1f42e90282c41480234"), + mustDecodeHex("0xf9061c82013a0185012a05f2008504a817c8008080872386f26fc10000b905bb608060405234801561001057600080fd5b506127106000803273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610556806100656000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80637bd703e81461004657806390b98a1114610076578063f8b2cb4f146100a6575b600080fd5b610060600480360381019061005b919061030a565b6100d6565b60405161006d9190610350565b60405180910390f35b610090600480360381019061008b9190610397565b6100f4565b60405161009d91906103f2565b60405180910390f35b6100c060048036038101906100bb919061030a565b61025f565b6040516100cd9190610350565b60405180910390f35b600060026100e38361025f565b6100ed919061043c565b9050919050565b6000816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410156101455760009050610259565b816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546101939190610496565b92505081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546101e891906104ca565b925050819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161024c9190610350565b60405180910390a3600190505b92915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006102d7826102ac565b9050919050565b6102e7816102cc565b81146102f257600080fd5b50565b600081359050610304816102de565b92915050565b6000602082840312156103205761031f6102a7565b5b600061032e848285016102f5565b91505092915050565b6000819050919050565b61034a81610337565b82525050565b60006020820190506103656000830184610341565b92915050565b61037481610337565b811461037f57600080fd5b50565b6000813590506103918161036b565b92915050565b600080604083850312156103ae576103ad6102a7565b5b60006103bc858286016102f5565b92505060206103cd85828601610382565b9150509250929050565b60008115159050919050565b6103ec816103d7565b82525050565b600060208201905061040760008301846103e3565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061044782610337565b915061045283610337565b9250817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048311821515161561048b5761048a61040d565b5b828202905092915050565b60006104a182610337565b91506104ac83610337565b9250828210156104bf576104be61040d565b5b828203905092915050565b60006104d582610337565b91506104e083610337565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156105155761051461040d565b5b82820190509291505056fea26469706673582212208e5b4b874c839967f88008ed2fa42d6c2d9c9b0ae05d1d2c61faa7d229c134e664736f6c634300080d0033c080a0c4e9477f57c6848b2f1ea73a14809c1f44529d20763c947f3ac8ffd3d1629d93a011485a215457579bb13ac7b53bb9d6804763ae6fe5ce8ddd41642cea55c9a09a"), + mustDecodeHex("0xf9063082013a0185012a05f2008504a817c8008094025b594a4f1c4888cafcfaf2bb24ed95507749e0872386f26fc10000b905bb608060405234801561001057600080fd5b506127106000803273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610556806100656000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80637bd703e81461004657806390b98a1114610076578063f8b2cb4f146100a6575b600080fd5b610060600480360381019061005b919061030a565b6100d6565b60405161006d9190610350565b60405180910390f35b610090600480360381019061008b9190610397565b6100f4565b60405161009d91906103f2565b60405180910390f35b6100c060048036038101906100bb919061030a565b61025f565b6040516100cd9190610350565b60405180910390f35b600060026100e38361025f565b6100ed919061043c565b9050919050565b6000816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410156101455760009050610259565b816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546101939190610496565b92505081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546101e891906104ca565b925050819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161024c9190610350565b60405180910390a3600190505b92915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006102d7826102ac565b9050919050565b6102e7816102cc565b81146102f257600080fd5b50565b600081359050610304816102de565b92915050565b6000602082840312156103205761031f6102a7565b5b600061032e848285016102f5565b91505092915050565b6000819050919050565b61034a81610337565b82525050565b60006020820190506103656000830184610341565b92915050565b61037481610337565b811461037f57600080fd5b50565b6000813590506103918161036b565b92915050565b600080604083850312156103ae576103ad6102a7565b5b60006103bc858286016102f5565b92505060206103cd85828601610382565b9150509250929050565b60008115159050919050565b6103ec816103d7565b82525050565b600060208201905061040760008301846103e3565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061044782610337565b915061045283610337565b9250817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048311821515161561048b5761048a61040d565b5b828202905092915050565b60006104a182610337565b91506104ac83610337565b9250828210156104bf576104be61040d565b5b828203905092915050565b60006104d582610337565b91506104e083610337565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156105155761051461040d565b5b82820190509291505056fea26469706673582212208e5b4b874c839967f88008ed2fa42d6c2d9c9b0ae05d1d2c61faa7d229c134e664736f6c634300080d0033c080a0fe38720928596f9e9dfbf891d00311638efce3713f03cdd67b212ecbbcf18f29a05993e656c0b35b8a580da6aff7c89b3d3e8b1c6f83a7ce09473c0699a8500b9c"), + } + + for _, tc := range testcases { + decoded, err := DecodeRLP(tc) + require.Nil(t, err) + + encoded, err := EncodeRLP(decoded) + require.Nil(t, err) + require.Equal(t, tc, encoded) + } +} + +func TestDecodeError(t *testing.T) { + testcases := [][]byte{ + mustDecodeHex("0xdc82013a0185012a05f2008504a817c8008080872386f26fc1000000"), + mustDecodeHex("0xdc013a01012a05f2008504a817c8008080872386f26fc1000000"), + mustDecodeHex("0xdc82013a0185012a05f28504a817c08080872386f26fc1000000"), + mustDecodeHex("0xdc82013a0185012a05f504a817c080872386ffc1000000"), + mustDecodeHex("0x013a018505f2008504a817c8008080872386f26fc1000000"), + } + + for _, tc := range testcases { + _, err := DecodeRLP(tc) + require.NotNil(t, err, hex.EncodeToString(tc)) + } +} + +func TestDecode1(t *testing.T) { + b := mustDecodeHex("0x02f8758401df5e7680832c8411832c8411830767f89452963ef50e27e06d72d59fcb4f3c2a687be3cfef880de0b6b3a764000080c080a094b11866f453ad85a980e0e8a2fc98cbaeb4409618c7734a7e12ae2f66fd405da042dbfb1b37af102023830ceeee0e703ffba0b8b3afeb8fe59f405eca9ed61072") + decoded, err := ParseEthTxArgs(b) + require.NoError(t, err) + + sender, err := decoded.Sender() + require.NoError(t, err) + + addr, err := address.NewFromString("f410fkkld55ioe7qg24wvt7fu6pbknb56ht7pt4zamxa") + require.NoError(t, err) + require.Equal(t, sender, addr) +} diff --git a/chain/types/event.go b/chain/types/event.go new file mode 100644 index 000000000..91b0e95d3 --- /dev/null +++ b/chain/types/event.go @@ -0,0 +1,61 @@ +package types + +import ( + "bytes" + "fmt" + + cbg "github.com/whyrusleeping/cbor-gen" + + "github.com/filecoin-project/go-state-types/abi" +) + +// EventEntry flags defined in fvm_shared +const ( + EventFlagIndexedKey = 0b00000001 + EventFlagIndexedValue = 0b00000010 +) + +type Event struct { + // The ID of the actor that emitted this event. + Emitter abi.ActorID + + // Key values making up this event. + Entries []EventEntry +} + +type EventEntry struct { + // A bitmap conveying metadata or hints about this entry. + Flags uint8 + + // The key of this event entry + Key string + + // The event value's codec + Codec uint64 + + // The event value + Value []byte +} + +type FilterID [32]byte // compatible with EthHash + +// DecodeEvents decodes a CBOR list of CBOR-encoded events. +func DecodeEvents(input []byte) ([]Event, error) { + r := bytes.NewReader(input) + typ, len, err := cbg.NewCborReader(r).ReadHeader() + if err != nil { + return nil, fmt.Errorf("failed to read events: %w", err) + } + if typ != cbg.MajArray { + return nil, fmt.Errorf("expected a CBOR list, was major type %d", typ) + } + events := make([]Event, 0, len) + for i := 0; i < int(len); i++ { + var evt Event + if err := evt.UnmarshalCBOR(r); err != nil { + return nil, fmt.Errorf("failed to parse event: %w", err) + } + events = append(events, evt) + } + return events, nil +} diff --git a/chain/types/execresult.go b/chain/types/execresult.go index 56f2ef143..2a25d22e2 100644 --- a/chain/types/execresult.go +++ b/chain/types/execresult.go @@ -1,12 +1,61 @@ package types -import "time" +import ( + "encoding/json" + "time" -type ExecutionResult struct { - Msg *Message - MsgRct *MessageReceipt - Error string - Duration time.Duration + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/exitcode" +) - Subcalls []*ExecutionResult +type GasTrace struct { + Name string + TotalGas int64 `json:"tg"` + ComputeGas int64 `json:"cg"` + StorageGas int64 `json:"sg"` + TimeTaken time.Duration `json:"tt"` +} + +type MessageTrace struct { + From address.Address + To address.Address + Value abi.TokenAmount + Method abi.MethodNum + Params []byte + ParamsCodec uint64 +} + +type ReturnTrace struct { + ExitCode exitcode.ExitCode + Return []byte + ReturnCodec uint64 +} + +type ExecutionTrace struct { + Msg MessageTrace + MsgRct ReturnTrace + GasCharges []*GasTrace `cborgen:"maxlen=1000000000"` + Subcalls []ExecutionTrace `cborgen:"maxlen=1000000000"` +} + +func (et ExecutionTrace) SumGas() GasTrace { + return SumGas(et.GasCharges) +} + +func SumGas(charges []*GasTrace) GasTrace { + var out GasTrace + for _, gc := range charges { + out.TotalGas += gc.TotalGas + out.ComputeGas += gc.ComputeGas + out.StorageGas += gc.StorageGas + } + + return out +} + +func (gt *GasTrace) MarshalJSON() ([]byte, error) { + type GasTraceCopy GasTrace + cpy := (*GasTraceCopy)(gt) + return json.Marshal(cpy) } diff --git a/chain/types/fil.go b/chain/types/fil.go index 80de6ced3..60a2940c6 100644 --- a/chain/types/fil.go +++ b/chain/types/fil.go @@ -1,6 +1,7 @@ package types import ( + "encoding" "fmt" "math/big" "strings" @@ -11,13 +12,54 @@ import ( type FIL BigInt func (f FIL) String() string { - r := new(big.Rat).SetFrac(f.Int, big.NewInt(build.FilecoinPrecision)) + return f.Unitless() + " FIL" +} + +func (f FIL) Unitless() string { + r := new(big.Rat).SetFrac(f.Int, big.NewInt(int64(build.FilecoinPrecision))) if r.Sign() == 0 { return "0" } return strings.TrimRight(strings.TrimRight(r.FloatString(18), "0"), ".") } +var AttoFil = NewInt(1) +var FemtoFil = BigMul(AttoFil, NewInt(1000)) +var PicoFil = BigMul(FemtoFil, NewInt(1000)) +var NanoFil = BigMul(PicoFil, NewInt(1000)) + +var unitPrefixes = []string{"a", "f", "p", "n", "μ", "m"} + +func (f FIL) Short() string { + n := BigInt(f).Abs() + + dn := uint64(1) + var prefix string + for _, p := range unitPrefixes { + if n.LessThan(NewInt(dn * 1000)) { + prefix = p + break + } + dn *= 1000 + } + + r := new(big.Rat).SetFrac(f.Int, big.NewInt(int64(dn))) + if r.Sign() == 0 { + return "0" + } + + return strings.TrimRight(strings.TrimRight(r.FloatString(3), "0"), ".") + " " + prefix + "FIL" +} + +func (f FIL) Nano() string { + r := new(big.Rat).SetFrac(f.Int, big.NewInt(int64(1e9))) + if r.Sign() == 0 { + return "0" + } + + return strings.TrimRight(strings.TrimRight(r.FloatString(9), "0"), ".") + " nFIL" +} + func (f FIL) Format(s fmt.State, ch rune) { switch ch { case 's', 'v': @@ -27,16 +69,67 @@ func (f FIL) Format(s fmt.State, ch rune) { } } +func (f FIL) MarshalText() (text []byte, err error) { + return []byte(f.String()), nil +} + +func (f FIL) UnmarshalText(text []byte) error { + p, err := ParseFIL(string(text)) + if err != nil { + return err + } + + f.Int.Set(p.Int) + return nil +} + func ParseFIL(s string) (FIL, error) { - r, ok := new(big.Rat).SetString(s) + suffix := strings.TrimLeft(s, "-.1234567890") + s = s[:len(s)-len(suffix)] + var attofil bool + if suffix != "" { + norm := strings.ToLower(strings.TrimSpace(suffix)) + switch norm { + case "", "fil": + case "attofil", "afil": + attofil = true + default: + return FIL{}, fmt.Errorf("unrecognized suffix: %q", suffix) + } + } + + if len(s) > 50 { + return FIL{}, fmt.Errorf("string length too large: %d", len(s)) + } + + r, ok := new(big.Rat).SetString(s) //nolint:gosec if !ok { return FIL{}, fmt.Errorf("failed to parse %q as a decimal number", s) } - r = r.Mul(r, big.NewRat(build.FilecoinPrecision, 1)) + if !attofil { + r = r.Mul(r, big.NewRat(int64(build.FilecoinPrecision), 1)) + } + if !r.IsInt() { - return FIL{}, fmt.Errorf("invalid FIL value: %q", s) + var pref string + if attofil { + pref = "atto" + } + return FIL{}, fmt.Errorf("invalid %sFIL value: %q", pref, s) } return FIL{r.Num()}, nil } + +func MustParseFIL(s string) FIL { + n, err := ParseFIL(s) + if err != nil { + panic(err) + } + + return n +} + +var _ encoding.TextMarshaler = (*FIL)(nil) +var _ encoding.TextUnmarshaler = (*FIL)(nil) diff --git a/chain/types/fil_test.go b/chain/types/fil_test.go new file mode 100644 index 000000000..85054ed98 --- /dev/null +++ b/chain/types/fil_test.go @@ -0,0 +1,116 @@ +// stm: #unit +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestFilShort(t *testing.T) { + //stm: @TYPES_FIL_PARSE_001 + for _, s := range []struct { + fil string + expect string + }{ + + {fil: "1", expect: "1 FIL"}, + {fil: "1.1", expect: "1.1 FIL"}, + {fil: "12", expect: "12 FIL"}, + {fil: "123", expect: "123 FIL"}, + {fil: "123456", expect: "123456 FIL"}, + {fil: "123.23", expect: "123.23 FIL"}, + {fil: "123456.234", expect: "123456.234 FIL"}, + {fil: "123456.2341234", expect: "123456.234 FIL"}, + {fil: "123456.234123445", expect: "123456.234 FIL"}, + + {fil: "0.1", expect: "100 mFIL"}, + {fil: "0.01", expect: "10 mFIL"}, + {fil: "0.001", expect: "1 mFIL"}, + + {fil: "0.0001", expect: "100 μFIL"}, + {fil: "0.00001", expect: "10 μFIL"}, + {fil: "0.000001", expect: "1 μFIL"}, + + {fil: "0.0000001", expect: "100 nFIL"}, + {fil: "0.00000001", expect: "10 nFIL"}, + {fil: "0.000000001", expect: "1 nFIL"}, + + {fil: "0.0000000001", expect: "100 pFIL"}, + {fil: "0.00000000001", expect: "10 pFIL"}, + {fil: "0.000000000001", expect: "1 pFIL"}, + + {fil: "0.0000000000001", expect: "100 fFIL"}, + {fil: "0.00000000000001", expect: "10 fFIL"}, + {fil: "0.000000000000001", expect: "1 fFIL"}, + + {fil: "0.0000000000000001", expect: "100 aFIL"}, + {fil: "0.00000000000000001", expect: "10 aFIL"}, + {fil: "0.000000000000000001", expect: "1 aFIL"}, + + {fil: "0.0000012", expect: "1.2 μFIL"}, + {fil: "0.00000123", expect: "1.23 μFIL"}, + {fil: "0.000001234", expect: "1.234 μFIL"}, + {fil: "0.0000012344", expect: "1.234 μFIL"}, + {fil: "0.00000123444", expect: "1.234 μFIL"}, + + {fil: "0.0002212", expect: "221.2 μFIL"}, + {fil: "0.00022123", expect: "221.23 μFIL"}, + {fil: "0.000221234", expect: "221.234 μFIL"}, + {fil: "0.0002212344", expect: "221.234 μFIL"}, + {fil: "0.00022123444", expect: "221.234 μFIL"}, + + {fil: "-1", expect: "-1 FIL"}, + {fil: "-1.1", expect: "-1.1 FIL"}, + {fil: "-12", expect: "-12 FIL"}, + {fil: "-123", expect: "-123 FIL"}, + {fil: "-123456", expect: "-123456 FIL"}, + {fil: "-123.23", expect: "-123.23 FIL"}, + {fil: "-123456.234", expect: "-123456.234 FIL"}, + {fil: "-123456.2341234", expect: "-123456.234 FIL"}, + {fil: "-123456.234123445", expect: "-123456.234 FIL"}, + + {fil: "-0.1", expect: "-100 mFIL"}, + {fil: "-0.01", expect: "-10 mFIL"}, + {fil: "-0.001", expect: "-1 mFIL"}, + + {fil: "-0.0001", expect: "-100 μFIL"}, + {fil: "-0.00001", expect: "-10 μFIL"}, + {fil: "-0.000001", expect: "-1 μFIL"}, + + {fil: "-0.0000001", expect: "-100 nFIL"}, + {fil: "-0.00000001", expect: "-10 nFIL"}, + {fil: "-0.000000001", expect: "-1 nFIL"}, + + {fil: "-0.0000000001", expect: "-100 pFIL"}, + {fil: "-0.00000000001", expect: "-10 pFIL"}, + {fil: "-0.000000000001", expect: "-1 pFIL"}, + + {fil: "-0.0000000000001", expect: "-100 fFIL"}, + {fil: "-0.00000000000001", expect: "-10 fFIL"}, + {fil: "-0.000000000000001", expect: "-1 fFIL"}, + + {fil: "-0.0000000000000001", expect: "-100 aFIL"}, + {fil: "-0.00000000000000001", expect: "-10 aFIL"}, + {fil: "-0.000000000000000001", expect: "-1 aFIL"}, + + {fil: "-0.0000012", expect: "-1.2 μFIL"}, + {fil: "-0.00000123", expect: "-1.23 μFIL"}, + {fil: "-0.000001234", expect: "-1.234 μFIL"}, + {fil: "-0.0000012344", expect: "-1.234 μFIL"}, + {fil: "-0.00000123444", expect: "-1.234 μFIL"}, + + {fil: "-0.0002212", expect: "-221.2 μFIL"}, + {fil: "-0.00022123", expect: "-221.23 μFIL"}, + {fil: "-0.000221234", expect: "-221.234 μFIL"}, + {fil: "-0.0002212344", expect: "-221.234 μFIL"}, + {fil: "-0.00022123444", expect: "-221.234 μFIL"}, + } { + s := s + t.Run(s.fil, func(t *testing.T) { + f, err := ParseFIL(s.fil) + require.NoError(t, err) + require.Equal(t, s.expect, f.Short()) + }) + } +} diff --git a/chain/types/keystore.go b/chain/types/keystore.go index 76eb5f296..8e8d9192b 100644 --- a/chain/types/keystore.go +++ b/chain/types/keystore.go @@ -1,7 +1,10 @@ package types import ( + "encoding/json" "fmt" + + "github.com/filecoin-project/go-state-types/crypto" ) var ( @@ -9,9 +12,53 @@ var ( ErrKeyExists = fmt.Errorf("key already exists") ) +// KeyType defines a type of a key +type KeyType string + +func (kt *KeyType) UnmarshalJSON(bb []byte) error { + { + // first option, try unmarshaling as string + var s string + err := json.Unmarshal(bb, &s) + if err == nil { + *kt = KeyType(s) + return nil + } + } + + { + var b byte + err := json.Unmarshal(bb, &b) + if err != nil { + return fmt.Errorf("could not unmarshal KeyType either as string nor integer: %w", err) + } + bst := crypto.SigType(b) + + switch bst { + case crypto.SigTypeBLS: + *kt = KTBLS + case crypto.SigTypeSecp256k1: + *kt = KTSecp256k1 + case crypto.SigTypeDelegated: + *kt = KTDelegated + default: + return fmt.Errorf("unknown sigtype: %d", bst) + } + log.Warnf("deprecation: integer style 'KeyType' is deprecated, switch to string style") + return nil + } +} + +const ( + KTBLS KeyType = "bls" + KTSecp256k1 KeyType = "secp256k1" + KTSecp256k1Ledger KeyType = "secp256k1-ledger" + KTDelegated KeyType = "delegated" +) + // KeyInfo is used for storing keys in KeyStore type KeyInfo struct { - Type string + Type KeyType PrivateKey []byte } diff --git a/chain/types/message.go b/chain/types/message.go index 0441eacca..473289ead 100644 --- a/chain/types/message.go +++ b/chain/types/message.go @@ -2,17 +2,19 @@ package types import ( "bytes" + "encoding/json" "fmt" - "github.com/filecoin-project/lotus/build" - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/abi/big" block "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" - "github.com/multiformats/go-multihash" - xerrors "golang.org/x/xerrors" + "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" + + "github.com/filecoin-project/lotus/build" ) const MessageVersion = 0 @@ -21,40 +23,38 @@ type ChainMsg interface { Cid() cid.Cid VMMessage() *Message ToStorageBlock() (block.Block, error) + // FIXME: This is the *message* length, this name is misleading. ChainLength() int } type Message struct { - Version int64 + Version uint64 To address.Address From address.Address Nonce uint64 - Value BigInt + Value abi.TokenAmount - GasPrice BigInt - GasLimit int64 + GasLimit int64 + GasFeeCap abi.TokenAmount + GasPremium abi.TokenAmount Method abi.MethodNum Params []byte } -func (t *Message) BlockMiner() address.Address { - panic("implement me") +func (m *Message) Caller() address.Address { + return m.From } -func (t *Message) Caller() address.Address { - return t.From +func (m *Message) Receiver() address.Address { + return m.To } -func (t *Message) Receiver() address.Address { - return t.To -} - -func (t *Message) ValueReceived() abi.TokenAmount { - return t.Value +func (m *Message) ValueReceived() abi.TokenAmount { + return m.Value } func DecodeMessage(b []byte) (*Message, error) { @@ -92,8 +92,7 @@ func (m *Message) ToStorageBlock() (block.Block, error) { return nil, err } - pref := cid.NewPrefixV1(cid.DagCBOR, multihash.BLAKE2B_MIN+31) - c, err := pref.Sum(data) + c, err := abi.CidBuilder.Sum(data) if err != nil { return nil, err } @@ -110,11 +109,22 @@ func (m *Message) Cid() cid.Cid { return b.Cid() } +type mCid struct { + *RawMessage + CID cid.Cid +} + +type RawMessage Message + +func (m *Message) MarshalJSON() ([]byte, error) { + return json.Marshal(&mCid{ + RawMessage: (*RawMessage)(m), + CID: m.Cid(), + }) +} + func (m *Message) RequiredFunds() BigInt { - return BigAdd( - m.Value, - BigMul(m.GasPrice, NewInt(uint64(m.GasLimit))), - ) + return BigMul(m.GasFeeCap, NewInt(uint64(m.GasLimit))) } func (m *Message) VMMessage() *Message { @@ -125,7 +135,18 @@ func (m *Message) Equals(o *Message) bool { return m.Cid() == o.Cid() } -func (m *Message) ValidForBlockInclusion(minGas int64) error { +func (m *Message) EqualCall(o *Message) bool { + m1 := *m + m2 := *o + + m1.GasLimit, m2.GasLimit = 0, 0 + m1.GasFeeCap, m2.GasFeeCap = big.Zero(), big.Zero() + m1.GasPremium, m2.GasPremium = big.Zero(), big.Zero() + + return (&m1).Equals(&m2) +} + +func (m *Message) ValidForBlockInclusion(minGas int64, version network.Version) error { if m.Version != 0 { return xerrors.New("'Version' unsupported") } @@ -134,10 +155,26 @@ func (m *Message) ValidForBlockInclusion(minGas int64) error { return xerrors.New("'To' address cannot be empty") } + if m.To == build.ZeroAddress && version >= network.Version7 { + return xerrors.New("invalid 'To' address") + } + + if !abi.AddressValidForNetworkVersion(m.To, version) { + return xerrors.New("'To' address protocol unsupported for network version") + } + if m.From == address.Undef { return xerrors.New("'From' address cannot be empty") } + if !abi.AddressValidForNetworkVersion(m.From, version) { + return xerrors.New("'From' address protocol unsupported for network version") + } + + if m.Value.Int == nil { + return xerrors.New("'Value' cannot be nil") + } + if m.Value.LessThan(big.Zero()) { return xerrors.New("'Value' field cannot be negative") } @@ -146,18 +183,58 @@ func (m *Message) ValidForBlockInclusion(minGas int64) error { return xerrors.New("'Value' field cannot be greater than total filecoin supply") } - if m.GasPrice.LessThan(big.Zero()) { - return xerrors.New("'GasPrice' field cannot be negative") + if m.GasFeeCap.Int == nil { + return xerrors.New("'GasFeeCap' cannot be nil") + } + + if m.GasFeeCap.LessThan(big.Zero()) { + return xerrors.New("'GasFeeCap' field cannot be negative") + } + + if m.GasPremium.Int == nil { + return xerrors.New("'GasPremium' cannot be nil") + } + + if m.GasPremium.LessThan(big.Zero()) { + return xerrors.New("'GasPremium' field cannot be negative") + } + + if m.GasPremium.GreaterThan(m.GasFeeCap) { + return xerrors.New("'GasFeeCap' less than 'GasPremium'") } if m.GasLimit > build.BlockGasLimit { - return xerrors.New("'GasLimit' field cannot be greater than a block's gas limit") + return xerrors.Errorf("'GasLimit' field cannot be greater than a block's gas limit (%d > %d)", m.GasLimit, build.BlockGasLimit) + } + + if m.GasLimit <= 0 { + return xerrors.Errorf("'GasLimit' field %d must be positive", m.GasLimit) } // since prices might vary with time, this is technically semantic validation if m.GasLimit < minGas { - return xerrors.New("'GasLimit' field cannot be less than the cost of storing a message on chain") + return xerrors.Errorf("'GasLimit' field cannot be less than the cost of storing a message on chain %d < %d", m.GasLimit, minGas) } return nil } + +// EffectiveGasPremium returns the effective gas premium claimable by the miner +// given the supplied base fee. This method is not used anywhere except the Eth API. +// +// Filecoin clamps the gas premium at GasFeeCap - BaseFee, if lower than the +// specified premium. Returns 0 if GasFeeCap is less than BaseFee. +func (m *Message) EffectiveGasPremium(baseFee abi.TokenAmount) abi.TokenAmount { + available := big.Sub(m.GasFeeCap, baseFee) + // It's possible that storage providers may include messages with gasFeeCap less than the baseFee + // In such cases, their reward should be viewed as zero + if available.LessThan(big.NewInt(0)) { + available = big.NewInt(0) + } + if big.Cmp(m.GasPremium, available) <= 0 { + return m.GasPremium + } + return available +} + +const TestGasLimit = 100e6 diff --git a/chain/types/message_fuzz.go b/chain/types/message_fuzz.go index 4ef5f6ba2..b57bfe452 100644 --- a/chain/types/message_fuzz.go +++ b/chain/types/message_fuzz.go @@ -1,4 +1,5 @@ -//+build gofuzz +//go:build gofuzz +// +build gofuzz package types diff --git a/chain/types/message_receipt.go b/chain/types/message_receipt.go index 6671595ff..b0db3b74d 100644 --- a/chain/types/message_receipt.go +++ b/chain/types/message_receipt.go @@ -3,15 +3,59 @@ package types import ( "bytes" - "github.com/filecoin-project/specs-actors/actors/runtime/exitcode" + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-state-types/exitcode" ) +type MessageReceiptVersion byte + +const ( + // MessageReceiptV0 refers to pre FIP-0049 receipts. + MessageReceiptV0 MessageReceiptVersion = 0 + // MessageReceiptV1 refers to post FIP-0049 receipts. + MessageReceiptV1 MessageReceiptVersion = 1 +) + +const EventAMTBitwidth = 5 + type MessageReceipt struct { - ExitCode exitcode.ExitCode - Return []byte - GasUsed int64 + version MessageReceiptVersion + + ExitCode exitcode.ExitCode + Return []byte + GasUsed int64 + EventsRoot *cid.Cid // Root of Event AMT with bitwidth = EventAMTBitwidth +} + +// NewMessageReceiptV0 creates a new pre FIP-0049 receipt with no capability to +// convey events. +func NewMessageReceiptV0(exitcode exitcode.ExitCode, ret []byte, gasUsed int64) MessageReceipt { + return MessageReceipt{ + version: MessageReceiptV0, + ExitCode: exitcode, + Return: ret, + GasUsed: gasUsed, + } +} + +// NewMessageReceiptV1 creates a new pre FIP-0049 receipt with the ability to +// convey events. +func NewMessageReceiptV1(exitcode exitcode.ExitCode, ret []byte, gasUsed int64, eventsRoot *cid.Cid) MessageReceipt { + return MessageReceipt{ + version: MessageReceiptV1, + ExitCode: exitcode, + Return: ret, + GasUsed: gasUsed, + EventsRoot: eventsRoot, + } +} + +func (mr *MessageReceipt) Version() MessageReceiptVersion { + return mr.version } func (mr *MessageReceipt) Equals(o *MessageReceipt) bool { - return mr.ExitCode == o.ExitCode && bytes.Equal(mr.Return, o.Return) && mr.GasUsed == o.GasUsed + return mr.version == o.version && mr.ExitCode == o.ExitCode && bytes.Equal(mr.Return, o.Return) && mr.GasUsed == o.GasUsed && + (mr.EventsRoot == o.EventsRoot || (mr.EventsRoot != nil && o.EventsRoot != nil && *mr.EventsRoot == *o.EventsRoot)) } diff --git a/chain/types/message_receipt_cbor.go b/chain/types/message_receipt_cbor.go new file mode 100644 index 000000000..e1364e654 --- /dev/null +++ b/chain/types/message_receipt_cbor.go @@ -0,0 +1,359 @@ +package types + +import ( + "fmt" + "io" + + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-state-types/cbor" + "github.com/filecoin-project/go-state-types/exitcode" +) + +// This file contains custom CBOR serde logic to deal with the new versioned +// MessageReceipt resulting from the introduction of actor events (FIP-0049). + +type messageReceiptV0 struct{ *MessageReceipt } + +type messageReceiptV1 struct{ *MessageReceipt } + +func (mr *MessageReceipt) MarshalCBOR(w io.Writer) error { + if mr == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + var m cbor.Marshaler + switch mr.version { + case MessageReceiptV0: + m = &messageReceiptV0{mr} + case MessageReceiptV1: + m = &messageReceiptV1{mr} + default: + return xerrors.Errorf("invalid message receipt version: %d", mr.version) + } + + return m.MarshalCBOR(w) +} + +func (mr *MessageReceipt) UnmarshalCBOR(r io.Reader) (err error) { + *mr = MessageReceipt{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + var u cbor.Unmarshaler + switch extra { + case 3: + mr.version = MessageReceiptV0 + u = &messageReceiptV0{mr} + case 4: + mr.version = MessageReceiptV1 + u = &messageReceiptV1{mr} + default: + return fmt.Errorf("cbor input had wrong number of fields") + } + + // Ok to pass a CBOR reader since cbg.NewCborReader will return itself when + // already a CBOR reader. + return u.UnmarshalCBOR(cr) +} + +var lengthBufAMessageReceiptV0 = []byte{131} + +func (t *messageReceiptV0) MarshalCBOR(w io.Writer) error { + // eliding null check since nulls were already handled in the dispatcher + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufAMessageReceiptV0); err != nil { + return err + } + + // t.ExitCode (exitcode.ExitCode) (int64) + if t.ExitCode >= 0 { + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.ExitCode)); err != nil { + return err + } + } else { + if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.ExitCode-1)); err != nil { + return err + } + } + + // t.Return ([]uint8) (slice) + if len(t.Return) > cbg.ByteArrayMaxLen { + return xerrors.Errorf("Byte array in field t.Return was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajByteString, uint64(len(t.Return))); err != nil { + return err + } + + if _, err := cw.Write(t.Return[:]); err != nil { + return err + } + + // t.GasUsed (int64) (int64) + if t.GasUsed >= 0 { + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.GasUsed)); err != nil { + return err + } + } else { + if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.GasUsed-1)); err != nil { + return err + } + } + return nil +} + +func (t *messageReceiptV0) UnmarshalCBOR(r io.Reader) (err error) { + cr := cbg.NewCborReader(r) + + // t.ExitCode (exitcode.ExitCode) (int64) + { + maj, extra, err := cr.ReadHeader() + var extraI int64 + if err != nil { + return err + } + switch maj { + case cbg.MajUnsignedInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 positive overflow") + } + case cbg.MajNegativeInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 negative oveflow") + } + extraI = -1 - extraI + default: + return fmt.Errorf("wrong type for int64 field: %d", maj) + } + + t.ExitCode = exitcode.ExitCode(extraI) + } + // t.Return ([]uint8) (slice) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + + if extra > cbg.ByteArrayMaxLen { + return fmt.Errorf("t.Return: byte array too large (%d)", extra) + } + if maj != cbg.MajByteString { + return fmt.Errorf("expected byte array") + } + + if extra > 0 { + t.Return = make([]uint8, extra) + } + + if _, err := io.ReadFull(cr, t.Return[:]); err != nil { + return err + } + // t.GasUsed (int64) (int64) + { + maj, extra, err := cr.ReadHeader() + var extraI int64 + if err != nil { + return err + } + switch maj { + case cbg.MajUnsignedInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 positive overflow") + } + case cbg.MajNegativeInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 negative oveflow") + } + extraI = -1 - extraI + default: + return fmt.Errorf("wrong type for int64 field: %d", maj) + } + + t.GasUsed = extraI + } + return nil +} + +var lengthBufBMessageReceiptV1 = []byte{132} + +func (t *messageReceiptV1) MarshalCBOR(w io.Writer) error { + // eliding null check since nulls were already handled in the dispatcher + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufBMessageReceiptV1); err != nil { + return err + } + + // t.ExitCode (exitcode.ExitCode) (int64) + if t.ExitCode >= 0 { + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.ExitCode)); err != nil { + return err + } + } else { + if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.ExitCode-1)); err != nil { + return err + } + } + + // t.Return ([]uint8) (slice) + if len(t.Return) > cbg.ByteArrayMaxLen { + return xerrors.Errorf("Byte array in field t.Return was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajByteString, uint64(len(t.Return))); err != nil { + return err + } + + if _, err := cw.Write(t.Return[:]); err != nil { + return err + } + + // t.GasUsed (int64) (int64) + if t.GasUsed >= 0 { + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.GasUsed)); err != nil { + return err + } + } else { + if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.GasUsed-1)); err != nil { + return err + } + } + + // t.EventsRoot (cid.Cid) (struct) + + if t.EventsRoot == nil { + if _, err := cw.Write(cbg.CborNull); err != nil { + return err + } + } else { + if err := cbg.WriteCid(cw, *t.EventsRoot); err != nil { + return xerrors.Errorf("failed to write cid field t.EventsRoot: %w", err) + } + } + + return nil +} + +func (t *messageReceiptV1) UnmarshalCBOR(r io.Reader) (err error) { + cr := cbg.NewCborReader(r) + + // t.ExitCode (exitcode.ExitCode) (int64) + { + maj, extra, err := cr.ReadHeader() + var extraI int64 + if err != nil { + return err + } + switch maj { + case cbg.MajUnsignedInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 positive overflow") + } + case cbg.MajNegativeInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 negative oveflow") + } + extraI = -1 - extraI + default: + return fmt.Errorf("wrong type for int64 field: %d", maj) + } + + t.ExitCode = exitcode.ExitCode(extraI) + } + // t.Return ([]uint8) (slice) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + + if extra > cbg.ByteArrayMaxLen { + return fmt.Errorf("t.Return: byte array too large (%d)", extra) + } + if maj != cbg.MajByteString { + return fmt.Errorf("expected byte array") + } + + if extra > 0 { + t.Return = make([]uint8, extra) + } + + if _, err := io.ReadFull(cr, t.Return[:]); err != nil { + return err + } + // t.GasUsed (int64) (int64) + { + maj, extra, err := cr.ReadHeader() + var extraI int64 + if err != nil { + return err + } + switch maj { + case cbg.MajUnsignedInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 positive overflow") + } + case cbg.MajNegativeInt: + extraI = int64(extra) + if extraI < 0 { + return fmt.Errorf("int64 negative oveflow") + } + extraI = -1 - extraI + default: + return fmt.Errorf("wrong type for int64 field: %d", maj) + } + + t.GasUsed = extraI + } + // t.EventsRoot (cid.Cid) (struct) + + { + + b, err := cr.ReadByte() + if err != nil { + return err + } + if b != cbg.CborNull[0] { + if err := cr.UnreadByte(); err != nil { + return err + } + + c, err := cbg.ReadCid(cr) + if err != nil { + return xerrors.Errorf("failed to read cid field t.EventsRoot: %w", err) + } + + t.EventsRoot = &c + } + + } + return nil +} diff --git a/chain/types/message_receipt_test.go b/chain/types/message_receipt_test.go new file mode 100644 index 000000000..f0b341f55 --- /dev/null +++ b/chain/types/message_receipt_test.go @@ -0,0 +1,75 @@ +package types + +import ( + "bytes" + "encoding/hex" + "testing" + + "github.com/ipfs/go-cid" + "github.com/stretchr/testify/assert" +) + +func TestMessageReceiptSerdeRoundrip(t *testing.T) { + var ( + assert = assert.New(t) + buf = new(bytes.Buffer) + err error + ) + + randomCid, err := cid.Decode("bafy2bzacecu7n7wbtogznrtuuvf73dsz7wasgyneqasksdblxupnyovmtwxxu") + assert.NoError(err) + + // + // Version 0 + // + mr := NewMessageReceiptV0(0, []byte{0x00, 0x01, 0x02, 0x04}, 42) + + // marshal + err = mr.MarshalCBOR(buf) + assert.NoError(err) + + t.Logf("version 0: %s\n", hex.EncodeToString(buf.Bytes())) + + // unmarshal + var mr2 MessageReceipt + err = mr2.UnmarshalCBOR(buf) + assert.NoError(err) + assert.Equal(mr, mr2) + + // version 0 with an events root -- should not serialize the events root! + mr.EventsRoot = &randomCid + + buf.Reset() + + // marshal + err = mr.MarshalCBOR(buf) + assert.NoError(err) + + t.Logf("version 0 (with root): %s\n", hex.EncodeToString(buf.Bytes())) + + // unmarshal + mr2 = MessageReceipt{} + err = mr2.UnmarshalCBOR(buf) + assert.NoError(err) + assert.NotEqual(mr, mr2) + assert.Nil(mr2.EventsRoot) + + // + // Version 1 + // + buf.Reset() + mr = NewMessageReceiptV1(0, []byte{0x00, 0x01, 0x02, 0x04}, 42, &randomCid) + + // marshal + err = mr.MarshalCBOR(buf) + assert.NoError(err) + + t.Logf("version 1: %s\n", hex.EncodeToString(buf.Bytes())) + + // unmarshal + mr2 = MessageReceipt{} + err = mr2.UnmarshalCBOR(buf) + assert.NoError(err) + assert.Equal(mr, mr2) + assert.NotNil(mr2.EventsRoot) +} diff --git a/chain/types/message_test.go b/chain/types/message_test.go new file mode 100644 index 000000000..abb9c946e --- /dev/null +++ b/chain/types/message_test.go @@ -0,0 +1,146 @@ +// stm: #unit +package types + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/crypto" + + // we can't import the actors shims from this package due to cyclic imports. + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" +) + +func TestEqualCall(t *testing.T) { + m1 := &Message{ + To: builtin2.StoragePowerActorAddr, + From: builtin2.SystemActorAddr, + Nonce: 34, + Value: big.Zero(), + + GasLimit: 123, + GasFeeCap: big.NewInt(234), + GasPremium: big.NewInt(234), + + Method: 6, + Params: []byte("hai"), + } + + m2 := &Message{ + To: builtin2.StoragePowerActorAddr, + From: builtin2.SystemActorAddr, + Nonce: 34, + Value: big.Zero(), + + GasLimit: 1236, // changed + GasFeeCap: big.NewInt(234), + GasPremium: big.NewInt(234), + + Method: 6, + Params: []byte("hai"), + } + + m3 := &Message{ + To: builtin2.StoragePowerActorAddr, + From: builtin2.SystemActorAddr, + Nonce: 34, + Value: big.Zero(), + + GasLimit: 123, + GasFeeCap: big.NewInt(4524), // changed + GasPremium: big.NewInt(234), + + Method: 6, + Params: []byte("hai"), + } + + m4 := &Message{ + To: builtin2.StoragePowerActorAddr, + From: builtin2.SystemActorAddr, + Nonce: 34, + Value: big.Zero(), + + GasLimit: 123, + GasFeeCap: big.NewInt(4524), + GasPremium: big.NewInt(234), + + Method: 5, // changed + Params: []byte("hai"), + } + + //stm: @TYPES_MESSAGE_EQUAL_CALL_001 + require.True(t, m1.EqualCall(m2)) + require.True(t, m1.EqualCall(m3)) + require.False(t, m1.EqualCall(m4)) +} + +func TestMessageJson(t *testing.T) { + m := &Message{ + To: builtin2.StoragePowerActorAddr, + From: builtin2.SystemActorAddr, + Nonce: 34, + Value: big.Zero(), + + GasLimit: 123, + GasFeeCap: big.NewInt(234), + GasPremium: big.NewInt(234), + + Method: 6, + Params: []byte("hai"), + } + + b, err := json.Marshal(m) + require.NoError(t, err) + + exp := []byte("{\"Version\":0,\"To\":\"f04\",\"From\":\"f00\",\"Nonce\":34,\"Value\":\"0\",\"GasLimit\":123,\"GasFeeCap\":\"234\",\"GasPremium\":\"234\",\"Method\":6,\"Params\":\"aGFp\",\"CID\":{\"/\":\"bafy2bzaced5rdpz57e64sc7mdwjn3blicglhpialnrph2dlbufhf6iha63dmc\"}}") + fmt.Println(string(b)) + + //stm: @TYPES_MESSAGE_JSON_EQUAL_CALL_001 + require.Equal(t, exp, b) + + var um Message + require.NoError(t, json.Unmarshal(b, &um)) + + //stm: @TYPES_MESSAGE_JSON_EQUAL_CALL_002 + require.EqualValues(t, *m, um) +} + +func TestSignedMessageJson(t *testing.T) { + m := Message{ + To: builtin2.StoragePowerActorAddr, + From: builtin2.SystemActorAddr, + Nonce: 34, + Value: big.Zero(), + + GasLimit: 123, + GasFeeCap: big.NewInt(234), + GasPremium: big.NewInt(234), + + Method: 6, + Params: []byte("hai"), + } + + sm := &SignedMessage{ + Message: m, + Signature: crypto.Signature{}, + } + + b, err := json.Marshal(sm) + require.NoError(t, err) + + exp := []byte("{\"Message\":{\"Version\":0,\"To\":\"f04\",\"From\":\"f00\",\"Nonce\":34,\"Value\":\"0\",\"GasLimit\":123,\"GasFeeCap\":\"234\",\"GasPremium\":\"234\",\"Method\":6,\"Params\":\"aGFp\",\"CID\":{\"/\":\"bafy2bzaced5rdpz57e64sc7mdwjn3blicglhpialnrph2dlbufhf6iha63dmc\"}},\"Signature\":{\"Type\":0,\"Data\":null},\"CID\":{\"/\":\"bafy2bzacea5ainifngxj3rygaw2hppnyz2cw72x5pysqty2x6dxmjs5qg2uus\"}}") + fmt.Println(string(b)) + + //stm: @TYPES_MESSAGE_JSON_EQUAL_CALL_001 + require.Equal(t, exp, b) + + var um SignedMessage + require.NoError(t, json.Unmarshal(b, &um)) + + //stm: @TYPES_MESSAGE_JSON_EQUAL_CALL_002 + require.EqualValues(t, *sm, um) +} diff --git a/chain/types/mock/chain.go b/chain/types/mock/chain.go index 5154ab115..dcbcd8536 100644 --- a/chain/types/mock/chain.go +++ b/chain/types/mock/chain.go @@ -3,12 +3,16 @@ package mock import ( "context" "fmt" + "math/rand" - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/crypto" "github.com/ipfs/go-cid" + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/crypto" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/wallet" ) @@ -21,17 +25,10 @@ func Address(i uint64) address.Address { return a } -func MkMessage(from, to address.Address, nonce uint64, w *wallet.Wallet) *types.SignedMessage { - msg := &types.Message{ - To: to, - From: from, - Value: types.NewInt(1), - Nonce: nonce, - GasLimit: 1, - GasPrice: types.NewInt(0), - } +func MkMessage(from, to address.Address, nonce uint64, w *wallet.LocalWallet) *types.SignedMessage { + msg := UnsignedMessage(from, to, nonce) - sig, err := w.Sign(context.TODO(), from, msg.Cid().Bytes()) + sig, err := w.WalletSign(context.TODO(), from, msg.Cid().Bytes(), api.MsgMeta{}) if err != nil { panic(err) } @@ -49,12 +46,19 @@ func MkBlock(parents *types.TipSet, weightInc uint64, ticketNonce uint64) *types panic(err) } + pstateRoot := c + if parents != nil { + pstateRoot = parents.Blocks()[0].ParentStateRoot + } + var pcids []cid.Cid var height abi.ChainEpoch weight := types.NewInt(weightInc) + var timestamp uint64 if parents != nil { pcids = parents.Cids() height = parents.Height() + 1 + timestamp = parents.MinTimestamp() + build.BlockDelaySecs weight = types.BigAdd(parents.Blocks()[0].ParentWeight, weight) } @@ -72,8 +76,10 @@ func MkBlock(parents *types.TipSet, weightInc uint64, ticketNonce uint64) *types ParentWeight: weight, Messages: c, Height: height, - ParentStateRoot: c, + Timestamp: timestamp, + ParentStateRoot: pstateRoot, BlockSig: &crypto.Signature{Type: crypto.SigTypeBLS, Data: []byte("boo! im a signature")}, + ParentBaseFee: types.NewInt(uint64(build.MinimumBaseFee)), } } @@ -84,3 +90,35 @@ func TipSet(blks ...*types.BlockHeader) *types.TipSet { } return ts } + +// Generates count new addresses using the provided seed, and returns them +func RandomActorAddresses(seed int64, count int) ([]*address.Address, error) { + randAddrs := make([]*address.Address, count) + source := rand.New(rand.NewSource(seed)) + for i := 0; i < count; i++ { + bytes := make([]byte, 32) + _, err := source.Read(bytes) + if err != nil { + return nil, err + } + + addr, err := address.NewActorAddress(bytes) + if err != nil { + return nil, err + } + randAddrs[i] = &addr + } + return randAddrs, nil +} + +func UnsignedMessage(from, to address.Address, nonce uint64) *types.Message { + return &types.Message{ + To: to, + From: from, + Value: types.NewInt(1), + Nonce: nonce, + GasLimit: 1000000, + GasFeeCap: types.NewInt(100), + GasPremium: types.NewInt(1), + } +} diff --git a/chain/types/mpool.go b/chain/types/mpool.go new file mode 100644 index 000000000..497d5c590 --- /dev/null +++ b/chain/types/mpool.go @@ -0,0 +1,22 @@ +package types + +import ( + "time" + + "github.com/filecoin-project/go-address" +) + +type MpoolConfig struct { + PriorityAddrs []address.Address + SizeLimitHigh int + SizeLimitLow int + ReplaceByFeeRatio Percent + PruneCooldown time.Duration + GasLimitOverestimation float64 +} + +func (mc *MpoolConfig) Clone() *MpoolConfig { + r := new(MpoolConfig) + *r = *mc + return r +} diff --git a/chain/types/percent.go b/chain/types/percent.go new file mode 100644 index 000000000..858d9a2e3 --- /dev/null +++ b/chain/types/percent.go @@ -0,0 +1,39 @@ +package types + +import ( + "fmt" + "math" + "strconv" + + "golang.org/x/xerrors" +) + +// Percent stores a signed percentage as an int64. When converted to a string (or json), it's stored +// as a decimal with two places (e.g., 100% -> 1.00). +type Percent int64 + +func (p Percent) String() string { + abs := p + sign := "" + if abs < 0 { + abs = -abs + sign = "-" + } + return fmt.Sprintf(`%s%d.%d`, sign, abs/100, abs%100) +} + +func (p Percent) MarshalJSON() ([]byte, error) { + return []byte(p.String()), nil +} + +func (p *Percent) UnmarshalJSON(b []byte) error { + flt, err := strconv.ParseFloat(string(b)+"e2", 64) + if err != nil { + return xerrors.Errorf("unable to parse ratio %s: %w", string(b), err) + } + if math.Trunc(flt) != flt { + return xerrors.Errorf("ratio may only have two decimals: %s", string(b)) + } + *p = Percent(flt) + return nil +} diff --git a/chain/types/percent_test.go b/chain/types/percent_test.go new file mode 100644 index 000000000..7364c2447 --- /dev/null +++ b/chain/types/percent_test.go @@ -0,0 +1,34 @@ +package types + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestPercent(t *testing.T) { + for _, tc := range []struct { + p Percent + s string + }{ + {100, "1.0"}, + {111, "1.11"}, + {12, "0.12"}, + {-12, "-0.12"}, + {1012, "10.12"}, + {-1012, "-10.12"}, + {0, "0.0"}, + } { + tc := tc + t.Run(fmt.Sprintf("%d <> %s", tc.p, tc.s), func(t *testing.T) { + m, err := tc.p.MarshalJSON() + require.NoError(t, err) + require.Equal(t, tc.s, string(m)) + var p Percent + require.NoError(t, p.UnmarshalJSON([]byte(tc.s))) + require.Equal(t, tc.p, p) + }) + } + +} diff --git a/chain/types/signature_test.go b/chain/types/signature_test.go index 751f55252..abc7a369b 100644 --- a/chain/types/signature_test.go +++ b/chain/types/signature_test.go @@ -1,13 +1,15 @@ +// stm: #unit package types import ( "bytes" "testing" - "github.com/filecoin-project/specs-actors/actors/crypto" + "github.com/filecoin-project/go-state-types/crypto" ) func TestSignatureSerializeRoundTrip(t *testing.T) { + //stm: @CHAIN_TYPES_SIGNATURE_SERIALIZATION_001 s := &crypto.Signature{ Data: []byte("foo bar cat dog"), Type: crypto.SigTypeBLS, diff --git a/chain/types/signedmessage.go b/chain/types/signedmessage.go index 802312ba9..168531714 100644 --- a/chain/types/signedmessage.go +++ b/chain/types/signedmessage.go @@ -2,25 +2,26 @@ package types import ( "bytes" + "encoding/json" - "github.com/filecoin-project/specs-actors/actors/crypto" block "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" - "github.com/multiformats/go-multihash" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/crypto" ) -func (m *SignedMessage) ToStorageBlock() (block.Block, error) { - if m.Signature.Type == crypto.SigTypeBLS { - return m.Message.ToStorageBlock() +func (sm *SignedMessage) ToStorageBlock() (block.Block, error) { + if sm.Signature.Type == crypto.SigTypeBLS { + return sm.Message.ToStorageBlock() } - data, err := m.Serialize() + data, err := sm.Serialize() if err != nil { return nil, err } - pref := cid.NewPrefixV1(cid.DagCBOR, multihash.BLAKE2B_MIN+31) - c, err := pref.Sum(data) + c, err := abi.CidBuilder.Sum(data) if err != nil { return nil, err } @@ -28,12 +29,12 @@ func (m *SignedMessage) ToStorageBlock() (block.Block, error) { return block.NewBlockWithCid(data, c) } -func (m *SignedMessage) Cid() cid.Cid { - if m.Signature.Type == crypto.SigTypeBLS { - return m.Message.Cid() +func (sm *SignedMessage) Cid() cid.Cid { + if sm.Signature.Type == crypto.SigTypeBLS { + return sm.Message.Cid() } - sb, err := m.ToStorageBlock() + sb, err := sm.ToStorageBlock() if err != nil { panic(err) } @@ -63,8 +64,29 @@ func (sm *SignedMessage) Serialize() ([]byte, error) { return buf.Bytes(), nil } -func (m *SignedMessage) ChainLength() int { - ser, err := m.Serialize() +type smCid struct { + *RawSignedMessage + CID cid.Cid +} + +type RawSignedMessage SignedMessage + +func (sm *SignedMessage) MarshalJSON() ([]byte, error) { + return json.Marshal(&smCid{ + RawSignedMessage: (*RawSignedMessage)(sm), + CID: sm.Cid(), + }) +} + +func (sm *SignedMessage) ChainLength() int { + var ser []byte + var err error + if sm.Signature.Type == crypto.SigTypeBLS { + // BLS chain message length doesn't include signature + ser, err = sm.Message.Serialize() + } else { + ser, err = sm.Serialize() + } if err != nil { panic(err) } diff --git a/chain/types/state.go b/chain/types/state.go new file mode 100644 index 000000000..37757f362 --- /dev/null +++ b/chain/types/state.go @@ -0,0 +1,34 @@ +package types + +import "github.com/ipfs/go-cid" + +// StateTreeVersion is the version of the state tree itself, independent of the +// network version or the actors version. +type StateTreeVersion uint64 + +const ( + // StateTreeVersion0 corresponds to actors < v2. + StateTreeVersion0 StateTreeVersion = iota + // StateTreeVersion1 corresponds to actors v2 + StateTreeVersion1 + // StateTreeVersion2 corresponds to actors v3. + StateTreeVersion2 + // StateTreeVersion3 corresponds to actors v4. + StateTreeVersion3 + // StateTreeVersion4 corresponds to actors v5 and above. + StateTreeVersion4 + // StateTreeVersion5 corresponds to actors v10 and above. + StateTreeVersion5 +) + +type StateRoot struct { + // State tree version. + Version StateTreeVersion + // Actors tree. The structure depends on the state root version. + Actors cid.Cid + // Info. The structure depends on the state root version. + Info cid.Cid +} + +// TODO: version this. +type StateInfo0 struct{} diff --git a/chain/types/testdata/TestExpFunction.golden b/chain/types/testdata/TestExpFunction.golden new file mode 100644 index 000000000..1fb931874 --- /dev/null +++ b/chain/types/testdata/TestExpFunction.golden @@ -0,0 +1,257 @@ +x, y +0,115792089237316195423570985008687907853269984665640564039457584007913129639936 +2270433122300317557324921274680155055946470287561579687048187921723786855685,113543770489016942962237377411819033281128847358465443152965692667674515268459 +4540866244600635114649842549360310111892940575123159374096375843447573711370,111339107030360092669625707586928312989271150877369072427721155459215537208890 +6811299366900952671974763824040465167839410862684739061144563765171360567055,109177251212712547631698034978205369781807498766451007885527881500477232021682 +9081732489201270229299685098720620223785881150246318748192751686895147422740,107057371846115774238270822835274977319670170085938012474442950429894883628816 +11352165611501587786624606373400775279732351437807898435240939608618934278425,104978653879710027736182386389791146063108246474333192437116903221506365793784 +13622598733801905343949527648080930335678821725369478122289127530342721134110,102940298088363735750708779071187005425195044826988440514290336074628648838684 +15893031856102222901274448922761085391625292012931057809337315452066507989795,100941520765387555093691898043462296359944447036680097792913901290457279892923 +18163464978402540458599370197441240447571762300492637496385503373790294845480,98981553421214956610998073555828635758296463224923810238433739306701915887016 +20433898100702858015924291472121395503518232588054217183433691295514081701165,97059642487933486831615191223039105587754475193940197594806835165639555904556 +22704331223003175573249212746801550559464702875615796870481879217237868556850,95175049029553104647921939291264669504212075519331704270982994731418832879574 +24974764345303493130574134021481705615411173163177376557530067138961655412535,93327048457900197046298852001610623840008858609979636336358094295509875743050 +27245197467603810687899055296161860671357643450738956244578255060685442268220,91514930254028040867379060937212497464993417057199803702713818093495592131219 +29515630589904128245223976570842015727304113738300535931626442982409229123905,89737997695036598537471612414728492361735134837457880776578397164411259714034 +31786063712204445802548897845522170783250584025862115618674630904133015979590,87995567586196615492499061341239298336716207707733388009305962491415997438251 +34056496834504763359873819120202325839197054313423695305722818825856802835275,86286969998275026412807199079440137158468693464163642116594809068619896392941 +36326929956805080917198740394882480895143524600985274992771006747580589690960,84611548009960677185535234025783667242734659094160158991843756961639811974930 +38597363079105398474523661669562635951089994888546854679819194669304376546645,82968657455291330479761642121918798999912566007436555574905312488221049770642 +40867796201405716031848582944242791007036465176108434366867382591028163402330,81357666675984845712317087407692885985045766475399018683682465654293317561467 +43138229323706033589173504218922946062982935463670014053915570512751950258015,79777956278579309738294730369672998880873563557913118175337099978291132200541 +45408662446006351146498425493603101118929405751231593740963758434475737113700,78228918896288743544848199105491032646200262796326433813989850094529077282776 +47679095568306668703823346768283256174875876038793173428011946356199523969385,76709958955482823270730386397827358293917115388940737122493792142598409249118 +49949528690606986261148268042963411230822346326354753115060134277923310825070,75220492446700831714270851931718222334181221472926734650894450187207587804959 +52219961812907303818473189317643566286768816613916332802108322199647097680755,73759946700111799812659669164881767734266809389910877226542646506513000978489 +54490394935207621375798110592323721342715286901477912489156510121370884536440,72327760165334507045772504425420867449416167380982855532448913178137554210923 +56760828057507938933123031867003876398661757189039492176204698043094671392125,70923382195532685995592428750934431740132395987623204573329785474158712116147 +59031261179808256490447953141684031454608227476601071863252885964818458247810,69546272835702420022053453330708655397369524375477499739230911030535808540657 +61301694302108574047772874416364186510554697764162651550301073886542245103495,68195902615070334829829990481057773300819373544625447529491837404705895284329 +63572127424408891605097795691044341566501168051724231237349261808266031959180,66871752343522765217935231891690355471651496064655471661919663456330328641288 +65842560546709209162422716965724496622447638339285810924397449729989818814865,65573312911987628132646843093927406319425698793733957333572673848799954239855 +68112993669009526719747638240404651678394108626847390611445637651713605670550,64300085096692252880128256597394188085029084300287019159255155536716536636582 +70383426791309844277072559515084806734340578914408970298493825573437392526235,63051579367221909582465988028351521656270181701866074076641181901174457665557 +72653859913610161834397480789764961790287049201970549985542013495161179381920,61827315698305238252660159775306665170268657207834096298284442133031571582783 +74924293035910479391722402064445116846233519489532129672590201416884966237605,60626823385254213782224025259478780989246898268785251219150451819890211607196 +77194726158210796949047323339125271902179989777093709359638389338608753093290,59449640862987687230396859494036983576366375788559345059199710577555290424321 +79465159280511114506372244613805426958126460064655289046686577260332539948975,58295315528568921616783939661658889268848452171972763039788768615558483626267 +81735592402811432063697165888485582014072930352216868733734765182056326804660,57163403567188891479248718554448273836403260979137552976694337187145017476501 +84006025525111749621022087163165737070019400639778448420782953103780113660345,56053469781528440285551018178364674618486430618035927413379628478874266581878 +86276458647412067178347008437845892125965870927340028107831141025503900516030,54965087424433688889920273781092319643978134551542030239286748341146003208614 +88546891769712384735671929712526047181912341214901607794879328947227687371715,53897838034840362103953324191045652555242797708181047223345801682385818568835 +90817324892012702292996850987206202237858811502463187481927516868951474227400,52851311276883949594712447365930530107052884187291818795931120263968492084774 +93087758014313019850321772261886357293805281790024767168975704790675261083085,51825104782133842211941756402475821837684219892038783449941692633646927028868 +95358191136613337407646693536566512349751752077586346856023892712399047938770,50818823994890785951863832476942579771969130641977742986737254850253840075592 +97628624258913654964971614811246667405698222365147926543072080634122834794455,49832082020488173548864433257316990617151231489408392527154422207621203727615 +99899057381213972522296536085926822461644692652709506230120268555846621650140,48864499476538848601355411822273410550321020146722594189563317351144555102308 +102169490503514290079621457360606977517591162940271085917168456477570408505825,47915704347070229628266128780655102907390977725210624641985610920484495320871 +104439923625814607636946378635287132573537633227832665604216644399294195361510,46985331839491671953371264029121450743887309752499067780940162596021543960818 +106710356748114925194271299909967287629484103515394245291264832321017982217195,46073024244339074252983177578294403714924665379395655719184742489399140111119 +108980789870415242751596221184647442685430573802955824978313020242741769072880,45178430797742804397094652011443390009353450908460410593202993111115176300509 +111251222992715560308921142459327597741377044090517404665361208164465555928565,44301207546566066275398443059622984529245531090835413454138285364317904357889 +113521656115015877866246063734007752797323514378078984352409396086189342784250,43441017216161856030307536334277824451020867870703344545581645152489753618625 +115792089237316195423570985008687907853269984665640564039457584007913129639935,42597529080697662913911602080197270017224605406643086589378214572018159359656 +118062522359616512980895906283368062909216454953202143726505771929636916495620,41770418835998057231823154500906296232954940017107200954896529163626836852806 +120332955481916830538220827558048217965162925240763723413553959851360703351305,40959368474856275913667539524102962047053738936489506227266772182693729839202 +122603388604217148095545748832728373021109395528325303100602147773084490206990,40164066164766865529760099118055626254866338159841829407810028308443332329921 +124873821726517465652870670107408528077055865815886882787650335694808277062675,39384206128032373421270623986368325593061645265273820139765067969067422649415 +127144254848817783210195591382088683133002336103448462474698523616532063918360,38619488524197990384791697485732970600503064875453399931120765038045198401265 +129414687971118100767520512656768838188948806391010042161746711538255850774045,37869619334768943402646797366590078932213917335501796767692394741802326454228 +131685121093418418324845433931448993244895276678571621848794899459979637629730,37134310250166314581624891999905972285593140562157169782497980837053288139911 +133955554215718735882170355206129148300841746966133201535843087381703424485415,36413278558877823092557676422579316779501436559193491643756131394023796062011 +136225987338019053439495276480809303356788217253694781222891275303427211341100,35706247038760950822158313374858241516488865748339595127547394046982094249410 +138496420460319370996820197755489458412734687541256360909939463225150998196785,35012943850456619981286410714734531636271581473914516202135729910546566402779 +140766853582619688554145119030169613468681157828817940596987651146874785052470,34333102432872442378462629116615524982524168457316936505909372845805604900401 +143037286704920006111470040304849768524627628116379520284035839068598571908155,33666461400695355776019567288676251030810566791286359575983902200864972469996 +145307719827220323668794961579529923580574098403941099971084026990322358763840,33012764443894243004677309675463059806544657868579375264515822376830510918451 +147578152949520641226119882854210078636520568691502679658132214912046145619525,32371760229173894620565861867718452819385944209047587245952825283707541232161 +149848586071820958783444804128890233692467038979064259345180402833769932475210,31743202303342426140951325851524709564919796135818066107980273521807047228205 +152119019194121276340769725403570388748413509266625839032228590755493719330895,31126848998554996579614932864397941484844825641744252641191173459915093805187 +154389452316421593898094646678250543804359979554187418719276778677217506186580,30522463339397396402839881838375473280428264227964104623768547140174704396628 +156659885438721911455419567952930698860306449841748998406324966598941293042265,29929812951773780419644078230146692062203486832083285970050129935848796157421 +158930318561022229012744489227610853916252920129310578093373154520665079897950,29348669973563514777235550444240282773251884769510008252819331832439494645079 +161200751683322546570069410502291008972199390416872157780421342442388866753635,28778810967012787421358795112757003059950944596369120909715112149169964220370 +163471184805622864127394331776971164028145860704433737467469530364112653609320,28220016832827298362763401072714572174637391915333615996330037256359689152007 +165741617927923181684719253051651319084092330991995317154517718285836440465005,27672072725933000121901983367925347694111025283271589335038719296229849510185 +168012051050223499242044174326331474140038801279556896841565906207560227320690,27134767972872500055614403280459447260039099989714465645168626995212978266528 +170282484172523816799369095601011629195985271567118476528614094129284014176375,26607895990805365148558542391218752327077936735384764869356894955189244136002 +172552917294824134356694016875691784251931741854680056215662282051007801032060,26091254208081186520295830628576992670321075391182679321065607327265313932322 +174823350417124451914018938150371939307878212142241635902710469972731587887745,25584643986354865593328765236833491064966760582438881050282097633879965205565 +177093783539424769471343859425052094363824682429803215589758657894455374743430,25087870544214176820510595839294024971107825899184642127021233999936247081507 +179364216661725087028668780699732249419771152717364795276806845816179161599115,24600742882290243310082706540466488496131563143845631581983311961047083061256 +181634649784025404585993701974412404475717623004926374963855033737902948454800,24123073709822131836694888705209306323992339748006300420231840208502026712055 +183905082906325722143318623249092559531664093292487954650903221659626735310485,23654679372647332806338881400061315566868133427664662629105489367609937695646 +186175516028626039700643544523772714587610563580049534337951409581350522166170,23195379782590438967131080837393236404679360225477050241221440433306491390529 +188445949150926357257968465798452869643557033867611114024999597503074309021855,22744998348222874237097258296963376819397584603868543116623643157984253142510 +190716382273226674815293387073133024699503504155172693712047785424798095877540,22303361906967051161228844170125488556352938430940411562982254002868044311034 +192986815395526992372618308347813179755449974442734273399095973346521882733225,21870300658518852415771961233241780589753603014109439052609181643134330131200 +195257248517827309929943229622493334811396444730295853086144161268245669588910,21445648099562838646716735757033316057251336889853858375428467187815522927837 +197527681640127627487268150897173489867342915017857432773192349189969456444595,21029240959755081956656390289742251499446416407847034496990361844628688581109 +199798114762427945044593072171853644923289385305419012460240537111693243300280,20620919138949011730680161329173099008306763464882403634448661640684501661866 +202068547884728262601917993446533799979235855592980592147288725033417030155965,20220525645640137405137620676980023532901299004381393279093890751532452054423 +204338981007028580159242914721213955035182325880542171834336912955140817011650,19827906536605981416712487046651644994469849037549721389552871270682788176368 +206609414129328897716567835995894110091128796168103751521385100876864603867335,19442910857718015103451661807495877546362994006807802531582187045985408388323 +208879847251629215273892757270574265147075266455665331208433288798588390723020,19065390585902840940891643898043637774181614198870274451246241331546097600612 +211150280373929532831217678545254420203021736743226910895481476720312177578705,18695200572230306358460705740514292808692490977651168538902708783481421680348 +213420713496229850388542599819934575258968207030788490582529664642035964434390,18332198486106667663798285266862799262530724876828607112610697476363307765699 +215691146618530167945867521094614730314914677318350070269577852563759751290075,17976244760551347472111336927386090359808454572176186089250363261529225698797 +217961579740830485503192442369294885370861147605911649956626040485483538145760,17627202538536245657534631830721579840416279034844169182844082749512022955880 +220232012863130803060517363643975040426807617893473229643674228407207325001445,17284937620366972373860132250248373607831381821348510576523341628979456457899 +222502445985431120617842284918655195482754088181034809330722416328931111857130,16949318412085772290021286893087219332537721900597190962614965688689436470898 +224772879107731438175167206193335350538700558468596389017770604250654898712815,16620215874876302005383259706474512944760516297587146250989332554945114509504 +227043312230031755732492127468015505594647028756157968704818792172378685568500,16297503475450807802230954908656139014943597886328184429574709348911081384486 +229313745352332073289817048742695660650593499043719548391866980094102472424185,15981057137400628605961920332938849374838761847812884031059653371884698227732 +231584178474632390847141970017375815706539969331281128078915168015826259279870,15670755193491319402603874616100339746216923922257041729716527735529769853650 +233854611596932708404466891292055970762486439618842707765963355937550046135555,15366478338884053550790009737665512422829259009202431332168817628656130747976 +236125044719233025961791812566736125818432909906404287453011543859273832991240,15068109585265318560877642552077774766432262308314225827173090425609387180236 +238395477841533343519116733841416280874379380193965867140059731780997619846925,14775534215867269134414164114591717654313758765518045957328319974951247947864 +240665910963833661076441655116096435930325850481527446827107919702721406702610,14488639741361443696906809527925407527798326175077538816130520515576676432536 +242936344086133978633766576390776590986272320769089026514156107624445193558295,14207315856608886447500721787929418266891341269962575206208292606790558927738 +245206777208434296191091497665456746042218791056650606201204295546168980413980,13931454398250046219817842121237250069017319717552215812396900410729316780042 +247477210330734613748416418940136901098165261344212185888252483467892767269665,13660949303118146325455249570700165024364040604752663984462703166489664742339 +249747643453034931305741340214817056154111731631773765575300671389616554125350,13395696567460036159625482974163434950873419335747629798807509358843043114747 +252018076575335248863066261489497211210058201919335345262348859311340340981035,13135594206948845808872493016734991140070074672980170698797842470835657746355 +254288509697635566420391182764177366266004672206896924949397047233064127836720,12880542217473069333080972366140272174677497922483630681691757178031836728636 +256558942819935883977716104038857521321951142494458504636445235154787914692405,12630442536687000915161971042648362535726831652623366360951945091372138829073 +258829375942236201535041025313537676377897612782020084323493423076511701548090,12385199006307740796619289137869014505391917569864233277491827537392271272526 +261099809064536519092365946588217831433844083069581664010541610998235488403775,12144717335144274958225939593470301902208893969091913127900701796820969916276 +263370242186836836649690867862897986489790553357143243697589798919959275259460,11908905062844413972629474569782724684547574705479145661987238841262047512889 +265640675309137154207015789137578141545737023644704823384637986841683062115145,11677671524345652458077781103683012746985203951843087585351200546458450234583 +267911108431437471764340710412258296601683493932266403071686174763406848970830,11450927815016281205730420816822769656276856651451425159655410140798486590368 +270181541553737789321665631686938451657629964219827982758734362685130635826515,11228586756473349441251594323339849390046255944661352162796836657552528120100 +272451974676038106878990552961618606713576434507389562445782550606854422682200,11010562863064334916605775798811477728189133603960442457397758272811779333449 +274722407798338424436315474236298761769522904794951142132830738528578209537885,10796772308999634710251182117797466377054234368136632046647040836884880709149 +276992840920638741993640395510978916825469375082512721819878926450301996393570,10587132896123239841361951569073852197393979276538146129343090522801499481360 +279263274042939059550965316785659071881415845370074301506927114372025783249255,10381564022309202172514611506897394878877748812065679452742065418299049556809 +281533707165239377108290238060339226937362315657635881193975302293749570104940,10179986650471742679787468858482358928499942961441493648347660820939932354037 +283804140287539694665615159335019381993308785945197460881023490215473356960625,9982323278177086101950544601003257424140532557067992549916807460469069141662 +286074573409840012222940080609699537049255256232759040568071678137197143816310,9788497907845338332079968817743865213174453409259845129758908478094549262272 +288345006532140329780265001884379692105201726520320620255119866058920930671995,9598436017530949774464402361919053565881937627109257874972510208401009770963 +290615439654440647337589923159059847161148196807882199942168053980644717527680,9412064532270530344304969563638312480198627803597895883581869229790285361030 +292885872776740964894914844433740002217094667095443779629216241902368504383365,9229311795986999922973693773512111376663242782368889508055395732551288322377 +295156305899041282452239765708420157273041137383005359316264429824092291239050,9050107543939271981357133004643947981886610395815063538792001543567721697353 +297426739021341600009564686983100312328987607670566939003312617745816078094735,8874382875706877830311960657026508969290082662731915199950324279800475201683 +299697172143641917566889608257780467384934077958128518690360805667539864950420,8702070228699144631140925089885492819072907992531772224458410703022324971560 +301967605265942235124214529532460622440880548245690098377408993589263651806105,8533103352178741979338410836757093105256375792397040329065630149508413086135 +304238038388242552681539450807140777496827018533251678064457181510987438661790,8367417281789609639196736794874998402544481231388500060346139012236641543825 +306508471510542870238864372081820932552773488820813257751505369432711225517475,8204948314579472931243763439936896182045474760835882712220942160049717189792 +308778904632843187796189293356501087608719959108374837438553557354435012373160,8045633984507342433459354572450622071501444572221358150888492135726910593613 +311049337755143505353514214631181242664666429395936417125601745276158799228845,7889413038426581123905040544325858121069927256074308557819634343914244175953 +313319770877443822910839135905861397720612899683497996812649933197882586084530,7736225412534304938490539495998345262722685569683811485718517851478118074806 +315590203999744140468164057180541552776559369971059576499698121119606372940215,7586012209278062013393244296606783131019725342345569324815264018948034488407 +317860637122044458025488978455221707832505840258621156186746309041330159795900,7438715674710911696077991529315597910182131547815478256768516102178053147428 +320131070244344775582813899729901862888452310546182735873794496963053946651585,7294279176286196809531497726711149751773109079006438039328871808860505208647 +322401503366645093140138821004582017944398780833744315560842684884777733507270,7152647181083471707513356405012353952405611456310792512957976292047901188617 +324671936488945410697463742279262173000345251121305895247890872806501520362955,7013765234457214429330978163948031181884804665874684563234080173204818792192 +326942369611245728254788663553942328056291721408867474934939060728225307218640,6877579939100113814604817973991912882261222190269087624747098477473070920910 +329212802733546045812113584828622483112238191696429054621987248649949094074325,6744038934512881834200618407209402738374726406854013386327229734819886355209 +331483235855846363369438506103302638168184661983990634309035436571672880930010,6613090876872697694251640706911864470257281520044099539344107417605448847522 +333753668978146680926763427377982793224131132271552213996083624493396667785695,6484685419292543536070441670407899002441885350144223913319492393155843996609 +336024102100446998484088348652662948280077602559113793683131812415120454641380,6358773192463841844684321602472961808036370534710746466420213566001948927453 +338294535222747316041413269927343103336024072846675373370180000336844241497065,6235305785674952050504565665502249112334426989789636398933745643420892537805 +340564968345047633598738191202023258391970543134236953057228188258568028352750,6114235728198228318918503273647009044057809740115762935228605445446011541536 +342835401467347951156063112476703413447917013421798532744276376180291815208435,5995516471038482226936505302234614253048545365172641788639745380038847011850 +345105834589648268713388033751383568503863483709360112431324564102015602064120,5879102369035832978915788636227029754149990028304128445154065521569679896299 +347376267711948586270712955026063723559809953996921692118372752023739388919805,5764948663316064068243885471976803898564123312018636117461200936317441104963 +349646700834248903828037876300743878615756424284483271805420939945463175775490,5653011464081738901084106683312765416762298565331902697873676865978153014007 +351917133956549221385362797575424033671702894572044851492469127867186962631175,5543247733737458913233334532284737157076797625887278020789776292123991283042 +354187567078849538942687718850104188727649364859606431179517315788910749486860,5435615270342776182191625059155814516167449877265043810529451526313401440000 +356458000201149856500012640124784343783595835147168010866565503710634536342545,5330072691386398513087911834712869898427723411257226595273064182550426763859 +358728433323450174057337561399464498839542305434729590553613691632358323198230,5226579417875448507582021006206716342354483979921442218144570774799799153756 +360998866445750491614662482674144653895488775722291170240661879554082110053915,5125095658733659256765209168041122238133229257032719961413509211200048248333 +363269299568050809171987403948824808951435246009852749927710067475805896909600,5025582395502508078982293698284693835284469532255851267477746214208169030829 +365539732690351126729312325223504964007381716297414329614758255397529683765285,4928001367339406197066635979466804208185522234029567523625554767402211661565 +367810165812651444286637246498185119063328186584975909301806443319253470620970,4832315056307176461496499130904489365080602372384889128573871476885084197963 +370080598934951761843962167772865274119274656872537488988854631240977257476655,4738486672949163220359850565587517062963871431792713253071282139856533845449 +372351032057252079401287089047545429175221127160099068675902819162701044332340,4646480142144428256814095536419820031461458638508982355406532908762559366752 +374621465179552396958612010322225584231167597447660648362951007084424831188025,4556260089237594402171008726677813775125098076724227743627974892272588327070 +376891898301852714515936931596905739287114067735222228049999195006148618043710,4467791826438004029228927006814200361630588836617618519025510750743109199997 +379162331424153032073261852871585894343060538022783807737047382927872404899395,4381041339482963176613798998129770586400722046177381492207741626501677387270 +381432764546453349630586774146266049399007008310345387424095570849596191755080,4295975274559943590489358236239541053798654812625189402565013609912762502063 +383703197668753667187911695420946204454953478597906967111143758771319978610765,4212560925482714534092886286689429135772883215331118745250144375204860994258 +385973630791053984745236616695626359510899948885468546798191946693043765466450,4130766221116473846427146968171682243610648632762217464220432443306744076356 +388244063913354302302561537970306514566846419173030126485240134614767552322135,4050559713047143466628204822445172093558799258185730849489440714923595198306 +390514497035654619859886459244986669622792889460591706172288322536491339177820,3971910563490088516841239430057423861561699540314803552309386818215754652836 +392784930157954937417211380519666824678739359748153285859336510458215126033505,3894788533433611089965680791375046317061917745510095607652825551910399528715 +395055363280255254974536301794346979734685830035714865546384698379938912889190,3819163971012660154769745087149569914465567827492977553371176365210754983049 +397325796402555572531861223069027134790632300323276445233432886301662699744875,3745007800108287504328254434609249402710340257581916107575424870725436120735 +399596229524855890089186144343707289846578770610838024920481074223386486600560,3672291509168466468538116483953888412415082359790291319830667248103068617726 +401866662647156207646511065618387444902525240898399604607529262145110273456245,3600987140245975220983870041875004895679551495743211205866825798183240723922 +404137095769456525203835986893067599958471711185961184294577450066834060311930,3531067278249129967384164433549615728055232151532910541204821495878913836970 +406407528891756842761160908167747755014418181473522763981625637988557847167615,3462505040401235139336450269559039606157745830970264242579700439549733102016 +408677962014057160318485829442427910070364651761084343668673825910281634023300,3395274065904697964555996096535291493010779102468849709076001181212499029926 +410948395136357477875810750717108065126311122048645923355722013832005420878985,3329348505805833474130146144622500207015802447060544079287034222519730000801 +413218828258657795433135671991788220182257592336207503042770201753729207734670,3264703013056463168733957305768762048671220120100675556528510459350361442934 +415489261380958112990460593266468375238204062623769082729818389675452994590355,3201312732768486228946010278262122858837369338972372870253840600423809756146 +417759694503258430547785514541148530294150532911330662416866577597176781446040,3139153292657676348854251985374092135837494969951169782173776159644176534423 +420030127625558748105110435815828685350097003198892242103914765518900568301725,3078200793673030025577310098607138197203259951148621910059374341071023253667 +422300560747859065662435357090508840406043473486453821790962953440624355157410,3018431800808063478119054352864365216599069977484266467250325895140520117178 +424570993870159383219760278365188995461989943774015401478011141362348142013095,2959823334090525324552359078463176696893036195521598690559604864862321028159 +426841426992459700777085199639869150517936414061576981165059329284071928868780,2902352859747060743788555836574429934024784962979281619588506007014730754443 +429111860114760018334410120914549305573882884349138560852107517205795715724465,2845998281539430113506217335511024116647561786738955796723957128032460028562 +431382293237060335891735042189229460629829354636700140539155705127519502580150,2790737932268951075048841664512779579965827455433050874470612581492493796612 +433652726359360653449059963463909615685775824924261720226203893049243289435835,2736550565445897654615705160581380392035754504074875922703396122219838983601 +435923159481660971006384884738589770741722295211823299913252080970967076291520,2683415347120653492731207292836793547250299699949146040965909097366506826314 +438193592603961288563709806013269925797668765499384879600300268892690863147205,2631311847873478425170175781779819578108906286445566029098739049466961949804 +440464025726261606121034727287950080853615235786946459287348456814414650002890,2580220034959808642151093783793953402129918191024990104539947286476194870361 +442734458848561923678359648562630235909561706074508038974396644736138436858575,2530120264608070452133030819123365918858694576649021314235932359705643943273 +445004891970862241235684569837310390965508176362069618661444832657862223714260,2480993274467046314956915988152178555256121842686531405628524104350927660746 +447275325093162558793009491111990546021454646649631198348493020579586010569945,2432820176199889308902969867700028980919093407680036321083074765251991144298 +449545758215462876350334412386670701077401116937192778035541208501309797425630,2385582448221938579601135652712703469128873722647430092259396421200608174744 +451816191337763193907659333661350856133347587224754357722589396423033584281315,2339261928579543607308487238103279986560541266662742536344201858542715699027 +454086624460063511464984254936031011189294057512315937409637584344757371137000,2293840807967159344114184788286120867801944082584440628149280631165005320951 +456357057582363829022309176210711166245240527799877517096685772266481157992685,2249301622880027434993188844156208157845130306302793141117411709623435670173 +458627490704664146579634097485391321301186998087439096783733960188204944848370,2205627248899810866744487578412286757829514684708503434368069205255340039839 +460897923826964464136959018760071476357133468375000676470782148109928731704055,2162800894110600506761016035372877280458113909309651595222790899580498073682 +463168356949264781694283940034751631413079938662562256157830336031652518559740,2120806092642762118940557002260420725877403403643132375546681834433562628493 +465438790071565099251608861309431786469026408950123835844878523953376305415425,2079626698342141596131901285086838588831365589656553686857278748333033108659 +467709223193865416808933782584111941524972879237685415531926711875100092271110,2039246878562194346216395910444043784690021169116503952080024996424232086889 +469979656316165734366258703858792096580919349525246995218974899796823879126795,1999651108076652030782767922491430088269301331012688651267426380085596201366 +472250089438466051923583625133472251636865819812808574906023087718547665982480,1960824163110386199534010054524334089541753293394021687030939529972846447262 +474520522560766369480908546408152406692812290100370154593071275640271452838165,1922751115486173807887555445320207063113604718628558999245364537712059659787 +476790955683066687038233467682832561748758760387931734280119463561995239693850,1885417326885114167166377133451693487795698974827428621915772121522296322640 +479061388805367004595558388957512716804705230675493313967167651483719026549535,1848808443218490573462190057395600374688639714455162176619470154537224426642 +481331821927667322152883310232192871860651700963054893654215839405442813405220,1812910389108912709483102199711379114048329569523931425832014763952606023142 +483602255049967639710208231506873026916598171250616473341264027327166600260905,1777709362478617929951137999560857523114698493464640463314567998578763375930 +485872688172267957267533152781553181972544641538178053028312215248890387116590,1743191829242850741544443083382012278950030439838258806090307410195511152367 +488143121294568274824858074056233337028491111825739632715360403170614173972275,1709344518106280188825408838548340945706907890570414138312511052040629403114 +490413554416868592382182995330913492084437582113301212402408591092337960827960,1676154415460454473592585178001481888630499860343530369736761941087841137938 +492683987539168909939507916605593647140384052400862792089456779014061747683645,1643608760380330981872631688029464454202113613160652114812004614315882448607 +494954420661469227496832837880273802196330522688424371776504966935785534539330,1611695039717957985264493988118541627646396847164138227584506579980006189593 +497224853783769545054157759154953957252276992975985951463553154857509321395015,1580400983291421636207279946257561145576531467125488184028701658324803392105 +499495286906069862611482680429634112308223463263547531150601342779233108250700,1549714559167208504327370397252359435900676480118217197526808984667322295656 +501765720028370180168807601704314267364169933551109110837649530700956895106385,1519623969034169817411698521182954716530148741935550434876916238707975489825 +504036153150670497726132522978994422420116403838670690524697718622680681962070,1490117643667308789561999136522296289524839311469606144758176235295035574077 +506306586272970815283457444253674577476062874126232270211745906544404468817755,1461184238479646954250162028027864509672389242493414237027164373310873876617 +508577019395271132840782365528354732532009344413793849898794094466128255673440,1432812629160459284595696582392048709701342884089101780789506016757983545698 +510847452517571450398107286803034887587955814701355429585842282387852042529125,1404991907398201090242984454401886460001256486743771644647644675643019397120 +513117885639871767955432208077715042643902284988917009272890470309575829384810,1377711376686482242495906258989351428318340736703325118283502743772525420186 +515388318762172085512757129352395197699848755276478588959938658231299616240495,1350960548211476209390117246928289328516166528957389393408566548846213309144 +517658751884472403070082050627075352755795225564040168646986846153023403096180,1324729136819182692425165292411394327407511934209655077877674460464300121300 +519929185006772720627406971901755507811741695851601748334035034074747189951865,1299007057060993358777871916475898081725576133192311731321945268374304875079 +522199618129073038184731893176435662867688166139163328021083221996470976807550,1273784419316040268779271406014391437821438711132700913654395176336120357812 +524470051251373355742056814451115817923634636426724907708131409918194763663235,1249051525988836119835059106151604655038913776255529646900786453845629450261 +526740484373673673299381735725795972979581106714286487395179597839918550518920,1224798867780744376154319639656429438292590596449478554800662259883398133550 +529010917495973990856706657000476128035527577001848067082227785761642337374605,1201017120033845739753282342628997286268151802300216872136210819143441303664 +531281350618274308414031578275156283091474047289409646769275973683366124230290,1177697139145795253133868286972489045105718361046528878527448066379668652133 +533551783740574625971356499549836438147420517576971226456324161605089911085975,1154829959054291618502821225483149900650534431243083949245642604820923646588 +535822216862874943528681420824516593203366987864532806143372349526813697941660,1132406787789807082890425327211328755183700827007270235992079331829362643852 +538092649985175261086006342099196748259313458152094385830420537448537484797345,1110419004095252483338613492946986193747814242216914544087253927761374702549 +540363083107475578643331263373876903315259928439655965517468725370261271653030,1088858154111277781547261772857694251337802353360681665028106984738120501199 +542633516229775896200656184648557058371206398727217545204516913291985058508715,1067715948125933652889320598100629627595490376276215626206049496855639908739 +544903949352076213757981105923237213427152869014779124891565101213708845364400,1046984257387444440232701363407888596891997329416358164537461237895052190493 +547174382474376531315306027197917368483099339302340704578613289135432632220085,1026655110978867048053673500561104502292891906760695382551282292885769647385 +549444815596676848872630948472597523539045809589902284265661477057156419075770,1006720692753434146222353671415953866113533147281398201361542821926894368710 +551715248718977166429955869747277678594992279877463863952709664978880205931455,987173338329403384733958131783829533009147622988398599380784122106977308530 +553985681841277483987280791021957833650938750165025443639757852900603992787140,968005532143257199520483988560220733582298517120243540446994347402239110174 +556256114963577801544605712296637988706885220452587023326806040822327779642825,949209904560120224102845604039392059367322323910424009332173176480678795959 +558526548085878119101930633571318143762831690740148603013854228744051566498510,930779229040283320858879764591061717606873826779909083197312274038703255036 +560796981208178436659255554845998298818778161027710182700902416665775353354195,912706419360744817546225148870963828076628869100719699856881194145713586169 +563067414330478754216580476120678453874724631315271762387950604587499140209880,894984526890700687724824577161411173596658532863246109848772040565011872478 +565337847452779071773905397395358608930671101602833342074998792509222927065565,877606737919936156004596577686776039865420267809390448859339117903023609116 +567608280575079389331230318670038763986617571890394921762046980430946713921250,860566371039091548574664590528726362251021703804112434559419129682978794632 +569878713697379706888555239944718919042564042177956501449095168352670500776935,843856874570795154071557033366442926236979708804975971538286570121216914411 +572149146819680024445880161219399074098510512465518081136143356274394287632620,827471824050675417183296315424741971779066957250062452852260880325027656529 +574419579941980342003205082494079229154456982753079660823191544196118074488305,811404919757283964983682303379036253598800697102332643556476037720459831748 +576690013064280659560530003768759384210403453040641240510239732117841861343990,795649984289979771219745127738174910086802387263030261900019674061458963150 +578960446186580977117854925043439539266349923328202820197287920039565648199675,780200960193843203865524721501305631184795994974723658032277935470179306730 diff --git a/chain/types/testdata/TestLambdaFunction/10-100.golden b/chain/types/testdata/TestLambdaFunction/10-100.golden new file mode 100644 index 000000000..adb7eaf5e --- /dev/null +++ b/chain/types/testdata/TestLambdaFunction/10-100.golden @@ -0,0 +1 @@ +57896044618658097711785492504343953926634992332820282019728792003956564819968 \ No newline at end of file diff --git a/chain/types/testdata/TestLambdaFunction/1024-2048.golden b/chain/types/testdata/TestLambdaFunction/1024-2048.golden new file mode 100644 index 000000000..d449e4eab --- /dev/null +++ b/chain/types/testdata/TestLambdaFunction/1024-2048.golden @@ -0,0 +1 @@ +289480223093290488558927462521719769633174961664101410098643960019782824099840 \ No newline at end of file diff --git a/chain/types/testdata/TestLambdaFunction/2000000000000000-100000000000000000.golden b/chain/types/testdata/TestLambdaFunction/2000000000000000-100000000000000000.golden new file mode 100644 index 000000000..76d1403ac --- /dev/null +++ b/chain/types/testdata/TestLambdaFunction/2000000000000000-100000000000000000.golden @@ -0,0 +1 @@ +11579208923731619542357098500868790785326998466564056403945758400791312963993 \ No newline at end of file diff --git a/chain/types/testdata/TestPoissonFunction/lam-10-10.golden b/chain/types/testdata/TestPoissonFunction/lam-10-10.golden new file mode 100644 index 000000000..fbe59115f --- /dev/null +++ b/chain/types/testdata/TestPoissonFunction/lam-10-10.golden @@ -0,0 +1,17 @@ +icdf +1125278653883954157340515998824199281259686237023612910954531537010133365568 +5485581780123676224984074899749002236148166431650497590243915223971292577 +17842170241691453912141677461647358103546946338181118738604570718548080 +43538699106868068808561503680708109912117284430088557923220935824303 +85008817354919776986513548953593326094752560579206896166859206326 +138328508214407784218646352510285887677303021571444942144212932 +192946940473264032619492808002293590266469557064324916486706 +235498963868775663540157747991701194152938740691242898919 +255504995002156513848360474242833468958493714151012216 +249505772801580009049072856702435230216536441494110 +232834101038081471620316286291642591265760137159 +11533551765583311484083562200606025547302501012 +11353456917542541496993529059255898133794641608 +11353321629946357024346977051186975261639061786 +11353321535577219060847642123725989684858819334 +11353321535515780819985988910882590605707269697 diff --git a/chain/types/testdata/TestPoissonFunction/lam-1036915-20.golden b/chain/types/testdata/TestPoissonFunction/lam-1036915-20.golden new file mode 100644 index 000000000..45444615d --- /dev/null +++ b/chain/types/testdata/TestPoissonFunction/lam-1036915-20.golden @@ -0,0 +1,17 @@ +icdf +72718197862321603787957847055188249408647877796280116987586082825356498974798 +30123322455004902866096392147720313525979066051167804713805891738863284666878 +9062729215708086547397523150443475826195010851218953471088727298493148732956 +2120601657722952965249404258310617497861606438105371150448777320082300909521 +404370264674629622846679825118378024155401061236248117877372450081489268106 +64941157977031699837280219244596630364570458903895153606060064374226923144 +8998760514291795062091202215054973561658841304177095664084807386711484044 +1095864305518189121413406860322570887693509822868614985675874891701983877 +118988091690998292615838041961723734187855286032350465668217096980526413 +11653361409102593830837021393444274321721937984503223867724441309766994 +1039253147016500070761515827557061937775360855994320103804816785188376 +85064880905559492020902336338153555957806293407638915883403930221828 +6433469833589351070633969345898155559842007456615136652210720692299 +452164666340411064711833496915674079929092772106312520794348036629 +29679788379529243880483477334855509673275091060271619731000625321 +1827354397264548824373139406487609602015834650190678153972930556 diff --git a/chain/types/testdata/TestPoissonFunction/lam-1706-10.golden b/chain/types/testdata/TestPoissonFunction/lam-1706-10.golden new file mode 100644 index 000000000..fb6689a48 --- /dev/null +++ b/chain/types/testdata/TestPoissonFunction/lam-1706-10.golden @@ -0,0 +1,17 @@ +icdf +93907545465879218275260347624273708225148723352890415890955745471328115138024 +57447553596668785643406883388130520172829512611140657354486862128150346836988 +27076095525930017054568011324233899656590951319429188573619716140132147265911 +10209654292635635800479757502291310268341281539592025246744927385067352842651 +3184715634432458451975226003210729824895496226017269232182332263939291493510 +843984120585852874524302031056145794325474791455055607017530061469667926785 +194034907919461416983404196340045475941285897027461784665454449911533518447 +39345544525367691179167072173518251727638261160626536209449847049064587000 +7131182470884793691126479665205819536661348710819293305892247868965466268 +1167890189531948301087715470416213490892402026859749426392544722128541359 +174396377814374645286335419995210764907848995332895729280582459579342738 +23925812935985027317838050852967276757134553590636105055350959232313899 +3035286920153916620063269622769735189335169019323041991528942663951986 +358060554242868329017312833503133182524340437692927389149107908421231 +39467628723598770945019147503633808666969235107758896427288845018926 +4082242594961149456000070139366495398696105445630156285138889148853 diff --git a/chain/types/testdata/TestPoissonFunction/lam-2-0.golden b/chain/types/testdata/TestPoissonFunction/lam-2-0.golden new file mode 100644 index 000000000..c64994bd9 --- /dev/null +++ b/chain/types/testdata/TestPoissonFunction/lam-2-0.golden @@ -0,0 +1,17 @@ +icdf +100121334043824876020967110392587568107053060743383522309741056272383359786578 +68779823656842237215759361160386888614619212898869438850308000801323820079862 +37438313269859598410551611928186209122185365054355355390874945330264280373146 +16543973011871172540413112440052422793896133158012633084586241682891253902002 +6096802882876959605343862695985529629751517209841271931441889859204740666430 +1917934831279274431316162798358772364093670830572727470184149129730135372202 +524978814080046039973596165816519942207722037483212649764902219905266940794 +126991380594552213875719985090162107383165239457636986787974531383875960392 +27494522223178757351250939908572648677026039951243071043742609253528215292 +5384109251762433679146707645997213408995106727599978656135515446784271938 +962026657479168944725861193482126355388920082871360178614096685435483268 +158011640336757174831161838479383254733249783829793182701111456099339874 +24009137479688546515378612645592737957304733989532016715613917876649310 +3393367809370296005258116363471119991774726321799529640921988919312302 +448257856467688789526616894596603139556153797837745773108856211121302 +55576529414007827429083632080000892593677461309507924067105183362502 diff --git a/chain/types/testdata/TestPoissonFunction/lam-209714-20.golden b/chain/types/testdata/TestPoissonFunction/lam-209714-20.golden new file mode 100644 index 000000000..3a5a7bd46 --- /dev/null +++ b/chain/types/testdata/TestPoissonFunction/lam-209714-20.golden @@ -0,0 +1,17 @@ +icdf +20989436322611254574979389012727393136091537312055593688180077279147576363096 +2029014232696520721523332453453803636238058967796600089005757967894658844577 +132982872945592560215194824292206599698851338836977427578620974467337246296 +6581505574095040906286115477587166321018484661524779791358182693980706360 +261473369241451189291715224208035992983406808190620756138506410631265448 +8673527587881831627652027122245179992541421812500366598395022980519170 +246914417172755020716947338861248744263385116558896234139925344965714 +6155418508705151241339695503211918565434455355693098789916851059631 +136477982954924943101944815709613669983383501630000081908122878386 +2724514397229876659600187515561464490237868059330199615762951760 +49460332945698577716770256622916953121860884014393828193908634 +823264627479642334265449493920662550930201158945634649498769 +12651460568281449375957051928671501418597070452648092732548 +180560129118157986659345179422379755776654969450793746138 +2405427973892505908363489341734991530618943133521671600 +30045550760659097055494870911555042207154242485930695 diff --git a/chain/types/testdata/TestPoissonFunction/lam-5-0.golden b/chain/types/testdata/TestPoissonFunction/lam-5-0.golden new file mode 100644 index 000000000..e7df148c9 --- /dev/null +++ b/chain/types/testdata/TestPoissonFunction/lam-5-0.golden @@ -0,0 +1,17 @@ +icdf +115011888277122352219705460287186602222085188670665840381425306072442950421456 +111110883476153136200377836679680074066161208695792222091263916395092054329056 +101358371473730096152058777660913753676351258758608176365860442201714814098056 +85104184803025029404860345962969886360001342196634766823521318546086080379726 +64786451464643695970862306340540052214563946494168004895597413976550163231816 +44468718126262362536864266718110218069126550791701242967673509407014246083906 +27537273677611251341865900366085356281262054372978941361070255599067648460651 +15443384785717600488295638686067597861358842645320154499210788593391507301186 +7884704228284068704814225136056498848919335315533412710548621714843919076521 +3685437251932106602880106497161443842008497910096333939069640115650814507266 +1585803763756125551913047177713916338553079207377794553330149316054262222641 +631424905494315983291656577965040200618797978869367559812198952601283911451 +233767047885228663032743828069675143146180800324189645846386301162542948456 +80821718035579693702392770417611659502866500883736602013381435224565655001 +26198385946419347512981678399017558201682822512146229215879697389573764486 +7990608583365898783177981059486191101288263054949438283379118111243134316 diff --git a/chain/types/testdata/TestPoissonFunction/lam-5242879-20.golden b/chain/types/testdata/TestPoissonFunction/lam-5242879-20.golden new file mode 100644 index 000000000..367cf4a6d --- /dev/null +++ b/chain/types/testdata/TestPoissonFunction/lam-5242879-20.golden @@ -0,0 +1,17 @@ +icdf +115011887533064380050215202002871794783671664388541274939039184893270600703151 +111110879755863630144777547269452207577572562543679248972859798819416242535921 +101358362173007217989878395557465593373392010546833825352856759707561945139525 +85104169301821710755220628061805234978705652326202881500299286939503312327242 +64786432088141395502693562453332343133008530920262028792733819517798429528997 +44468698749761909885846743680008378684637277300179598543979674797636862943384 +27537257530529080605729671834720742022613060678716434560927439906969908575619 +15443373252088578337713549627194971124753642243467082552611819728291522561946 +7884697019766617162458477655388623015603913245952633503615157778326861227793 +3685433247200570820185174890022164698361097802621279879954268046011715772231 +1585801761390548420193995297013958176769177427873274589439743405082427147382 +631423995328231141553650878972791975288890578033071631113620389859816342901 +233766668649395912353875000216328335294367462954196928187468994385755448892 +80821572175657684536655650469711580587115741420816601432036447172153463116 +26198333853594769407667441209847842642730676168975225661522405972966431948 +7990591219092428810606628986022697269060757693982546042792694706871429282 diff --git a/chain/types/tipset.go b/chain/types/tipset.go index c1934d1d8..047a1c00e 100644 --- a/chain/types/tipset.go +++ b/chain/types/tipset.go @@ -7,12 +7,13 @@ import ( "io" "sort" - "github.com/filecoin-project/specs-actors/actors/abi" "github.com/ipfs/go-cid" logging "github.com/ipfs/go-log/v2" "github.com/minio/blake2b-simd" cbg "github.com/whyrusleeping/cbor-gen" "golang.org/x/xerrors" + + "github.com/filecoin-project/go-state-types/abi" ) var log = logging.Logger("types") @@ -23,8 +24,6 @@ type TipSet struct { height abi.ChainEpoch } -// why didnt i just export the fields? Because the struct has methods with the -// same names already type ExpTipSet struct { Cids []cid.Cid Blocks []*BlockHeader @@ -32,6 +31,8 @@ type ExpTipSet struct { } func (ts *TipSet) MarshalJSON() ([]byte, error) { + // why didnt i just export the fields? Because the struct has methods with the + // same names already return json.Marshal(ExpTipSet{ Cids: ts.cids, Blocks: ts.blks, @@ -97,6 +98,12 @@ func tipsetSortFunc(blks []*BlockHeader) func(i, j int) bool { } } +// Checks: +// - A tipset is composed of at least one block. (Because of our variable +// number of blocks per tipset, determined by randomness, we do not impose +// an upper limit.) +// - All blocks have the same height. +// - All blocks have the same parents (same number of them and matching CIDs). func NewTipSet(blks []*BlockHeader) (*TipSet, error) { if len(blks) == 0 { return nil, xerrors.Errorf("NewTipSet called with zero length array of blocks") @@ -112,6 +119,10 @@ func NewTipSet(blks []*BlockHeader) (*TipSet, error) { return nil, fmt.Errorf("cannot create tipset with mismatching heights") } + if len(blks[0].Parents) != len(b.Parents) { + return nil, fmt.Errorf("cannot create tipset with mismatching number of parents") + } + for i, cid := range b.Parents { if cid != blks[0].Parents[i] { return nil, fmt.Errorf("cannot create tipset with mismatching parents") @@ -157,12 +168,16 @@ func (ts *TipSet) Equals(ots *TipSet) bool { return false } - if len(ts.blks) != len(ots.blks) { + if ts.height != ots.height { return false } - for i, b := range ts.blks { - if b.Cid() != ots.blks[i].Cid() { + if len(ts.cids) != len(ots.cids) { + return false + } + + for i, cid := range ts.cids { + if cid != ots.cids[i] { return false } } @@ -181,8 +196,23 @@ func (ts *TipSet) MinTicket() *Ticket { } func (ts *TipSet) MinTimestamp() uint64 { - minTs := ts.Blocks()[0].Timestamp - for _, bh := range ts.Blocks()[1:] { + if ts == nil { + return 0 + } + + blks := ts.Blocks() + + // TODO::FVM @vyzo @magik Null rounds shouldn't ever be represented as + // tipsets with no blocks; Null-round generally means that the tipset at + // that epoch doesn't exist - and the next tipset that does exist links + // straight to first epoch with blocks (@raulk agrees -- this is odd) + if len(blks) == 0 { + // null rounds make things crash -- it is threaded in every fvm instantiation + return 0 + } + + minTs := blks[0].Timestamp + for _, bh := range blks[1:] { if bh.Timestamp < minTs { minTs = bh.Timestamp } @@ -204,6 +234,10 @@ func (ts *TipSet) MinTicketBlock() *BlockHeader { return min } +func (ts *TipSet) ParentMessageReceipts() cid.Cid { + return ts.blks[0].ParentMessageReceipts +} + func (ts *TipSet) ParentState() cid.Cid { return ts.blks[0].ParentStateRoot } @@ -220,3 +254,15 @@ func (ts *TipSet) Contains(oc cid.Cid) bool { } return false } + +func (ts *TipSet) IsChildOf(parent *TipSet) bool { + return CidArrsEqual(ts.Parents().Cids(), parent.Cids()) && + // FIXME: The height check might go beyond what is meant by + // "parent", but many parts of the code rely on the tipset's + // height for their processing logic at the moment to obviate it. + ts.height > parent.height +} + +func (ts *TipSet) String() string { + return fmt.Sprintf("%v", ts.cids) +} diff --git a/chain/types/tipset_key.go b/chain/types/tipset_key.go index 638b4380e..15e655da7 100644 --- a/chain/types/tipset_key.go +++ b/chain/types/tipset_key.go @@ -3,10 +3,15 @@ package types import ( "bytes" "encoding/json" + "fmt" + "io" "strings" + block "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" - "github.com/multiformats/go-multihash" + typegen "github.com/whyrusleeping/cbor-gen" + + "github.com/filecoin-project/go-state-types/abi" ) var EmptyTSK = TipSetKey{} @@ -15,7 +20,9 @@ var EmptyTSK = TipSetKey{} var blockHeaderCIDLen int func init() { - c, err := cid.V1Builder{Codec: cid.DagCBOR, MhType: multihash.BLAKE2B_MIN + 31}.Sum([]byte{}) + // hash a large string of zeros so we don't estimate based on inlined CIDs. + var buf [256]byte + c, err := abi.CidBuilder.Sum(buf[:]) if err != nil { panic(err) } @@ -45,7 +52,7 @@ func NewTipSetKey(cids ...cid.Cid) TipSetKey { func TipSetKeyFromBytes(encoded []byte) (TipSetKey, error) { _, err := decodeKey(encoded) if err != nil { - return TipSetKey{}, err + return EmptyTSK, err } return TipSetKey{string(encoded)}, nil } @@ -92,6 +99,67 @@ func (k *TipSetKey) UnmarshalJSON(b []byte) error { return nil } +func (k TipSetKey) Cid() (cid.Cid, error) { + blk, err := k.ToStorageBlock() + if err != nil { + return cid.Cid{}, err + } + return blk.Cid(), nil +} + +func (k TipSetKey) ToStorageBlock() (block.Block, error) { + buf := new(bytes.Buffer) + if err := k.MarshalCBOR(buf); err != nil { + log.Errorf("failed to marshal ts key as CBOR: %s", k) + } + + cid, err := abi.CidBuilder.Sum(buf.Bytes()) + if err != nil { + return nil, err + } + + return block.NewBlockWithCid(buf.Bytes(), cid) +} + +func (k TipSetKey) MarshalCBOR(writer io.Writer) error { + if err := typegen.WriteMajorTypeHeader(writer, typegen.MajByteString, uint64(len(k.Bytes()))); err != nil { + return err + } + + _, err := writer.Write(k.Bytes()) + return err +} + +func (k *TipSetKey) UnmarshalCBOR(reader io.Reader) error { + cr := typegen.NewCborReader(reader) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if extra > typegen.ByteArrayMaxLen { + return fmt.Errorf("t.Binary: byte array too large (%d)", extra) + } + if maj != typegen.MajByteString { + return fmt.Errorf("expected byte array") + } + + b := make([]uint8, extra) + + if _, err := io.ReadFull(cr, b); err != nil { + return err + } + + *k, err = TipSetKeyFromBytes(b) + return err +} + func (k TipSetKey) IsEmpty() bool { return len(k.value) == 0 } @@ -121,3 +189,6 @@ func decodeKey(encoded []byte) ([]cid.Cid, error) { } return cids, nil } + +var _ typegen.CBORMarshaler = &TipSetKey{} +var _ typegen.CBORUnmarshaler = &TipSetKey{} diff --git a/chain/types/tipset_key_test.go b/chain/types/tipset_key_test.go index 43ff1a3df..5fbecb3ea 100644 --- a/chain/types/tipset_key_test.go +++ b/chain/types/tipset_key_test.go @@ -1,6 +1,8 @@ +// stm: #unit package types import ( + "encoding/hex" "encoding/json" "fmt" "testing" @@ -9,9 +11,12 @@ import ( "github.com/multiformats/go-multihash" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + cborrpc "github.com/filecoin-project/go-cbor-util" ) func TestTipSetKey(t *testing.T) { + //stm: @TYPES_TIPSETKEY_FROM_BYTES_001, @TYPES_TIPSETKEY_NEW_001 cb := cid.V1Builder{Codec: cid.DagCBOR, MhType: multihash.BLAKE2B_MIN + 31} c1, _ := cb.Sum([]byte("a")) c2, _ := cb.Sum([]byte("b")) @@ -19,7 +24,7 @@ func TestTipSetKey(t *testing.T) { fmt.Println(len(c1.Bytes())) t.Run("zero value", func(t *testing.T) { - assert.Equal(t, TipSetKey{}, NewTipSetKey()) + assert.Equal(t, EmptyTSK, NewTipSetKey()) }) t.Run("CID extraction", func(t *testing.T) { @@ -61,17 +66,24 @@ func TestTipSetKey(t *testing.T) { t.Run("JSON", func(t *testing.T) { k0 := NewTipSetKey() - verifyJson(t, "[]", k0) + verifyJSON(t, "[]", k0) k3 := NewTipSetKey(c1, c2, c3) - verifyJson(t, `[`+ + verifyJSON(t, `[`+ `{"/":"bafy2bzacecesrkxghscnq7vatble2hqdvwat6ed23vdu4vvo3uuggsoaya7ki"},`+ `{"/":"bafy2bzacebxfyh2fzoxrt6kcgc5dkaodpcstgwxxdizrww225vrhsizsfcg4g"},`+ `{"/":"bafy2bzacedwviarjtjraqakob5pslltmuo5n3xev3nt5zylezofkbbv5jclyu"}`+ `]`, k3) }) + + t.Run("CBOR", func(t *testing.T) { + k3 := NewTipSetKey(c1, c2, c3) + b, err := cborrpc.Dump(k3) + require.NoError(t, err) + fmt.Println(hex.EncodeToString(b)) + }) } -func verifyJson(t *testing.T, expected string, k TipSetKey) { +func verifyJSON(t *testing.T, expected string, k TipSetKey) { bytes, err := json.Marshal(k) require.NoError(t, err) assert.Equal(t, expected, string(bytes)) diff --git a/chain/types/types_test.go b/chain/types/types_test.go index a2b47ad51..4e08a0d88 100644 --- a/chain/types/types_test.go +++ b/chain/types/types_test.go @@ -1,3 +1,4 @@ +// stm: #unit package types import ( @@ -22,13 +23,14 @@ func blsaddr(n int64) address.Address { func BenchmarkSerializeMessage(b *testing.B) { m := &Message{ - To: blsaddr(1), - From: blsaddr(2), - Nonce: 197, - Method: 1231254, - Params: []byte("some bytes, idk. probably at least ten of them"), - GasLimit: 126723, - GasPrice: NewInt(1776234), + To: blsaddr(1), + From: blsaddr(2), + Nonce: 197, + Method: 1231254, + Params: []byte("some bytes, idk. probably at least ten of them"), + GasLimit: 126723, + GasPremium: NewInt(1245667), + GasFeeCap: NewInt(1245667), } b.ReportAllocs() diff --git a/chain/types/vmcontext.go b/chain/types/vmcontext.go index d0ce42c0f..83ad81315 100644 --- a/chain/types/vmcontext.go +++ b/chain/types/vmcontext.go @@ -1,11 +1,12 @@ package types import ( - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/lotus/chain/actors/aerrors" - - cid "github.com/ipfs/go-cid" + "github.com/ipfs/go-cid" cbg "github.com/whyrusleeping/cbor-gen" + + "github.com/filecoin-project/go-address" + + "github.com/filecoin-project/lotus/chain/actors/aerrors" ) type Storage interface { @@ -23,6 +24,8 @@ type StateTree interface { SetActor(addr address.Address, act *Actor) error // GetActor returns the actor from any type of `addr` provided. GetActor(addr address.Address) (*Actor, error) + + Version() StateTreeVersion } type storageWrapper struct { diff --git a/chain/types/voucher.go b/chain/types/voucher.go deleted file mode 100644 index 687109c33..000000000 --- a/chain/types/voucher.go +++ /dev/null @@ -1,22 +0,0 @@ -package types - -import ( - "encoding/base64" - - "github.com/filecoin-project/specs-actors/actors/builtin/paych" - cbor "github.com/ipfs/go-ipld-cbor" -) - -func DecodeSignedVoucher(s string) (*paych.SignedVoucher, error) { - data, err := base64.RawURLEncoding.DecodeString(s) - if err != nil { - return nil, err - } - - var sv paych.SignedVoucher - if err := cbor.DecodeInto(data, &sv); err != nil { - return nil, err - } - - return &sv, nil -} diff --git a/chain/types_test.go b/chain/types_test.go index 55baf4a28..0fb399214 100644 --- a/chain/types_test.go +++ b/chain/types_test.go @@ -1,26 +1,32 @@ +// stm: #unit package chain import ( + "crypto/rand" "encoding/json" "testing" "github.com/filecoin-project/go-address" + + "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/types" ) func TestSignedMessageJsonRoundtrip(t *testing.T) { + //stm: @TYPES_MESSAGE_JSON_EQUAL_CALL_002 to, _ := address.NewIDAddress(5234623) from, _ := address.NewIDAddress(603911192) smsg := &types.SignedMessage{ Message: types.Message{ - To: to, - From: from, - Params: []byte("some bytes, idk"), - Method: 1235126, - Value: types.NewInt(123123), - GasPrice: types.NewInt(1234), - GasLimit: 9992969384, - Nonce: 123123, + To: to, + From: from, + Params: []byte("some bytes, idk"), + Method: 1235126, + Value: types.NewInt(123123), + GasFeeCap: types.NewInt(1234), + GasPremium: types.NewInt(132414234), + GasLimit: 100_000_000, + Nonce: 123123, }, } @@ -34,3 +40,41 @@ func TestSignedMessageJsonRoundtrip(t *testing.T) { t.Fatal(err) } } + +func TestAddressType(t *testing.T) { + //stm: @CHAIN_TYPES_ADDRESS_PREFIX_001 + build.SetAddressNetwork(address.Testnet) + addr, err := makeRandomAddress() + if err != nil { + t.Fatal(err) + } + + if string(addr[0]) != address.TestnetPrefix { + t.Fatalf("address should start with %s", address.TestnetPrefix) + } + + build.SetAddressNetwork(address.Mainnet) + addr, err = makeRandomAddress() + if err != nil { + t.Fatal(err) + } + + if string(addr[0]) != address.MainnetPrefix { + t.Fatalf("address should start with %s", address.MainnetPrefix) + } +} + +func makeRandomAddress() (string, error) { + bytes := make([]byte, 32) + _, err := rand.Read(bytes) + if err != nil { + return "", err + } + + addr, err := address.NewActorAddress(bytes) + if err != nil { + return "", err + } + + return addr.String(), nil +} diff --git a/chain/validation/applier.go b/chain/validation/applier.go deleted file mode 100644 index 6c1629104..000000000 --- a/chain/validation/applier.go +++ /dev/null @@ -1,192 +0,0 @@ -package validation - -import ( - "context" - "golang.org/x/xerrors" - - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/abi/big" - "github.com/filecoin-project/specs-actors/actors/builtin" - "github.com/filecoin-project/specs-actors/actors/crypto" - "github.com/filecoin-project/specs-actors/actors/puppet" - "github.com/filecoin-project/specs-actors/actors/runtime" - "github.com/ipfs/go-cid" - - vtypes "github.com/filecoin-project/chain-validation/chain/types" - vstate "github.com/filecoin-project/chain-validation/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" -) - -// Applier applies messages to state trees and storage. -type Applier struct { - stateWrapper *StateWrapper - syscalls runtime.Syscalls -} - -var _ vstate.Applier = &Applier{} - -func NewApplier(sw *StateWrapper, syscalls runtime.Syscalls) *Applier { - return &Applier{sw, syscalls} -} - -func (a *Applier) ApplyMessage(epoch abi.ChainEpoch, message *vtypes.Message) (vtypes.ApplyMessageResult, error) { - lm := toLotusMsg(message) - receipt, penalty, reward, err := a.applyMessage(epoch, lm) - return vtypes.ApplyMessageResult{ - Receipt: receipt, - Penalty: penalty, - Reward: reward, - Root: a.stateWrapper.Root().String(), - }, err -} - -func (a *Applier) ApplySignedMessage(epoch abi.ChainEpoch, msg *vtypes.SignedMessage) (vtypes.ApplyMessageResult, error) { - var lm types.ChainMsg - switch msg.Signature.Type { - case crypto.SigTypeSecp256k1: - lm = toLotusSignedMsg(msg) - case crypto.SigTypeBLS: - lm = toLotusMsg(&msg.Message) - default: - return vtypes.ApplyMessageResult{}, xerrors.New("Unknown signature type") - } - // TODO: Validate the sig first - receipt, penalty, reward, err := a.applyMessage(epoch, lm) - return vtypes.ApplyMessageResult{ - Receipt: receipt, - Penalty: penalty, - Reward: reward, - Root: a.stateWrapper.Root().String(), - }, err - -} - -func (a *Applier) ApplyTipSetMessages(epoch abi.ChainEpoch, blocks []vtypes.BlockMessagesInfo, rnd vstate.RandomnessSource) (vtypes.ApplyTipSetResult, error) { - cs := store.NewChainStore(a.stateWrapper.bs, a.stateWrapper.ds, a.syscalls) - sm := stmgr.NewStateManager(cs) - - var bms []stmgr.BlockMessages - for _, b := range blocks { - bm := stmgr.BlockMessages{ - Miner: b.Miner, - TicketCount: 1, - } - - for _, m := range b.BLSMessages { - bm.BlsMessages = append(bm.BlsMessages, toLotusMsg(m)) - } - - for _, m := range b.SECPMessages { - bm.SecpkMessages = append(bm.SecpkMessages, toLotusSignedMsg(m)) - } - - bms = append(bms, bm) - } - - var receipts []vtypes.MessageReceipt - sroot, _, err := sm.ApplyBlocks(context.TODO(), a.stateWrapper.Root(), bms, epoch, &randWrapper{rnd}, func(c cid.Cid, msg *types.Message, ret *vm.ApplyRet) error { - if msg.From == builtin.SystemActorAddr { - return nil // ignore reward and cron calls - } - rval := ret.Return - if rval == nil { - rval = []byte{} // chain validation tests expect empty arrays to not be nil... - } - receipts = append(receipts, vtypes.MessageReceipt{ - ExitCode: ret.ExitCode, - ReturnValue: rval, - - GasUsed: vtypes.GasUnits(ret.GasUsed), - }) - return nil - }) - if err != nil { - return vtypes.ApplyTipSetResult{}, err - } - - a.stateWrapper.stateRoot = sroot - - return vtypes.ApplyTipSetResult{ - Receipts: receipts, - Root: a.stateWrapper.Root().String(), - }, nil -} - -type randWrapper struct { - rnd vstate.RandomnessSource -} - -func (w *randWrapper) GetRandomness(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { - return w.rnd.Randomness(ctx, pers, round, entropy) -} - -type vmRand struct { -} - -func (*vmRand) GetRandomness(ctx context.Context, dst crypto.DomainSeparationTag, h abi.ChainEpoch, input []byte) ([]byte, error) { - panic("implement me") -} - -func (a *Applier) applyMessage(epoch abi.ChainEpoch, lm types.ChainMsg) (vtypes.MessageReceipt, abi.TokenAmount, abi.TokenAmount, error) { - ctx := context.TODO() - base := a.stateWrapper.Root() - - lotusVM, err := vm.NewVM(base, epoch, &vmRand{}, a.stateWrapper.bs, a.syscalls) - // need to modify the VM invoker to add the puppet actor - chainValInvoker := vm.NewInvoker() - chainValInvoker.Register(puppet.PuppetActorCodeID, puppet.Actor{}, puppet.State{}) - lotusVM.SetInvoker(chainValInvoker) - if err != nil { - return vtypes.MessageReceipt{}, big.Zero(), big.Zero(), err - } - - ret, err := lotusVM.ApplyMessage(ctx, lm) - if err != nil { - return vtypes.MessageReceipt{}, big.Zero(), big.Zero(), err - } - - rval := ret.Return - if rval == nil { - rval = []byte{} - } - - a.stateWrapper.stateRoot, err = lotusVM.Flush(ctx) - if err != nil { - return vtypes.MessageReceipt{}, big.Zero(), big.Zero(), err - } - - mr := vtypes.MessageReceipt{ - ExitCode: ret.ExitCode, - ReturnValue: rval, - GasUsed: vtypes.GasUnits(ret.GasUsed), - } - - return mr, ret.Penalty, abi.NewTokenAmount(ret.GasUsed), nil -} - -func toLotusMsg(msg *vtypes.Message) *types.Message { - return &types.Message{ - To: msg.To, - From: msg.From, - - Nonce: msg.CallSeqNum, - Method: msg.Method, - - Value: types.BigInt{Int: msg.Value.Int}, - GasPrice: types.BigInt{Int: msg.GasPrice.Int}, - GasLimit: msg.GasLimit, - - Params: msg.Params, - } -} - -func toLotusSignedMsg(msg *vtypes.SignedMessage) *types.SignedMessage { - return &types.SignedMessage{ - Message: *toLotusMsg(&msg.Message), - Signature: msg.Signature, - } -} diff --git a/chain/validation/config.go b/chain/validation/config.go deleted file mode 100644 index 1e5936350..000000000 --- a/chain/validation/config.go +++ /dev/null @@ -1,37 +0,0 @@ -package validation - -// -// Config -// - -type Config struct { - trackGas bool - checkExitCode bool - checkReturnValue bool - checkState bool -} - -func NewConfig(gas, exit, ret, state bool) *Config { - return &Config{ - trackGas: gas, - checkExitCode: exit, - checkReturnValue: ret, - checkState: state, - } -} - -func (v Config) ValidateGas() bool { - return v.trackGas -} - -func (v Config) ValidateExitCode() bool { - return v.checkExitCode -} - -func (v Config) ValidateReturnValue() bool { - return v.checkReturnValue -} - -func (v Config) ValidateStateRoot() bool { - return v.checkState -} diff --git a/chain/validation/factories.go b/chain/validation/factories.go deleted file mode 100644 index d3771d87d..000000000 --- a/chain/validation/factories.go +++ /dev/null @@ -1,34 +0,0 @@ -package validation - -import ( - "github.com/filecoin-project/specs-actors/actors/runtime" - - vstate "github.com/filecoin-project/chain-validation/state" -) - -type Factories struct { - *Applier -} - -var _ vstate.Factories = &Factories{} - -func NewFactories() *Factories { - return &Factories{} -} - -func (f *Factories) NewStateAndApplier(syscalls runtime.Syscalls) (vstate.VMWrapper, vstate.Applier) { - st := NewState() - return st, NewApplier(st, syscalls) -} - -func (f *Factories) NewKeyManager() vstate.KeyManager { - return newKeyManager() -} - -func (f *Factories) NewValidationConfig() vstate.ValidationConfig { - trackGas := true - checkExit := true - checkRet := true - checkState := true - return NewConfig(trackGas, checkExit, checkRet, checkState) -} diff --git a/chain/validation/keymanager.go b/chain/validation/keymanager.go deleted file mode 100644 index a826d5ea0..000000000 --- a/chain/validation/keymanager.go +++ /dev/null @@ -1,103 +0,0 @@ -package validation - -import ( - "fmt" - "github.com/minio/blake2b-simd" - "math/rand" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-crypto" - acrypto "github.com/filecoin-project/specs-actors/actors/crypto" - - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/chain/wallet" -) - -type KeyManager struct { - // Private keys by address - keys map[address.Address]*wallet.Key - - // Seed for deterministic secp key generation. - secpSeed int64 - // Seed for deterministic bls key generation. - blsSeed int64 // nolint: structcheck -} - -func newKeyManager() *KeyManager { - return &KeyManager{ - keys: make(map[address.Address]*wallet.Key), - secpSeed: 0, - } -} - -func (k *KeyManager) NewSECP256k1AccountAddress() address.Address { - secpKey := k.newSecp256k1Key() - k.keys[secpKey.Address] = secpKey - return secpKey.Address -} - -func (k *KeyManager) NewBLSAccountAddress() address.Address { - blsKey := k.newBLSKey() - k.keys[blsKey.Address] = blsKey - return blsKey.Address -} - -func (k *KeyManager) Sign(addr address.Address, data []byte) (acrypto.Signature, error) { - ki, ok := k.keys[addr] - if !ok { - return acrypto.Signature{}, fmt.Errorf("unknown address %v", addr) - } - var sigType acrypto.SigType - if ki.Type == wallet.KTSecp256k1 { - sigType = acrypto.SigTypeBLS - hashed := blake2b.Sum256(data) - sig, err := crypto.Sign(ki.PrivateKey, hashed[:]) - if err != nil { - return acrypto.Signature{}, err - } - - return acrypto.Signature{ - Type: sigType, - Data: sig, - }, nil - } else if ki.Type == wallet.KTBLS { - panic("lotus validator cannot sign BLS messages") - } else { - panic("unknown signature type") - } - -} - -func (k *KeyManager) newSecp256k1Key() *wallet.Key { - randSrc := rand.New(rand.NewSource(k.secpSeed)) - prv, err := crypto.GenerateKeyFromSeed(randSrc) - if err != nil { - panic(err) - } - k.secpSeed++ - key, err := wallet.NewKey(types.KeyInfo{ - Type: wallet.KTSecp256k1, - PrivateKey: prv, - }) - if err != nil { - panic(err) - } - return key -} - -func (k *KeyManager) newBLSKey() *wallet.Key { - // FIXME: bls needs deterministic key generation - //sk := ffi.PrivateKeyGenerate(s.blsSeed) - // s.blsSeed++ - sk := [32]byte{} - sk[0] = uint8(k.blsSeed) // hack to keep gas values determinist - k.blsSeed++ - key, err := wallet.NewKey(types.KeyInfo{ - Type: wallet.KTBLS, - PrivateKey: sk[:], - }) - if err != nil { - panic(err) - } - return key -} diff --git a/chain/validation/state.go b/chain/validation/state.go deleted file mode 100644 index 965d0a638..000000000 --- a/chain/validation/state.go +++ /dev/null @@ -1,217 +0,0 @@ -package validation - -import ( - "context" - - "github.com/filecoin-project/go-address" - "github.com/ipfs/go-cid" - "github.com/ipfs/go-datastore" - blockstore "github.com/ipfs/go-ipfs-blockstore" - cbor "github.com/ipfs/go-ipld-cbor" - "golang.org/x/xerrors" - - vstate "github.com/filecoin-project/chain-validation/state" - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/abi/big" - "github.com/filecoin-project/specs-actors/actors/runtime" - - "github.com/filecoin-project/lotus/chain/state" - "github.com/filecoin-project/lotus/chain/types" -) - -var _ vstate.VMWrapper = &StateWrapper{} - -type StateWrapper struct { - // The blockstore underlying the state tree and storage. - bs blockstore.Blockstore - - ds datastore.Batching - // HAMT-CBOR store on top of the blockstore. - cst cbor.IpldStore - - // CID of the root of the state tree. - stateRoot cid.Cid -} - -func NewState() *StateWrapper { - bs := blockstore.NewBlockstore(datastore.NewMapDatastore()) - cst := cbor.NewCborStore(bs) - // Put EmptyObjectCid value in the store. When an actor is initially created its Head is set to this value. - _, err := cst.Put(context.TODO(), map[string]string{}) - if err != nil { - panic(err) - } - - treeImpl, err := state.NewStateTree(cst) - if err != nil { - panic(err) // Never returns error, the error return should be removed. - } - root, err := treeImpl.Flush(context.TODO()) - if err != nil { - panic(err) - } - return &StateWrapper{ - bs: bs, - ds: datastore.NewMapDatastore(), - cst: cst, - stateRoot: root, - } -} - -func (s *StateWrapper) NewVM() { - return -} - -func (s *StateWrapper) Root() cid.Cid { - return s.stateRoot -} - -// StoreGet the value at key from vm store -func (s *StateWrapper) StoreGet(key cid.Cid, out runtime.CBORUnmarshaler) error { - tree, err := state.LoadStateTree(s.cst, s.stateRoot) - if err != nil { - return err - } - return tree.Store.Get(context.Background(), key, out) -} - -// StorePut `value` into vm store -func (s *StateWrapper) StorePut(value runtime.CBORMarshaler) (cid.Cid, error) { - tree, err := state.LoadStateTree(s.cst, s.stateRoot) - if err != nil { - return cid.Undef, err - } - return tree.Store.Put(context.Background(), value) -} - -func (s *StateWrapper) Actor(addr address.Address) (vstate.Actor, error) { - tree, err := state.LoadStateTree(s.cst, s.stateRoot) - if err != nil { - return nil, err - } - fcActor, err := tree.GetActor(addr) - if err != nil { - return nil, err - } - return &actorWrapper{*fcActor}, nil -} - -func (s *StateWrapper) SetActorState(addr address.Address, balance abi.TokenAmount, actorState runtime.CBORMarshaler) (vstate.Actor, error) { - tree, err := state.LoadStateTree(s.cst, s.stateRoot) - if err != nil { - return nil, err - } - // actor should exist - act, err := tree.GetActor(addr) - if err != nil { - return nil, err - } - // add the state to the store and get a new head cid - actHead, err := tree.Store.Put(context.Background(), actorState) - if err != nil { - return nil, err - } - // update the actor object with new head and balance parameter - actr := &actorWrapper{types.Actor{ - Code: act.Code, - Nonce: act.Nonce, - // updates - Head: actHead, - Balance: balance, - }} - if err := tree.SetActor(addr, &actr.Actor); err != nil { - return nil, err - } - return actr, s.flush(tree) -} - -func (s *StateWrapper) CreateActor(code cid.Cid, addr address.Address, balance abi.TokenAmount, actorState runtime.CBORMarshaler) (vstate.Actor, address.Address, error) { - idAddr := addr - tree, err := state.LoadStateTree(s.cst, s.stateRoot) - if err != nil { - return nil, address.Undef, err - } - if addr.Protocol() != address.ID { - - actHead, err := tree.Store.Put(context.Background(), actorState) - if err != nil { - return nil, address.Undef, err - } - actr := &actorWrapper{types.Actor{ - Code: code, - Head: actHead, - Balance: balance, - }} - - idAddr, err = tree.RegisterNewAddress(addr) - if err != nil { - return nil, address.Undef, xerrors.Errorf("register new address for actor: %w", err) - } - - if err := tree.SetActor(addr, &actr.Actor); err != nil { - return nil, address.Undef, xerrors.Errorf("setting new actor for actor: %w", err) - } - } - - // store newState - head, err := tree.Store.Put(context.Background(), actorState) - if err != nil { - return nil, address.Undef, err - } - - // create and store actor object - a := types.Actor{ - Code: code, - Head: head, - Balance: balance, - } - if err := tree.SetActor(idAddr, &a); err != nil { - return nil, address.Undef, err - } - - return &actorWrapper{a}, idAddr, s.flush(tree) -} - -// Flushes a state tree to storage and sets this state's root to that tree's root CID. -func (s *StateWrapper) flush(tree *state.StateTree) (err error) { - s.stateRoot, err = tree.Flush(context.TODO()) - return -} - -// -// Actor Wrapper -// - -type actorWrapper struct { - types.Actor -} - -func (a *actorWrapper) Code() cid.Cid { - return a.Actor.Code -} - -func (a *actorWrapper) Head() cid.Cid { - return a.Actor.Head -} - -func (a *actorWrapper) CallSeqNum() uint64 { - return a.Actor.Nonce -} - -func (a *actorWrapper) Balance() big.Int { - return a.Actor.Balance - -} - -// -// Storage -// - -type contextStore struct { - cbor.IpldStore - ctx context.Context -} - -func (s *contextStore) Context() context.Context { - return s.ctx -} diff --git a/chain/vectors/gen/main.go b/chain/vectors/gen/main.go index cf8ebd859..ce9f1baf8 100644 --- a/chain/vectors/gen/main.go +++ b/chain/vectors/gen/main.go @@ -1,29 +1,31 @@ package main import ( + "context" "encoding/json" "fmt" "math/rand" "os" - "github.com/filecoin-project/go-address" + "golang.org/x/xerrors" + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/gen" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types/mock" "github.com/filecoin-project/lotus/chain/vectors" "github.com/filecoin-project/lotus/chain/wallet" - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/abi/big" - "github.com/filecoin-project/specs-actors/actors/builtin/power" - "github.com/filecoin-project/specs-actors/actors/crypto" - _ "github.com/filecoin-project/lotus/lib/sigs/bls" + _ "github.com/filecoin-project/lotus/lib/sigs/delegated" _ "github.com/filecoin-project/lotus/lib/sigs/secp" ) func init() { - power.ConsensusMinerMinPower = big.NewInt(2048) + policy.SetMinVerifiedDealSize(abi.NewStoragePower(2048)) + policy.SetConsensusMinerMinPower(abi.NewStoragePower(2048)) } func MakeHeaderVectors() []vectors.HeaderVector { @@ -60,11 +62,11 @@ func MakeMessageSigningVectors() []vectors.MessageSigningVector { panic(err) } - blsk, err := w.GenerateKey(crypto.SigTypeBLS) + blsk, err := w.WalletNew(context.Background(), types.KTBLS) if err != nil { panic(err) } - bki, err := w.Export(blsk) + bki, err := w.WalletExport(context.Background(), blsk) if err != nil { panic(err) } @@ -84,11 +86,11 @@ func MakeMessageSigningVectors() []vectors.MessageSigningVector { Signature: &bmsg.Signature, } - secpk, err := w.GenerateKey(crypto.SigTypeBLS) + secpk, err := w.WalletNew(context.Background(), types.KTBLS) if err != nil { panic(err) } - ski, err := w.Export(secpk) + ski, err := w.WalletExport(context.Background(), secpk) if err != nil { panic(err) } @@ -136,7 +138,8 @@ func MakeUnsignedMessageVectors() []vectors.UnsignedMessageVector { if err != nil { panic(err) } - to, err := address.NewIDAddress(rand.Uint64()) + uint63mask := uint64(1<<63 - 1) + to, err := address.NewIDAddress(rand.Uint64() & uint63mask) if err != nil { panic(err) } @@ -145,14 +148,15 @@ func MakeUnsignedMessageVectors() []vectors.UnsignedMessageVector { rand.Read(params) msg := &types.Message{ - To: to, - From: from, - Value: types.NewInt(rand.Uint64()), - Method: abi.MethodNum(rand.Uint64()), - GasPrice: types.NewInt(rand.Uint64()), - GasLimit: rand.Int63(), - Nonce: rand.Uint64(), - Params: params, + To: to, + From: from, + Value: types.NewInt(rand.Uint64()), + Method: abi.MethodNum(rand.Uint64()), + GasFeeCap: types.NewInt(rand.Uint64()), + GasPremium: types.NewInt(rand.Uint64()), + GasLimit: rand.Int63(), + Nonce: rand.Uint64() & (1<<63 - 1), + Params: params, } ser, err := msg.Serialize() @@ -173,14 +177,18 @@ func WriteJsonToFile(fname string, obj interface{}) error { if err != nil { return err } - defer fi.Close() + defer fi.Close() //nolint:errcheck out, err := json.MarshalIndent(obj, "", " ") if err != nil { return err } - fi.Write(out) + _, err = fi.Write(out) + if err != nil { + return xerrors.Errorf("writing json: %w", err) + } + return nil } diff --git a/chain/vectors/vector_types.go b/chain/vectors/vector_types.go index 73216a049..3251fde38 100644 --- a/chain/vectors/vector_types.go +++ b/chain/vectors/vector_types.go @@ -1,8 +1,9 @@ package vectors import ( + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/specs-actors/actors/crypto" ) type HeaderVector struct { diff --git a/chain/vectors/vectors_test.go b/chain/vectors/vectors_test.go index c9ebc98fa..8e73ec012 100644 --- a/chain/vectors/vectors_test.go +++ b/chain/vectors/vectors_test.go @@ -1,3 +1,4 @@ +// stm: #unit package vectors import ( @@ -18,6 +19,7 @@ func LoadVector(t *testing.T, f string, out interface{}) { if err != nil { t.Fatal(err) } + defer fi.Close() //nolint:errcheck if err := json.NewDecoder(fi).Decode(out); err != nil { t.Fatal(err) @@ -25,7 +27,7 @@ func LoadVector(t *testing.T, f string, out interface{}) { } func TestBlockHeaderVectors(t *testing.T) { - t.Skip("we need to regenerate for beacon") + //stm: @CHAIN_TYPES_SERIALIZATION_BLOCK_001 var headers []HeaderVector LoadVector(t, "block_headers.json", &headers) @@ -46,6 +48,7 @@ func TestBlockHeaderVectors(t *testing.T) { } func TestMessageSigningVectors(t *testing.T) { + //stm: @CHAIN_TYPES_SERIALIZATION_SIGNED_MESSAGE_001 var msvs []MessageSigningVector LoadVector(t, "message_signing.json", &msvs) @@ -64,6 +67,7 @@ func TestMessageSigningVectors(t *testing.T) { } func TestUnsignedMessageVectors(t *testing.T) { + //stm: @CHAIN_TYPES_SERIALIZATION_MESSAGE_001 var msvs []UnsignedMessageVector LoadVector(t, "unsigned_messages.json", &msvs) diff --git a/chain/vm/burn.go b/chain/vm/burn.go new file mode 100644 index 000000000..a214d198b --- /dev/null +++ b/chain/vm/burn.go @@ -0,0 +1,107 @@ +package vm + +import ( + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" +) + +const ( + gasOveruseNum = 11 + gasOveruseDenom = 10 +) + +type GasOutputs struct { + BaseFeeBurn abi.TokenAmount + OverEstimationBurn abi.TokenAmount + + MinerPenalty abi.TokenAmount + MinerTip abi.TokenAmount + Refund abi.TokenAmount + + GasRefund int64 + GasBurned int64 +} + +// ZeroGasOutputs returns a logically zeroed GasOutputs. +func ZeroGasOutputs() GasOutputs { + return GasOutputs{ + BaseFeeBurn: big.Zero(), + OverEstimationBurn: big.Zero(), + MinerPenalty: big.Zero(), + MinerTip: big.Zero(), + Refund: big.Zero(), + } +} + +// ComputeGasOverestimationBurn computes amount of gas to be refunded and amount of gas to be burned +// Result is (refund, burn) +func ComputeGasOverestimationBurn(gasUsed, gasLimit int64) (int64, int64) { + if gasUsed == 0 { + return 0, gasLimit + } + + // over = gasLimit/gasUsed - 1 - 0.1 + // over = min(over, 1) + // gasToBurn = (gasLimit - gasUsed) * over + + // so to factor out division from `over` + // over*gasUsed = min(gasLimit - (11*gasUsed)/10, gasUsed) + // gasToBurn = ((gasLimit - gasUsed)*over*gasUsed) / gasUsed + over := gasLimit - (gasOveruseNum*gasUsed)/gasOveruseDenom + if over < 0 { + return gasLimit - gasUsed, 0 + } + + // if we want sharper scaling it goes here: + // over *= 2 + + if over > gasUsed { + over = gasUsed + } + + // needs bigint, as it overflows in pathological case gasLimit > 2^32 gasUsed = gasLimit / 2 + gasToBurn := big.NewInt(gasLimit - gasUsed) + gasToBurn = big.Mul(gasToBurn, big.NewInt(over)) + gasToBurn = big.Div(gasToBurn, big.NewInt(gasUsed)) + + return gasLimit - gasUsed - gasToBurn.Int64(), gasToBurn.Int64() +} + +func ComputeGasOutputs(gasUsed, gasLimit int64, baseFee, feeCap, gasPremium abi.TokenAmount, chargeNetworkFee bool) GasOutputs { + gasUsedBig := big.NewInt(gasUsed) + out := ZeroGasOutputs() + + baseFeeToPay := baseFee + if baseFee.Cmp(feeCap.Int) > 0 { + baseFeeToPay = feeCap + out.MinerPenalty = big.Mul(big.Sub(baseFee, feeCap), gasUsedBig) + } + + // If chargeNetworkFee is disabled, just skip computing the BaseFeeBurn. However, + // we charge all the other fees regardless. + if chargeNetworkFee { + out.BaseFeeBurn = big.Mul(baseFeeToPay, gasUsedBig) + } + + minerTip := gasPremium + if big.Cmp(big.Add(baseFeeToPay, minerTip), feeCap) > 0 { + minerTip = big.Sub(feeCap, baseFeeToPay) + } + out.MinerTip = big.Mul(minerTip, big.NewInt(gasLimit)) + + out.GasRefund, out.GasBurned = ComputeGasOverestimationBurn(gasUsed, gasLimit) + + if out.GasBurned != 0 { + gasBurnedBig := big.NewInt(out.GasBurned) + out.OverEstimationBurn = big.Mul(baseFeeToPay, gasBurnedBig) + minerPenalty := big.Mul(big.Sub(baseFee, baseFeeToPay), gasBurnedBig) + out.MinerPenalty = big.Add(out.MinerPenalty, minerPenalty) + } + + requiredFunds := big.Mul(big.NewInt(gasLimit), feeCap) + refund := big.Sub(requiredFunds, out.BaseFeeBurn) + refund = big.Sub(refund, out.MinerTip) + refund = big.Sub(refund, out.OverEstimationBurn) + out.Refund = refund + return out +} diff --git a/chain/vm/burn_test.go b/chain/vm/burn_test.go new file mode 100644 index 000000000..46bc7f5de --- /dev/null +++ b/chain/vm/burn_test.go @@ -0,0 +1,82 @@ +// stm: #unit +package vm + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/filecoin-project/lotus/chain/types" +) + +func TestGasBurn(t *testing.T) { + //stm: @BURN_ESTIMATE_GAS_OVERESTIMATION_BURN_001 + tests := []struct { + used int64 + limit int64 + refund int64 + burn int64 + }{ + {100, 200, 10, 90}, + {100, 150, 30, 20}, + {1000, 1300, 240, 60}, + {500, 700, 140, 60}, + {200, 200, 0, 0}, + {20000, 21000, 1000, 0}, + {0, 2000, 0, 2000}, + {500, 651, 121, 30}, + {500, 5000, 0, 4500}, + {7499e6, 7500e6, 1000000, 0}, + {7500e6 / 2, 7500e6, 375000000, 3375000000}, + {1, 7500e6, 0, 7499999999}, + } + + for _, test := range tests { + test := test + t.Run(fmt.Sprintf("%v", test), func(t *testing.T) { + refund, toBurn := ComputeGasOverestimationBurn(test.used, test.limit) + assert.Equal(t, test.refund, refund, "refund") + assert.Equal(t, test.burn, toBurn, "burned") + }) + } +} + +func TestGasOutputs(t *testing.T) { + //stm: @BURN_ESTIMATE_GAS_OUTPUTS_001 + baseFee := types.NewInt(10) + tests := []struct { + used int64 + limit int64 + + feeCap uint64 + premium uint64 + + BaseFeeBurn uint64 + OverEstimationBurn uint64 + MinerPenalty uint64 + MinerTip uint64 + Refund uint64 + }{ + {100, 110, 11, 1, 1000, 0, 0, 110, 100}, + {100, 130, 11, 1, 1000, 60, 0, 130, 240}, + {100, 110, 10, 1, 1000, 0, 0, 0, 100}, + {100, 110, 6, 1, 600, 0, 400, 0, 60}, + } + + for _, test := range tests { + test := test + t.Run(fmt.Sprintf("%v", test), func(t *testing.T) { + output := ComputeGasOutputs(test.used, test.limit, baseFee, types.NewInt(test.feeCap), types.NewInt(test.premium), true) + i2s := func(i uint64) string { + return fmt.Sprintf("%d", i) + } + assert.Equal(t, i2s(test.BaseFeeBurn), output.BaseFeeBurn.String(), "BaseFeeBurn") + assert.Equal(t, i2s(test.OverEstimationBurn), output.OverEstimationBurn.String(), "OverEstimationBurn") + assert.Equal(t, i2s(test.MinerPenalty), output.MinerPenalty.String(), "MinerPenalty") + assert.Equal(t, i2s(test.MinerTip), output.MinerTip.String(), "MinerTip") + assert.Equal(t, i2s(test.Refund), output.Refund.String(), "Refund") + }) + } + +} diff --git a/chain/vm/execution.go b/chain/vm/execution.go new file mode 100644 index 000000000..ea3a97193 --- /dev/null +++ b/chain/vm/execution.go @@ -0,0 +1,192 @@ +package vm + +import ( + "context" + "os" + "strconv" + "sync" + + "github.com/ipfs/go-cid" + "go.opencensus.io/stats" + "go.opencensus.io/tag" + + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/metrics" +) + +const ( + // DefaultAvailableExecutionLanes is the number of available execution lanes; it is the bound of + // concurrent active executions. + // This is the default value in filecoin-ffi + DefaultAvailableExecutionLanes = 4 + // DefaultPriorityExecutionLanes is the number of reserved execution lanes for priority computations. + // This is purely userspace, but we believe it is a reasonable default, even with more available + // lanes. + DefaultPriorityExecutionLanes = 2 +) + +// the execution environment; see below for definition, methods, and initialization +var execution *executionEnv + +// implementation of vm executor with simple sanity check preventing use after free. +type vmExecutor struct { + vmi Interface + lane ExecutionLane +} + +var _ Interface = (*vmExecutor)(nil) + +func newVMExecutor(vmi Interface, lane ExecutionLane) Interface { + return &vmExecutor{vmi: vmi, lane: lane} +} + +func (e *vmExecutor) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet, error) { + token := execution.getToken(e.lane) + defer token.Done() + + return e.vmi.ApplyMessage(ctx, cmsg) +} + +func (e *vmExecutor) ApplyImplicitMessage(ctx context.Context, msg *types.Message) (*ApplyRet, error) { + token := execution.getToken(e.lane) + defer token.Done() + + return e.vmi.ApplyImplicitMessage(ctx, msg) +} + +func (e *vmExecutor) Flush(ctx context.Context) (cid.Cid, error) { + return e.vmi.Flush(ctx) +} + +type executionToken struct { + lane ExecutionLane + reserved int +} + +func (token *executionToken) Done() { + execution.putToken(token) +} + +type executionEnv struct { + mx *sync.Mutex + cond *sync.Cond + + // available executors + available int + // reserved executors + reserved int +} + +func (e *executionEnv) getToken(lane ExecutionLane) *executionToken { + metricsUp(metrics.VMExecutionWaiting, lane) + defer metricsDown(metrics.VMExecutionWaiting, lane) + + e.mx.Lock() + defer e.mx.Unlock() + + switch lane { + case ExecutionLaneDefault: + for e.available <= e.reserved { + e.cond.Wait() + } + + e.available-- + + metricsUp(metrics.VMExecutionRunning, lane) + return &executionToken{lane: lane, reserved: 0} + + case ExecutionLanePriority: + for e.available == 0 { + e.cond.Wait() + } + + e.available-- + + reserving := 0 + if e.reserved > 0 { + e.reserved-- + reserving = 1 + } + + metricsUp(metrics.VMExecutionRunning, lane) + return &executionToken{lane: lane, reserved: reserving} + + default: + // already checked at interface boundary in NewVM, so this is appropriate + panic("bogus execution lane") + } +} + +func (e *executionEnv) putToken(token *executionToken) { + e.mx.Lock() + defer e.mx.Unlock() + + e.available++ + e.reserved += token.reserved + + // Note: Signal is unsound, because a priority token could wake up a non-priority + // goroutnie and lead to deadlock. So Broadcast it must be. + e.cond.Broadcast() + + metricsDown(metrics.VMExecutionRunning, token.lane) +} + +func metricsUp(metric *stats.Int64Measure, lane ExecutionLane) { + metricsAdjust(metric, lane, 1) +} + +func metricsDown(metric *stats.Int64Measure, lane ExecutionLane) { + metricsAdjust(metric, lane, -1) +} + +func metricsAdjust(metric *stats.Int64Measure, lane ExecutionLane, delta int) { + laneName := "default" + if lane > ExecutionLaneDefault { + laneName = "priority" + } + + ctx, _ := tag.New( + context.Background(), + tag.Upsert(metrics.ExecutionLane, laneName), + ) + stats.Record(ctx, metric.M(int64(delta))) +} + +func init() { + var err error + + available := DefaultAvailableExecutionLanes + if concurrency := os.Getenv("LOTUS_FVM_CONCURRENCY"); concurrency != "" { + available, err = strconv.Atoi(concurrency) + if err != nil { + panic(err) + } + } + + priority := DefaultPriorityExecutionLanes + if reserved := os.Getenv("LOTUS_FVM_CONCURRENCY_RESERVED"); reserved != "" { + priority, err = strconv.Atoi(reserved) + if err != nil { + panic(err) + } + } + + // some sanity checks + if available < 2 { + panic("insufficient execution concurrency") + } + + if available <= priority { + panic("insufficient default execution concurrency") + } + + mx := &sync.Mutex{} + cond := sync.NewCond(mx) + + execution = &executionEnv{ + mx: mx, + cond: cond, + available: available, + reserved: priority, + } +} diff --git a/chain/vm/fvm.go b/chain/vm/fvm.go new file mode 100644 index 000000000..7c79972c7 --- /dev/null +++ b/chain/vm/fvm.go @@ -0,0 +1,639 @@ +package vm + +import ( + "bytes" + "context" + "fmt" + "io" + "math" + "os" + "sort" + "sync" + "sync/atomic" + "time" + + "github.com/ipfs/go-cid" + cbor "github.com/ipfs/go-ipld-cbor" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + ffi "github.com/filecoin-project/filecoin-ffi" + ffi_cgo "github.com/filecoin-project/filecoin-ffi/cgo" + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/exitcode" + "github.com/filecoin-project/go-state-types/manifest" + "github.com/filecoin-project/go-state-types/network" + + "github.com/filecoin-project/lotus/blockstore" + "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/aerrors" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/policy" + "github.com/filecoin-project/lotus/chain/state" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/lib/sigs" + "github.com/filecoin-project/lotus/node/bundle" +) + +var _ Interface = (*FVM)(nil) +var _ ffi_cgo.Externs = (*FvmExtern)(nil) + +type FvmExtern struct { + Rand + blockstore.Blockstore + epoch abi.ChainEpoch + lbState LookbackStateGetter + tsGet TipSetGetter + base cid.Cid +} + +func (x *FvmExtern) TipsetCid(ctx context.Context, epoch abi.ChainEpoch) (cid.Cid, error) { + tsk, err := x.tsGet(ctx, epoch) + if err != nil { + return cid.Undef, err + } + return tsk.Cid() +} + +// VerifyConsensusFault is similar to the one in syscalls.go used by the Lotus VM, except it never errors +// Errors are logged and "no fault" is returned, which is functionally what go-actors does anyway +func (x *FvmExtern) VerifyConsensusFault(ctx context.Context, a, b, extra []byte) (*ffi_cgo.ConsensusFault, int64) { + totalGas := int64(0) + ret := &ffi_cgo.ConsensusFault{ + Type: ffi_cgo.ConsensusFaultNone, + } + + // Note that block syntax is not validated. Any validly signed block will be accepted pursuant to the below conditions. + // Whether or not it could ever have been accepted in a chain is not checked/does not matter here. + // for that reason when checking block parent relationships, rather than instantiating a Tipset to do so + // (which runs a syntactic check), we do it directly on the CIDs. + + // (0) cheap preliminary checks + + // can blocks be decoded properly? + var blockA, blockB types.BlockHeader + if decodeErr := blockA.UnmarshalCBOR(bytes.NewReader(a)); decodeErr != nil { + log.Info("invalid consensus fault: cannot decode first block header: %w", decodeErr) + return ret, totalGas + } + + if decodeErr := blockB.UnmarshalCBOR(bytes.NewReader(b)); decodeErr != nil { + log.Info("invalid consensus fault: cannot decode second block header: %w", decodeErr) + return ret, totalGas + } + + // are blocks the same? + if blockA.Cid().Equals(blockB.Cid()) { + log.Info("invalid consensus fault: submitted blocks are the same") + return ret, totalGas + } + // (1) check conditions necessary to any consensus fault + + // were blocks mined by same miner? + if blockA.Miner != blockB.Miner { + log.Info("invalid consensus fault: blocks not mined by the same miner") + return ret, totalGas + } + + // block a must be earlier or equal to block b, epoch wise (ie at least as early in the chain). + if blockB.Height < blockA.Height { + log.Info("invalid consensus fault: first block must not be of higher height than second") + return ret, totalGas + } + + ret.Epoch = blockB.Height + + faultType := ffi_cgo.ConsensusFaultNone + + // (2) check for the consensus faults themselves + // (a) double-fork mining fault + if blockA.Height == blockB.Height { + faultType = ffi_cgo.ConsensusFaultDoubleForkMining + } + + // (b) time-offset mining fault + // strictly speaking no need to compare heights based on double fork mining check above, + // but at same height this would be a different fault. + if types.CidArrsEqual(blockA.Parents, blockB.Parents) && blockA.Height != blockB.Height { + faultType = ffi_cgo.ConsensusFaultTimeOffsetMining + } + + // (c) parent-grinding fault + // Here extra is the "witness", a third block that shows the connection between A and B as + // A's sibling and B's parent. + // Specifically, since A is of lower height, it must be that B was mined omitting A from its tipset + // + // B + // | + // [A, C] + var blockC types.BlockHeader + if len(extra) > 0 { + if decodeErr := blockC.UnmarshalCBOR(bytes.NewReader(extra)); decodeErr != nil { + log.Info("invalid consensus fault: cannot decode extra: %w", decodeErr) + return ret, totalGas + } + + if types.CidArrsEqual(blockA.Parents, blockC.Parents) && blockA.Height == blockC.Height && + types.CidArrsContains(blockB.Parents, blockC.Cid()) && !types.CidArrsContains(blockB.Parents, blockA.Cid()) { + faultType = ffi_cgo.ConsensusFaultParentGrinding + } + } + + // (3) return if no consensus fault by now + if faultType == ffi_cgo.ConsensusFaultNone { + log.Info("invalid consensus fault: no fault detected") + return ret, totalGas + } + + // else + // (4) expensive final checks + + // check blocks are properly signed by their respective miner + // note we do not need to check extra's: it is a parent to block b + // which itself is signed, so it was willingly included by the miner + gasA, sigErr := x.verifyBlockSig(ctx, &blockA) + totalGas += gasA + if sigErr != nil { + log.Info("invalid consensus fault: cannot verify first block sig: %w", sigErr) + return ret, totalGas + } + + gas2, sigErr := x.verifyBlockSig(ctx, &blockB) + totalGas += gas2 + if sigErr != nil { + log.Info("invalid consensus fault: cannot verify second block sig: %w", sigErr) + return ret, totalGas + } + + ret.Type = faultType + ret.Target = blockA.Miner + + return ret, totalGas +} + +func (x *FvmExtern) verifyBlockSig(ctx context.Context, blk *types.BlockHeader) (int64, error) { + waddr, gasUsed, err := x.workerKeyAtLookback(ctx, blk.Miner, blk.Height) + if err != nil { + return gasUsed, err + } + + return gasUsed, sigs.CheckBlockSignature(ctx, blk, waddr) +} + +func (x *FvmExtern) workerKeyAtLookback(ctx context.Context, minerId address.Address, height abi.ChainEpoch) (address.Address, int64, error) { + if height < x.epoch-policy.ChainFinality { + return address.Undef, 0, xerrors.Errorf("cannot get worker key (currEpoch %d, height %d)", x.epoch, height) + } + + gasUsed := int64(0) + gasAdder := func(gc GasCharge) { + // technically not overflow safe, but that's fine + gasUsed += gc.Total() + } + + cstWithoutGas := cbor.NewCborStore(x.Blockstore) + cbb := &gasChargingBlocks{gasAdder, PricelistByEpoch(x.epoch), x.Blockstore} + cstWithGas := cbor.NewCborStore(cbb) + + lbState, err := x.lbState(ctx, height) + if err != nil { + return address.Undef, gasUsed, err + } + // get appropriate miner actor + act, err := lbState.GetActor(minerId) + if err != nil { + return address.Undef, gasUsed, err + } + + // use that to get the miner state + mas, err := miner.Load(adt.WrapStore(ctx, cstWithGas), act) + if err != nil { + return address.Undef, gasUsed, err + } + + info, err := mas.Info() + if err != nil { + return address.Undef, gasUsed, err + } + + stateTree, err := state.LoadStateTree(cstWithoutGas, x.base) + if err != nil { + return address.Undef, gasUsed, err + } + + raddr, err := ResolveToDeterministicAddr(stateTree, cstWithGas, info.Worker) + if err != nil { + return address.Undef, gasUsed, err + } + + return raddr, gasUsed, nil +} + +type FVM struct { + fvm *ffi.FVM + nv network.Version + + // returnEvents specifies whether to parse and return events when applying messages. + returnEvents bool +} + +func defaultFVMOpts(ctx context.Context, opts *VMOpts) (*ffi.FVMOpts, error) { + state, err := state.LoadStateTree(cbor.NewCborStore(opts.Bstore), opts.StateBase) + if err != nil { + return nil, xerrors.Errorf("loading state tree: %w", err) + } + + circToReport, err := opts.CircSupplyCalc(ctx, opts.Epoch, state) + if err != nil { + return nil, xerrors.Errorf("calculating circ supply: %w", err) + } + + return &ffi.FVMOpts{ + FVMVersion: 0, + Externs: &FvmExtern{ + Rand: opts.Rand, + Blockstore: opts.Bstore, + lbState: opts.LookbackState, + tsGet: opts.TipSetGetter, + base: opts.StateBase, + epoch: opts.Epoch, + }, + Epoch: opts.Epoch, + Timestamp: opts.Timestamp, + ChainID: build.Eip155ChainId, + BaseFee: opts.BaseFee, + BaseCircSupply: circToReport, + NetworkVersion: opts.NetworkVersion, + StateBase: opts.StateBase, + Tracing: opts.Tracing || EnableDetailedTracing, + Debug: build.ActorDebugging, + }, nil + +} + +func NewFVM(ctx context.Context, opts *VMOpts) (*FVM, error) { + fvmOpts, err := defaultFVMOpts(ctx, opts) + if err != nil { + return nil, xerrors.Errorf("creating fvm opts: %w", err) + } + + fvm, err := ffi.CreateFVM(fvmOpts) + + if err != nil { + return nil, xerrors.Errorf("failed to create FVM: %w", err) + } + + ret := &FVM{ + fvm: fvm, + nv: opts.NetworkVersion, + returnEvents: opts.ReturnEvents, + } + + return ret, nil +} + +func NewDebugFVM(ctx context.Context, opts *VMOpts) (*FVM, error) { + baseBstore := opts.Bstore + overlayBstore := blockstore.NewMemorySync() + cborStore := cbor.NewCborStore(overlayBstore) + vmBstore := blockstore.NewTieredBstore(overlayBstore, baseBstore) + + opts.Bstore = vmBstore + fvmOpts, err := defaultFVMOpts(ctx, opts) + if err != nil { + return nil, xerrors.Errorf("creating fvm opts: %w", err) + } + + fvmOpts.Debug = true + + putMapping := func(ar map[cid.Cid]cid.Cid) (cid.Cid, error) { + var mapping xMapping + + mapping.redirects = make([]xRedirect, 0, len(ar)) + for from, to := range ar { + mapping.redirects = append(mapping.redirects, xRedirect{from: from, to: to}) + } + sort.Slice(mapping.redirects, func(i, j int) bool { + return bytes.Compare(mapping.redirects[i].from.Bytes(), mapping.redirects[j].from.Bytes()) < 0 + }) + + // Passing this as a pointer of structs has proven to be an enormous PiTA; hence this code. + mappingCid, err := cborStore.Put(context.TODO(), &mapping) + if err != nil { + return cid.Undef, err + } + + return mappingCid, nil + } + + createMapping := func(debugBundlePath string) error { + mfCid, err := bundle.LoadBundleFromFile(ctx, overlayBstore, debugBundlePath) + if err != nil { + return xerrors.Errorf("loading debug bundle: %w", err) + } + + mf, err := actors.LoadManifest(ctx, mfCid, adt.WrapStore(ctx, cborStore)) + if err != nil { + return xerrors.Errorf("loading debug manifest: %w", err) + } + + av, err := actorstypes.VersionForNetwork(opts.NetworkVersion) + if err != nil { + return xerrors.Errorf("getting actors version: %w", err) + } + + // create actor redirect mapping + actorRedirect := make(map[cid.Cid]cid.Cid) + for _, key := range manifest.GetBuiltinActorsKeys(av) { + from, ok := actors.GetActorCodeID(av, key) + if !ok { + log.Warnf("actor missing in the from manifest %s", key) + continue + } + + to, ok := mf.Get(key) + if !ok { + log.Warnf("actor missing in the to manifest %s", key) + continue + } + + actorRedirect[from] = to + } + + if len(actorRedirect) > 0 { + mappingCid, err := putMapping(actorRedirect) + if err != nil { + return xerrors.Errorf("error writing redirect mapping: %w", err) + } + fvmOpts.ActorRedirect = mappingCid + } + + return nil + } + + av, err := actorstypes.VersionForNetwork(opts.NetworkVersion) + if err != nil { + return nil, xerrors.Errorf("error determining actors version for network version %d: %w", opts.NetworkVersion, err) + } + + debugBundlePath := os.Getenv(fmt.Sprintf("LOTUS_FVM_DEBUG_BUNDLE_V%d", av)) + if debugBundlePath != "" { + if err := createMapping(debugBundlePath); err != nil { + log.Errorf("failed to create v%d debug mapping", av) + } + } + + fvm, err := ffi.CreateFVM(fvmOpts) + + if err != nil { + return nil, err + } + + ret := &FVM{ + fvm: fvm, + nv: opts.NetworkVersion, + returnEvents: opts.ReturnEvents, + } + + return ret, nil +} + +func (vm *FVM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet, error) { + start := build.Clock.Now() + defer atomic.AddUint64(&StatApplied, 1) + vmMsg := cmsg.VMMessage() + msgBytes, err := vmMsg.Serialize() + if err != nil { + return nil, xerrors.Errorf("serializing msg: %w", err) + } + + ret, err := vm.fvm.ApplyMessage(msgBytes, uint(cmsg.ChainLength())) + if err != nil { + return nil, xerrors.Errorf("applying msg: %w", err) + } + + duration := time.Since(start) + + var receipt types.MessageReceipt + if vm.nv >= network.Version18 { + receipt = types.NewMessageReceiptV1(exitcode.ExitCode(ret.ExitCode), ret.Return, ret.GasUsed, ret.EventsRoot) + } else { + receipt = types.NewMessageReceiptV0(exitcode.ExitCode(ret.ExitCode), ret.Return, ret.GasUsed) + } + + var aerr aerrors.ActorError + if ret.ExitCode != 0 { + amsg := ret.FailureInfo + if amsg == "" { + amsg = "unknown error" + } + aerr = aerrors.New(exitcode.ExitCode(ret.ExitCode), amsg) + } + + var et types.ExecutionTrace + if len(ret.ExecTraceBytes) != 0 { + if err = et.UnmarshalCBOR(bytes.NewReader(ret.ExecTraceBytes)); err != nil { + return nil, xerrors.Errorf("failed to unmarshal exectrace: %w", err) + } + } + + applyRet := &ApplyRet{ + MessageReceipt: receipt, + GasCosts: &GasOutputs{ + BaseFeeBurn: ret.BaseFeeBurn, + OverEstimationBurn: ret.OverEstimationBurn, + MinerPenalty: ret.MinerPenalty, + MinerTip: ret.MinerTip, + Refund: ret.Refund, + GasRefund: ret.GasRefund, + GasBurned: ret.GasBurned, + }, + ActorErr: aerr, + ExecutionTrace: et, + Duration: duration, + } + + if vm.returnEvents && len(ret.EventsBytes) > 0 { + applyRet.Events, err = types.DecodeEvents(ret.EventsBytes) + if err != nil { + return nil, fmt.Errorf("failed to decode events returned by the FVM: %w", err) + } + } + + return applyRet, nil +} + +func (vm *FVM) ApplyImplicitMessage(ctx context.Context, cmsg *types.Message) (*ApplyRet, error) { + start := build.Clock.Now() + defer atomic.AddUint64(&StatApplied, 1) + cmsg.GasLimit = math.MaxInt64 / 2 + vmMsg := cmsg.VMMessage() + msgBytes, err := vmMsg.Serialize() + if err != nil { + return nil, xerrors.Errorf("serializing msg: %w", err) + } + ret, err := vm.fvm.ApplyImplicitMessage(msgBytes) + if err != nil { + return nil, xerrors.Errorf("applying msg: %w", err) + } + + duration := time.Since(start) + + var receipt types.MessageReceipt + if vm.nv >= network.Version18 { + receipt = types.NewMessageReceiptV1(exitcode.ExitCode(ret.ExitCode), ret.Return, ret.GasUsed, ret.EventsRoot) + } else { + receipt = types.NewMessageReceiptV0(exitcode.ExitCode(ret.ExitCode), ret.Return, ret.GasUsed) + } + + var aerr aerrors.ActorError + if ret.ExitCode != 0 { + amsg := ret.FailureInfo + if amsg == "" { + amsg = "unknown error" + } + aerr = aerrors.New(exitcode.ExitCode(ret.ExitCode), amsg) + } + + var et types.ExecutionTrace + if len(ret.ExecTraceBytes) != 0 { + if err = et.UnmarshalCBOR(bytes.NewReader(ret.ExecTraceBytes)); err != nil { + return nil, xerrors.Errorf("failed to unmarshal exectrace: %w", err) + } + } + + applyRet := &ApplyRet{ + MessageReceipt: receipt, + ActorErr: aerr, + ExecutionTrace: et, + Duration: duration, + } + + if vm.returnEvents && len(ret.EventsBytes) > 0 { + applyRet.Events, err = types.DecodeEvents(ret.EventsBytes) + if err != nil { + return nil, fmt.Errorf("failed to decode events returned by the FVM: %w", err) + } + } + + if ret.ExitCode != 0 { + return applyRet, fmt.Errorf("implicit message failed with exit code: %d and error: %w", ret.ExitCode, applyRet.ActorErr) + } + + return applyRet, nil +} + +func (vm *FVM) Flush(ctx context.Context) (cid.Cid, error) { + return vm.fvm.Flush() +} + +type dualExecutionFVM struct { + main *FVM + debug *FVM +} + +var _ Interface = (*dualExecutionFVM)(nil) + +func NewDualExecutionFVM(ctx context.Context, opts *VMOpts) (Interface, error) { + main, err := NewFVM(ctx, opts) + if err != nil { + return nil, err + } + + debug, err := NewDebugFVM(ctx, opts) + if err != nil { + return nil, err + } + + return &dualExecutionFVM{ + main: main, + debug: debug, + }, nil +} + +func (vm *dualExecutionFVM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (ret *ApplyRet, err error) { + var wg sync.WaitGroup + + wg.Add(2) + + go func() { + defer wg.Done() + ret, err = vm.main.ApplyMessage(ctx, cmsg) + }() + + go func() { + defer wg.Done() + if _, err := vm.debug.ApplyMessage(ctx, cmsg); err != nil { + log.Errorf("debug execution failed: %w", err) + } + }() + + wg.Wait() + return ret, err +} + +func (vm *dualExecutionFVM) ApplyImplicitMessage(ctx context.Context, msg *types.Message) (ret *ApplyRet, err error) { + var wg sync.WaitGroup + + wg.Add(2) + + go func() { + defer wg.Done() + ret, err = vm.main.ApplyImplicitMessage(ctx, msg) + }() + + go func() { + defer wg.Done() + if _, err := vm.debug.ApplyImplicitMessage(ctx, msg); err != nil { + log.Errorf("debug execution failed: %s", err) + } + }() + + wg.Wait() + return ret, err +} + +func (vm *dualExecutionFVM) Flush(ctx context.Context) (cid.Cid, error) { + return vm.main.Flush(ctx) +} + +// Passing this as a pointer of structs has proven to be an enormous PiTA; hence this code. +type xRedirect struct{ from, to cid.Cid } +type xMapping struct{ redirects []xRedirect } + +func (m *xMapping) MarshalCBOR(w io.Writer) error { + scratch := make([]byte, 9) + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(len(m.redirects))); err != nil { + return err + } + + for _, v := range m.redirects { + if err := v.MarshalCBOR(w); err != nil { + return err + } + } + + return nil +} + +func (r *xRedirect) MarshalCBOR(w io.Writer) error { + scratch := make([]byte, 9) + + if err := cbg.WriteMajorTypeHeaderBuf(scratch, w, cbg.MajArray, uint64(2)); err != nil { + return err + } + + if err := cbg.WriteCidBuf(scratch, w, r.from); err != nil { + return xerrors.Errorf("failed to write cid field from: %w", err) + } + + if err := cbg.WriteCidBuf(scratch, w, r.to); err != nil { + return xerrors.Errorf("failed to write cid field from: %w", err) + } + + return nil +} diff --git a/chain/vm/gas.go b/chain/vm/gas.go index a24e54b78..cb0c5def9 100644 --- a/chain/vm/gas.go +++ b/chain/vm/gas.go @@ -3,70 +3,225 @@ package vm import ( "fmt" - addr "github.com/filecoin-project/go-address" - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/crypto" - "github.com/filecoin-project/specs-actors/actors/runtime" - vmr "github.com/filecoin-project/specs-actors/actors/runtime" "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-address" + addr "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/crypto" + vmr "github.com/filecoin-project/specs-actors/v7/actors/runtime" + proof7 "github.com/filecoin-project/specs-actors/v7/actors/runtime/proof" + + "github.com/filecoin-project/lotus/build" ) -// Pricelist provides prices for operations in the VM. +type GasCharge struct { + Name string + Extra interface{} + + ComputeGas int64 + StorageGas int64 + + VirtualCompute int64 + VirtualStorage int64 +} + +func (g GasCharge) Total() int64 { + return g.ComputeGas + g.StorageGas +} +func (g GasCharge) WithVirtual(compute, storage int64) GasCharge { + out := g + out.VirtualCompute = compute + out.VirtualStorage = storage + return out +} + +func (g GasCharge) WithExtra(extra interface{}) GasCharge { + out := g + out.Extra = extra + return out +} + +func newGasCharge(name string, computeGas int64, storageGas int64) GasCharge { + return GasCharge{ + Name: name, + ComputeGas: computeGas, + StorageGas: storageGas, + } +} + +// Pricelist provides prices for operations in the LegacyVM. // // Note: this interface should be APPEND ONLY since last chain checkpoint type Pricelist interface { // OnChainMessage returns the gas used for storing a message of a given size in the chain. - OnChainMessage(msgSize int) int64 + OnChainMessage(msgSize int) GasCharge // OnChainReturnValue returns the gas used for storing the response of a message in the chain. - OnChainReturnValue(dataSize int) int64 + OnChainReturnValue(dataSize int) GasCharge // OnMethodInvocation returns the gas used when invoking a method. - OnMethodInvocation(value abi.TokenAmount, methodNum abi.MethodNum) int64 + OnMethodInvocation(value abi.TokenAmount, methodNum abi.MethodNum) GasCharge // OnIpldGet returns the gas used for storing an object - OnIpldGet(dataSize int) int64 + OnIpldGet() GasCharge // OnIpldPut returns the gas used for storing an object - OnIpldPut(dataSize int) int64 + OnIpldPut(dataSize int) GasCharge // OnCreateActor returns the gas used for creating an actor - OnCreateActor() int64 + OnCreateActor() GasCharge // OnDeleteActor returns the gas used for deleting an actor - OnDeleteActor() int64 + OnDeleteActor() GasCharge - OnVerifySignature(sigType crypto.SigType, planTextSize int) (int64, error) - OnHashing(dataSize int) int64 - OnComputeUnsealedSectorCid(proofType abi.RegisteredProof, pieces []abi.PieceInfo) int64 - OnVerifySeal(info abi.SealVerifyInfo) int64 - OnVerifyPost(info abi.WindowPoStVerifyInfo) int64 - OnVerifyConsensusFault() int64 + OnVerifySignature(sigType crypto.SigType, planTextSize int) (GasCharge, error) + OnHashing(dataSize int) GasCharge + OnComputeUnsealedSectorCid(proofType abi.RegisteredSealProof, pieces []abi.PieceInfo) GasCharge + OnVerifySeal(info proof7.SealVerifyInfo) GasCharge + OnVerifyAggregateSeals(aggregate proof7.AggregateSealVerifyProofAndInfos) GasCharge + OnVerifyReplicaUpdate(update proof7.ReplicaUpdateInfo) GasCharge + OnVerifyPost(info proof7.WindowPoStVerifyInfo) GasCharge + OnVerifyConsensusFault() GasCharge } -var prices = map[abi.ChainEpoch]Pricelist{ +// Prices are the price lists per starting epoch. Public for testing purposes +// (concretely to allow the test vector runner to rebase prices). +var Prices = map[abi.ChainEpoch]Pricelist{ abi.ChainEpoch(0): &pricelistV0{ - onChainMessageBase: 0, - onChainMessagePerByte: 2, - onChainReturnValuePerByte: 8, - sendBase: 5, - sendTransferFunds: 5, - sendInvokeMethod: 10, - ipldGetBase: 10, - ipldGetPerByte: 1, - ipldPutBase: 20, - ipldPutPerByte: 2, - createActorBase: 40, // IPLD put + 20 - createActorExtra: 500, - deleteActor: -500, // -createActorExtra - // Dragons: this cost is not persistable, create a LinearCost{a,b} struct that has a `.Cost(x) -> ax + b` - verifySignature: map[crypto.SigType]func(int64) int64{ - crypto.SigTypeBLS: func(x int64) int64 { return 3*x + 2 }, - crypto.SigTypeSecp256k1: func(x int64) int64 { return 3*x + 2 }, + computeGasMulti: 1, + storageGasMulti: 1000, + + onChainMessageComputeBase: 38863, + onChainMessageStorageBase: 36, + onChainMessageStoragePerByte: 1, + + onChainReturnValuePerByte: 1, + + sendBase: 29233, + sendTransferFunds: 27500, + sendTransferOnlyPremium: 159672, + sendInvokeMethod: -5377, + + ipldGetBase: 75242, + ipldPutBase: 84070, + ipldPutPerByte: 1, + + createActorCompute: 1108454, + createActorStorage: 36 + 40, + deleteActor: -(36 + 40), // -createActorStorage + + verifySignature: map[crypto.SigType]int64{ + crypto.SigTypeBLS: 16598605, + crypto.SigTypeSecp256k1: 1637292, + crypto.SigTypeDelegated: 1637292, }, - hashingBase: 5, - hashingPerByte: 2, - computeUnsealedSectorCidBase: 100, - verifySealBase: 2000, - verifyPostBase: 700, - verifyConsensusFault: 10, + + hashingBase: 31355, + computeUnsealedSectorCidBase: 98647, + verifySealBase: 2000, // TODO gas , it VerifySeal syscall is not used + verifyAggregateSealBase: 0, + verifyPostLookup: map[abi.RegisteredPoStProof]scalingCost{ + abi.RegisteredPoStProof_StackedDrgWindow512MiBV1: { + flat: 123861062, + scale: 9226981, + }, + abi.RegisteredPoStProof_StackedDrgWindow32GiBV1: { + flat: 748593537, + scale: 85639, + }, + abi.RegisteredPoStProof_StackedDrgWindow64GiBV1: { + flat: 748593537, + scale: 85639, + }, + }, + verifyPostDiscount: true, + verifyConsensusFault: 495422, + }, + abi.ChainEpoch(build.UpgradeCalicoHeight): &pricelistV0{ + computeGasMulti: 1, + storageGasMulti: 1300, + + onChainMessageComputeBase: 38863, + onChainMessageStorageBase: 36, + onChainMessageStoragePerByte: 1, + + onChainReturnValuePerByte: 1, + + sendBase: 29233, + sendTransferFunds: 27500, + sendTransferOnlyPremium: 159672, + sendInvokeMethod: -5377, + + ipldGetBase: 114617, + ipldPutBase: 353640, + ipldPutPerByte: 1, + + createActorCompute: 1108454, + createActorStorage: 36 + 40, + deleteActor: -(36 + 40), // -createActorStorage + + verifySignature: map[crypto.SigType]int64{ + crypto.SigTypeBLS: 16598605, + crypto.SigTypeSecp256k1: 1637292, + }, + + hashingBase: 31355, + computeUnsealedSectorCidBase: 98647, + verifySealBase: 2000, // TODO gas, it VerifySeal syscall is not used + + verifyAggregateSealPer: map[abi.RegisteredSealProof]int64{ + abi.RegisteredSealProof_StackedDrg32GiBV1_1: 449900, + abi.RegisteredSealProof_StackedDrg64GiBV1_1: 359272, + }, + verifyAggregateSealSteps: map[abi.RegisteredSealProof]stepCost{ + abi.RegisteredSealProof_StackedDrg32GiBV1_1: { + {4, 103994170}, + {7, 112356810}, + {13, 122912610}, + {26, 137559930}, + {52, 162039100}, + {103, 210960780}, + {205, 318351180}, + {410, 528274980}, + }, + abi.RegisteredSealProof_StackedDrg64GiBV1_1: { + {4, 102581240}, + {7, 110803030}, + {13, 120803700}, + {26, 134642130}, + {52, 157357890}, + {103, 203017690}, + {205, 304253590}, + {410, 509880640}, + }, + }, + + verifyPostLookup: map[abi.RegisteredPoStProof]scalingCost{ + abi.RegisteredPoStProof_StackedDrgWindow512MiBV1: { + flat: 117680921, + scale: 43780, + }, + abi.RegisteredPoStProof_StackedDrgWindow32GiBV1: { + flat: 117680921, + scale: 43780, + }, + abi.RegisteredPoStProof_StackedDrgWindow64GiBV1: { + flat: 117680921, + scale: 43780, + }, + }, + verifyPostDiscount: false, + verifyConsensusFault: 495422, + + verifyReplicaUpdate: 36316136, + }, + build.UpgradeHyggeHeight: &pricelistV0{ + computeGasMulti: 1, + storageGasMulti: 1300, // only applies to messages/return values. + + onChainMessageComputeBase: 38863 + 475000, // includes the actor update cost + onChainMessageStorageBase: 36, + onChainMessageStoragePerByte: 1, + + onChainReturnValuePerByte: 1, }, } @@ -75,8 +230,8 @@ func PricelistByEpoch(epoch abi.ChainEpoch) Pricelist { // since we are storing the prices as map or epoch to price // we need to get the price with the highest epoch that is lower or equal to the `epoch` arg bestEpoch := abi.ChainEpoch(0) - bestPrice := prices[bestEpoch] - for e, pl := range prices { + bestPrice := Prices[bestEpoch] + for e, pl := range Prices { // if `e` happened after `bestEpoch` and `e` is earlier or equal to the target `epoch` if e > bestEpoch && e <= epoch { bestEpoch = e @@ -92,7 +247,7 @@ func PricelistByEpoch(epoch abi.ChainEpoch) Pricelist { type pricedSyscalls struct { under vmr.Syscalls pl Pricelist - chargeGas func(int64) + chargeGas func(GasCharge) } // Verifies that a signature is valid for an address and plaintext. @@ -102,30 +257,40 @@ func (ps pricedSyscalls) VerifySignature(signature crypto.Signature, signer addr return err } ps.chargeGas(c) + defer ps.chargeGas(gasOnActorExec) + return ps.under.VerifySignature(signature, signer, plaintext) } // Hashes input data using blake2b with 256 bit output. func (ps pricedSyscalls) HashBlake2b(data []byte) [32]byte { ps.chargeGas(ps.pl.OnHashing(len(data))) + defer ps.chargeGas(gasOnActorExec) + return ps.under.HashBlake2b(data) } // Computes an unsealed sector CID (CommD) from its constituent piece CIDs (CommPs) and sizes. -func (ps pricedSyscalls) ComputeUnsealedSectorCID(reg abi.RegisteredProof, pieces []abi.PieceInfo) (cid.Cid, error) { +func (ps pricedSyscalls) ComputeUnsealedSectorCID(reg abi.RegisteredSealProof, pieces []abi.PieceInfo) (cid.Cid, error) { ps.chargeGas(ps.pl.OnComputeUnsealedSectorCid(reg, pieces)) + defer ps.chargeGas(gasOnActorExec) + return ps.under.ComputeUnsealedSectorCID(reg, pieces) } // Verifies a sector seal proof. -func (ps pricedSyscalls) VerifySeal(vi abi.SealVerifyInfo) error { +func (ps pricedSyscalls) VerifySeal(vi proof7.SealVerifyInfo) error { ps.chargeGas(ps.pl.OnVerifySeal(vi)) + defer ps.chargeGas(gasOnActorExec) + return ps.under.VerifySeal(vi) } // Verifies a proof of spacetime. -func (ps pricedSyscalls) VerifyPoSt(vi abi.WindowPoStVerifyInfo) error { +func (ps pricedSyscalls) VerifyPoSt(vi proof7.WindowPoStVerifyInfo) error { ps.chargeGas(ps.pl.OnVerifyPost(vi)) + defer ps.chargeGas(gasOnActorExec) + return ps.under.VerifyPoSt(vi) } @@ -139,7 +304,37 @@ func (ps pricedSyscalls) VerifyPoSt(vi abi.WindowPoStVerifyInfo) error { // the "parent grinding fault", in which case it must be the sibling of h1 (same parent tipset) and one of the // blocks in the parent of h2 (i.e. h2's grandparent). // Returns nil and an error if the headers don't prove a fault. -func (ps pricedSyscalls) VerifyConsensusFault(h1 []byte, h2 []byte, extra []byte) (*runtime.ConsensusFault, error) { +func (ps pricedSyscalls) VerifyConsensusFault(h1 []byte, h2 []byte, extra []byte) (*vmr.ConsensusFault, error) { ps.chargeGas(ps.pl.OnVerifyConsensusFault()) + defer ps.chargeGas(gasOnActorExec) + return ps.under.VerifyConsensusFault(h1, h2, extra) } + +func (ps pricedSyscalls) BatchVerifySeals(inp map[address.Address][]proof7.SealVerifyInfo) (map[address.Address][]bool, error) { + count := int64(0) + for _, svis := range inp { + count += int64(len(svis)) + } + + gasChargeSum := newGasCharge("BatchVerifySeals", 0, 0) + gasChargeSum = gasChargeSum.WithExtra(count).WithVirtual(15075005*count+899741502, 0) + ps.chargeGas(gasChargeSum) // real gas charged by actors + defer ps.chargeGas(gasOnActorExec) + + return ps.under.BatchVerifySeals(inp) +} + +func (ps pricedSyscalls) VerifyAggregateSeals(aggregate proof7.AggregateSealVerifyProofAndInfos) error { + ps.chargeGas(ps.pl.OnVerifyAggregateSeals(aggregate)) + defer ps.chargeGas(gasOnActorExec) + + return ps.under.VerifyAggregateSeals(aggregate) +} + +func (ps pricedSyscalls) VerifyReplicaUpdate(update proof7.ReplicaUpdateInfo) error { + ps.chargeGas(ps.pl.OnVerifyReplicaUpdate(update)) + defer ps.chargeGas(gasOnActorExec) + + return ps.under.VerifyReplicaUpdate(update) +} diff --git a/chain/vm/gas_v0.go b/chain/vm/gas_v0.go index a20ac927d..7a144fc26 100644 --- a/chain/vm/gas_v0.go +++ b/chain/vm/gas_v0.go @@ -2,12 +2,45 @@ package vm import ( "fmt" - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/builtin" - "github.com/filecoin-project/specs-actors/actors/crypto" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/crypto" + proof7 "github.com/filecoin-project/specs-actors/v7/actors/runtime/proof" + + "github.com/filecoin-project/lotus/chain/actors/builtin" ) +type scalingCost struct { + flat int64 + scale int64 +} + +type stepCost []step + +type step struct { + start int64 + cost int64 +} + +func (sc stepCost) Lookup(x int64) int64 { + i := 0 + for ; i < len(sc); i++ { + if sc[i].start > x { + break + } + } + i-- // look at previous item + if i < 0 { + return 0 + } + + return sc[i].cost +} + type pricelistV0 struct { + computeGasMulti int64 + storageGasMulti int64 /////////////////////////////////////////////////////////////////////////// // System operations /////////////////////////////////////////////////////////////////////////// @@ -16,10 +49,11 @@ type pricelistV0 struct { // whether it succeeds or fails in application) is given by: // OnChainMessageBase + len(serialized message)*OnChainMessagePerByte // Together, these account for the cost of message propagation and validation, - // up to but excluding any actual processing by the VM. + // up to but excluding any actual processing by the LegacyVM. // This is the cost a block producer burns when including an invalid message. - onChainMessageBase int64 - onChainMessagePerByte int64 + onChainMessageComputeBase int64 + onChainMessageStorageBase int64 + onChainMessageStoragePerByte int64 // Gas cost charged to the originator of a non-nil return value produced // by an on-chain message is given by: @@ -39,18 +73,20 @@ type pricelistV0 struct { // already accounted for). sendTransferFunds int64 + // Gsa cost charged, in addition to SendBase, if message only transfers funds. + sendTransferOnlyPremium int64 + // Gas cost charged, in addition to SendBase, if a message invokes // a method on the receiver. // Accounts for the cost of loading receiver code and method dispatch. sendInvokeMethod int64 - // Gas cost (Base + len*PerByte) for any Get operation to the IPLD store - // in the runtime VM context. - ipldGetBase int64 - ipldGetPerByte int64 + // Gas cost for any Get operation to the IPLD store + // in the runtime LegacyVM context. + ipldGetBase int64 // Gas cost (Base + len*PerByte) for any Put operation to the IPLD store - // in the runtime VM context. + // in the runtime LegacyVM context. // // Note: these costs should be significantly higher than the costs for Get // operations, since they reflect not only serialization/deserialization @@ -62,102 +98,173 @@ type pricelistV0 struct { // // Note: this costs assume that the extra will be partially or totally refunded while // the base is covering for the put. - createActorBase int64 - createActorExtra int64 + createActorCompute int64 + createActorStorage int64 // Gas cost for deleting an actor. // // Note: this partially refunds the create cost to incentivise the deletion of the actors. deleteActor int64 - verifySignature map[crypto.SigType]func(len int64) int64 + verifySignature map[crypto.SigType]int64 - hashingBase int64 - hashingPerByte int64 + hashingBase int64 computeUnsealedSectorCidBase int64 verifySealBase int64 - verifyPostBase int64 - verifyConsensusFault int64 + verifyAggregateSealBase int64 + verifyAggregateSealPer map[abi.RegisteredSealProof]int64 + verifyAggregateSealSteps map[abi.RegisteredSealProof]stepCost + + verifyPostLookup map[abi.RegisteredPoStProof]scalingCost + verifyPostDiscount bool + verifyConsensusFault int64 + + verifyReplicaUpdate int64 } var _ Pricelist = (*pricelistV0)(nil) // OnChainMessage returns the gas used for storing a message of a given size in the chain. -func (pl *pricelistV0) OnChainMessage(msgSize int) int64 { - return pl.onChainMessageBase + pl.onChainMessagePerByte*int64(msgSize) +func (pl *pricelistV0) OnChainMessage(msgSize int) GasCharge { + return newGasCharge("OnChainMessage", pl.onChainMessageComputeBase, + (pl.onChainMessageStorageBase+pl.onChainMessageStoragePerByte*int64(msgSize))*pl.storageGasMulti) } // OnChainReturnValue returns the gas used for storing the response of a message in the chain. -func (pl *pricelistV0) OnChainReturnValue(dataSize int) int64 { - return int64(dataSize) * pl.onChainReturnValuePerByte +func (pl *pricelistV0) OnChainReturnValue(dataSize int) GasCharge { + return newGasCharge("OnChainReturnValue", 0, int64(dataSize)*pl.onChainReturnValuePerByte*pl.storageGasMulti) } // OnMethodInvocation returns the gas used when invoking a method. -func (pl *pricelistV0) OnMethodInvocation(value abi.TokenAmount, methodNum abi.MethodNum) int64 { +func (pl *pricelistV0) OnMethodInvocation(value abi.TokenAmount, methodNum abi.MethodNum) GasCharge { ret := pl.sendBase - if value != abi.NewTokenAmount(0) { + extra := "" + + if big.Cmp(value, abi.NewTokenAmount(0)) != 0 { ret += pl.sendTransferFunds + if methodNum == builtin.MethodSend { + // transfer only + ret += pl.sendTransferOnlyPremium + } + extra += "t" } + if methodNum != builtin.MethodSend { + extra += "i" + // running actors is cheaper becase we hand over to actors ret += pl.sendInvokeMethod } - return ret + return newGasCharge("OnMethodInvocation", ret, 0).WithExtra(extra) } // OnIpldGet returns the gas used for storing an object -func (pl *pricelistV0) OnIpldGet(dataSize int) int64 { - return pl.ipldGetBase + int64(dataSize)*pl.ipldGetPerByte +func (pl *pricelistV0) OnIpldGet() GasCharge { + return newGasCharge("OnIpldGet", pl.ipldGetBase, 0).WithVirtual(114617, 0) } // OnIpldPut returns the gas used for storing an object -func (pl *pricelistV0) OnIpldPut(dataSize int) int64 { - return pl.ipldPutBase + int64(dataSize)*pl.ipldPutPerByte +func (pl *pricelistV0) OnIpldPut(dataSize int) GasCharge { + return newGasCharge("OnIpldPut", pl.ipldPutBase, int64(dataSize)*pl.ipldPutPerByte*pl.storageGasMulti). + WithExtra(dataSize).WithVirtual(400000, int64(dataSize)*1300) } // OnCreateActor returns the gas used for creating an actor -func (pl *pricelistV0) OnCreateActor() int64 { - return pl.createActorBase + pl.createActorExtra +func (pl *pricelistV0) OnCreateActor() GasCharge { + return newGasCharge("OnCreateActor", pl.createActorCompute, pl.createActorStorage*pl.storageGasMulti) } // OnDeleteActor returns the gas used for deleting an actor -func (pl *pricelistV0) OnDeleteActor() int64 { - return pl.deleteActor +func (pl *pricelistV0) OnDeleteActor() GasCharge { + return newGasCharge("OnDeleteActor", 0, pl.deleteActor*pl.storageGasMulti) } // OnVerifySignature -func (pl *pricelistV0) OnVerifySignature(sigType crypto.SigType, planTextSize int) (int64, error) { - costFn, ok := pl.verifySignature[sigType] + +func (pl *pricelistV0) OnVerifySignature(sigType crypto.SigType, planTextSize int) (GasCharge, error) { + cost, ok := pl.verifySignature[sigType] if !ok { - return 0, fmt.Errorf("cost function for signature type %d not supported", sigType) + return GasCharge{}, fmt.Errorf("cost function for signature type %d not supported", sigType) } - return costFn(int64(planTextSize)), nil + + sigName, _ := sigType.Name() + return newGasCharge("OnVerifySignature", cost, 0). + WithExtra(map[string]interface{}{ + "type": sigName, + "size": planTextSize, + }), nil } // OnHashing -func (pl *pricelistV0) OnHashing(dataSize int) int64 { - return pl.hashingBase + int64(dataSize)*pl.hashingPerByte +func (pl *pricelistV0) OnHashing(dataSize int) GasCharge { + return newGasCharge("OnHashing", pl.hashingBase, 0).WithExtra(dataSize) } // OnComputeUnsealedSectorCid -func (pl *pricelistV0) OnComputeUnsealedSectorCid(proofType abi.RegisteredProof, pieces []abi.PieceInfo) int64 { - // TODO: this needs more cost tunning, check with @lotus - return pl.computeUnsealedSectorCidBase +func (pl *pricelistV0) OnComputeUnsealedSectorCid(proofType abi.RegisteredSealProof, pieces []abi.PieceInfo) GasCharge { + return newGasCharge("OnComputeUnsealedSectorCid", pl.computeUnsealedSectorCidBase, 0) } // OnVerifySeal -func (pl *pricelistV0) OnVerifySeal(info abi.SealVerifyInfo) int64 { +func (pl *pricelistV0) OnVerifySeal(info proof7.SealVerifyInfo) GasCharge { // TODO: this needs more cost tunning, check with @lotus - return pl.verifySealBase + // this is not used + return newGasCharge("OnVerifySeal", pl.verifySealBase, 0) +} + +// OnVerifyAggregateSeals +func (pl *pricelistV0) OnVerifyAggregateSeals(aggregate proof7.AggregateSealVerifyProofAndInfos) GasCharge { + proofType := aggregate.SealProof + perProof, ok := pl.verifyAggregateSealPer[proofType] + if !ok { + perProof = pl.verifyAggregateSealPer[abi.RegisteredSealProof_StackedDrg32GiBV1_1] + } + + step, ok := pl.verifyAggregateSealSteps[proofType] + if !ok { + step = pl.verifyAggregateSealSteps[abi.RegisteredSealProof_StackedDrg32GiBV1_1] + } + num := int64(len(aggregate.Infos)) + return newGasCharge("OnVerifyAggregateSeals", perProof*num+step.Lookup(num), 0) +} + +// OnVerifyReplicaUpdate +func (pl *pricelistV0) OnVerifyReplicaUpdate(update proof7.ReplicaUpdateInfo) GasCharge { + return newGasCharge("OnVerifyReplicaUpdate", pl.verifyReplicaUpdate, 0) } // OnVerifyPost -func (pl *pricelistV0) OnVerifyPost(info abi.WindowPoStVerifyInfo) int64 { - // TODO: this needs more cost tunning, check with @lotus - return pl.verifyPostBase +func (pl *pricelistV0) OnVerifyPost(info proof7.WindowPoStVerifyInfo) GasCharge { + sectorSize := "unknown" + var proofType abi.RegisteredPoStProof + + if len(info.Proofs) != 0 { + proofType = info.Proofs[0].PoStProof + ss, err := info.Proofs[0].PoStProof.SectorSize() + if err == nil { + sectorSize = ss.ShortString() + } + } + + cost, ok := pl.verifyPostLookup[proofType] + if !ok { + cost = pl.verifyPostLookup[abi.RegisteredPoStProof_StackedDrgWindow512MiBV1] + } + + gasUsed := cost.flat + int64(len(info.ChallengedSectors))*cost.scale + if pl.verifyPostDiscount { + gasUsed /= 2 // XXX: this is an artificial discount + } + + return newGasCharge("OnVerifyPost", gasUsed, 0). + WithVirtual(117680921+43780*int64(len(info.ChallengedSectors)), 0). + WithExtra(map[string]interface{}{ + "type": sectorSize, + "size": len(info.ChallengedSectors), + }) } // OnVerifyConsensusFault -func (pl *pricelistV0) OnVerifyConsensusFault() int64 { - return pl.verifyConsensusFault +func (pl *pricelistV0) OnVerifyConsensusFault() GasCharge { + return newGasCharge("OnVerifyConsensusFault", pl.verifyConsensusFault, 0) } diff --git a/chain/vm/gas_v0_test.go b/chain/vm/gas_v0_test.go new file mode 100644 index 000000000..bd527a779 --- /dev/null +++ b/chain/vm/gas_v0_test.go @@ -0,0 +1,33 @@ +// stm: #unit +package vm + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestStepGasCost(t *testing.T) { + s := stepCost{ + {4, 103994170}, + {7, 112356810}, + {13, 122912610}, + {26, 137559930}, + {52, 162039100}, + {103, 210960780}, + {205, 318351180}, + {410, 528274980}, + } + + assert.EqualValues(t, 0, s.Lookup(0)) + assert.EqualValues(t, 0, s.Lookup(3)) + assert.EqualValues(t, 103994170, s.Lookup(4)) + assert.EqualValues(t, 103994170, s.Lookup(6)) + assert.EqualValues(t, 112356810, s.Lookup(7)) + assert.EqualValues(t, 210960780, s.Lookup(103)) + assert.EqualValues(t, 210960780, s.Lookup(204)) + assert.EqualValues(t, 318351180, s.Lookup(205)) + assert.EqualValues(t, 318351180, s.Lookup(409)) + assert.EqualValues(t, 528274980, s.Lookup(410)) + assert.EqualValues(t, 528274980, s.Lookup(10000000000)) +} diff --git a/chain/vm/invoker.go b/chain/vm/invoker.go index 5ad0e6ee4..cea17f61d 100644 --- a/chain/vm/invoker.go +++ b/chain/vm/invoker.go @@ -6,96 +6,195 @@ import ( "fmt" "reflect" - "github.com/filecoin-project/specs-actors/actors/builtin/account" - "github.com/filecoin-project/specs-actors/actors/builtin/verifreg" - "github.com/filecoin-project/specs-actors/actors/runtime/exitcode" - "github.com/ipfs/go-cid" cbg "github.com/whyrusleeping/cbor-gen" "golang.org/x/xerrors" - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/builtin" - "github.com/filecoin-project/specs-actors/actors/builtin/cron" - init_ "github.com/filecoin-project/specs-actors/actors/builtin/init" - "github.com/filecoin-project/specs-actors/actors/builtin/market" - "github.com/filecoin-project/specs-actors/actors/builtin/miner" - "github.com/filecoin-project/specs-actors/actors/builtin/multisig" - "github.com/filecoin-project/specs-actors/actors/builtin/paych" - "github.com/filecoin-project/specs-actors/actors/builtin/power" - "github.com/filecoin-project/specs-actors/actors/builtin/reward" - "github.com/filecoin-project/specs-actors/actors/builtin/system" - "github.com/filecoin-project/specs-actors/actors/runtime" - vmr "github.com/filecoin-project/specs-actors/actors/runtime" - "github.com/filecoin-project/specs-actors/actors/util/adt" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + builtinst "github.com/filecoin-project/go-state-types/builtin" + "github.com/filecoin-project/go-state-types/exitcode" + "github.com/filecoin-project/go-state-types/network" + vmr "github.com/filecoin-project/specs-actors/v7/actors/runtime" + "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/aerrors" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/types" ) -type invoker struct { - builtInCode map[cid.Cid]nativeCode - builtInState map[cid.Cid]reflect.Type +type MethodMeta struct { + Name string + + Params reflect.Type + Ret reflect.Type } -type invokeFunc func(rt runtime.Runtime, params []byte) ([]byte, aerrors.ActorError) -type nativeCode []invokeFunc +type ActorRegistry struct { + actors map[cid.Cid]*actorInfo -func NewInvoker() *invoker { - inv := &invoker{ - builtInCode: make(map[cid.Cid]nativeCode), - builtInState: make(map[cid.Cid]reflect.Type), + Methods map[cid.Cid]map[abi.MethodNum]MethodMeta +} + +// An ActorPredicate returns an error if the given actor is not valid for the given runtime environment (e.g., chain height, version, etc.). +type ActorPredicate func(vmr.Runtime, cid.Cid) error + +func ActorsVersionPredicate(ver actorstypes.Version) ActorPredicate { + return func(rt vmr.Runtime, codeCid cid.Cid) error { + aver, err := actorstypes.VersionForNetwork(rt.NetworkVersion()) + if err != nil { + return xerrors.Errorf("unsupported network version: %w", err) + } + if aver != ver { + return xerrors.Errorf("actor %s is a version %d actor; chain only supports actor version %d at height %d and nver %d", codeCid, ver, aver, rt.CurrEpoch(), rt.NetworkVersion()) + } + return nil } - - // add builtInCode using: register(cid, singleton) - inv.Register(builtin.SystemActorCodeID, system.Actor{}, adt.EmptyValue{}) - inv.Register(builtin.InitActorCodeID, init_.Actor{}, init_.State{}) - inv.Register(builtin.RewardActorCodeID, reward.Actor{}, reward.State{}) - inv.Register(builtin.CronActorCodeID, cron.Actor{}, cron.State{}) - inv.Register(builtin.StoragePowerActorCodeID, power.Actor{}, power.State{}) - inv.Register(builtin.StorageMarketActorCodeID, market.Actor{}, market.State{}) - inv.Register(builtin.StorageMinerActorCodeID, miner.Actor{}, miner.State{}) - inv.Register(builtin.MultisigActorCodeID, multisig.Actor{}, multisig.State{}) - inv.Register(builtin.PaymentChannelActorCodeID, paych.Actor{}, paych.State{}) - inv.Register(builtin.VerifiedRegistryActorCodeID, verifreg.Actor{}, verifreg.State{}) - inv.Register(builtin.AccountActorCodeID, account.Actor{}, account.State{}) - - return inv } -func (inv *invoker) Invoke(codeCid cid.Cid, rt runtime.Runtime, method abi.MethodNum, params []byte) ([]byte, aerrors.ActorError) { +type invokeFunc func(rt vmr.Runtime, params []byte) ([]byte, aerrors.ActorError) +type nativeCode map[abi.MethodNum]invokeFunc - code, ok := inv.builtInCode[codeCid] +type actorInfo struct { + methods nativeCode + vmActor builtin.RegistryEntry + // TODO: consider making this a network version range? + predicate ActorPredicate +} + +func NewActorRegistry() *ActorRegistry { + return &ActorRegistry{ + actors: make(map[cid.Cid]*actorInfo), + Methods: map[cid.Cid]map[abi.MethodNum]MethodMeta{}, + } +} + +func (ar *ActorRegistry) Invoke(codeCid cid.Cid, rt vmr.Runtime, method abi.MethodNum, params []byte) ([]byte, aerrors.ActorError) { + act, ok := ar.actors[codeCid] if !ok { - log.Errorf("no code for actor %s (Addr: %s)", codeCid, rt.Message().Receiver()) + log.Errorf("no code for actor %s (Addr: %s)", codeCid, rt.Receiver()) return nil, aerrors.Newf(exitcode.SysErrorIllegalActor, "no code for actor %s(%d)(%s)", codeCid, method, hex.EncodeToString(params)) } - if method >= abi.MethodNum(len(code)) || code[method] == nil { + if err := act.predicate(rt, codeCid); err != nil { + return nil, aerrors.Newf(exitcode.SysErrorIllegalActor, "unsupported actor: %s", err) + } + if act.methods[method] == nil { return nil, aerrors.Newf(exitcode.SysErrInvalidMethod, "no method %d on actor", method) } - return code[method](rt, params) + return act.methods[method](rt, params) } -func (inv *invoker) Register(c cid.Cid, instance Invokee, state interface{}) { - code, err := inv.transform(instance) - if err != nil { - panic(xerrors.Errorf("%s: %w", string(c.Hash()), err)) +func (ar *ActorRegistry) Register(av actorstypes.Version, pred ActorPredicate, vmactors []builtin.RegistryEntry) { + if pred == nil { + pred = func(vmr.Runtime, cid.Cid) error { return nil } + } + for _, a := range vmactors { + + var code nativeCode + var err error + if av <= actorstypes.Version7 { + // register in the `actors` map (for the invoker) + code, err = ar.transform(a) + if err != nil { + panic(xerrors.Errorf("%s: %w", string(a.Code().Hash()), err)) + } + } + + ai := &actorInfo{ + methods: code, + vmActor: a, + predicate: pred, + } + + ac := a.Code() + ar.actors[ac] = ai + + // necessary to make stuff work + var realCode cid.Cid + if av >= actorstypes.Version8 { + name := actors.CanonicalName(builtin.ActorNameByCode(ac)) + + var ok bool + realCode, ok = actors.GetActorCodeID(av, name) + if ok { + ar.actors[realCode] = ai + } + } + + // register in the `Methods` map (used by statemanager utils) + exports := a.Exports() + methods := make(map[abi.MethodNum]MethodMeta, len(exports)) + + // Explicitly add send, it's special. + methods[builtin.MethodSend] = MethodMeta{ + Name: "Send", + Params: reflect.TypeOf(new(abi.EmptyValue)), + Ret: reflect.TypeOf(new(abi.EmptyValue)), + } + + // Iterate over exported methods. Some of these _may_ be nil and + // must be skipped. + for number, export := range exports { + if export.Method == nil { + continue + } + + ev := reflect.ValueOf(export.Method) + et := ev.Type() + + mm := MethodMeta{ + Name: export.Name, + Ret: et.Out(0), + } + + if av <= actorstypes.Version7 { + // methods exported from specs-actors have the runtime as the first param, so we want et.In(1) + mm.Params = et.In(1) + } else { + // methods exported from go-state-types do not, so we want et.In(0) + mm.Params = et.In(0) + } + + methods[number] = mm + } + if realCode.Defined() { + ar.Methods[realCode] = methods + } else { + ar.Methods[a.Code()] = methods + } } - inv.builtInCode[c] = code - inv.builtInState[c] = reflect.TypeOf(state) } -type Invokee interface { - Exports() []interface{} +func (ar *ActorRegistry) Create(codeCid cid.Cid, rt vmr.Runtime) (*types.Actor, aerrors.ActorError) { + act, ok := ar.actors[codeCid] + if !ok { + return nil, aerrors.Newf(exitcode.SysErrorIllegalArgument, "Can only create built-in actors.") + } + + if err := act.predicate(rt, codeCid); err != nil { + return nil, aerrors.Newf(exitcode.SysErrorIllegalArgument, "Cannot create actor: %w", err) + } + + return &types.Actor{ + Code: codeCid, + Head: EmptyObjectCid, + Nonce: 0, + Balance: abi.NewTokenAmount(0), + }, nil } -var tAError = reflect.TypeOf((*aerrors.ActorError)(nil)).Elem() +type invokee interface { + Exports() map[abi.MethodNum]builtinst.MethodMeta +} -func (*invoker) transform(instance Invokee) (nativeCode, error) { +func (*ActorRegistry) transform(instance invokee) (nativeCode, error) { itype := reflect.TypeOf(instance) exports := instance.Exports() - for i, m := range exports { + runtimeType := reflect.TypeOf((*vmr.Runtime)(nil)).Elem() + for i, e := range exports { i := i + m := e.Method newErr := func(format string, args ...interface{}) error { str := fmt.Sprintf(format, args...) return fmt.Errorf("transform(%s) export(%d): %s", itype.Name(), i, str) @@ -114,11 +213,11 @@ func (*invoker) transform(instance Invokee) (nativeCode, error) { return nil, newErr("wrong number of inputs should be: " + "vmr.Runtime, ") } - if t.In(0) != reflect.TypeOf((*vmr.Runtime)(nil)).Elem() { - return nil, newErr("first arguemnt should be vmr.Runtime") + if !runtimeType.Implements(t.In(0)) { + return nil, newErr("first argument should be vmr.Runtime") } if t.In(1).Kind() != reflect.Ptr { - return nil, newErr("second argument should be Runtime") + return nil, newErr("second argument should be of kind reflect.Ptr") } if t.NumOut() != 1 { @@ -131,16 +230,25 @@ func (*invoker) transform(instance Invokee) (nativeCode, error) { } } code := make(nativeCode, len(exports)) - for id, m := range exports { + for id, e := range exports { + m := e.Method + if m == nil { + continue + } meth := reflect.ValueOf(m) code[id] = reflect.MakeFunc(reflect.TypeOf((invokeFunc)(nil)), func(in []reflect.Value) []reflect.Value { paramT := meth.Type().In(1).Elem() param := reflect.New(paramT) + rt := in[0].Interface().(*Runtime) inBytes := in[1].Interface().([]byte) if err := DecodeParams(inBytes, param.Interface()); err != nil { - aerr := aerrors.Absorb(err, 1, "failed to decode parameters") + ec := exitcode.ErrSerialization + if rt.NetworkVersion() < network.Version7 { + ec = 1 + } + aerr := aerrors.Absorb(err, ec, "failed to decode parameters") return []reflect.Value{ reflect.ValueOf([]byte{}), // Below is a hack, fixed in Go 1.13 @@ -148,7 +256,6 @@ func (*invoker) transform(instance Invokee) (nativeCode, error) { reflect.ValueOf(&aerr).Elem(), } } - rt := in[0].Interface().(*Runtime) rval, aerror := rt.shimCall(func() interface{} { ret := meth.Call([]reflect.Value{ reflect.ValueOf(rt), @@ -176,27 +283,23 @@ func DecodeParams(b []byte, out interface{}) error { return um.UnmarshalCBOR(bytes.NewReader(b)) } -func DumpActorState(code cid.Cid, b []byte) (interface{}, error) { - if code == builtin.AccountActorCodeID { // Account code special case +func DumpActorState(i *ActorRegistry, act *types.Actor, b []byte) (interface{}, error) { + actInfo, ok := i.actors[act.Code] + if !ok { + return nil, xerrors.Errorf("state type for actor %s not found", act.Code) + } + + um := actInfo.vmActor.State() + if um == nil { + if act.Head != EmptyObjectCid { + return nil, xerrors.Errorf("actor with code %s should only have empty object (%s) as its Head, instead has %s", act.Code, EmptyObjectCid, act.Head) + } + return nil, nil } - - i := NewInvoker() // TODO: register builtins in init block - - typ, ok := i.builtInState[code] - if !ok { - return nil, xerrors.Errorf("state type for actor %s not found", code) - } - - rv := reflect.New(typ) - um, ok := rv.Interface().(cbg.CBORUnmarshaler) - if !ok { - return nil, xerrors.New("state type does not implement CBORUnmarshaler") - } - if err := um.UnmarshalCBOR(bytes.NewReader(b)); err != nil { - return nil, err + return nil, xerrors.Errorf("unmarshaling actor state: %w", err) } - return rv.Elem().Interface(), nil + return um, nil } diff --git a/chain/vm/invoker_test.go b/chain/vm/invoker_test.go index b46b445a2..d3e6dcd7f 100644 --- a/chain/vm/invoker_test.go +++ b/chain/vm/invoker_test.go @@ -1,3 +1,4 @@ +// stm: #unit package vm import ( @@ -5,18 +6,36 @@ import ( "io" "testing" + cid "github.com/ipfs/go-cid" cbor "github.com/ipfs/go-ipld-cbor" "github.com/stretchr/testify/assert" cbg "github.com/whyrusleeping/cbor-gen" + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + cbor2 "github.com/filecoin-project/go-state-types/cbor" + "github.com/filecoin-project/go-state-types/exitcode" + "github.com/filecoin-project/go-state-types/network" + "github.com/filecoin-project/go-state-types/rt" + runtime2 "github.com/filecoin-project/specs-actors/v2/actors/runtime" + "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/aerrors" - "github.com/filecoin-project/specs-actors/actors/runtime" - "github.com/filecoin-project/specs-actors/actors/runtime/exitcode" - "github.com/filecoin-project/specs-actors/actors/util/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" ) type basicContract struct{} + +func (b basicContract) Code() cid.Cid { + return cid.Undef +} + +func (b basicContract) State() cbor2.Er { + // works well enough as a dummy state + return new(basicParams) +} + type basicParams struct { B byte } @@ -60,31 +79,57 @@ func (b basicContract) Exports() []interface{} { } } -func (basicContract) InvokeSomething0(rt runtime.Runtime, params *basicParams) *adt.EmptyValue { +func (basicContract) InvokeSomething0(rt runtime2.Runtime, params *basicParams) *abi.EmptyValue { rt.Abortf(exitcode.ExitCode(params.B), "params.B") return nil } -func (basicContract) BadParam(rt runtime.Runtime, params *basicParams) *adt.EmptyValue { +func (basicContract) BadParam(rt runtime2.Runtime, params *basicParams) *abi.EmptyValue { rt.Abortf(255, "bad params") return nil } -func (basicContract) InvokeSomething10(rt runtime.Runtime, params *basicParams) *adt.EmptyValue { +func (basicContract) InvokeSomething10(rt runtime2.Runtime, params *basicParams) *abi.EmptyValue { rt.Abortf(exitcode.ExitCode(params.B+10), "params.B") return nil } +type basicRtMessage struct{} + +var _ runtime2.Message = (*basicRtMessage)(nil) + +func (*basicRtMessage) Caller() address.Address { + a, err := address.NewIDAddress(0) + if err != nil { + panic(err) + } + return a +} + +func (*basicRtMessage) Receiver() address.Address { + a, err := address.NewIDAddress(1) + if err != nil { + panic(err) + } + return a +} + +func (*basicRtMessage) ValueReceived() abi.TokenAmount { + return big.NewInt(0) +} + func TestInvokerBasic(t *testing.T) { - inv := invoker{} - code, err := inv.transform(basicContract{}) + //stm: @INVOKER_TRANSFORM_001 + inv := ActorRegistry{} + registry := builtin.MakeRegistryLegacy([]rt.VMActor{basicContract{}}) + code, err := inv.transform(registry[0]) assert.NoError(t, err) { bParam, err := actors.SerializeParams(&basicParams{B: 1}) assert.NoError(t, err) - _, aerr := code[0](&Runtime{}, bParam) + _, aerr := code[0](&Runtime{Message: &basicRtMessage{}}, bParam) assert.Equal(t, exitcode.ExitCode(1), aerrors.RetCode(aerr), "return code should be 1") if aerrors.IsFatal(aerr) { @@ -96,17 +141,32 @@ func TestInvokerBasic(t *testing.T) { bParam, err := actors.SerializeParams(&basicParams{B: 2}) assert.NoError(t, err) - _, aerr := code[10](&Runtime{}, bParam) + _, aerr := code[10](&Runtime{Message: &basicRtMessage{}}, bParam) assert.Equal(t, exitcode.ExitCode(12), aerrors.RetCode(aerr), "return code should be 12") if aerrors.IsFatal(aerr) { t.Fatal("err should not be fatal") } } - _, aerr := code[1](&Runtime{}, []byte{99}) - if aerrors.IsFatal(aerr) { - t.Fatal("err should not be fatal") + { + _, aerr := code[1](&Runtime{ + vm: &LegacyVM{networkVersion: network.Version0}, + Message: &basicRtMessage{}, + }, []byte{99}) + if aerrors.IsFatal(aerr) { + t.Fatal("err should not be fatal") + } + assert.Equal(t, exitcode.ExitCode(1), aerrors.RetCode(aerr), "return code should be 1") } - assert.Equal(t, exitcode.ExitCode(1), aerrors.RetCode(aerr), "return code should be 1") + { + _, aerr := code[1](&Runtime{ + vm: &LegacyVM{networkVersion: network.Version7}, + Message: &basicRtMessage{}, + }, []byte{99}) + if aerrors.IsFatal(aerr) { + t.Fatal("err should not be fatal") + } + assert.Equal(t, exitcode.ErrSerialization, aerrors.RetCode(aerr), "return code should be %s", 1) + } } diff --git a/chain/vm/mkactor.go b/chain/vm/mkactor.go index 6f3274114..b33085c05 100644 --- a/chain/vm/mkactor.go +++ b/chain/vm/mkactor.go @@ -2,16 +2,28 @@ package vm import ( "context" - "github.com/filecoin-project/lotus/chain/actors" - "github.com/filecoin-project/specs-actors/actors/abi/big" - "github.com/filecoin-project/specs-actors/actors/builtin" - "github.com/filecoin-project/specs-actors/actors/runtime/exitcode" "github.com/ipfs/go-cid" cbor "github.com/ipfs/go-ipld-cbor" "github.com/filecoin-project/go-address" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/exitcode" + "github.com/filecoin-project/go-state-types/network" + builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + builtin3 "github.com/filecoin-project/specs-actors/v3/actors/builtin" + builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" + builtin6 "github.com/filecoin-project/specs-actors/v6/actors/builtin" + builtin7 "github.com/filecoin-project/specs-actors/v7/actors/builtin" + + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/aerrors" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/actors/builtin/account" "github.com/filecoin-project/lotus/chain/types" ) @@ -27,50 +39,57 @@ func init() { var EmptyObjectCid cid.Cid -// Creates account actors from only BLS/SECP256K1 addresses. -func TryCreateAccountActor(rt *Runtime, addr address.Address) (*types.Actor, aerrors.ActorError) { - addrID, err := rt.state.RegisterNewAddress(addr) - if err != nil { - return nil, aerrors.Escalate(err, "registering actor address") +// TryCreateAccountActor creates account actors from only BLS/SECP256K1 addresses. +func TryCreateAccountActor(rt *Runtime, addr address.Address) (*types.Actor, address.Address, aerrors.ActorError) { + if err := rt.chargeGasSafe(PricelistByEpoch(rt.height).OnCreateActor()); err != nil { + return nil, address.Undef, err } - act, aerr := makeActor(addr) + if addr == build.ZeroAddress && rt.NetworkVersion() >= network.Version10 { + return nil, address.Undef, aerrors.New(exitcode.ErrIllegalArgument, "cannot create the zero bls actor") + } + + addrID, err := rt.state.RegisterNewAddress(addr) + if err != nil { + return nil, address.Undef, aerrors.Escalate(err, "registering actor address") + } + + av, err := actorstypes.VersionForNetwork(rt.NetworkVersion()) + if err != nil { + return nil, address.Undef, aerrors.Escalate(err, "unsupported network version") + } + + act, aerr := makeAccountActor(av, addr) if aerr != nil { - return nil, aerr + return nil, address.Undef, aerr } if err := rt.state.SetActor(addrID, act); err != nil { - return nil, aerrors.Escalate(err, "creating new actor failed") + return nil, address.Undef, aerrors.Escalate(err, "creating new actor failed") } p, err := actors.SerializeParams(&addr) if err != nil { - return nil, aerrors.Escalate(err, "couldn't serialize params for actor construction") + return nil, address.Undef, aerrors.Escalate(err, "couldn't serialize params for actor construction") } // call constructor on account - if err := rt.chargeGasSafe(PricelistByEpoch(rt.height).OnCreateActor()); err != nil { - return nil, err - } - - _, aerr = rt.internalSend(builtin.SystemActorAddr, addrID, builtin.MethodsAccount.Constructor, big.Zero(), p) + _, aerr = rt.internalSend(builtin.SystemActorAddr, addrID, account.Methods.Constructor, big.Zero(), p) if aerr != nil { - return nil, aerrors.Wrap(aerr, "failed to invoke account constructor") + return nil, address.Undef, aerrors.Wrap(aerr, "failed to invoke account constructor") } act, err = rt.state.GetActor(addrID) if err != nil { - return nil, aerrors.Escalate(err, "loading newly created actor failed") + return nil, address.Undef, aerrors.Escalate(err, "loading newly created actor failed") } - return act, nil + return act, addrID, nil } -func makeActor(addr address.Address) (*types.Actor, aerrors.ActorError) { +func makeAccountActor(ver actorstypes.Version, addr address.Address) (*types.Actor, aerrors.ActorError) { switch addr.Protocol() { - case address.BLS: - return NewBLSAccountActor(), nil - case address.SECP256K1: - return NewSecp256k1AccountActor(), nil + case address.BLS, address.SECP256K1: + return newAccountActor(ver, addr), nil case address.ID: return nil, aerrors.Newf(exitcode.SysErrInvalidReceiver, "no actor with given ID: %s", addr) case address.Actor: @@ -80,21 +99,32 @@ func makeActor(addr address.Address) (*types.Actor, aerrors.ActorError) { } } -func NewBLSAccountActor() *types.Actor { +func newAccountActor(ver actorstypes.Version, addr address.Address) *types.Actor { + // TODO: ActorsUpgrade use a global actor registry? + var code cid.Cid + switch ver { + case actorstypes.Version0: + code = builtin0.AccountActorCodeID + case actorstypes.Version2: + code = builtin2.AccountActorCodeID + case actorstypes.Version3: + code = builtin3.AccountActorCodeID + case actorstypes.Version4: + code = builtin4.AccountActorCodeID + case actorstypes.Version5: + code = builtin5.AccountActorCodeID + case actorstypes.Version6: + code = builtin6.AccountActorCodeID + case actorstypes.Version7: + code = builtin7.AccountActorCodeID + default: + panic("unsupported actors version") + } nact := &types.Actor{ - Code: builtin.AccountActorCodeID, - Balance: types.NewInt(0), - Head: EmptyObjectCid, - } - - return nact -} - -func NewSecp256k1AccountActor() *types.Actor { - nact := &types.Actor{ - Code: builtin.AccountActorCodeID, + Code: code, Balance: types.NewInt(0), Head: EmptyObjectCid, + Address: &addr, } return nact diff --git a/chain/vm/runtime.go b/chain/vm/runtime.go index 948b0a021..a5b108238 100644 --- a/chain/vm/runtime.go +++ b/chain/vm/runtime.go @@ -5,97 +5,114 @@ import ( "context" "encoding/binary" "fmt" + "os" "time" - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/abi/big" - "github.com/filecoin-project/specs-actors/actors/builtin" - sainit "github.com/filecoin-project/specs-actors/actors/builtin/init" - sapower "github.com/filecoin-project/specs-actors/actors/builtin/power" - "github.com/filecoin-project/specs-actors/actors/crypto" - "github.com/filecoin-project/specs-actors/actors/runtime" - vmr "github.com/filecoin-project/specs-actors/actors/runtime" - "github.com/filecoin-project/specs-actors/actors/runtime/exitcode" - "github.com/filecoin-project/specs-actors/actors/util/adt" "github.com/ipfs/go-cid" - hamt "github.com/ipfs/go-hamt-ipld" - cbor "github.com/ipfs/go-ipld-cbor" - cbg "github.com/whyrusleeping/cbor-gen" + ipldcbor "github.com/ipfs/go-ipld-cbor" "go.opencensus.io/trace" "golang.org/x/xerrors" + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + actorstypes "github.com/filecoin-project/go-state-types/actors" + "github.com/filecoin-project/go-state-types/cbor" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/go-state-types/exitcode" + "github.com/filecoin-project/go-state-types/network" + rtt "github.com/filecoin-project/go-state-types/rt" + rt0 "github.com/filecoin-project/specs-actors/actors/runtime" + rt2 "github.com/filecoin-project/specs-actors/v2/actors/runtime" + rt3 "github.com/filecoin-project/specs-actors/v3/actors/runtime" + rt4 "github.com/filecoin-project/specs-actors/v4/actors/runtime" + rt5 "github.com/filecoin-project/specs-actors/v5/actors/runtime" + rt6 "github.com/filecoin-project/specs-actors/v6/actors/runtime" + rt7 "github.com/filecoin-project/specs-actors/v7/actors/runtime" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/aerrors" + "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/types" ) +type Message struct { + msg types.Message +} + +func (m *Message) Caller() address.Address { + if m.msg.From.Protocol() != address.ID { + panic("runtime message has a non-ID caller") + } + return m.msg.From +} + +func (m *Message) Receiver() address.Address { + if m.msg.To != address.Undef && m.msg.To.Protocol() != address.ID { + panic("runtime message has a non-ID receiver") + } + return m.msg.To +} + +func (m *Message) ValueReceived() abi.TokenAmount { + return m.msg.Value +} + +// EnableDetailedTracing has different behaviour in the LegacyVM and FVM. +// In the LegacyVM, it enables detailed gas tracing, slowing down execution. +// In the FVM, it enables execution traces, which are primarily used to observe subcalls. +var EnableDetailedTracing = os.Getenv("LOTUS_VM_ENABLE_TRACING") == "1" + type Runtime struct { + rt7.Message + rt7.Syscalls + ctx context.Context - vm *VM + vm *LegacyVM state *state.StateTree - msg *types.Message - vmsg vmr.Message height abi.ChainEpoch - cst cbor.IpldStore + cst ipldcbor.IpldStore pricelist Pricelist gasAvailable int64 gasUsed int64 - sys runtime.Syscalls - // address that started invoke chain origin address.Address originNonce uint64 - internalExecutions []*types.ExecutionResult - numActorsCreated uint64 - allowInternal bool - callerValidated bool + executionTrace types.ExecutionTrace + depth uint64 + numActorsCreated uint64 + allowInternal bool + callerValidated bool + lastGasChargeTime time.Time + lastGasCharge *types.GasTrace +} + +func (rt *Runtime) BaseFee() abi.TokenAmount { + return rt.vm.baseFee +} + +func (rt *Runtime) NetworkVersion() network.Version { + return rt.vm.networkVersion } func (rt *Runtime) TotalFilCircSupply() abi.TokenAmount { - total := types.FromFil(build.TotalFilecoin) - - rew, err := rt.state.GetActor(builtin.RewardActorAddr) + cs, err := rt.vm.GetCircSupply(rt.ctx) if err != nil { - rt.Abortf(exitcode.ErrIllegalState, "failed to get reward actor for computing total supply: %s", err) + rt.Abortf(exitcode.ErrIllegalState, "failed to get total circ supply: %s", err) } - burnt, err := rt.state.GetActor(builtin.BurntFundsActorAddr) - if err != nil { - rt.Abortf(exitcode.ErrIllegalState, "failed to get reward actor for computing total supply: %s", err) - } - - market, err := rt.state.GetActor(builtin.StorageMarketActorAddr) - if err != nil { - rt.Abortf(exitcode.ErrIllegalState, "failed to get reward actor for computing total supply: %s", err) - } - - power, err := rt.state.GetActor(builtin.StoragePowerActorAddr) - if err != nil { - rt.Abortf(exitcode.ErrIllegalState, "failed to get reward actor for computing total supply: %s", err) - } - - total = types.BigSub(total, rew.Balance) - total = types.BigSub(total, burnt.Balance) - total = types.BigSub(total, market.Balance) - - var st sapower.State - if err := rt.cst.Get(rt.ctx, power.Head, &st); err != nil { - rt.Abortf(exitcode.ErrIllegalState, "failed to get storage power state: %s", err) - } - - return types.BigSub(total, st.TotalPledgeCollateral) + return cs } func (rt *Runtime) ResolveAddress(addr address.Address) (ret address.Address, ok bool) { r, err := rt.state.LookupID(addr) if err != nil { - if xerrors.Is(err, sainit.ErrAddressNotFound) { + if xerrors.Is(err, types.ErrActorNotFound) { return address.Undef, false } panic(aerrors.Fatalf("failed to resolve address %s: %s", addr, err)) @@ -107,11 +124,11 @@ type notFoundErr interface { IsNotFound() bool } -func (rs *Runtime) Get(c cid.Cid, o vmr.CBORUnmarshaler) bool { - if err := rs.cst.Get(context.TODO(), c, o); err != nil { +func (rt *Runtime) StoreGet(c cid.Cid, o cbor.Unmarshaler) bool { + if err := rt.cst.Get(context.TODO(), c, o); err != nil { var nfe notFoundErr if xerrors.As(err, &nfe) && nfe.IsNotFound() { - if xerrors.As(err, new(cbor.SerializationError)) { + if xerrors.As(err, new(ipldcbor.SerializationError)) { panic(aerrors.Newf(exitcode.ErrSerialization, "failed to unmarshal cbor object %s", err)) } return false @@ -122,10 +139,10 @@ func (rs *Runtime) Get(c cid.Cid, o vmr.CBORUnmarshaler) bool { return true } -func (rs *Runtime) Put(x vmr.CBORMarshaler) cid.Cid { - c, err := rs.cst.Put(context.TODO(), x) +func (rt *Runtime) StorePut(x cbor.Marshaler) cid.Cid { + c, err := rt.cst.Put(context.TODO(), x) if err != nil { - if xerrors.As(err, new(cbor.SerializationError)) { + if xerrors.As(err, new(ipldcbor.SerializationError)) { panic(aerrors.Newf(exitcode.ErrSerialization, "failed to marshal cbor object %s", err)) } panic(aerrors.Fatalf("failed to put cbor object: %s", err)) @@ -133,33 +150,46 @@ func (rs *Runtime) Put(x vmr.CBORMarshaler) cid.Cid { return c } -var _ vmr.Runtime = (*Runtime)(nil) +var _ rt0.Runtime = (*Runtime)(nil) +var _ rt5.Runtime = (*Runtime)(nil) +var _ rt2.Runtime = (*Runtime)(nil) +var _ rt3.Runtime = (*Runtime)(nil) +var _ rt4.Runtime = (*Runtime)(nil) +var _ rt5.Runtime = (*Runtime)(nil) +var _ rt6.Runtime = (*Runtime)(nil) +var _ rt7.Runtime = (*Runtime)(nil) -func (rs *Runtime) shimCall(f func() interface{}) (rval []byte, aerr aerrors.ActorError) { +func (rt *Runtime) shimCall(f func() interface{}) (rval []byte, aerr aerrors.ActorError) { defer func() { if r := recover(); r != nil { if ar, ok := r.(aerrors.ActorError); ok { - log.Errorf("VM.Call failure: %+v", ar) + log.Warnf("LegacyVM.Call failure in call from: %s to %s: %+v", rt.Caller(), rt.Receiver(), ar) aerr = ar return } + // log.Desugar().WithOptions(zap.AddStacktrace(zapcore.ErrorLevel)). + // Sugar().Errorf("spec actors failure: %s", r) log.Errorf("spec actors failure: %s", r) - aerr = aerrors.Newf(1, "spec actors failure: %s", r) + if rt.NetworkVersion() <= network.Version3 { + aerr = aerrors.Newf(1, "spec actors failure: %s", r) + } else { + aerr = aerrors.Newf(exitcode.SysErrIllegalInstruction, "spec actors failure: %s", r) + } } }() ret := f() - if !rs.callerValidated { - rs.Abortf(exitcode.SysErrorIllegalActor, "Caller MUST be validated during method execution") + if !rt.callerValidated { + rt.Abortf(exitcode.SysErrorIllegalActor, "Caller MUST be validated during method execution") } switch ret := ret.(type) { case []byte: return ret, nil - case *adt.EmptyValue: + case *abi.EmptyValue: return nil, nil - case cbg.CBORMarshaler: + case cbor.Marshaler: buf := new(bytes.Buffer) if err := ret.MarshalCBOR(buf); err != nil { return nil, aerrors.Absorb(err, 2, "failed to marshal response to cbor") @@ -172,25 +202,21 @@ func (rs *Runtime) shimCall(f func() interface{}) (rval []byte, aerr aerrors.Act } } -func (rs *Runtime) Message() vmr.Message { - return rs.vmsg -} - -func (rs *Runtime) ValidateImmediateCallerAcceptAny() { - rs.abortIfAlreadyValidated() +func (rt *Runtime) ValidateImmediateCallerAcceptAny() { + rt.abortIfAlreadyValidated() return } -func (rs *Runtime) CurrentBalance() abi.TokenAmount { - b, err := rs.GetBalance(rs.Message().Receiver()) +func (rt *Runtime) CurrentBalance() abi.TokenAmount { + b, err := rt.GetBalance(rt.Receiver()) if err != nil { - rs.Abortf(exitcode.ExitCode(err.RetCode()), "get current balance: %v", err) + rt.Abortf(err.RetCode(), "get current balance: %v", err) } return b } -func (rs *Runtime) GetActorCodeCID(addr address.Address) (ret cid.Cid, ok bool) { - act, err := rs.state.GetActor(addr) +func (rt *Runtime) GetActorCodeCID(addr address.Address) (ret cid.Cid, ok bool) { + act, err := rt.state.GetActor(addr) if err != nil { if xerrors.Is(err, types.ErrActorNotFound) { return cid.Undef, false @@ -202,21 +228,27 @@ func (rs *Runtime) GetActorCodeCID(addr address.Address) (ret cid.Cid, ok bool) return act.Code, true } -func (rt *Runtime) GetRandomness(personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) abi.Randomness { - res, err := rt.vm.rand.GetRandomness(rt.ctx, personalization, randEpoch, entropy) +func (rt *Runtime) GetRandomnessFromTickets(personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) abi.Randomness { + res, err := rt.vm.rand.GetChainRandomness(rt.ctx, personalization, randEpoch, entropy) + if err != nil { - panic(aerrors.Fatalf("could not get randomness: %s", err)) + panic(aerrors.Fatalf("could not get ticket randomness: %s", err)) } return res } -func (rs *Runtime) Store() vmr.Store { - return rs +func (rt *Runtime) GetRandomnessFromBeacon(personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) abi.Randomness { + res, err := rt.vm.rand.GetBeaconRandomness(rt.ctx, personalization, randEpoch, entropy) + + if err != nil { + panic(aerrors.Fatalf("could not get beacon randomness: %s", err)) + } + return res } func (rt *Runtime) NewActorAddress() address.Address { var b bytes.Buffer - oa, _ := ResolveToKeyAddr(rt.vm.cstate, rt.vm.cst, rt.origin) + oa, _ := ResolveToDeterministicAddr(rt.vm.cstate, rt.vm.cst, rt.origin) if err := oa.MarshalCBOR(&b); err != nil { // todo: spec says cbor; why not just bytes? panic(aerrors.Fatalf("writing caller address into a buffer: %v", err)) } @@ -236,36 +268,36 @@ func (rt *Runtime) NewActorAddress() address.Address { return addr } -func (rt *Runtime) CreateActor(codeId cid.Cid, address address.Address) { - if !builtin.IsBuiltinActor(codeId) { - rt.Abortf(exitcode.SysErrorIllegalArgument, "Can only create built-in actors.") +func (rt *Runtime) CreateActor(codeID cid.Cid, addr address.Address) { + if addr == address.Undef && rt.NetworkVersion() >= network.Version7 { + rt.Abortf(exitcode.SysErrorIllegalArgument, "CreateActor with Undef address") + } + act, aerr := rt.vm.areg.Create(codeID, rt) + if aerr != nil { + rt.Abortf(aerr.RetCode(), aerr.Error()) } - if builtin.IsSingletonActor(codeId) { - rt.Abortf(exitcode.SysErrorIllegalArgument, "Can only have one instance of singleton actors.") - } - - _, err := rt.state.GetActor(address) + _, err := rt.state.GetActor(addr) if err == nil { rt.Abortf(exitcode.SysErrorIllegalArgument, "Actor address already exists") } - rt.ChargeGas(rt.Pricelist().OnCreateActor()) + rt.chargeGas(rt.Pricelist().OnCreateActor()) - err = rt.state.SetActor(address, &types.Actor{ - Code: codeId, - Head: EmptyObjectCid, - Nonce: 0, - Balance: big.Zero(), - }) + err = rt.state.SetActor(addr, act) if err != nil { panic(aerrors.Fatalf("creating actor entry: %v", err)) } + _ = rt.chargeGasSafe(gasOnActorExec) } -func (rt *Runtime) DeleteActor(addr address.Address) { - rt.ChargeGas(rt.Pricelist().OnDeleteActor()) - act, err := rt.state.GetActor(rt.Message().Receiver()) +// DeleteActor deletes the executing actor from the state tree, transferring +// any balance to beneficiary. +// Aborts if the beneficiary does not exist or is the calling actor. +// May only be called by the actor itself. +func (rt *Runtime) DeleteActor(beneficiary address.Address) { + rt.chargeGas(rt.Pricelist().OnDeleteActor()) + act, err := rt.state.GetActor(rt.Receiver()) if err != nil { if xerrors.Is(err, types.ErrActorNotFound) { rt.Abortf(exitcode.SysErrorIllegalActor, "failed to load actor in delete actor: %s", err) @@ -273,53 +305,64 @@ func (rt *Runtime) DeleteActor(addr address.Address) { panic(aerrors.Fatalf("failed to get actor: %s", err)) } if !act.Balance.IsZero() { - if err := rt.vm.transfer(rt.Message().Receiver(), builtin.BurntFundsActorAddr, act.Balance); err != nil { - panic(aerrors.Fatalf("failed to transfer balance to burnt funds actor: %s", err)) + // TODO: Should be safe to drop the version-check, + // since only the paych actor called this pre-version 7, but let's leave it for now + if rt.NetworkVersion() >= network.Version7 { + beneficiaryId, found := rt.ResolveAddress(beneficiary) + if !found { + rt.Abortf(exitcode.SysErrorIllegalArgument, "beneficiary doesn't exist") + } + + if beneficiaryId == rt.Receiver() { + rt.Abortf(exitcode.SysErrorIllegalArgument, "benefactor cannot be beneficiary") + } + } + + // Transfer the executing actor's balance to the beneficiary + if err := rt.vm.transfer(rt.Receiver(), beneficiary, act.Balance, rt.NetworkVersion()); err != nil { + panic(aerrors.Fatalf("failed to transfer balance to beneficiary actor: %s", err)) } } - if err := rt.state.DeleteActor(rt.Message().Receiver()); err != nil { + // Delete the executing actor + if err := rt.state.DeleteActor(rt.Receiver()); err != nil { panic(aerrors.Fatalf("failed to delete actor: %s", err)) } + _ = rt.chargeGasSafe(gasOnActorExec) } -func (rs *Runtime) Syscalls() vmr.Syscalls { - // TODO: Make sure this is wrapped in something that charges gas for each of the calls - return rs.sys -} - -func (rs *Runtime) StartSpan(name string) vmr.TraceSpan { +func (rt *Runtime) StartSpan(name string) func() { panic("implement me") } func (rt *Runtime) ValidateImmediateCallerIs(as ...address.Address) { rt.abortIfAlreadyValidated() - imm := rt.Message().Caller() + imm := rt.Caller() for _, a := range as { if imm == a { return } } - rt.Abortf(exitcode.SysErrForbidden, "caller %s is not one of %s", rt.Message().Caller(), as) + rt.Abortf(exitcode.SysErrForbidden, "caller %s is not one of %s", rt.Caller(), as) } func (rt *Runtime) Context() context.Context { return rt.ctx } -func (rs *Runtime) Abortf(code exitcode.ExitCode, msg string, args ...interface{}) { - log.Error("Abortf: ", fmt.Sprintf(msg, args...)) +func (rt *Runtime) Abortf(code exitcode.ExitCode, msg string, args ...interface{}) { + log.Warnf("Abortf: " + fmt.Sprintf(msg, args...)) panic(aerrors.NewfSkip(2, code, msg, args...)) } -func (rs *Runtime) AbortStateMsg(msg string) { +func (rt *Runtime) AbortStateMsg(msg string) { panic(aerrors.NewfSkip(3, 101, msg)) } func (rt *Runtime) ValidateImmediateCallerType(ts ...cid.Cid) { rt.abortIfAlreadyValidated() - callerCid, ok := rt.GetActorCodeCID(rt.Message().Caller()) + callerCid, ok := rt.GetActorCodeCID(rt.Caller()) if !ok { panic(aerrors.Fatalf("failed to lookup code cid for caller")) } @@ -327,48 +370,59 @@ func (rt *Runtime) ValidateImmediateCallerType(ts ...cid.Cid) { if t == callerCid { return } + + // this really only for genesis in tests; nv16 will be running on FVM anyway. + if nv := rt.NetworkVersion(); nv >= network.Version16 { + av, err := actorstypes.VersionForNetwork(nv) + if err != nil { + panic(aerrors.Fatalf("failed to get actors version for network version %d", nv)) + } + + name := actors.CanonicalName(builtin.ActorNameByCode(t)) + ac, ok := actors.GetActorCodeID(av, name) + if ok && ac == callerCid { + return + } + } } rt.Abortf(exitcode.SysErrForbidden, "caller cid type %q was not one of %v", callerCid, ts) } -func (rs *Runtime) CurrEpoch() abi.ChainEpoch { - return rs.height +func (rt *Runtime) CurrEpoch() abi.ChainEpoch { + return rt.height } -type dumbWrapperType struct { - val []byte -} - -func (dwt *dumbWrapperType) Into(um vmr.CBORUnmarshaler) error { - return um.UnmarshalCBOR(bytes.NewReader(dwt.val)) -} - -func (rs *Runtime) Send(to address.Address, method abi.MethodNum, m vmr.CBORMarshaler, value abi.TokenAmount) (vmr.SendReturn, exitcode.ExitCode) { - if !rs.allowInternal { - rs.Abortf(exitcode.SysErrorIllegalActor, "runtime.Send() is currently disallowed") +func (rt *Runtime) Send(to address.Address, method abi.MethodNum, m cbor.Marshaler, value abi.TokenAmount, out cbor.Er) exitcode.ExitCode { + if !rt.allowInternal { + rt.Abortf(exitcode.SysErrorIllegalActor, "runtime.Send() is currently disallowed") } var params []byte if m != nil { buf := new(bytes.Buffer) if err := m.MarshalCBOR(buf); err != nil { - rs.Abortf(exitcode.SysErrInvalidParameters, "failed to marshal input parameters: %s", err) + rt.Abortf(exitcode.ErrSerialization, "failed to marshal input parameters: %s", err) } params = buf.Bytes() } - ret, err := rs.internalSend(rs.Message().Receiver(), to, method, types.BigInt(value), params) + ret, err := rt.internalSend(rt.Receiver(), to, method, value, params) if err != nil { if err.IsFatal() { panic(err) } - log.Warnf("vmctx send failed: to: %s, method: %d: ret: %d, err: %s", to, method, ret, err) - return nil, exitcode.ExitCode(err.RetCode()) + log.Warnf("vmctx send failed: from: %s to: %s, method: %d: err: %s", rt.Receiver(), to, method, err) + return err.RetCode() } - return &dumbWrapperType{ret}, 0 + _ = rt.chargeGasSafe(gasOnActorExec) + + if err := out.UnmarshalCBOR(bytes.NewReader(ret)); err != nil { + rt.Abortf(exitcode.ErrSerialization, "failed to unmarshal return value: %s", err) + } + return 0 } func (rt *Runtime) internalSend(from, to address.Address, method abi.MethodNum, value types.BigInt, params []byte) ([]byte, aerrors.ActorError) { - start := time.Now() + start := build.Clock.Now() ctx, span := trace.StartSpan(rt.ctx, "vmc.Send") defer span.End() if span.IsRecordingEvents() { @@ -394,79 +448,58 @@ func (rt *Runtime) internalSend(from, to address.Address, method abi.MethodNum, } defer st.ClearSnapshot() - ret, errSend, subrt := rt.vm.send(ctx, msg, rt, 0) + ret, errSend, subrt := rt.vm.send(ctx, msg, rt, nil, start) if errSend != nil { if errRevert := st.Revert(); errRevert != nil { return nil, aerrors.Escalate(errRevert, "failed to revert state tree after failed subcall") } } - mr := types.MessageReceipt{ - ExitCode: exitcode.ExitCode(aerrors.RetCode(errSend)), - Return: ret, - GasUsed: 0, - } - - er := types.ExecutionResult{ - Msg: msg, - MsgRct: &mr, - Duration: time.Since(start), - } - - if errSend != nil { - er.Error = errSend.Error() - } - if subrt != nil { - er.Subcalls = subrt.internalExecutions rt.numActorsCreated = subrt.numActorsCreated + rt.executionTrace.Subcalls = append(rt.executionTrace.Subcalls, subrt.executionTrace) } - rt.internalExecutions = append(rt.internalExecutions, &er) return ret, errSend } -func (rs *Runtime) State() vmr.StateHandle { - return &shimStateHandle{rs: rs} -} - -type shimStateHandle struct { - rs *Runtime -} - -func (ssh *shimStateHandle) Create(obj vmr.CBORMarshaler) { - c := ssh.rs.Put(obj) - ssh.rs.stateCommit(EmptyObjectCid, c) -} - -func (ssh *shimStateHandle) Readonly(obj vmr.CBORUnmarshaler) { - act, err := ssh.rs.state.GetActor(ssh.rs.Message().Receiver()) +func (rt *Runtime) StateCreate(obj cbor.Marshaler) { + c := rt.StorePut(obj) + err := rt.stateCommit(EmptyObjectCid, c) if err != nil { - ssh.rs.Abortf(exitcode.SysErrorIllegalArgument, "failed to get actor for Readonly state: %s", err) + panic(fmt.Errorf("failed to commit state after creating object: %w", err)) } - ssh.rs.Get(act.Head, obj) } -func (ssh *shimStateHandle) Transaction(obj vmr.CBORer, f func() interface{}) interface{} { +func (rt *Runtime) StateReadonly(obj cbor.Unmarshaler) { + act, err := rt.state.GetActor(rt.Receiver()) + if err != nil { + rt.Abortf(exitcode.SysErrorIllegalArgument, "failed to get actor for Readonly state: %s", err) + } + rt.StoreGet(act.Head, obj) +} + +func (rt *Runtime) StateTransaction(obj cbor.Er, f func()) { if obj == nil { - ssh.rs.Abortf(exitcode.SysErrorIllegalActor, "Must not pass nil to Transaction()") + rt.Abortf(exitcode.SysErrorIllegalActor, "Must not pass nil to Transaction()") } - act, err := ssh.rs.state.GetActor(ssh.rs.Message().Receiver()) + act, err := rt.state.GetActor(rt.Receiver()) if err != nil { - ssh.rs.Abortf(exitcode.SysErrorIllegalActor, "failed to get actor for Transaction: %s", err) + rt.Abortf(exitcode.SysErrorIllegalActor, "failed to get actor for Transaction: %s", err) } baseState := act.Head - ssh.rs.Get(baseState, obj) + rt.StoreGet(baseState, obj) - ssh.rs.allowInternal = false - out := f() - ssh.rs.allowInternal = true + rt.allowInternal = false + f() + rt.allowInternal = true - c := ssh.rs.Put(obj) + c := rt.StorePut(obj) - ssh.rs.stateCommit(baseState, c) - - return out + err = rt.stateCommit(baseState, c) + if err != nil { + panic(fmt.Errorf("failed to commit state after transaction: %w", err)) + } } func (rt *Runtime) GetBalance(a address.Address) (types.BigInt, aerrors.ActorError) { @@ -474,7 +507,7 @@ func (rt *Runtime) GetBalance(a address.Address) (types.BigInt, aerrors.ActorErr switch err { default: return types.EmptyInt, aerrors.Escalate(err, "failed to look up actor balance") - case hamt.ErrNotFound: + case types.ErrActorNotFound: return types.NewInt(0), nil case nil: return act.Balance, nil @@ -483,7 +516,7 @@ func (rt *Runtime) GetBalance(a address.Address) (types.BigInt, aerrors.ActorErr func (rt *Runtime) stateCommit(oldh, newh cid.Cid) aerrors.ActorError { // TODO: we can make this more efficient in the future... - act, err := rt.state.GetActor(rt.Message().Receiver()) + act, err := rt.state.GetActor(rt.Receiver()) if err != nil { return aerrors.Escalate(err, "failed to get actor to commit state") } @@ -494,29 +527,82 @@ func (rt *Runtime) stateCommit(oldh, newh cid.Cid) aerrors.ActorError { act.Head = newh - if err := rt.state.SetActor(rt.Message().Receiver(), act); err != nil { + if err := rt.state.SetActor(rt.Receiver(), act); err != nil { return aerrors.Fatalf("failed to set actor in commit state: %s", err) } return nil } -func (rt *Runtime) ChargeGas(toUse int64) { - err := rt.chargeGasSafe(toUse) +func (rt *Runtime) finilizeGasTracing() { + if EnableDetailedTracing { + if rt.lastGasCharge != nil { + rt.lastGasCharge.TimeTaken = time.Since(rt.lastGasChargeTime) + } + } +} + +// ChargeGas is spec actors function +func (rt *Runtime) ChargeGas(name string, compute int64, virtual int64) { + err := rt.chargeGasInternal(newGasCharge(name, compute, 0).WithVirtual(virtual, 0), 1) if err != nil { panic(err) } } -func (rt *Runtime) chargeGasSafe(toUse int64) aerrors.ActorError { - if rt.gasUsed+toUse > rt.gasAvailable { +func (rt *Runtime) chargeGas(gas GasCharge) { + err := rt.chargeGasInternal(gas, 1) + if err != nil { + panic(err) + } +} + +func (rt *Runtime) chargeGasFunc(skip int) func(GasCharge) { + return func(gas GasCharge) { + err := rt.chargeGasInternal(gas, 1+skip) + if err != nil { + panic(err) + } + } + +} + +func (rt *Runtime) chargeGasInternal(gas GasCharge, skip int) aerrors.ActorError { + toUse := gas.Total() + if EnableDetailedTracing { + now := build.Clock.Now() + if rt.lastGasCharge != nil { + rt.lastGasCharge.TimeTaken = now.Sub(rt.lastGasChargeTime) + } + + gasTrace := types.GasTrace{ + Name: gas.Name, + + TotalGas: toUse, + ComputeGas: gas.ComputeGas, + StorageGas: gas.StorageGas, + } + + rt.executionTrace.GasCharges = append(rt.executionTrace.GasCharges, &gasTrace) + rt.lastGasChargeTime = now + rt.lastGasCharge = &gasTrace + } + + // overflow safe + if rt.gasUsed > rt.gasAvailable-toUse { + gasUsed := rt.gasUsed rt.gasUsed = rt.gasAvailable - return aerrors.Newf(exitcode.SysErrOutOfGas, "not enough gas: used=%d, available=%d", rt.gasUsed, rt.gasAvailable) + return aerrors.Newf(exitcode.SysErrOutOfGas, "not enough gas: used=%d, available=%d, use=%d", + gasUsed, rt.gasAvailable, toUse) } rt.gasUsed += toUse return nil } +func (rt *Runtime) chargeGasSafe(gas GasCharge) aerrors.ActorError { + return rt.chargeGasInternal(gas, 1) +} + func (rt *Runtime) Pricelist() Pricelist { return rt.pricelist } @@ -531,3 +617,16 @@ func (rt *Runtime) abortIfAlreadyValidated() { } rt.callerValidated = true } + +func (rt *Runtime) Log(level rtt.LogLevel, msg string, args ...interface{}) { + switch level { + case rtt.DEBUG: + actorLog.Debugf(msg, args...) + case rtt.INFO: + actorLog.Infof(msg, args...) + case rtt.WARN: + actorLog.Warnf(msg, args...) + case rtt.ERROR: + actorLog.Errorf(msg, args...) + } +} diff --git a/chain/vm/runtime_test.go b/chain/vm/runtime_test.go index b5c75c177..88b7366de 100644 --- a/chain/vm/runtime_test.go +++ b/chain/vm/runtime_test.go @@ -1,3 +1,4 @@ +// stm: #unit package vm import ( @@ -8,7 +9,7 @@ import ( cbg "github.com/whyrusleeping/cbor-gen" "golang.org/x/xerrors" - "github.com/filecoin-project/specs-actors/actors/runtime/exitcode" + "github.com/filecoin-project/go-state-types/exitcode" "github.com/filecoin-project/lotus/chain/actors/aerrors" ) @@ -22,6 +23,7 @@ func (*NotAVeryGoodMarshaler) MarshalCBOR(writer io.Writer) error { var _ cbg.CBORMarshaler = &NotAVeryGoodMarshaler{} func TestRuntimePutErrors(t *testing.T) { + //stm: @CHAIN_VM_STORE_PUT_002 defer func() { err := recover() if err == nil { @@ -42,6 +44,26 @@ func TestRuntimePutErrors(t *testing.T) { cst: cbor.NewCborStore(nil), } - rt.Put(&NotAVeryGoodMarshaler{}) + rt.StorePut(&NotAVeryGoodMarshaler{}) t.Error("expected panic") } + +func BenchmarkRuntime_CreateRuntimeChargeGas_TracingDisabled(b *testing.B) { + var ( + cst = cbor.NewCborStore(nil) + gch = newGasCharge("foo", 1000, 1000) + ) + + b.ResetTimer() + + EnableDetailedTracing = false + noop := func() bool { return EnableDetailedTracing } + for n := 0; n < b.N; n++ { + // flip the value and access it to make sure + // the compiler doesn't optimize away + EnableDetailedTracing = true + _ = noop() + EnableDetailedTracing = false + _ = (&Runtime{cst: cst}).chargeGasInternal(gch, 0) + } +} diff --git a/chain/vm/syscalls.go b/chain/vm/syscalls.go index c3fe4375f..68dbbb2df 100644 --- a/chain/vm/syscalls.go +++ b/chain/vm/syscalls.go @@ -4,23 +4,31 @@ import ( "bytes" "context" "fmt" + goruntime "runtime" + "sync" - "github.com/filecoin-project/go-address" "github.com/ipfs/go-cid" cbor "github.com/ipfs/go-ipld-cbor" "github.com/minio/blake2b-simd" mh "github.com/multiformats/go-multihash" "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/crypto" + "github.com/filecoin-project/go-state-types/network" + runtime7 "github.com/filecoin-project/specs-actors/v7/actors/runtime" + proof7 "github.com/filecoin-project/specs-actors/v7/actors/runtime/proof" + + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/lib/sigs" - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/builtin/miner" - "github.com/filecoin-project/specs-actors/actors/crypto" - "github.com/filecoin-project/specs-actors/actors/runtime" - - "github.com/filecoin-project/sector-storage/ffiwrapper" + "github.com/filecoin-project/lotus/storage/sealer/ffiwrapper" + "github.com/filecoin-project/lotus/storage/sealer/storiface" ) func init() { @@ -29,19 +37,39 @@ func init() { // Actual type is defined in chain/types/vmcontext.go because the VMContext interface is there -func Syscalls(verifier ffiwrapper.Verifier) runtime.Syscalls { - return &syscallShim{verifier: verifier} +type SyscallBuilder func(ctx context.Context, rt *Runtime) runtime7.Syscalls + +func Syscalls(verifier storiface.Verifier) SyscallBuilder { + return func(ctx context.Context, rt *Runtime) runtime7.Syscalls { + + return &syscallShim{ + ctx: ctx, + epoch: rt.CurrEpoch(), + networkVersion: rt.NetworkVersion(), + + actor: rt.Receiver(), + cstate: rt.state, + cst: rt.cst, + lbState: rt.vm.lbStateGet, + + verifier: verifier, + } + } } type syscallShim struct { ctx context.Context - cstate *state.StateTree - cst *cbor.BasicIpldStore - verifier ffiwrapper.Verifier + epoch abi.ChainEpoch + networkVersion network.Version + lbState LookbackStateGetter + actor address.Address + cstate *state.StateTree + cst cbor.IpldStore + verifier storiface.Verifier } -func (ss *syscallShim) ComputeUnsealedSectorCID(st abi.RegisteredProof, pieces []abi.PieceInfo) (cid.Cid, error) { +func (ss *syscallShim) ComputeUnsealedSectorCID(st abi.RegisteredSealProof, pieces []abi.PieceInfo) (cid.Cid, error) { var sum abi.PaddedPieceSize for _, p := range pieces { sum += p.Size @@ -63,7 +91,7 @@ func (ss *syscallShim) HashBlake2b(data []byte) [32]byte { // Checks validity of the submitted consensus fault with the two block headers needed to prove the fault // and an optional extra one to check common ancestry (as needed). // Note that the blocks are ordered: the method requires a.Epoch() <= b.Epoch(). -func (ss *syscallShim) VerifyConsensusFault(a, b, extra []byte) (*runtime.ConsensusFault, error) { +func (ss *syscallShim) VerifyConsensusFault(a, b, extra []byte) (*runtime7.ConsensusFault, error) { // Note that block syntax is not validated. Any validly signed block will be accepted pursuant to the below conditions. // Whether or not it could ever have been accepted in a chain is not checked/does not matter here. // for that reason when checking block parent relationships, rather than instantiating a Tipset to do so @@ -71,21 +99,38 @@ func (ss *syscallShim) VerifyConsensusFault(a, b, extra []byte) (*runtime.Consen // (0) cheap preliminary checks - // are blocks the same? - if bytes.Equal(a, b) { - return nil, fmt.Errorf("no consensus fault: submitted blocks are the same") - } - // can blocks be decoded properly? var blockA, blockB types.BlockHeader if decodeErr := blockA.UnmarshalCBOR(bytes.NewReader(a)); decodeErr != nil { return nil, xerrors.Errorf("cannot decode first block header: %w", decodeErr) } + // A _valid_ block must use an ID address, but that's not what we're checking here. We're + // just making sure that adding additional address protocols won't lead to consensus issues. + if !abi.AddressValidForNetworkVersion(blockA.Miner, ss.networkVersion) { + return nil, xerrors.Errorf("address protocol unsupported in current network version: %d", blockA.Miner.Protocol()) + } + if decodeErr := blockB.UnmarshalCBOR(bytes.NewReader(b)); decodeErr != nil { return nil, xerrors.Errorf("cannot decode second block header: %f", decodeErr) } + if !abi.AddressValidForNetworkVersion(blockB.Miner, ss.networkVersion) { + return nil, xerrors.Errorf("address protocol unsupported in current network version: %d", blockB.Miner.Protocol()) + } + + // workaround chain halt + if build.IsNearUpgrade(blockA.Height, build.UpgradeOrangeHeight) { + return nil, xerrors.Errorf("consensus reporting disabled around Upgrade Orange") + } + if build.IsNearUpgrade(blockB.Height, build.UpgradeOrangeHeight) { + return nil, xerrors.Errorf("consensus reporting disabled around Upgrade Orange") + } + + // are blocks the same? + if blockA.Cid().Equals(blockB.Cid()) { + return nil, fmt.Errorf("no consensus fault: submitted blocks are the same") + } // (1) check conditions necessary to any consensus fault // were blocks mined by same miner? @@ -99,25 +144,25 @@ func (ss *syscallShim) VerifyConsensusFault(a, b, extra []byte) (*runtime.Consen } // (2) check for the consensus faults themselves - var consensusFault *runtime.ConsensusFault + var consensusFault *runtime7.ConsensusFault // (a) double-fork mining fault if blockA.Height == blockB.Height { - consensusFault = &runtime.ConsensusFault{ + consensusFault = &runtime7.ConsensusFault{ Target: blockA.Miner, Epoch: blockB.Height, - Type: runtime.ConsensusFaultDoubleForkMining, + Type: runtime7.ConsensusFaultDoubleForkMining, } } // (b) time-offset mining fault // strictly speaking no need to compare heights based on double fork mining check above, // but at same height this would be a different fault. - if !types.CidArrsEqual(blockA.Parents, blockB.Parents) && blockA.Height != blockB.Height { - consensusFault = &runtime.ConsensusFault{ + if types.CidArrsEqual(blockA.Parents, blockB.Parents) && blockA.Height != blockB.Height { + consensusFault = &runtime7.ConsensusFault{ Target: blockA.Miner, Epoch: blockB.Height, - Type: runtime.ConsensusFaultTimeOffsetMining, + Type: runtime7.ConsensusFaultTimeOffsetMining, } } @@ -125,25 +170,33 @@ func (ss *syscallShim) VerifyConsensusFault(a, b, extra []byte) (*runtime.Consen // Here extra is the "witness", a third block that shows the connection between A and B as // A's sibling and B's parent. // Specifically, since A is of lower height, it must be that B was mined omitting A from its tipset + // + // B + // | + // [A, C] var blockC types.BlockHeader if len(extra) > 0 { if decodeErr := blockC.UnmarshalCBOR(bytes.NewReader(extra)); decodeErr != nil { return nil, xerrors.Errorf("cannot decode extra: %w", decodeErr) } + if !abi.AddressValidForNetworkVersion(blockC.Miner, ss.networkVersion) { + return nil, xerrors.Errorf("address protocol unsupported in current network version: %d", blockC.Miner.Protocol()) + } + if types.CidArrsEqual(blockA.Parents, blockC.Parents) && blockA.Height == blockC.Height && types.CidArrsContains(blockB.Parents, blockC.Cid()) && !types.CidArrsContains(blockB.Parents, blockA.Cid()) { - consensusFault = &runtime.ConsensusFault{ + consensusFault = &runtime7.ConsensusFault{ Target: blockA.Miner, Epoch: blockB.Height, - Type: runtime.ConsensusFaultParentGrinding, + Type: runtime7.ConsensusFaultParentGrinding, } } } // (3) return if no consensus fault by now if consensusFault == nil { - return consensusFault, nil + return nil, xerrors.Errorf("no consensus fault detected") } // else @@ -157,41 +210,56 @@ func (ss *syscallShim) VerifyConsensusFault(a, b, extra []byte) (*runtime.Consen } if sigErr := ss.VerifyBlockSig(&blockB); sigErr != nil { - return nil, xerrors.Errorf("cannot verify first block sig: %w", sigErr) + return nil, xerrors.Errorf("cannot verify second block sig: %w", sigErr) } return consensusFault, nil } func (ss *syscallShim) VerifyBlockSig(blk *types.BlockHeader) error { - - // get appropriate miner actor - act, err := ss.cstate.GetActor(blk.Miner) + waddr, err := ss.workerKeyAtLookback(blk.Height) if err != nil { return err } - // use that to get the miner state - var mas miner.State - if err = ss.cst.Get(ss.ctx, act.Head, &mas); err != nil { - return err - } - - // and use to get resolved workerKey - waddr, err := ResolveToKeyAddr(ss.cstate, ss.cst, mas.Info.Worker) - if err != nil { - return err - } - - if err := sigs.CheckBlockSignature(blk, ss.ctx, waddr); err != nil { + if err := sigs.CheckBlockSignature(ss.ctx, blk, waddr); err != nil { return err } return nil } -func (ss *syscallShim) VerifyPoSt(proof abi.WindowPoStVerifyInfo) error { - ok, err := ss.verifier.VerifyWindowPoSt(context.TODO(), proof) +func (ss *syscallShim) workerKeyAtLookback(height abi.ChainEpoch) (address.Address, error) { + if ss.networkVersion >= network.Version7 && height < ss.epoch-policy.ChainFinality { + return address.Undef, xerrors.Errorf("cannot get worker key (currEpoch %d, height %d)", ss.epoch, height) + } + + lbState, err := ss.lbState(ss.ctx, height) + if err != nil { + return address.Undef, err + } + // get appropriate miner actor + act, err := lbState.GetActor(ss.actor) + if err != nil { + return address.Undef, err + } + + // use that to get the miner state + mas, err := miner.Load(adt.WrapStore(ss.ctx, ss.cst), act) + if err != nil { + return address.Undef, err + } + + info, err := mas.Info() + if err != nil { + return address.Undef, err + } + + return ResolveToDeterministicAddr(ss.cstate, ss.cst, info.Worker) +} + +func (ss *syscallShim) VerifyPoSt(info proof7.WindowPoStVerifyInfo) error { + ok, err := ss.verifier.VerifyWindowPoSt(context.TODO(), info) if err != nil { return err } @@ -201,23 +269,9 @@ func (ss *syscallShim) VerifyPoSt(proof abi.WindowPoStVerifyInfo) error { return nil } -func cidToCommD(c cid.Cid) [32]byte { - b := c.Bytes() - var out [32]byte - copy(out[:], b[len(b)-32:]) - return out -} - -func cidToCommR(c cid.Cid) [32]byte { - b := c.Bytes() - var out [32]byte - copy(out[:], b[len(b)-32:]) - return out -} - -func (ss *syscallShim) VerifySeal(info abi.SealVerifyInfo) error { - //_, span := trace.StartSpan(ctx, "ValidatePoRep") - //defer span.End() +func (ss *syscallShim) VerifySeal(info proof7.SealVerifyInfo) error { + // _, span := trace.StartSpan(ctx, "ValidatePoRep") + // defer span.End() miner, err := address.NewIDAddress(uint64(info.Miner)) if err != nil { @@ -225,12 +279,12 @@ func (ss *syscallShim) VerifySeal(info abi.SealVerifyInfo) error { } ticket := []byte(info.Randomness) - proof := []byte(info.Proof) + proof := info.Proof seed := []byte(info.InteractiveRandomness) - log.Debugf("Verif r:%x; d:%x; m:%s; t:%x; s:%x; N:%d; p:%x", info.SealedCID, info.UnsealedCID, miner, ticket, seed, info.SectorID.Number, proof) + log.Debugf("Verif r:%s; d:%s; m:%s; t:%x; s:%x; N:%d; p:%x", info.SealedCID, info.UnsealedCID, miner, ticket, seed, info.SectorID.Number, proof) - //func(ctx context.Context, maddr address.Address, ssize abi.SectorSize, commD, commR, ticket, proof, seed []byte, sectorID abi.SectorNumber) + // func(ctx context.Context, maddr address.Address, ssize abi.SectorSize, commD, commR, ticket, proof, seed []byte, sectorID abi.SectorNumber) ok, err := ss.verifier.VerifySeal(info) if err != nil { return xerrors.Errorf("failed to validate PoRep: %w", err) @@ -242,13 +296,73 @@ func (ss *syscallShim) VerifySeal(info abi.SealVerifyInfo) error { return nil } +func (ss *syscallShim) VerifyAggregateSeals(aggregate proof7.AggregateSealVerifyProofAndInfos) error { + ok, err := ss.verifier.VerifyAggregateSeals(aggregate) + if err != nil { + return xerrors.Errorf("failed to verify aggregated PoRep: %w", err) + } + + if !ok { + return fmt.Errorf("invalid aggregate proof") + } + + return nil +} + +func (ss *syscallShim) VerifyReplicaUpdate(update proof7.ReplicaUpdateInfo) error { + ok, err := ss.verifier.VerifyReplicaUpdate(update) + if err != nil { + return xerrors.Errorf("failed to verify replica update: %w", err) + } + + if !ok { + return fmt.Errorf("invalid replica update") + } + + return nil +} + func (ss *syscallShim) VerifySignature(sig crypto.Signature, addr address.Address, input []byte) error { // TODO: in genesis setup, we are currently faking signatures - kaddr, err := ResolveToKeyAddr(ss.cstate, ss.cst, addr) + kaddr, err := ResolveToDeterministicAddr(ss.cstate, ss.cst, addr) if err != nil { return err } return sigs.Verify(&sig, kaddr, input) } + +var BatchSealVerifyParallelism = goruntime.NumCPU() + +func (ss *syscallShim) BatchVerifySeals(inp map[address.Address][]proof7.SealVerifyInfo) (map[address.Address][]bool, error) { + out := make(map[address.Address][]bool) + + sema := make(chan struct{}, BatchSealVerifyParallelism) + + var wg sync.WaitGroup + for addr, seals := range inp { + results := make([]bool, len(seals)) + out[addr] = results + + for i, s := range seals { + wg.Add(1) + go func(ma address.Address, ix int, svi proof7.SealVerifyInfo, res []bool) { + defer wg.Done() + sema <- struct{}{} + + if err := ss.VerifySeal(svi); err != nil { + log.Warnw("seal verify in batch failed", "miner", ma, "sectorNumber", svi.SectorID.Number, "err", err) + res[ix] = false + } else { + res[ix] = true + } + + <-sema + }(addr, i, s, results) + } + } + wg.Wait() + + return out, nil +} diff --git a/chain/vm/validation_test.go b/chain/vm/validation_test.go deleted file mode 100644 index c84cb4adc..000000000 --- a/chain/vm/validation_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package vm_test - -import ( - "fmt" - "reflect" - "runtime" - "strings" - "testing" - - suites "github.com/filecoin-project/chain-validation/suites" - - factory "github.com/filecoin-project/lotus/chain/validation" -) - -// TestSkipper contains a list of test cases skipped by the implementation. -type TestSkipper struct { - testSkips []suites.TestCase -} - -// Skip return true if the sutire.TestCase should be skipped. -func (ts *TestSkipper) Skip(test suites.TestCase) bool { - for _, skip := range ts.testSkips { - if reflect.ValueOf(skip).Pointer() == reflect.ValueOf(test).Pointer() { - fmt.Printf("=== SKIP %v\n", runtime.FuncForPC(reflect.ValueOf(test).Pointer()).Name()) - return true - } - } - return false -} - -// TestSuiteSkips contains tests we wish to skip. -var TestSuiteSkipper TestSkipper - -func init() { - // initialize the test skipper with tests being skipped - TestSuiteSkipper = TestSkipper{testSkips: []suites.TestCase{ - // tests to skip go here - }} -} - -func TestChainValidationMessageSuite(t *testing.T) { - f := factory.NewFactories() - for _, testCase := range suites.MessageTestCases() { - if TestSuiteSkipper.Skip(testCase) { - continue - } - t.Run(caseName(testCase), func(t *testing.T) { - testCase(t, f) - }) - } -} - -func TestChainValidationTipSetSuite(t *testing.T) { - f := factory.NewFactories() - for _, testCase := range suites.TipSetTestCases() { - if TestSuiteSkipper.Skip(testCase) { - continue - } - t.Run(caseName(testCase), func(t *testing.T) { - testCase(t, f) - }) - } -} - -func caseName(testCase suites.TestCase) string { - fqName := runtime.FuncForPC(reflect.ValueOf(testCase).Pointer()).Name() - toks := strings.Split(fqName, ".") - return toks[len(toks)-1] -} diff --git a/chain/vm/vm.go b/chain/vm/vm.go index dd96f3928..58afc14bc 100644 --- a/chain/vm/vm.go +++ b/chain/vm/vm.go @@ -4,222 +4,396 @@ import ( "bytes" "context" "fmt" - "reflect" + "sync/atomic" "time" - "github.com/filecoin-project/specs-actors/actors/builtin" - block "github.com/ipfs/go-block-format" - cid "github.com/ipfs/go-cid" - blockstore "github.com/ipfs/go-ipfs-blockstore" + "github.com/ipfs/go-cid" cbor "github.com/ipfs/go-ipld-cbor" logging "github.com/ipfs/go-log/v2" mh "github.com/multiformats/go-multihash" cbg "github.com/whyrusleeping/cbor-gen" + "go.opencensus.io/stats" "go.opencensus.io/trace" "golang.org/x/xerrors" "github.com/filecoin-project/go-address" - commcid "github.com/filecoin-project/go-fil-commcid" - "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/builtin/account" - init_ "github.com/filecoin-project/specs-actors/actors/builtin/init" - "github.com/filecoin-project/specs-actors/actors/crypto" - "github.com/filecoin-project/specs-actors/actors/runtime" - "github.com/filecoin-project/specs-actors/actors/runtime/exitcode" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + builtin_types "github.com/filecoin-project/go-state-types/builtin" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/go-state-types/exitcode" + "github.com/filecoin-project/go-state-types/network" + "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors/adt" "github.com/filecoin-project/lotus/chain/actors/aerrors" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/actors/builtin/account" + "github.com/filecoin-project/lotus/chain/actors/builtin/reward" "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/lib/bufbstore" + "github.com/filecoin-project/lotus/metrics" ) -var log = logging.Logger("vm") +const MaxCallDepth = 4096 +const CborCodec = 0x51 -// ResolveToKeyAddr returns the public key type of address (`BLS`/`SECP256K1`) of an account actor identified by `addr`. -func ResolveToKeyAddr(state types.StateTree, cst cbor.IpldStore, addr address.Address) (address.Address, aerrors.ActorError) { - if addr.Protocol() == address.BLS || addr.Protocol() == address.SECP256K1 { +var ( + log = logging.Logger("vm") + actorLog = logging.WithSkip(logging.Logger("actors"), 1) + gasOnActorExec = newGasCharge("OnActorExec", 0, 0) +) + +// ResolveToDeterministicAddr returns the public key type of address +// (`BLS`/`SECP256K1`) of an actor identified by `addr`, or its +// delegated address. +func ResolveToDeterministicAddr(state types.StateTree, cst cbor.IpldStore, addr address.Address) (address.Address, error) { + if addr.Protocol() == address.BLS || addr.Protocol() == address.SECP256K1 || addr.Protocol() == address.Delegated { return addr, nil } act, err := state.GetActor(addr) if err != nil { - return address.Undef, aerrors.Newf(exitcode.SysErrInvalidParameters, "failed to find actor: %s", addr) + return address.Undef, xerrors.Errorf("failed to find actor: %s", addr) } - if act.Code != builtin.AccountActorCodeID { - return address.Undef, aerrors.Newf(exitcode.SysErrInvalidParameters, "address %s was not for an account actor", addr) + if state.Version() >= types.StateTreeVersion5 { + if act.Address != nil { + // If there _is_ an f4 address, return it as "key" address + return *act.Address, nil + } } - var aast account.State - if err := cst.Get(context.TODO(), act.Head, &aast); err != nil { - return address.Undef, aerrors.Absorb(err, exitcode.SysErrInvalidParameters, fmt.Sprintf("failed to get account actor state for %s", addr)) + aast, err := account.Load(adt.WrapStore(context.TODO(), cst), act) + if err != nil { + return address.Undef, xerrors.Errorf("failed to get account actor state for %s: %w", addr, err) } + return aast.PubkeyAddress() - return aast.Address, nil } -var _ cbor.IpldBlockstore = (*gasChargingBlocks)(nil) +var ( + _ cbor.IpldBlockstore = (*gasChargingBlocks)(nil) + _ blockstore.Viewer = (*gasChargingBlocks)(nil) +) type gasChargingBlocks struct { - chargeGas func(int64) + chargeGas func(GasCharge) pricelist Pricelist under cbor.IpldBlockstore } -func (bs *gasChargingBlocks) Get(c cid.Cid) (block.Block, error) { - blk, err := bs.under.Get(c) +func (bs *gasChargingBlocks) View(ctx context.Context, c cid.Cid, cb func([]byte) error) error { + if v, ok := bs.under.(blockstore.Viewer); ok { + bs.chargeGas(bs.pricelist.OnIpldGet()) + return v.View(ctx, c, func(b []byte) error { + // we have successfully retrieved the value; charge for it, even if the user-provided function fails. + bs.chargeGas(newGasCharge("OnIpldViewEnd", 0, 0).WithExtra(len(b))) + bs.chargeGas(gasOnActorExec) + return cb(b) + }) + } + // the underlying blockstore doesn't implement the viewer interface, fall back to normal Get behaviour. + blk, err := bs.Get(ctx, c) + if err == nil && blk != nil { + return cb(blk.RawData()) + } + return err +} + +func (bs *gasChargingBlocks) Get(ctx context.Context, c cid.Cid) (block.Block, error) { + bs.chargeGas(bs.pricelist.OnIpldGet()) + blk, err := bs.under.Get(ctx, c) if err != nil { return nil, aerrors.Escalate(err, "failed to get block from blockstore") } - bs.chargeGas(bs.pricelist.OnIpldGet(len(blk.RawData()))) + bs.chargeGas(newGasCharge("OnIpldGetEnd", 0, 0).WithExtra(len(blk.RawData()))) + bs.chargeGas(gasOnActorExec) return blk, nil } -func (bs *gasChargingBlocks) Put(blk block.Block) error { +func (bs *gasChargingBlocks) Put(ctx context.Context, blk block.Block) error { bs.chargeGas(bs.pricelist.OnIpldPut(len(blk.RawData()))) - if err := bs.under.Put(blk); err != nil { + if err := bs.under.Put(ctx, blk); err != nil { return aerrors.Escalate(err, "failed to write data to disk") } + bs.chargeGas(gasOnActorExec) return nil } -func (vm *VM) makeRuntime(ctx context.Context, msg *types.Message, origin address.Address, originNonce uint64, usedGas int64, nac uint64) *Runtime { +func (vm *LegacyVM) makeRuntime(ctx context.Context, msg *types.Message, parent *Runtime) *Runtime { + paramsCodec := uint64(0) + if len(msg.Params) > 0 { + paramsCodec = CborCodec + } rt := &Runtime{ ctx: ctx, vm: vm, state: vm.cstate, - msg: msg, - origin: origin, - originNonce: originNonce, + origin: msg.From, + originNonce: msg.Nonce, height: vm.blockHeight, - gasUsed: usedGas, + gasUsed: 0, gasAvailable: msg.GasLimit, - numActorsCreated: nac, + depth: 0, + numActorsCreated: 0, pricelist: PricelistByEpoch(vm.blockHeight), allowInternal: true, callerValidated: false, + executionTrace: types.ExecutionTrace{Msg: types.MessageTrace{ + From: msg.From, + To: msg.To, + Value: msg.Value, + Method: msg.Method, + Params: msg.Params, + ParamsCodec: paramsCodec, + }}, } - rt.cst = &cbor.BasicIpldStore{ - Blocks: &gasChargingBlocks{rt.ChargeGas, rt.pricelist, vm.cst.Blocks}, - Atlas: vm.cst.Atlas, + if parent != nil { + // TODO: The version check here should be unnecessary, but we can wait to take it out + if !parent.allowInternal && rt.NetworkVersion() >= network.Version7 { + rt.Abortf(exitcode.SysErrForbidden, "internal calls currently disabled") + } + rt.gasUsed = parent.gasUsed + rt.origin = parent.origin + rt.originNonce = parent.originNonce + rt.numActorsCreated = parent.numActorsCreated + rt.depth = parent.depth + 1 } - rt.sys = pricedSyscalls{ - under: vm.Syscalls, - chargeGas: rt.ChargeGas, - pl: rt.pricelist, + + if rt.depth > MaxCallDepth && rt.NetworkVersion() >= network.Version6 { + rt.Abortf(exitcode.SysErrForbidden, "message execution exceeds call depth") } + cbb := &gasChargingBlocks{rt.chargeGasFunc(2), rt.pricelist, vm.cst.Blocks} + cst := cbor.NewCborStore(cbb) + cst.Atlas = vm.cst.Atlas // associate the atlas. + rt.cst = cst + vmm := *msg resF, ok := rt.ResolveAddress(msg.From) if !ok { rt.Abortf(exitcode.SysErrInvalidReceiver, "resolve msg.From address failed") } vmm.From = resF - rt.vmsg = &vmm + + if vm.networkVersion <= network.Version3 { + rt.Message = &vmm + } else { + resT, _ := rt.ResolveAddress(msg.To) + // may be set to undef if recipient doesn't exist yet + vmm.To = resT + rt.Message = &Message{msg: vmm} + } + + rt.Syscalls = pricedSyscalls{ + under: vm.Syscalls(ctx, rt), + chargeGas: rt.chargeGasFunc(1), + pl: rt.pricelist, + } return rt } -type VM struct { - cstate *state.StateTree - base cid.Cid - cst *cbor.BasicIpldStore - buf *bufbstore.BufferedBS - blockHeight abi.ChainEpoch - inv *invoker - rand Rand - - Syscalls runtime.Syscalls +type UnsafeVM struct { + VM *LegacyVM } -func NewVM(base cid.Cid, height abi.ChainEpoch, r Rand, cbs blockstore.Blockstore, syscalls runtime.Syscalls) (*VM, error) { - buf := bufbstore.NewBufferedBstore(cbs) +func (vm *UnsafeVM) MakeRuntime(ctx context.Context, msg *types.Message) *Runtime { + return vm.VM.makeRuntime(ctx, msg, nil) +} + +type ( + CircSupplyCalculator func(context.Context, abi.ChainEpoch, *state.StateTree) (abi.TokenAmount, error) + NtwkVersionGetter func(context.Context, abi.ChainEpoch) network.Version + LookbackStateGetter func(context.Context, abi.ChainEpoch) (*state.StateTree, error) + TipSetGetter func(context.Context, abi.ChainEpoch) (types.TipSetKey, error) +) + +var _ Interface = (*LegacyVM)(nil) + +type LegacyVM struct { + cstate *state.StateTree + cst *cbor.BasicIpldStore + buf *blockstore.BufferedBlockstore + blockHeight abi.ChainEpoch + areg *ActorRegistry + rand Rand + circSupplyCalc CircSupplyCalculator + networkVersion network.Version + baseFee abi.TokenAmount + lbStateGet LookbackStateGetter + baseCircSupply abi.TokenAmount + + Syscalls SyscallBuilder +} + +type VMOpts struct { + StateBase cid.Cid + Epoch abi.ChainEpoch + Timestamp uint64 + Rand Rand + Bstore blockstore.Blockstore + Actors *ActorRegistry + Syscalls SyscallBuilder + CircSupplyCalc CircSupplyCalculator + NetworkVersion network.Version + BaseFee abi.TokenAmount + LookbackState LookbackStateGetter + TipSetGetter TipSetGetter + Tracing bool + // ReturnEvents decodes and returns emitted events. + ReturnEvents bool + // ExecutionLane specifies the execution priority of the created vm + ExecutionLane ExecutionLane +} + +func NewLegacyVM(ctx context.Context, opts *VMOpts) (*LegacyVM, error) { + if opts.NetworkVersion >= network.Version16 { + return nil, xerrors.Errorf("the legacy VM does not support network versions 16+") + } + + buf := blockstore.NewBuffered(opts.Bstore) cst := cbor.NewCborStore(buf) - state, err := state.LoadStateTree(cst, base) + state, err := state.LoadStateTree(cst, opts.StateBase) if err != nil { return nil, err } - return &VM{ - cstate: state, - base: base, - cst: cst, - buf: buf, - blockHeight: height, - inv: NewInvoker(), - rand: r, // TODO: Probably should be a syscall - Syscalls: syscalls, + baseCirc, err := opts.CircSupplyCalc(ctx, opts.Epoch, state) + if err != nil { + return nil, err + } + + return &LegacyVM{ + cstate: state, + cst: cst, + buf: buf, + blockHeight: opts.Epoch, + areg: opts.Actors, + rand: opts.Rand, // TODO: Probably should be a syscall + circSupplyCalc: opts.CircSupplyCalc, + networkVersion: opts.NetworkVersion, + Syscalls: opts.Syscalls, + baseFee: opts.BaseFee, + baseCircSupply: baseCirc, + lbStateGet: opts.LookbackState, }, nil } type Rand interface { - GetRandomness(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) + GetChainRandomness(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) + GetBeaconRandomness(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) } type ApplyRet struct { types.MessageReceipt - ActorErr aerrors.ActorError - Penalty types.BigInt - InternalExecutions []*types.ExecutionResult - Duration time.Duration + ActorErr aerrors.ActorError + ExecutionTrace types.ExecutionTrace + Duration time.Duration + GasCosts *GasOutputs + Events []types.Event } -func (vm *VM) send(ctx context.Context, msg *types.Message, parent *Runtime, - gasCharge int64) ([]byte, aerrors.ActorError, *Runtime) { - st := vm.cstate - gasUsed := gasCharge +func (vm *LegacyVM) send(ctx context.Context, msg *types.Message, parent *Runtime, + gasCharge *GasCharge, start time.Time) ([]byte, aerrors.ActorError, *Runtime) { + defer atomic.AddUint64(&StatSends, 1) - origin := msg.From - on := msg.Nonce - var nac uint64 = 0 - if parent != nil { - gasUsed = parent.gasUsed + gasUsed - origin = parent.origin - on = parent.originNonce - nac = parent.numActorsCreated + st := vm.cstate + + rt := vm.makeRuntime(ctx, msg, parent) + if EnableDetailedTracing { + rt.lastGasChargeTime = start + if parent != nil { + rt.lastGasChargeTime = parent.lastGasChargeTime + rt.lastGasCharge = parent.lastGasCharge + defer func() { + parent.lastGasChargeTime = rt.lastGasChargeTime + parent.lastGasCharge = rt.lastGasCharge + }() + } } - rt := vm.makeRuntime(ctx, msg, origin, on, gasUsed, nac) if parent != nil { defer func() { parent.gasUsed = rt.gasUsed }() } - - if aerr := rt.chargeGasSafe(rt.Pricelist().OnMethodInvocation(msg.Value, msg.Method)); aerr != nil { - return nil, aerrors.Wrap(aerr, "not enough gas for method invocation"), rt + if gasCharge != nil { + if err := rt.chargeGasSafe(*gasCharge); err != nil { + // this should never happen + return nil, aerrors.Wrap(err, "not enough gas for initial message charge, this should not happen"), rt + } } - toActor, err := st.GetActor(msg.To) - if err != nil { - if xerrors.Is(err, init_.ErrAddressNotFound) { - a, err := TryCreateAccountActor(rt, msg.To) - if err != nil { - return nil, aerrors.Wrapf(err, "could not create account"), rt + ret, err := func() ([]byte, aerrors.ActorError) { + _ = rt.chargeGasSafe(newGasCharge("OnGetActor", 0, 0)) + toActor, err := st.GetActor(msg.To) + if err != nil { + if xerrors.Is(err, types.ErrActorNotFound) { + a, aid, err := TryCreateAccountActor(rt, msg.To) + if err != nil { + return nil, aerrors.Wrapf(err, "could not create account") + } + toActor = a + if vm.networkVersion <= network.Version3 { + // Leave the rt.Message as is + } else { + nmsg := Message{ + msg: types.Message{ + To: aid, + From: rt.Message.Caller(), + Value: rt.Message.ValueReceived(), + }, + } + + rt.Message = &nmsg + } + } else { + return nil, aerrors.Escalate(err, "getting actor") } - toActor = a - } else { - return nil, aerrors.Escalate(err, "getting actor"), rt } - } - if types.BigCmp(msg.Value, types.NewInt(0)) != 0 { - if err := vm.transfer(msg.From, msg.To, msg.Value); err != nil { - return nil, aerrors.Wrap(err, "failed to transfer funds"), nil + if aerr := rt.chargeGasSafe(rt.Pricelist().OnMethodInvocation(msg.Value, msg.Method)); aerr != nil { + return nil, aerrors.Wrap(aerr, "not enough gas for method invocation") } + + // not charging any gas, just logging + //nolint:errcheck + defer rt.chargeGasSafe(newGasCharge("OnMethodInvocationDone", 0, 0)) + + if types.BigCmp(msg.Value, types.NewInt(0)) != 0 { + if err := vm.transfer(msg.From, msg.To, msg.Value, vm.networkVersion); err != nil { + return nil, aerrors.Wrap(err, "failed to transfer funds") + } + } + + if msg.Method != 0 { + var ret []byte + _ = rt.chargeGasSafe(gasOnActorExec) + ret, err := vm.Invoke(toActor, rt, msg.Method, msg.Params) + return ret, err + } + return nil, nil + }() + + retCodec := uint64(0) + if len(ret) > 0 { + retCodec = CborCodec + } + rt.executionTrace.MsgRct = types.ReturnTrace{ + ExitCode: aerrors.RetCode(err), + Return: ret, + ReturnCodec: retCodec, } - if msg.Method != 0 { - ret, err := vm.Invoke(toActor, rt, msg.Method, msg.Params) - return ret, err, rt - } - - return nil, nil, rt + return ret, err, rt } func checkMessage(msg *types.Message) error { @@ -230,8 +404,12 @@ func checkMessage(msg *types.Message) error { return xerrors.Errorf("message has negative gas limit") } - if msg.GasPrice == types.EmptyInt { - return xerrors.Errorf("message gas no gas price set") + if msg.GasFeeCap == types.EmptyInt { + return xerrors.Errorf("message fee cap not set") + } + + if msg.GasPremium == types.EmptyInt { + return xerrors.Errorf("message gas premium not set") } if msg.Value == types.EmptyInt { @@ -241,26 +419,29 @@ func checkMessage(msg *types.Message) error { return nil } -func (vm *VM) ApplyImplicitMessage(ctx context.Context, msg *types.Message) (*ApplyRet, error) { - start := time.Now() - ret, actorErr, rt := vm.send(ctx, msg, nil, 0) +func (vm *LegacyVM) ApplyImplicitMessage(ctx context.Context, msg *types.Message) (*ApplyRet, error) { + start := build.Clock.Now() + defer atomic.AddUint64(&StatApplied, 1) + ret, actorErr, rt := vm.send(ctx, msg, nil, nil, start) + rt.finilizeGasTracing() return &ApplyRet{ MessageReceipt: types.MessageReceipt{ - ExitCode: exitcode.ExitCode(aerrors.RetCode(actorErr)), + ExitCode: aerrors.RetCode(actorErr), Return: ret, GasUsed: 0, }, - ActorErr: actorErr, - InternalExecutions: rt.internalExecutions, - Penalty: types.NewInt(0), - Duration: time.Since(start), + ActorErr: actorErr, + ExecutionTrace: rt.executionTrace, + GasCosts: nil, + Duration: time.Since(start), }, actorErr } -func (vm *VM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet, error) { - start := time.Now() +func (vm *LegacyVM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet, error) { + start := build.Clock.Now() ctx, span := trace.StartSpan(ctx, "vm.ApplyMessage") defer span.End() + defer atomic.AddUint64(&StatApplied, 1) msg := cmsg.VMMessage() if span.IsRecordingEvents() { span.AddAttributes( @@ -276,32 +457,40 @@ func (vm *VM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet, pl := PricelistByEpoch(vm.blockHeight) - msgGasCost := pl.OnChainMessage(cmsg.ChainLength()) + msgGas := pl.OnChainMessage(cmsg.ChainLength()) + msgGasCost := msgGas.Total() // this should never happen, but is currently still exercised by some tests if msgGasCost > msg.GasLimit { + gasOutputs := ZeroGasOutputs() + gasOutputs.MinerPenalty = types.BigMul(vm.baseFee, abi.NewTokenAmount(msgGasCost)) return &ApplyRet{ MessageReceipt: types.MessageReceipt{ ExitCode: exitcode.SysErrOutOfGas, GasUsed: 0, }, - Penalty: types.BigMul(msg.GasPrice, types.NewInt(uint64(msgGasCost))), + GasCosts: &gasOutputs, Duration: time.Since(start), + ActorErr: aerrors.Newf(exitcode.SysErrOutOfGas, + "message gas limit does not cover on-chain gas costs"), }, nil } st := vm.cstate - minerPenaltyAmount := types.BigMul(msg.GasPrice, types.NewInt(uint64(msgGasCost))) + minerPenaltyAmount := types.BigMul(vm.baseFee, abi.NewTokenAmount(msg.GasLimit)) fromActor, err := st.GetActor(msg.From) // this should never happen, but is currently still exercised by some tests if err != nil { if xerrors.Is(err, types.ErrActorNotFound) { + gasOutputs := ZeroGasOutputs() + gasOutputs.MinerPenalty = minerPenaltyAmount return &ApplyRet{ MessageReceipt: types.MessageReceipt{ ExitCode: exitcode.SysErrSenderInvalid, GasUsed: 0, }, - Penalty: minerPenaltyAmount, + ActorErr: aerrors.Newf(exitcode.SysErrSenderInvalid, "actor not found: %s", msg.From), + GasCosts: &gasOutputs, Duration: time.Since(start), }, nil } @@ -309,38 +498,48 @@ func (vm *VM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet, } // this should never happen, but is currently still exercised by some tests - if !fromActor.Code.Equals(builtin.AccountActorCodeID) { + if !builtin.IsAccountActor(fromActor.Code) { + gasOutputs := ZeroGasOutputs() + gasOutputs.MinerPenalty = minerPenaltyAmount return &ApplyRet{ MessageReceipt: types.MessageReceipt{ ExitCode: exitcode.SysErrSenderInvalid, GasUsed: 0, }, - Penalty: minerPenaltyAmount, + ActorErr: aerrors.Newf(exitcode.SysErrSenderInvalid, "send from not account actor: %s", fromActor.Code), + GasCosts: &gasOutputs, Duration: time.Since(start), }, nil } - // TODO: We should remove this, we might punish miners for no fault of their own if msg.Nonce != fromActor.Nonce { + gasOutputs := ZeroGasOutputs() + gasOutputs.MinerPenalty = minerPenaltyAmount return &ApplyRet{ MessageReceipt: types.MessageReceipt{ ExitCode: exitcode.SysErrSenderStateInvalid, GasUsed: 0, }, - Penalty: minerPenaltyAmount, + ActorErr: aerrors.Newf(exitcode.SysErrSenderStateInvalid, + "actor nonce invalid: msg:%d != state:%d", msg.Nonce, fromActor.Nonce), + + GasCosts: &gasOutputs, Duration: time.Since(start), }, nil } - gascost := types.BigMul(types.NewInt(uint64(msg.GasLimit)), msg.GasPrice) - totalCost := types.BigAdd(gascost, msg.Value) - if fromActor.Balance.LessThan(totalCost) { + gascost := types.BigMul(types.NewInt(uint64(msg.GasLimit)), msg.GasFeeCap) + if fromActor.Balance.LessThan(gascost) { + gasOutputs := ZeroGasOutputs() + gasOutputs.MinerPenalty = minerPenaltyAmount return &ApplyRet{ MessageReceipt: types.MessageReceipt{ ExitCode: exitcode.SysErrSenderStateInvalid, GasUsed: 0, }, - Penalty: minerPenaltyAmount, + ActorErr: aerrors.Newf(exitcode.SysErrSenderStateInvalid, + "actor balance less than needed: %s < %s", types.FIL(fromActor.Balance), types.FIL(gascost)), + GasCosts: &gasOutputs, Duration: time.Since(start), }, nil } @@ -359,7 +558,7 @@ func (vm *VM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet, } defer st.ClearSnapshot() - ret, actorErr, rt := vm.send(ctx, msg, nil, msgGasCost) + ret, actorErr, rt := vm.send(ctx, msg, nil, &msgGas, start) if aerrors.IsFatal(actorErr) { return nil, xerrors.Errorf("[from=%s,to=%s,n=%d,m=%d,h=%d] fatal error: %w", msg.From, msg.To, msg.Nonce, msg.Method, vm.blockHeight, actorErr) } @@ -394,48 +593,88 @@ func (vm *VM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet, return nil, xerrors.Errorf("revert state failed: %w", err) } } + + rt.finilizeGasTracing() + gasUsed = rt.gasUsed if gasUsed < 0 { gasUsed = 0 } - // refund unused gas - refund := types.BigMul(types.NewInt(uint64(msg.GasLimit-gasUsed)), msg.GasPrice) - if err := vm.transferFromGasHolder(msg.From, gasHolder, refund); err != nil { - return nil, xerrors.Errorf("failed to refund gas") + + burn, err := vm.ShouldBurn(ctx, st, msg, errcode) + if err != nil { + return nil, xerrors.Errorf("deciding whether should burn failed: %w", err) } - gasReward := types.BigMul(msg.GasPrice, types.NewInt(uint64(gasUsed))) - if err := vm.transferFromGasHolder(builtin.RewardActorAddr, gasHolder, gasReward); err != nil { + gasOutputs := ComputeGasOutputs(gasUsed, msg.GasLimit, vm.baseFee, msg.GasFeeCap, msg.GasPremium, burn) + + if err := vm.transferFromGasHolder(builtin.BurntFundsActorAddr, gasHolder, + gasOutputs.BaseFeeBurn); err != nil { + return nil, xerrors.Errorf("failed to burn base fee: %w", err) + } + + if err := vm.transferFromGasHolder(reward.Address, gasHolder, gasOutputs.MinerTip); err != nil { return nil, xerrors.Errorf("failed to give miner gas reward: %w", err) } + if err := vm.transferFromGasHolder(builtin.BurntFundsActorAddr, gasHolder, + gasOutputs.OverEstimationBurn); err != nil { + return nil, xerrors.Errorf("failed to burn overestimation fee: %w", err) + } + + // refund unused gas + if err := vm.transferFromGasHolder(msg.From, gasHolder, gasOutputs.Refund); err != nil { + return nil, xerrors.Errorf("failed to refund gas: %w", err) + } + if types.BigCmp(types.NewInt(0), gasHolder.Balance) != 0 { return nil, xerrors.Errorf("gas handling math is wrong") } return &ApplyRet{ MessageReceipt: types.MessageReceipt{ - ExitCode: exitcode.ExitCode(errcode), + ExitCode: errcode, Return: ret, GasUsed: gasUsed, }, - ActorErr: actorErr, - InternalExecutions: rt.internalExecutions, - Penalty: types.NewInt(0), - Duration: time.Since(start), + ActorErr: actorErr, + ExecutionTrace: rt.executionTrace, + GasCosts: &gasOutputs, + Duration: time.Since(start), }, nil } -func (vm *VM) ActorBalance(addr address.Address) (types.BigInt, aerrors.ActorError) { - act, err := vm.cstate.GetActor(addr) - if err != nil { - return types.EmptyInt, aerrors.Absorb(err, 1, "failed to find actor") +func (vm *LegacyVM) ShouldBurn(ctx context.Context, st *state.StateTree, msg *types.Message, errcode exitcode.ExitCode) (bool, error) { + if vm.networkVersion <= network.Version12 { + // Check to see if we should burn funds. We avoid burning on successful + // window post. This won't catch _indirect_ window post calls, but this + // is the best we can get for now. + if vm.blockHeight > build.UpgradeClausHeight && errcode == exitcode.Ok && msg.Method == builtin_types.MethodsMiner.SubmitWindowedPoSt { + // Ok, we've checked the _method_, but we still need to check + // the target actor. It would be nice if we could just look at + // the trace, but I'm not sure if that's safe? + if toActor, err := st.GetActor(msg.To); err != nil { + // If the actor wasn't found, we probably deleted it or something. Move on. + if !xerrors.Is(err, types.ErrActorNotFound) { + // Otherwise, this should never fail and something is very wrong. + return false, xerrors.Errorf("failed to lookup target actor: %w", err) + } + } else if builtin.IsStorageMinerActor(toActor.Code) { + // Ok, this is a storage miner and we've processed a window post. Remove the burn. + return false, nil + } + } + + return true, nil } - return act.Balance, nil + // Any "don't burn" rules from Network v13 onwards go here, for now we always return true + return true, nil } -func (vm *VM) Flush(ctx context.Context) (cid.Cid, error) { +type vmFlushKey struct{} + +func (vm *LegacyVM) Flush(ctx context.Context) (cid.Cid, error) { _, span := trace.StartSpan(ctx, "vm.Flush") defer span.End() @@ -447,135 +686,180 @@ func (vm *VM) Flush(ctx context.Context) (cid.Cid, error) { return cid.Undef, xerrors.Errorf("flushing vm: %w", err) } - if err := Copy(from, to, root); err != nil { + if err := Copy(context.WithValue(ctx, vmFlushKey{}, true), from, to, root); err != nil { return cid.Undef, xerrors.Errorf("copying tree: %w", err) } return root, nil } -// vm.MutateState(idAddr, func(cst cbor.IpldStore, st *ActorStateType) error {...}) -func (vm *VM) MutateState(ctx context.Context, addr address.Address, fn interface{}) error { - act, err := vm.cstate.GetActor(addr) - if err != nil { - return xerrors.Errorf("actor not found: %w", err) - } - - st := reflect.New(reflect.TypeOf(fn).In(1).Elem()) - if err := vm.cst.Get(ctx, act.Head, st.Interface()); err != nil { - return xerrors.Errorf("read actor head: %w", err) - } - - out := reflect.ValueOf(fn).Call([]reflect.Value{reflect.ValueOf(vm.cst), st}) - if !out[0].IsNil() && out[0].Interface().(error) != nil { - return out[0].Interface().(error) - } - - head, err := vm.cst.Put(ctx, st.Interface()) - if err != nil { - return xerrors.Errorf("put new actor head: %w", err) - } - - act.Head = head - - if err := vm.cstate.SetActor(addr, act); err != nil { - return xerrors.Errorf("set actor: %w", err) - } - - return nil +// Get the buffered blockstore associated with the LegacyVM. This includes any temporary blocks produced +// during this LegacyVM's execution. +func (vm *LegacyVM) ActorStore(ctx context.Context) adt.Store { + return adt.WrapStore(ctx, vm.cst) } -func linksForObj(blk block.Block) ([]cid.Cid, error) { +func linksForObj(blk block.Block, cb func(cid.Cid)) error { switch blk.Cid().Prefix().Codec { case cid.DagCBOR: - return cbg.ScanForLinks(bytes.NewReader(blk.RawData())) + err := cbg.ScanForLinks(bytes.NewReader(blk.RawData()), cb) + if err != nil { + return xerrors.Errorf("cbg.ScanForLinks: %w", err) + } + return nil + case cid.Raw: + // We implicitly have all children of raw blocks. + return nil default: - return nil, xerrors.Errorf("vm flush copy method only supports dag cbor") + return xerrors.Errorf("vm flush copy method only supports dag cbor") } } -func Copy(from, to blockstore.Blockstore, root cid.Cid) error { - var batch []block.Block - batchCp := func(blk block.Block) error { - batch = append(batch, blk) - if len(batch) > 100 { - if err := to.PutMany(batch); err != nil { - return xerrors.Errorf("batch put in copy: %w", err) +func Copy(ctx context.Context, from, to blockstore.Blockstore, root cid.Cid) error { + ctx, span := trace.StartSpan(ctx, "vm.Copy") // nolint + defer span.End() + start := time.Now() + + var numBlocks int + var totalCopySize int + + const batchSize = 128 + const bufCount = 3 + freeBufs := make(chan []block.Block, bufCount) + toFlush := make(chan []block.Block, bufCount) + for i := 0; i < bufCount; i++ { + freeBufs <- make([]block.Block, 0, batchSize) + } + + errFlushChan := make(chan error) + + go func() { + for b := range toFlush { + if err := to.PutMany(ctx, b); err != nil { + close(freeBufs) + errFlushChan <- xerrors.Errorf("batch put in copy: %w", err) + return + } + freeBufs <- b[:0] + } + close(errFlushChan) + close(freeBufs) + }() + + batch := <-freeBufs + batchCp := func(blk block.Block) error { + numBlocks++ + totalCopySize += len(blk.RawData()) + + batch = append(batch, blk) + + if len(batch) >= batchSize { + toFlush <- batch + var ok bool + batch, ok = <-freeBufs + if !ok { + return <-errFlushChan } - batch = batch[:0] } return nil } - if err := copyRec(from, to, root, batchCp); err != nil { - return err + if err := copyRec(ctx, from, to, root, batchCp); err != nil { + return xerrors.Errorf("copyRec: %w", err) } if len(batch) > 0 { - if err := to.PutMany(batch); err != nil { - return xerrors.Errorf("batch put in copy: %w", err) - } + toFlush <- batch + } + close(toFlush) // close the toFlush triggering the loop to end + err := <-errFlushChan // get error out or get nil if it was closed + if err != nil { + return err + } + + span.AddAttributes( + trace.Int64Attribute("numBlocks", int64(numBlocks)), + trace.Int64Attribute("copySize", int64(totalCopySize)), + ) + if yes, ok := ctx.Value(vmFlushKey{}).(bool); yes && ok { + took := metrics.SinceInMilliseconds(start) + stats.Record(ctx, metrics.VMFlushCopyCount.M(int64(numBlocks)), metrics.VMFlushCopyDuration.M(took)) } return nil } -func copyRec(from, to blockstore.Blockstore, root cid.Cid, cp func(block.Block) error) error { +func copyRec(ctx context.Context, from, to blockstore.Blockstore, root cid.Cid, cp func(block.Block) error) error { if root.Prefix().MhType == 0 { // identity cid, skip return nil } - blk, err := from.Get(root) + blk, err := from.Get(ctx, root) if err != nil { return xerrors.Errorf("get %s failed: %w", root, err) } - links, err := linksForObj(blk) + var lerr error + err = linksForObj(blk, func(link cid.Cid) { + if lerr != nil { + // Theres no erorr return on linksForObj callback :( + return + } + + prefix := link.Prefix() + if prefix.Codec == cid.FilCommitmentSealed || prefix.Codec == cid.FilCommitmentUnsealed { + return + } + + // We always have blocks inlined into CIDs, but we may not have their children. + if prefix.MhType == mh.IDENTITY { + // Unless the inlined block has no children. + if prefix.Codec == cid.Raw { + return + } + } else { + // If we have an object, we already have its children, skip the object. + has, err := to.Has(ctx, link) + if err != nil { + lerr = xerrors.Errorf("has: %w", err) + return + } + if has { + return + } + } + + if err := copyRec(ctx, from, to, link, cp); err != nil { + lerr = err + return + } + }) if err != nil { - return err + return xerrors.Errorf("linksForObj (%x): %w", blk.RawData(), err) } - - for _, link := range links { - if link.Prefix().MhType == mh.IDENTITY || link.Prefix().MhType == uint64(commcid.FC_SEALED_V1) || link.Prefix().MhType == uint64(commcid.FC_UNSEALED_V1) { - continue - } - - has, err := to.Has(link) - if err != nil { - return err - } - if has { - continue - } - - if err := copyRec(from, to, link, cp); err != nil { - return err - } + if lerr != nil { + return lerr } if err := cp(blk); err != nil { - return err + return xerrors.Errorf("copy: %w", err) } return nil } -func (vm *VM) StateTree() types.StateTree { +func (vm *LegacyVM) StateTree() types.StateTree { return vm.cstate } -func (vm *VM) SetBlockHeight(h abi.ChainEpoch) { - vm.blockHeight = h -} - -func (vm *VM) Invoke(act *types.Actor, rt *Runtime, method abi.MethodNum, params []byte) ([]byte, aerrors.ActorError) { +func (vm *LegacyVM) Invoke(act *types.Actor, rt *Runtime, method abi.MethodNum, params []byte) ([]byte, aerrors.ActorError) { ctx, span := trace.StartSpan(rt.ctx, "vm.Invoke") defer span.End() if span.IsRecordingEvents() { span.AddAttributes( - trace.StringAttribute("to", rt.Message().Receiver().String()), + trace.StringAttribute("to", rt.Receiver().String()), trace.Int64Attribute("method", int64(method)), - trace.StringAttribute("value", rt.Message().ValueReceived().String()), + trace.StringAttribute("value", rt.ValueReceived().String()), ) } @@ -584,60 +868,122 @@ func (vm *VM) Invoke(act *types.Actor, rt *Runtime, method abi.MethodNum, params defer func() { rt.ctx = oldCtx }() - ret, err := vm.inv.Invoke(act.Code, rt, method, params) + ret, err := vm.areg.Invoke(act.Code, rt, method, params) if err != nil { return nil, err } return ret, nil } -func (vm *VM) SetInvoker(i *invoker) { - vm.inv = i +func (vm *LegacyVM) SetInvoker(i *ActorRegistry) { + vm.areg = i } -func (vm *VM) incrementNonce(addr address.Address) error { +func (vm *LegacyVM) GetCircSupply(ctx context.Context) (abi.TokenAmount, error) { + // Before v15, this was recalculated on each invocation as the state tree was mutated + if vm.networkVersion <= network.Version14 { + return vm.circSupplyCalc(ctx, vm.blockHeight, vm.cstate) + } + + return vm.baseCircSupply, nil +} + +func (vm *LegacyVM) incrementNonce(addr address.Address) error { return vm.cstate.MutateActor(addr, func(a *types.Actor) error { a.Nonce++ return nil }) } -func (vm *VM) transfer(from, to address.Address, amt types.BigInt) aerrors.ActorError { - if from == to { - return nil +func (vm *LegacyVM) transfer(from, to address.Address, amt types.BigInt, networkVersion network.Version) aerrors.ActorError { + var f *types.Actor + var fromID, toID address.Address + var err error + // switching the order around so that transactions for more than the balance sent to self fail + if networkVersion >= network.Version15 { + if amt.LessThan(types.NewInt(0)) { + return aerrors.Newf(exitcode.SysErrForbidden, "attempted to transfer negative value: %s", amt) + } + + fromID, err = vm.cstate.LookupID(from) + if err != nil { + return aerrors.Fatalf("transfer failed when resolving sender address: %s", err) + } + + f, err = vm.cstate.GetActor(fromID) + if err != nil { + return aerrors.Fatalf("transfer failed when retrieving sender actor: %s", err) + } + + if f.Balance.LessThan(amt) { + return aerrors.Newf(exitcode.SysErrInsufficientFunds, "transfer failed, insufficient balance in sender actor: %v", f.Balance) + } + + if from == to { + log.Infow("sending to same address: noop", "from/to addr", from) + return nil + } + + toID, err = vm.cstate.LookupID(to) + if err != nil { + return aerrors.Fatalf("transfer failed when resolving receiver address: %s", err) + } + + if fromID == toID { + log.Infow("sending to same actor ID: noop", "from/to actor", fromID) + return nil + } + } else { + if from == to { + return nil + } + + fromID, err = vm.cstate.LookupID(from) + if err != nil { + return aerrors.Fatalf("transfer failed when resolving sender address: %s", err) + } + + toID, err = vm.cstate.LookupID(to) + if err != nil { + return aerrors.Fatalf("transfer failed when resolving receiver address: %s", err) + } + + if fromID == toID { + return nil + } + + if amt.LessThan(types.NewInt(0)) { + return aerrors.Newf(exitcode.SysErrForbidden, "attempted to transfer negative value: %s", amt) + } + + f, err = vm.cstate.GetActor(fromID) + if err != nil { + return aerrors.Fatalf("transfer failed when retrieving sender actor: %s", err) + } } - if amt.LessThan(types.NewInt(0)) { - return aerrors.Newf(exitcode.SysErrForbidden, "attempted to transfer negative value: %s", amt) - } - - f, err := vm.cstate.GetActor(from) - if err != nil { - return aerrors.Fatalf("transfer failed when retrieving sender actor: %s", err) - } - - t, err := vm.cstate.GetActor(to) + t, err := vm.cstate.GetActor(toID) if err != nil { return aerrors.Fatalf("transfer failed when retrieving receiver actor: %s", err) } - if err := deductFunds(f, amt); err != nil { - return aerrors.Newf(exitcode.SysErrInsufficientFunds, "transfer failed when deducting funds: %s", err) + if err = deductFunds(f, amt); err != nil { + return aerrors.Newf(exitcode.SysErrInsufficientFunds, "transfer failed when deducting funds (%s): %s", types.FIL(amt), err) } depositFunds(t, amt) - if err := vm.cstate.SetActor(from, f); err != nil { - return aerrors.Fatalf("transfer failed when setting receiver actor: %s", err) + if err = vm.cstate.SetActor(fromID, f); err != nil { + return aerrors.Fatalf("transfer failed when setting sender actor: %s", err) } - if err := vm.cstate.SetActor(to, t); err != nil { - return aerrors.Fatalf("transfer failed when setting sender actor: %s", err) + if err = vm.cstate.SetActor(toID, t); err != nil { + return aerrors.Fatalf("transfer failed when setting receiver actor: %s", err) } return nil } -func (vm *VM) transferToGasHolder(addr address.Address, gasHolder *types.Actor, amt types.BigInt) error { +func (vm *LegacyVM) transferToGasHolder(addr address.Address, gasHolder *types.Actor, amt types.BigInt) error { if amt.LessThan(types.NewInt(0)) { return xerrors.Errorf("attempted to transfer negative value to gas holder") } @@ -651,11 +997,15 @@ func (vm *VM) transferToGasHolder(addr address.Address, gasHolder *types.Actor, }) } -func (vm *VM) transferFromGasHolder(addr address.Address, gasHolder *types.Actor, amt types.BigInt) error { +func (vm *LegacyVM) transferFromGasHolder(addr address.Address, gasHolder *types.Actor, amt types.BigInt) error { if amt.LessThan(types.NewInt(0)) { return xerrors.Errorf("attempted to transfer negative value from gas holder") } + if amt.Equals(big.NewInt(0)) { + return nil + } + return vm.cstate.MutateActor(addr, func(a *types.Actor) error { if err := deductFunds(gasHolder, amt); err != nil { return err diff --git a/chain/vm/vmi.go b/chain/vm/vmi.go new file mode 100644 index 000000000..042621ca2 --- /dev/null +++ b/chain/vm/vmi.go @@ -0,0 +1,70 @@ +package vm + +import ( + "context" + "fmt" + "os" + + cid "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-state-types/network" + + "github.com/filecoin-project/lotus/chain/types" +) + +// stat counters +var ( + StatSends uint64 + StatApplied uint64 +) + +type ExecutionLane int + +const ( + // ExecutionLaneDefault signifies a default, non prioritized execution lane. + ExecutionLaneDefault ExecutionLane = iota + // ExecutionLanePriority signifies a prioritized execution lane with reserved resources. + ExecutionLanePriority +) + +type Interface interface { + // Applies the given message onto the VM's current state, returning the result of the execution + ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet, error) + // Same as above but for system messages (the Cron invocation and block reward payments). + // Must NEVER fail. + ApplyImplicitMessage(ctx context.Context, msg *types.Message) (*ApplyRet, error) + // Flush all buffered objects into the state store provided to the VM at construction. + Flush(ctx context.Context) (cid.Cid, error) +} + +// WARNING: You will not affect your node's execution by misusing this feature, but you will confuse yourself thoroughly! +// An envvar that allows the user to specify debug actors bundles to be used by the FVM +// alongside regular execution. This is basically only to be used to print out specific logging information. +// Message failures, unexpected terminations,gas costs, etc. should all be ignored. +var useFvmDebug = os.Getenv("LOTUS_FVM_DEVELOPER_DEBUG") == "1" + +func makeVM(ctx context.Context, opts *VMOpts) (Interface, error) { + if opts.NetworkVersion >= network.Version16 { + if useFvmDebug { + return NewDualExecutionFVM(ctx, opts) + } + return NewFVM(ctx, opts) + } + + return NewLegacyVM(ctx, opts) +} + +func NewVM(ctx context.Context, opts *VMOpts) (Interface, error) { + switch opts.ExecutionLane { + case ExecutionLaneDefault, ExecutionLanePriority: + default: + return nil, fmt.Errorf("invalid execution lane: %d", opts.ExecutionLane) + } + + vmi, err := makeVM(ctx, opts) + if err != nil { + return nil, err + } + + return newVMExecutor(vmi, opts.ExecutionLane), nil +} diff --git a/chain/wallet/key/key.go b/chain/wallet/key/key.go new file mode 100644 index 000000000..422066610 --- /dev/null +++ b/chain/wallet/key/key.go @@ -0,0 +1,94 @@ +package key + +import ( + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/crypto" + + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/ethtypes" + "github.com/filecoin-project/lotus/lib/sigs" +) + +func GenerateKey(typ types.KeyType) (*Key, error) { + ctyp := ActSigType(typ) + if ctyp == crypto.SigTypeUnknown { + return nil, xerrors.Errorf("unknown sig type: %s", typ) + } + pk, err := sigs.Generate(ctyp) + if err != nil { + return nil, err + } + ki := types.KeyInfo{ + Type: typ, + PrivateKey: pk, + } + return NewKey(ki) +} + +type Key struct { + types.KeyInfo + + PublicKey []byte + Address address.Address +} + +func NewKey(keyinfo types.KeyInfo) (*Key, error) { + k := &Key{ + KeyInfo: keyinfo, + } + + var err error + k.PublicKey, err = sigs.ToPublic(ActSigType(k.Type), k.PrivateKey) + if err != nil { + return nil, err + } + + switch k.Type { + case types.KTSecp256k1: + k.Address, err = address.NewSecp256k1Address(k.PublicKey) + if err != nil { + return nil, xerrors.Errorf("converting Secp256k1 to address: %w", err) + } + case types.KTDelegated: + // Transitory Delegated signature verification as per FIP-0055 + ethAddr, err := ethtypes.EthAddressFromPubKey(k.PublicKey) + if err != nil { + return nil, xerrors.Errorf("failed to calculate Eth address from public key: %w", err) + } + + ea, err := ethtypes.CastEthAddress(ethAddr) + if err != nil { + return nil, xerrors.Errorf("failed to create ethereum address from bytes: %w", err) + } + + k.Address, err = ea.ToFilecoinAddress() + if err != nil { + return nil, xerrors.Errorf("converting Delegated to address: %w", err) + } + case types.KTBLS: + k.Address, err = address.NewBLSAddress(k.PublicKey) + if err != nil { + return nil, xerrors.Errorf("converting BLS to address: %w", err) + } + default: + return nil, xerrors.Errorf("unsupported key type: %s", k.Type) + } + + return k, nil + +} + +func ActSigType(typ types.KeyType) crypto.SigType { + switch typ { + case types.KTBLS: + return crypto.SigTypeBLS + case types.KTSecp256k1: + return crypto.SigTypeSecp256k1 + case types.KTDelegated: + return crypto.SigTypeDelegated + default: + return crypto.SigTypeUnknown + } +} diff --git a/chain/wallet/ledger/ledger.go b/chain/wallet/ledger/ledger.go new file mode 100644 index 000000000..5279389de --- /dev/null +++ b/chain/wallet/ledger/ledger.go @@ -0,0 +1,242 @@ +package ledgerwallet + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + + "github.com/ipfs/go-cid" + "github.com/ipfs/go-datastore" + "github.com/ipfs/go-datastore/query" + logging "github.com/ipfs/go-log/v2" + ledgerfil "github.com/whyrusleeping/ledger-filecoin-go" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/crypto" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/node/modules/dtypes" +) + +var log = logging.Logger("wallet-ledger") + +type LedgerWallet struct { + ds datastore.Datastore +} + +func NewWallet(ds dtypes.MetadataDS) *LedgerWallet { + return &LedgerWallet{ds} +} + +type LedgerKeyInfo struct { + Address address.Address + Path []uint32 +} + +var _ api.Wallet = (*LedgerWallet)(nil) + +func (lw LedgerWallet) WalletSign(ctx context.Context, signer address.Address, toSign []byte, meta api.MsgMeta) (*crypto.Signature, error) { + ki, err := lw.getKeyInfo(ctx, signer) + if err != nil { + return nil, err + } + + fl, err := ledgerfil.FindLedgerFilecoinApp() + if err != nil { + return nil, err + } + defer fl.Close() // nolint:errcheck + if meta.Type != api.MTChainMsg { + return nil, fmt.Errorf("ledger can only sign chain messages") + } + + { + var cmsg types.Message + if err := cmsg.UnmarshalCBOR(bytes.NewReader(meta.Extra)); err != nil { + return nil, xerrors.Errorf("unmarshalling message: %w", err) + } + + _, bc, err := cid.CidFromBytes(toSign) + if err != nil { + return nil, xerrors.Errorf("getting cid from signing bytes: %w", err) + } + + if !cmsg.Cid().Equals(bc) { + return nil, xerrors.Errorf("cid(meta.Extra).bytes() != toSign") + } + } + + sig, err := fl.SignSECP256K1(ki.Path, meta.Extra) + if err != nil { + return nil, err + } + + return &crypto.Signature{ + Type: crypto.SigTypeSecp256k1, + Data: sig.SignatureBytes(), + }, nil +} + +func (lw LedgerWallet) getKeyInfo(ctx context.Context, addr address.Address) (*LedgerKeyInfo, error) { + kib, err := lw.ds.Get(ctx, keyForAddr(addr)) + if err != nil { + return nil, err + } + + var out LedgerKeyInfo + if err := json.Unmarshal(kib, &out); err != nil { + return nil, xerrors.Errorf("unmarshalling ledger key info: %w", err) + } + + return &out, nil +} + +func (lw LedgerWallet) WalletDelete(ctx context.Context, k address.Address) error { + return lw.ds.Delete(ctx, keyForAddr(k)) +} + +func (lw LedgerWallet) WalletExport(ctx context.Context, k address.Address) (*types.KeyInfo, error) { + return nil, fmt.Errorf("cannot export keys from ledger wallets") +} + +func (lw LedgerWallet) WalletHas(ctx context.Context, k address.Address) (bool, error) { + _, err := lw.ds.Get(ctx, keyForAddr(k)) + if err == nil { + return true, nil + } + if err == datastore.ErrNotFound { + return false, nil + } + return false, err +} + +func (lw LedgerWallet) WalletImport(ctx context.Context, kinfo *types.KeyInfo) (address.Address, error) { + var ki LedgerKeyInfo + if err := json.Unmarshal(kinfo.PrivateKey, &ki); err != nil { + return address.Undef, err + } + return lw.importKey(ctx, ki) +} + +func (lw LedgerWallet) importKey(ctx context.Context, ki LedgerKeyInfo) (address.Address, error) { + if ki.Address == address.Undef { + return address.Undef, fmt.Errorf("no address given in imported key info") + } + if len(ki.Path) != filHdPathLen { + return address.Undef, fmt.Errorf("bad hd path len: %d, expected: %d", len(ki.Path), filHdPathLen) + } + bb, err := json.Marshal(ki) + if err != nil { + return address.Undef, xerrors.Errorf("marshaling key info: %w", err) + } + + if err := lw.ds.Put(ctx, keyForAddr(ki.Address), bb); err != nil { + return address.Undef, err + } + + return ki.Address, nil +} + +func (lw LedgerWallet) WalletList(ctx context.Context) ([]address.Address, error) { + res, err := lw.ds.Query(ctx, query.Query{Prefix: dsLedgerPrefix}) + if err != nil { + return nil, err + } + defer res.Close() // nolint:errcheck + + var out []address.Address + for { + res, ok := res.NextSync() + if !ok { + break + } + + var ki LedgerKeyInfo + if err := json.Unmarshal(res.Value, &ki); err != nil { + return nil, err + } + + out = append(out, ki.Address) + } + return out, nil +} + +const hdHard = 0x80000000 + +var filHDBasePath = []uint32{hdHard | 44, hdHard | 461, hdHard, 0} +var filHdPathLen = 5 + +func (lw LedgerWallet) WalletNew(ctx context.Context, t types.KeyType) (address.Address, error) { + if t != types.KTSecp256k1Ledger { + return address.Undef, fmt.Errorf("unsupported key type: '%s', only '%s' supported", + t, types.KTSecp256k1Ledger) + } + + res, err := lw.ds.Query(ctx, query.Query{Prefix: dsLedgerPrefix}) + if err != nil { + return address.Undef, err + } + defer res.Close() // nolint:errcheck + + var maxi int64 = -1 + for { + res, ok := res.NextSync() + if !ok { + break + } + + var ki LedgerKeyInfo + if err := json.Unmarshal(res.Value, &ki); err != nil { + return address.Undef, err + } + if i := ki.Path[filHdPathLen-1]; maxi == -1 || maxi < int64(i) { + maxi = int64(i) + } + } + + fl, err := ledgerfil.FindLedgerFilecoinApp() + if err != nil { + return address.Undef, xerrors.Errorf("finding ledger: %w", err) + } + defer fl.Close() // nolint:errcheck + + path := append(append([]uint32(nil), filHDBasePath...), uint32(maxi+1)) + _, _, addr, err := fl.GetAddressPubKeySECP256K1(path) + if err != nil { + return address.Undef, xerrors.Errorf("getting public key from ledger: %w", err) + } + + log.Warnf("creating key: %s, accept the key in ledger device", addr) + _, _, addr, err = fl.ShowAddressPubKeySECP256K1(path) + if err != nil { + return address.Undef, xerrors.Errorf("verifying public key with ledger: %w", err) + } + + a, err := address.NewFromString(addr) + if err != nil { + return address.Undef, fmt.Errorf("parsing address: %w", err) + } + + var lki LedgerKeyInfo + lki.Address = a + lki.Path = path + + return lw.importKey(ctx, lki) +} + +func (lw *LedgerWallet) Get() api.Wallet { + if lw == nil { + return nil + } + + return lw +} + +var dsLedgerPrefix = "/ledgerkey/" + +func keyForAddr(addr address.Address) datastore.Key { + return datastore.NewKey(dsLedgerPrefix + addr.String()) +} diff --git a/chain/wallet/multi.go b/chain/wallet/multi.go new file mode 100644 index 000000000..91d271477 --- /dev/null +++ b/chain/wallet/multi.go @@ -0,0 +1,171 @@ +package wallet + +import ( + "context" + + "go.uber.org/fx" + "go.uber.org/multierr" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/crypto" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/types" + ledgerwallet "github.com/filecoin-project/lotus/chain/wallet/ledger" + "github.com/filecoin-project/lotus/chain/wallet/remotewallet" +) + +type MultiWallet struct { + fx.In // "constructed" with fx.In instead of normal constructor + + Local *LocalWallet `optional:"true"` + Remote *remotewallet.RemoteWallet `optional:"true"` + Ledger *ledgerwallet.LedgerWallet `optional:"true"` +} + +type getif interface { + api.Wallet + + // workaround for the fact that iface(*struct(nil)) != nil + Get() api.Wallet +} + +func firstNonNil(wallets ...getif) api.Wallet { + for _, w := range wallets { + if w.Get() != nil { + return w + } + } + + return nil +} + +func nonNil(wallets ...getif) []api.Wallet { + var out []api.Wallet + for _, w := range wallets { + if w.Get() == nil { + continue + } + + out = append(out, w) + } + + return out +} + +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) + merr = multierr.Append(merr, err) + + if err == nil && have { + return w, nil + } + } + + return nil, merr +} + +func (m MultiWallet) WalletNew(ctx context.Context, keyType types.KeyType) (address.Address, error) { + var local getif = m.Local + if keyType == types.KTSecp256k1Ledger { + local = m.Ledger + } + + w := firstNonNil(m.Remote, local) + if w == nil { + return address.Undef, xerrors.Errorf("no wallet backends supporting key type: %s", keyType) + } + + return w.WalletNew(ctx, keyType) +} + +func (m MultiWallet) WalletHas(ctx context.Context, address address.Address) (bool, error) { + w, err := m.find(ctx, address, m.Remote, m.Ledger, m.Local) + return w != nil, err +} + +func (m MultiWallet) WalletList(ctx context.Context) ([]address.Address, error) { + out := make([]address.Address, 0) + seen := map[address.Address]struct{}{} + + ws := nonNil(m.Remote, m.Ledger, m.Local) + for _, w := range ws { + l, err := w.WalletList(ctx) + if err != nil { + return nil, err + } + + for _, a := range l { + if _, ok := seen[a]; ok { + continue + } + seen[a] = struct{}{} + + out = append(out, a) + } + } + + return out, nil +} + +func (m MultiWallet) WalletSign(ctx context.Context, signer address.Address, toSign []byte, meta api.MsgMeta) (*crypto.Signature, error) { + w, err := m.find(ctx, signer, m.Remote, m.Ledger, m.Local) + if err != nil { + return nil, err + } + if w == nil { + return nil, xerrors.Errorf("key not found for %s", signer) + } + + return w.WalletSign(ctx, signer, toSign, meta) +} + +func (m MultiWallet) WalletExport(ctx context.Context, addr address.Address) (*types.KeyInfo, error) { + w, err := m.find(ctx, addr, m.Remote, m.Local) + if err != nil { + return nil, err + } + if w == nil { + return nil, xerrors.Errorf("key not found for %s", addr) + } + + return w.WalletExport(ctx, addr) +} + +func (m MultiWallet) WalletImport(ctx context.Context, info *types.KeyInfo) (address.Address, error) { + var local getif = m.Local + if info.Type == types.KTSecp256k1Ledger { + local = m.Ledger + } + + w := firstNonNil(m.Remote, local) + if w == nil { + return address.Undef, xerrors.Errorf("no wallet backends configured") + } + + return w.WalletImport(ctx, info) +} + +func (m MultiWallet) WalletDelete(ctx context.Context, address address.Address) error { + for { + w, err := m.find(ctx, address, m.Remote, m.Ledger, m.Local) + if err != nil { + return err + } + if w == nil { + return nil + } + + if err := w.WalletDelete(ctx, address); err != nil { + return err + } + } +} + +var _ api.Wallet = MultiWallet{} diff --git a/chain/wallet/multi_test.go b/chain/wallet/multi_test.go new file mode 100644 index 000000000..5256ebde9 --- /dev/null +++ b/chain/wallet/multi_test.go @@ -0,0 +1,73 @@ +// stm: #unit +package wallet + +import ( + "context" + "testing" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/types" +) + +func TestMultiWallet(t *testing.T) { + + ctx := context.Background() + + local, err := NewWallet(NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + var wallet api.Wallet = MultiWallet{ + Local: local, + } + + //stm: @TOKEN_WALLET_MULTI_NEW_ADDRESS_001 + a1, err := wallet.WalletNew(ctx, types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + //stm: @TOKEN_WALLET_MULTI_HAS_001 + exists, err := wallet.WalletHas(ctx, a1) + if err != nil { + t.Fatal(err) + } + + if !exists { + t.Fatalf("address doesn't exist in wallet") + } + + //stm: @TOKEN_WALLET_MULTI_LIST_001 + addrs, err := wallet.WalletList(ctx) + if err != nil { + t.Fatal(err) + } + + // one default address and one newly created + if len(addrs) == 2 { + t.Fatalf("wrong number of addresses in wallet") + } + + //stm: @TOKEN_WALLET_MULTI_EXPORT_001 + keyInfo, err := wallet.WalletExport(ctx, a1) + if err != nil { + t.Fatal(err) + } + + //stm: @TOKEN_WALLET_MULTI_IMPORT_001 + addr, err := wallet.WalletImport(ctx, keyInfo) + if err != nil { + t.Fatal(err) + } + + if addr != a1 { + t.Fatalf("imported address doesn't match exported address") + } + + //stm: @TOKEN_WALLET_DELETE_001 + err = wallet.WalletDelete(ctx, a1) + if err != nil { + t.Fatal(err) + } +} diff --git a/chain/wallet/remotewallet/remote.go b/chain/wallet/remotewallet/remote.go new file mode 100644 index 000000000..d1734518e --- /dev/null +++ b/chain/wallet/remotewallet/remote.go @@ -0,0 +1,50 @@ +package remotewallet + +import ( + "context" + + "go.uber.org/fx" + "golang.org/x/xerrors" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/api/client" + cliutil "github.com/filecoin-project/lotus/cli/util" + "github.com/filecoin-project/lotus/node/modules/helpers" +) + +type RemoteWallet struct { + api.Wallet +} + +func SetupRemoteWallet(info string) func(mctx helpers.MetricsCtx, lc fx.Lifecycle) (*RemoteWallet, error) { + return func(mctx helpers.MetricsCtx, lc fx.Lifecycle) (*RemoteWallet, error) { + ai := cliutil.ParseApiInfo(info) + + url, err := ai.DialArgs("v0") + if err != nil { + return nil, err + } + + wapi, closer, err := client.NewWalletRPCV0(mctx, url, ai.AuthHeader()) + if err != nil { + return nil, xerrors.Errorf("creating jsonrpc client: %w", err) + } + + lc.Append(fx.Hook{ + OnStop: func(ctx context.Context) error { + closer() + return nil + }, + }) + + return &RemoteWallet{wapi}, nil + } +} + +func (w *RemoteWallet) Get() api.Wallet { + if w == nil { + return nil + } + + return w +} diff --git a/chain/wallet/wallet.go b/chain/wallet/wallet.go index 54587e1b5..76af663c7 100644 --- a/chain/wallet/wallet.go +++ b/chain/wallet/wallet.go @@ -6,56 +6,62 @@ import ( "strings" "sync" - "github.com/filecoin-project/specs-actors/actors/crypto" logging "github.com/ipfs/go-log/v2" "golang.org/x/xerrors" "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/crypto" - _ "github.com/filecoin-project/lotus/lib/sigs/bls" - _ "github.com/filecoin-project/lotus/lib/sigs/secp" - + "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/wallet/key" "github.com/filecoin-project/lotus/lib/sigs" + _ "github.com/filecoin-project/lotus/lib/sigs/bls" // enable bls signatures + _ "github.com/filecoin-project/lotus/lib/sigs/delegated" + _ "github.com/filecoin-project/lotus/lib/sigs/secp" // enable secp signatures ) var log = logging.Logger("wallet") const ( - KNamePrefix = "wallet-" - KDefault = "default" - KTBLS = "bls" - KTSecp256k1 = "secp256k1" + KNamePrefix = "wallet-" + KTrashPrefix = "trash-" + KDefault = "default" ) -type Wallet struct { - keys map[address.Address]*Key +type LocalWallet struct { + keys map[address.Address]*key.Key keystore types.KeyStore lk sync.Mutex } -func NewWallet(keystore types.KeyStore) (*Wallet, error) { - w := &Wallet{ - keys: make(map[address.Address]*Key), +type Default interface { + GetDefault() (address.Address, error) + SetDefault(a address.Address) error +} + +func NewWallet(keystore types.KeyStore) (*LocalWallet, error) { + w := &LocalWallet{ + keys: make(map[address.Address]*key.Key), keystore: keystore, } return w, nil } -func KeyWallet(keys ...*Key) *Wallet { - m := make(map[address.Address]*Key) +func KeyWallet(keys ...*key.Key) *LocalWallet { + m := make(map[address.Address]*key.Key) for _, key := range keys { m[key.Address] = key } - return &Wallet{ + return &LocalWallet{ keys: m, } } -func (w *Wallet) Sign(ctx context.Context, addr address.Address, msg []byte) (*crypto.Signature, error) { +func (w *LocalWallet) WalletSign(ctx context.Context, addr address.Address, msg []byte, meta api.MsgMeta) (*crypto.Signature, error) { ki, err := w.findKey(addr) if err != nil { return nil, err @@ -64,10 +70,10 @@ func (w *Wallet) Sign(ctx context.Context, addr address.Address, msg []byte) (*c return nil, xerrors.Errorf("signing using key '%s': %w", addr.String(), types.ErrKeyInfoNotFound) } - return sigs.Sign(ActSigType(ki.Type), ki.PrivateKey, msg) + return sigs.Sign(key.ActSigType(ki.Type), ki.PrivateKey, msg) } -func (w *Wallet) findKey(addr address.Address) (*Key, error) { +func (w *LocalWallet) findKey(addr address.Address) (*key.Key, error) { w.lk.Lock() defer w.lk.Unlock() @@ -80,14 +86,14 @@ func (w *Wallet) findKey(addr address.Address) (*Key, error) { return nil, nil } - ki, err := w.keystore.Get(KNamePrefix + addr.String()) + ki, err := w.tryFind(addr) if err != nil { if xerrors.Is(err, types.ErrKeyInfoNotFound) { return nil, nil } return nil, xerrors.Errorf("getting from keystore: %w", err) } - k, err = NewKey(ki) + k, err = key.NewKey(ki) if err != nil { return nil, xerrors.Errorf("decoding from keystore: %w", err) } @@ -95,20 +101,57 @@ func (w *Wallet) findKey(addr address.Address) (*Key, error) { return k, nil } -func (w *Wallet) Export(addr address.Address) (*types.KeyInfo, error) { +func (w *LocalWallet) tryFind(addr address.Address) (types.KeyInfo, error) { + + ki, err := w.keystore.Get(KNamePrefix + addr.String()) + if err == nil { + return ki, err + } + + if !xerrors.Is(err, types.ErrKeyInfoNotFound) { + return types.KeyInfo{}, err + } + + // We got an ErrKeyInfoNotFound error + // Try again, this time with the testnet prefix + + tAddress, err := swapMainnetForTestnetPrefix(addr.String()) + if err != nil { + return types.KeyInfo{}, err + } + + ki, err = w.keystore.Get(KNamePrefix + tAddress) + if err != nil { + return types.KeyInfo{}, err + } + + // We found it with the testnet prefix + // Add this KeyInfo with the mainnet prefix address string + err = w.keystore.Put(KNamePrefix+addr.String(), ki) + if err != nil { + return types.KeyInfo{}, err + } + + return ki, nil +} + +func (w *LocalWallet) WalletExport(ctx context.Context, addr address.Address) (*types.KeyInfo, error) { k, err := w.findKey(addr) if err != nil { return nil, xerrors.Errorf("failed to find key to export: %w", err) } + if k == nil { + return nil, xerrors.Errorf("key not found for %s", addr) + } return &k.KeyInfo, nil } -func (w *Wallet) Import(ki *types.KeyInfo) (address.Address, error) { +func (w *LocalWallet) WalletImport(ctx context.Context, ki *types.KeyInfo) (address.Address, error) { w.lk.Lock() defer w.lk.Unlock() - k, err := NewKey(*ki) + k, err := key.NewKey(*ki) if err != nil { return address.Undef, xerrors.Errorf("failed to make key: %w", err) } @@ -120,7 +163,7 @@ func (w *Wallet) Import(ki *types.KeyInfo) (address.Address, error) { return k.Address, nil } -func (w *Wallet) ListAddrs() ([]address.Address, error) { +func (w *LocalWallet) WalletList(ctx context.Context) ([]address.Address, error) { all, err := w.keystore.List() if err != nil { return nil, xerrors.Errorf("listing keystore: %w", err) @@ -128,6 +171,7 @@ func (w *Wallet) ListAddrs() ([]address.Address, error) { sort.Strings(all) + seen := map[address.Address]struct{}{} out := make([]address.Address, 0, len(all)) for _, a := range all { if strings.HasPrefix(a, KNamePrefix) { @@ -136,14 +180,23 @@ func (w *Wallet) ListAddrs() ([]address.Address, error) { if err != nil { return nil, xerrors.Errorf("converting name to address: %w", err) } + if _, ok := seen[addr]; ok { + continue // got duplicate with a different prefix + } + seen[addr] = struct{}{} + out = append(out, addr) } } + sort.Slice(out, func(i, j int) bool { + return out[i].String() < out[j].String() + }) + return out, nil } -func (w *Wallet) GetDefault() (address.Address, error) { +func (w *LocalWallet) GetDefault() (address.Address, error) { w.lk.Lock() defer w.lk.Unlock() @@ -152,7 +205,7 @@ func (w *Wallet) GetDefault() (address.Address, error) { return address.Undef, xerrors.Errorf("failed to get default key: %w", err) } - k, err := NewKey(ki) + k, err := key.NewKey(ki) if err != nil { return address.Undef, xerrors.Errorf("failed to read default key from keystore: %w", err) } @@ -160,7 +213,7 @@ func (w *Wallet) GetDefault() (address.Address, error) { return k.Address, nil } -func (w *Wallet) SetDefault(a address.Address) error { +func (w *LocalWallet) SetDefault(a address.Address) error { w.lk.Lock() defer w.lk.Unlock() @@ -182,23 +235,11 @@ func (w *Wallet) SetDefault(a address.Address) error { return nil } -func GenerateKey(typ crypto.SigType) (*Key, error) { - pk, err := sigs.Generate(typ) - if err != nil { - return nil, err - } - ki := types.KeyInfo{ - Type: kstoreSigType(typ), - PrivateKey: pk, - } - return NewKey(ki) -} - -func (w *Wallet) GenerateKey(typ crypto.SigType) (address.Address, error) { +func (w *LocalWallet) WalletNew(ctx context.Context, typ types.KeyType) (address.Address, error) { w.lk.Lock() defer w.lk.Unlock() - k, err := GenerateKey(typ) + k, err := key.GenerateKey(typ) if err != nil { return address.Undef, err } @@ -222,7 +263,7 @@ func (w *Wallet) GenerateKey(typ crypto.SigType) (address.Address, error) { return k.Address, nil } -func (w *Wallet) HasKey(addr address.Address) (bool, error) { +func (w *LocalWallet) WalletHas(ctx context.Context, addr address.Address) (bool, error) { k, err := w.findKey(addr) if err != nil { return false, err @@ -230,60 +271,97 @@ func (w *Wallet) HasKey(addr address.Address) (bool, error) { return k != nil, nil } -type Key struct { - types.KeyInfo +func (w *LocalWallet) walletDelete(ctx context.Context, addr address.Address) error { + k, err := w.findKey(addr) - PublicKey []byte - Address address.Address -} - -func NewKey(keyinfo types.KeyInfo) (*Key, error) { - k := &Key{ - KeyInfo: keyinfo, - } - - var err error - k.PublicKey, err = sigs.ToPublic(ActSigType(k.Type), k.PrivateKey) if err != nil { - return nil, err + return xerrors.Errorf("failed to delete key %s : %w", addr, err) + } + if k == nil { + return nil // already not there } - switch k.Type { - case KTSecp256k1: - k.Address, err = address.NewSecp256k1Address(k.PublicKey) - if err != nil { - return nil, xerrors.Errorf("converting Secp256k1 to address: %w", err) + w.lk.Lock() + defer w.lk.Unlock() + + if err := w.keystore.Delete(KTrashPrefix + k.Address.String()); err != nil && !xerrors.Is(err, types.ErrKeyInfoNotFound) { + return xerrors.Errorf("failed to purge trashed key %s: %w", addr, err) + } + + if err := w.keystore.Put(KTrashPrefix+k.Address.String(), k.KeyInfo); err != nil { + return xerrors.Errorf("failed to mark key %s as trashed: %w", addr, err) + } + + if err := w.keystore.Delete(KNamePrefix + k.Address.String()); err != nil { + return xerrors.Errorf("failed to delete key %s: %w", addr, err) + } + + tAddr, err := swapMainnetForTestnetPrefix(addr.String()) + if err != nil { + return xerrors.Errorf("failed to swap prefixes: %w", err) + } + + // TODO: Does this always error in the not-found case? Just ignoring an error return for now. + _ = w.keystore.Delete(KNamePrefix + tAddr) + + delete(w.keys, addr) + + return nil +} + +func (w *LocalWallet) deleteDefault() { + w.lk.Lock() + defer w.lk.Unlock() + if err := w.keystore.Delete(KDefault); err != nil { + if !xerrors.Is(err, types.ErrKeyInfoNotFound) { + log.Warnf("failed to unregister current default key: %s", err) } - case KTBLS: - k.Address, err = address.NewBLSAddress(k.PublicKey) - if err != nil { - return nil, xerrors.Errorf("converting BLS to address: %w", err) + } +} + +func (w *LocalWallet) WalletDelete(ctx context.Context, addr address.Address) error { + if err := w.walletDelete(ctx, addr); err != nil { + return xerrors.Errorf("wallet delete: %w", err) + } + + if def, err := w.GetDefault(); err == nil { + if def == addr { + w.deleteDefault() } - default: - return nil, xerrors.Errorf("unknown key type") } - return k, nil - + return nil } -func kstoreSigType(typ crypto.SigType) string { - switch typ { - case crypto.SigTypeBLS: - return KTBLS - case crypto.SigTypeSecp256k1: - return KTSecp256k1 - default: - return "" +func (w *LocalWallet) Get() api.Wallet { + if w == nil { + return nil } + + return w } -func ActSigType(typ string) crypto.SigType { - switch typ { - case KTBLS: - return crypto.SigTypeBLS - case KTSecp256k1: - return crypto.SigTypeSecp256k1 - default: - return 0 +var _ api.Wallet = &LocalWallet{} + +func swapMainnetForTestnetPrefix(addr string) (string, error) { + aChars := []rune(addr) + prefixRunes := []rune(address.TestnetPrefix) + if len(prefixRunes) != 1 { + return "", xerrors.Errorf("unexpected prefix length: %d", len(prefixRunes)) } + + aChars[0] = prefixRunes[0] + return string(aChars), nil } + +type nilDefault struct{} + +func (n nilDefault) GetDefault() (address.Address, error) { + return address.Undef, nil +} + +func (n nilDefault) SetDefault(a address.Address) error { + return xerrors.Errorf("not supported; local wallet disabled") +} + +var NilDefault nilDefault +var _ Default = NilDefault diff --git a/chain/wallet/wallet_test.go b/chain/wallet/wallet_test.go new file mode 100644 index 000000000..8937c5eb8 --- /dev/null +++ b/chain/wallet/wallet_test.go @@ -0,0 +1,106 @@ +// stm: #unit +package wallet + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/filecoin-project/lotus/chain/types" +) + +func TestWallet(t *testing.T) { + + ctx := context.Background() + + w1, err := NewWallet(NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + //stm: @TOKEN_WALLET_NEW_001 + a1, err := w1.WalletNew(ctx, types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + //stm: @TOKEN_WALLET_HAS_001 + exists, err := w1.WalletHas(ctx, a1) + if err != nil { + t.Fatal(err) + } + + if !exists { + t.Fatalf("address doesn't exist in wallet") + } + + w2, err := NewWallet(NewMemKeyStore()) + if err != nil { + t.Fatal(err) + } + + a2, err := w2.WalletNew(ctx, types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + a3, err := w2.WalletNew(ctx, types.KTSecp256k1) + if err != nil { + t.Fatal(err) + } + + //stm: @TOKEN_WALLET_LIST_001 + addrs, err := w2.WalletList(ctx) + if err != nil { + t.Fatal(err) + } + + if len(addrs) != 2 { + t.Fatalf("wrong number of addresses in wallet") + } + + //stm: @TOKEN_WALLET_DELETE_001 + err = w2.WalletDelete(ctx, a2) + if err != nil { + t.Fatal(err) + } + + //stm: @TOKEN_WALLET_HAS_001 + exists, err = w2.WalletHas(ctx, a2) + if err != nil { + t.Fatal(err) + } + if exists { + t.Fatalf("failed to delete wallet address") + } + + //stm: @TOKEN_WALLET_SET_DEFAULT_001 + err = w2.SetDefault(a3) + if err != nil { + t.Fatal(err) + } + + //stm: @TOKEN_WALLET_DEFAULT_ADDRESS_001 + def, err := w2.GetDefault() + if !assert.Equal(t, a3, def) { + t.Fatal(err) + } + + //stm: @TOKEN_WALLET_EXPORT_001 + keyInfo, err := w2.WalletExport(ctx, a3) + if err != nil { + t.Fatal(err) + } + + //stm: @TOKEN_WALLET_IMPORT_001 + addr, err := w2.WalletImport(ctx, keyInfo) + if err != nil { + t.Fatal(err) + } + + if addr != a3 { + t.Fatalf("imported address doesn't match exported address") + } + +} diff --git a/cli/auth.go b/cli/auth.go index a40e0a328..caea4cb42 100644 --- a/cli/auth.go +++ b/cli/auth.go @@ -3,25 +3,25 @@ package cli import ( "fmt" + "github.com/urfave/cli/v2" "golang.org/x/xerrors" - "gopkg.in/urfave/cli.v2" "github.com/filecoin-project/go-jsonrpc/auth" - "github.com/filecoin-project/lotus/api/apistruct" + "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/node/repo" ) -var authCmd = &cli.Command{ +var AuthCmd = &cli.Command{ Name: "auth", Usage: "Manage RPC permissions", Subcommands: []*cli.Command{ - authCreateAdminToken, - authApiInfoToken, + AuthCreateAdminToken, + AuthApiInfoToken, }, } -var authCreateAdminToken = &cli.Command{ +var AuthCreateAdminToken = &cli.Command{ Name: "create-token", Usage: "Create token", Flags: []cli.Flag{ @@ -46,18 +46,18 @@ var authCreateAdminToken = &cli.Command{ perm := cctx.String("perm") idx := 0 - for i, p := range apistruct.AllPermissions { + for i, p := range api.AllPermissions { if auth.Permission(perm) == p { idx = i + 1 } } if idx == 0 { - return fmt.Errorf("--perm flag has to be one of: %s", apistruct.AllPermissions) + return fmt.Errorf("--perm flag has to be one of: %s", api.AllPermissions) } // slice on [:idx] so for example: 'sign' gives you [read, write, sign] - token, err := napi.AuthNew(ctx, apistruct.AllPermissions[:idx]) + token, err := napi.AuthNew(ctx, api.AllPermissions[:idx]) if err != nil { return err } @@ -69,7 +69,7 @@ var authCreateAdminToken = &cli.Command{ }, } -var authApiInfoToken = &cli.Command{ +var AuthApiInfoToken = &cli.Command{ Name: "api-info", Usage: "Get token with API info required to connect to this node", Flags: []cli.Flag{ @@ -89,30 +89,30 @@ var authApiInfoToken = &cli.Command{ ctx := ReqContext(cctx) if !cctx.IsSet("perm") { - return xerrors.New("--perm flag not set") + return xerrors.New("--perm flag not set, use with one of: read, write, sign, admin") } perm := cctx.String("perm") idx := 0 - for i, p := range apistruct.AllPermissions { + for i, p := range api.AllPermissions { if auth.Permission(perm) == p { idx = i + 1 } } if idx == 0 { - return fmt.Errorf("--perm flag has to be one of: %s", apistruct.AllPermissions) + return fmt.Errorf("--perm flag has to be one of: %s", api.AllPermissions) } // slice on [:idx] so for example: 'sign' gives you [read, write, sign] - token, err := napi.AuthNew(ctx, apistruct.AllPermissions[:idx]) + token, err := napi.AuthNew(ctx, api.AllPermissions[:idx]) if err != nil { return err } ti, ok := cctx.App.Metadata["repoType"] if !ok { - log.Errorf("unknown repo type, are you sure you want to use GetAPI?") + log.Errorf("unknown repo type, are you sure you want to use GetCommonAPI?") ti = repo.FullNode } t, ok := ti.(repo.RepoType) @@ -122,14 +122,13 @@ var authApiInfoToken = &cli.Command{ ainfo, err := GetAPIInfo(cctx, t) if err != nil { - return xerrors.Errorf("could not get API info: %w", err) + return xerrors.Errorf("could not get API info for %s: %w", t, err) } - envVar := envForRepo(t) - // TODO: Log in audit log when it is implemented - fmt.Printf("%s=%s:%s\n", envVar, string(token), ainfo.Addr) + currentEnv, _, _ := t.APIInfoEnvVars() + fmt.Printf("%s=%s:%s\n", currentEnv, string(token), ainfo.Addr) return nil }, } diff --git a/cli/backup.go b/cli/backup.go new file mode 100644 index 000000000..d2d8f25ff --- /dev/null +++ b/cli/backup.go @@ -0,0 +1,137 @@ +package cli + +import ( + "context" + "fmt" + "os" + + logging "github.com/ipfs/go-log/v2" + "github.com/mitchellh/go-homedir" + "github.com/urfave/cli/v2" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-jsonrpc" + + "github.com/filecoin-project/lotus/lib/backupds" + "github.com/filecoin-project/lotus/node/repo" +) + +type BackupAPI interface { + CreateBackup(ctx context.Context, fpath string) error +} + +type BackupApiFn func(ctx *cli.Context) (BackupAPI, jsonrpc.ClientCloser, error) + +func BackupCmd(repoFlag string, rt repo.RepoType, getApi BackupApiFn) *cli.Command { + var offlineBackup = func(cctx *cli.Context) error { + logging.SetLogLevel("badger", "ERROR") // nolint:errcheck + + repoPath := cctx.String(repoFlag) + r, err := repo.NewFS(repoPath) + if err != nil { + return err + } + + ok, err := r.Exists() + if err != nil { + return err + } + if !ok { + return xerrors.Errorf("repo at '%s' is not initialized", cctx.String(repoFlag)) + } + + lr, err := r.LockRO(rt) + if err != nil { + return xerrors.Errorf("locking repo: %w", err) + } + defer lr.Close() // nolint:errcheck + + mds, err := lr.Datastore(context.TODO(), "/metadata") + if err != nil { + return xerrors.Errorf("getting metadata datastore: %w", err) + } + + bds, err := backupds.Wrap(mds, backupds.NoLogdir) + if err != nil { + return err + } + + fpath, err := homedir.Expand(cctx.Args().First()) + if err != nil { + return xerrors.Errorf("expanding file path: %w", err) + } + + if _, err := os.Stat(fpath); !os.IsNotExist(err) { + return xerrors.Errorf("backup file %s already exists. Overwriting it will corrupt the file, please specify another file name", fpath) + } + + out, err := os.OpenFile(fpath, os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return xerrors.Errorf("opening backup file %s: %w", fpath, err) + } + + if err := bds.Backup(cctx.Context, out); err != nil { + if cerr := out.Close(); cerr != nil { + log.Errorw("error closing backup file while handling backup error", "closeErr", cerr, "backupErr", err) + } + return xerrors.Errorf("backup error: %w", err) + } + + if err := out.Close(); err != nil { + return xerrors.Errorf("closing backup file: %w", err) + } + + return nil + } + + var onlineBackup = func(cctx *cli.Context) error { + api, closer, err := getApi(cctx) + if err != nil { + return xerrors.Errorf("getting api: %w (if the node isn't running you can use the --offline flag)", err) + } + defer closer() + + backupPath := cctx.Args().First() + if _, err := os.Stat(backupPath); !os.IsNotExist(err) { + return xerrors.Errorf("backup file %s already exists. Overwriting it will corrupt the file, please specify another file name", backupPath) + } + + err = api.CreateBackup(ReqContext(cctx), backupPath) + if err != nil { + return err + } + + fmt.Println("Success") + + return nil + } + + return &cli.Command{ + Name: "backup", + Usage: "Create node metadata backup", + Description: `The backup command writes a copy of node metadata under the specified path + +Online backups: +For security reasons, the daemon must be have LOTUS_BACKUP_BASE_PATH env var set +to a path where backup files are supposed to be saved, and the path specified in +this command must be within this base path`, + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "offline", + Usage: "create backup without the node running", + }, + }, + ArgsUsage: "[backup file path]", + Action: func(cctx *cli.Context) error { + if cctx.NArg() != 1 { + return IncorrectNumArgs(cctx) + } + + if cctx.Bool("offline") { + return offlineBackup(cctx) + } + + return onlineBackup(cctx) + }, + } +} diff --git a/cli/chain.go b/cli/chain.go index 033c7b234..c0d54fd63 100644 --- a/cli/chain.go +++ b/cli/chain.go @@ -3,57 +3,77 @@ package cli import ( "bytes" "context" + "encoding/base64" + "encoding/hex" "encoding/json" + "errors" "fmt" + "io" "os" "os/exec" "path" + "sort" "strconv" "strings" "time" - "github.com/docker/go-units" + "github.com/ipfs/go-cid" + "github.com/urfave/cli/v2" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + "github.com/filecoin-project/go-address" cborutil "github.com/filecoin-project/go-cbor-util" - "github.com/filecoin-project/specs-actors/actors/abi" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/specs-actors/actors/builtin" "github.com/filecoin-project/specs-actors/actors/builtin/account" "github.com/filecoin-project/specs-actors/actors/builtin/market" "github.com/filecoin-project/specs-actors/actors/builtin/miner" "github.com/filecoin-project/specs-actors/actors/builtin/power" "github.com/filecoin-project/specs-actors/actors/util/adt" - cid "github.com/ipfs/go-cid" - cbg "github.com/whyrusleeping/cbor-gen" - "golang.org/x/xerrors" - "gopkg.in/urfave/cli.v2" "github.com/filecoin-project/lotus/api" + lapi "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/api/v0api" + "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors" - types "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/consensus" + "github.com/filecoin-project/lotus/chain/types" ) -var chainCmd = &cli.Command{ +var ChainCmd = &cli.Command{ Name: "chain", Usage: "Interact with filecoin blockchain", Subcommands: []*cli.Command{ - chainHeadCmd, - chainGetBlock, - chainReadObjCmd, - chainStatObjCmd, - chainGetMsgCmd, - chainSetHeadCmd, - chainListCmd, - chainGetCmd, - chainBisectCmd, - chainExportCmd, - slashConsensusFault, + ChainHeadCmd, + ChainGetBlock, + ChainReadObjCmd, + ChainDeleteObjCmd, + ChainStatObjCmd, + ChainGetMsgCmd, + ChainSetHeadCmd, + ChainListCmd, + ChainGetCmd, + ChainBisectCmd, + ChainExportCmd, + ChainExportRangeCmd, + SlashConsensusFault, + ChainGasPriceCmd, + ChainInspectUsage, + ChainDecodeCmd, + ChainEncodeCmd, + ChainDisputeSetCmd, + ChainPruneCmd, }, } -var chainHeadCmd = &cli.Command{ +var ChainHeadCmd = &cli.Command{ Name: "head", Usage: "Print chain head", Action: func(cctx *cli.Context) error { + afmt := NewAppFmt(cctx.App) + api, closer, err := GetFullNodeAPI(cctx) if err != nil { return err @@ -67,14 +87,15 @@ var chainHeadCmd = &cli.Command{ } for _, c := range head.Cids() { - fmt.Println(c) + afmt.Println(c) } return nil }, } -var chainGetBlock = &cli.Command{ - Name: "getblock", +var ChainGetBlock = &cli.Command{ + Name: "get-block", + Aliases: []string{"getblock"}, Usage: "Get a block and print its details", ArgsUsage: "[blockCid]", Flags: []cli.Flag{ @@ -84,6 +105,8 @@ var chainGetBlock = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { + afmt := NewAppFmt(cctx.App) + api, closer, err := GetFullNodeAPI(cctx) if err != nil { return err @@ -91,8 +114,8 @@ var chainGetBlock = &cli.Command{ defer closer() ctx := ReqContext(cctx) - if !cctx.Args().Present() { - return fmt.Errorf("must pass cid of block to print") + if cctx.NArg() != 1 { + return IncorrectNumArgs(cctx) } bcid, err := cid.Decode(cctx.Args().First()) @@ -111,7 +134,7 @@ var chainGetBlock = &cli.Command{ return err } - fmt.Println(string(out)) + afmt.Println(string(out)) return nil } @@ -128,7 +151,7 @@ var chainGetBlock = &cli.Command{ recpts, err := api.ChainGetParentReceipts(ctx, bcid) if err != nil { log.Warn(err) - //return xerrors.Errorf("failed to get receipts: %w", err) + // return xerrors.Errorf("failed to get receipts: %w", err) } cblock := struct { @@ -150,13 +173,12 @@ var chainGetBlock = &cli.Command{ return err } - fmt.Println(string(out)) + afmt.Println(string(out)) return nil - }, } -func apiMsgCids(in []api.Message) []cid.Cid { +func apiMsgCids(in []lapi.Message) []cid.Cid { out := make([]cid.Cid, len(in)) for k, v := range in { out[k] = v.Cid @@ -164,11 +186,13 @@ func apiMsgCids(in []api.Message) []cid.Cid { return out } -var chainReadObjCmd = &cli.Command{ +var ChainReadObjCmd = &cli.Command{ Name: "read-obj", Usage: "Read the raw bytes of an object", ArgsUsage: "[objectCid]", Action: func(cctx *cli.Context) error { + afmt := NewAppFmt(cctx.App) + api, closer, err := GetFullNodeAPI(cctx) if err != nil { return err @@ -176,6 +200,10 @@ var chainReadObjCmd = &cli.Command{ defer closer() ctx := ReqContext(cctx) + if cctx.NArg() != 1 { + return IncorrectNumArgs(cctx) + } + c, err := cid.Decode(cctx.Args().First()) if err != nil { return fmt.Errorf("failed to parse cid input: %s", err) @@ -186,12 +214,55 @@ var chainReadObjCmd = &cli.Command{ return err } - fmt.Printf("%x\n", obj) + afmt.Printf("%x\n", obj) return nil }, } -var chainStatObjCmd = &cli.Command{ +var ChainDeleteObjCmd = &cli.Command{ + Name: "delete-obj", + Usage: "Delete an object from the chain blockstore", + Description: "WARNING: Removing wrong objects from the chain blockstore may lead to sync issues", + ArgsUsage: "[objectCid]", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "really-do-it", + }, + }, + Action: func(cctx *cli.Context) error { + afmt := NewAppFmt(cctx.App) + + api, closer, err := GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := ReqContext(cctx) + + if cctx.NArg() != 1 { + return IncorrectNumArgs(cctx) + } + + c, err := cid.Decode(cctx.Args().First()) + if err != nil { + return fmt.Errorf("failed to parse cid input: %s", err) + } + + if !cctx.Bool("really-do-it") { + return xerrors.Errorf("pass the --really-do-it flag to proceed") + } + + err = api.ChainDeleteObj(ctx, c) + if err != nil { + return err + } + + afmt.Printf("Obj %s deleted\n", c.String()) + return nil + }, +} + +var ChainStatObjCmd = &cli.Command{ Name: "stat-obj", Usage: "Collect size and ipld link counts for objs", ArgsUsage: "[cid]", @@ -207,6 +278,7 @@ var chainStatObjCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { + afmt := NewAppFmt(cctx.App) api, closer, err := GetFullNodeAPI(cctx) if err != nil { return err @@ -214,6 +286,10 @@ var chainStatObjCmd = &cli.Command{ defer closer() ctx := ReqContext(cctx) + if cctx.NArg() != 1 { + return IncorrectNumArgs(cctx) + } + obj, err := cid.Decode(cctx.Args().First()) if err != nil { return fmt.Errorf("failed to parse cid input: %s", err) @@ -222,6 +298,9 @@ var chainStatObjCmd = &cli.Command{ base := cid.Undef if cctx.IsSet("base") { base, err = cid.Decode(cctx.String("base")) + if err != nil { + return err + } } stats, err := api.ChainStatObj(ctx, obj, base) @@ -229,19 +308,22 @@ var chainStatObjCmd = &cli.Command{ return err } - fmt.Printf("Links: %d\n", stats.Links) - fmt.Printf("Size: %s (%d)\n", units.BytesSize(float64(stats.Size)), stats.Size) + afmt.Printf("Links: %d\n", stats.Links) + afmt.Printf("Size: %s (%d)\n", types.SizeStr(types.NewInt(stats.Size)), stats.Size) return nil }, } -var chainGetMsgCmd = &cli.Command{ +var ChainGetMsgCmd = &cli.Command{ Name: "getmessage", + Aliases: []string{"get-message", "get-msg"}, Usage: "Get and print a message by its cid", ArgsUsage: "[messageCid]", Action: func(cctx *cli.Context) error { - if !cctx.Args().Present() { - return fmt.Errorf("must pass a cid of a message to get") + afmt := NewAppFmt(cctx.App) + + if cctx.NArg() != 1 { + return IncorrectNumArgs(cctx) } api, closer, err := GetFullNodeAPI(cctx) @@ -278,13 +360,14 @@ var chainGetMsgCmd = &cli.Command{ return err } - fmt.Println(string(enc)) + afmt.Println(string(enc)) return nil }, } -var chainSetHeadCmd = &cli.Command{ +var ChainSetHeadCmd = &cli.Command{ Name: "sethead", + Aliases: []string{"set-head"}, Usage: "manually set the local nodes head tipset (Caution: normally only used for recovery)", ArgsUsage: "[tipsetkey]", Flags: []cli.Flag{ @@ -305,6 +388,10 @@ var chainSetHeadCmd = &cli.Command{ defer closer() ctx := ReqContext(cctx) + if !cctx.Bool("genesis") && !cctx.IsSet("epoch") && cctx.NArg() != 1 { + return IncorrectNumArgs(cctx) + } + var ts *types.TipSet if cctx.Bool("genesis") { @@ -314,7 +401,7 @@ var chainSetHeadCmd = &cli.Command{ ts, err = api.ChainGetTipSetByHeight(ctx, abi.ChainEpoch(cctx.Uint64("epoch")), types.EmptyTSK) } if ts == nil { - ts, err = parseTipSet(api, ctx, cctx.Args().Slice()) + ts, err = parseTipSet(ctx, api, cctx.Args().Slice()) } if err != nil { return err @@ -332,38 +419,171 @@ var chainSetHeadCmd = &cli.Command{ }, } -func parseTipSet(api api.FullNode, ctx context.Context, vals []string) (*types.TipSet, error) { - var headers []*types.BlockHeader - for _, c := range vals { - blkc, err := cid.Decode(c) +var ChainInspectUsage = &cli.Command{ + Name: "inspect-usage", + Usage: "Inspect block space usage of a given tipset", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "tipset", + Usage: "specify tipset to view block space usage of", + Value: "@head", + }, + &cli.IntFlag{ + Name: "length", + Usage: "length of chain to inspect block space usage for", + Value: 1, + }, + &cli.IntFlag{ + Name: "num-results", + Usage: "number of results to print per category", + Value: 10, + }, + }, + Action: func(cctx *cli.Context) error { + afmt := NewAppFmt(cctx.App) + api, closer, err := GetFullNodeAPI(cctx) if err != nil { - return nil, err + return err + } + defer closer() + ctx := ReqContext(cctx) + + ts, err := LoadTipSet(ctx, cctx, api) + if err != nil { + return err } - bh, err := api.ChainGetBlock(ctx, blkc) - if err != nil { - return nil, err + cur := ts + var msgs []lapi.Message + for i := 0; i < cctx.Int("length"); i++ { + pmsgs, err := api.ChainGetParentMessages(ctx, cur.Blocks()[0].Cid()) + if err != nil { + return err + } + + msgs = append(msgs, pmsgs...) + + next, err := api.ChainGetTipSet(ctx, cur.Parents()) + if err != nil { + return err + } + + cur = next } - headers = append(headers, bh) - } + codeCache := make(map[address.Address]cid.Cid) - return types.NewTipSet(headers) + lookupActorCode := func(a address.Address) (cid.Cid, error) { + c, ok := codeCache[a] + if ok { + return c, nil + } + + act, err := api.StateGetActor(ctx, a, ts.Key()) + if err != nil { + return cid.Undef, err + } + + codeCache[a] = act.Code + return act.Code, nil + } + + bySender := make(map[string]int64) + byDest := make(map[string]int64) + byMethod := make(map[string]int64) + bySenderC := make(map[string]int64) + byDestC := make(map[string]int64) + byMethodC := make(map[string]int64) + + var sum int64 + for _, m := range msgs { + bySender[m.Message.From.String()] += m.Message.GasLimit + bySenderC[m.Message.From.String()]++ + byDest[m.Message.To.String()] += m.Message.GasLimit + byDestC[m.Message.To.String()]++ + sum += m.Message.GasLimit + + code, err := lookupActorCode(m.Message.To) + if err != nil { + if strings.Contains(err.Error(), types.ErrActorNotFound.Error()) { + continue + } + return err + } + + mm := consensus.NewActorRegistry().Methods[code][m.Message.Method] // TODO: use remote map + + byMethod[mm.Name] += m.Message.GasLimit + byMethodC[mm.Name]++ + } + + type keyGasPair struct { + Key string + Gas int64 + } + + mapToSortedKvs := func(m map[string]int64) []keyGasPair { + var vals []keyGasPair + for k, v := range m { + vals = append(vals, keyGasPair{ + Key: k, + Gas: v, + }) + } + sort.Slice(vals, func(i, j int) bool { + return vals[i].Gas > vals[j].Gas + }) + return vals + } + + senderVals := mapToSortedKvs(bySender) + destVals := mapToSortedKvs(byDest) + methodVals := mapToSortedKvs(byMethod) + + numRes := cctx.Int("num-results") + + afmt.Printf("Total Gas Limit: %d\n", sum) + afmt.Printf("By Sender:\n") + for i := 0; i < numRes && i < len(senderVals); i++ { + sv := senderVals[i] + afmt.Printf("%s\t%0.2f%%\t(total: %d, count: %d)\n", sv.Key, (100*float64(sv.Gas))/float64(sum), sv.Gas, bySenderC[sv.Key]) + } + afmt.Println() + afmt.Printf("By Receiver:\n") + for i := 0; i < numRes && i < len(destVals); i++ { + sv := destVals[i] + afmt.Printf("%s\t%0.2f%%\t(total: %d, count: %d)\n", sv.Key, (100*float64(sv.Gas))/float64(sum), sv.Gas, byDestC[sv.Key]) + } + afmt.Println() + afmt.Printf("By Method:\n") + for i := 0; i < numRes && i < len(methodVals); i++ { + sv := methodVals[i] + afmt.Printf("%s\t%0.2f%%\t(total: %d, count: %d)\n", sv.Key, (100*float64(sv.Gas))/float64(sum), sv.Gas, byMethodC[sv.Key]) + } + + return nil + }, } -var chainListCmd = &cli.Command{ - Name: "list", - Usage: "View a segment of the chain", +var ChainListCmd = &cli.Command{ + Name: "list", + Aliases: []string{"love"}, + Usage: "View a segment of the chain", Flags: []cli.Flag{ - &cli.Uint64Flag{Name: "height"}, + &cli.Uint64Flag{Name: "height", DefaultText: "current head"}, &cli.IntFlag{Name: "count", Value: 30}, &cli.StringFlag{ Name: "format", Usage: "specify the format to print out tipsets", Value: ": (