From e6045bba85fef72352c98c778df4e01f61f9c5e7 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Wed, 17 Mar 2021 02:35:44 -0400 Subject: [PATCH 01/41] Introduce a release issue template --- documentation/misc/RELEASE_ISSUE_TEMPLATE.md | 103 ++++++++ scripts/mkreleaselog | 233 +++++++++++++++++++ 2 files changed, 336 insertions(+) create mode 100644 documentation/misc/RELEASE_ISSUE_TEMPLATE.md create mode 100644 scripts/mkreleaselog diff --git a/documentation/misc/RELEASE_ISSUE_TEMPLATE.md b/documentation/misc/RELEASE_ISSUE_TEMPLATE.md new file mode 100644 index 000000000..76b61bab3 --- /dev/null +++ b/documentation/misc/RELEASE_ISSUE_TEMPLATE.md @@ -0,0 +1,103 @@ +> Release Issue Template + +# Lotus X.Y.Z Release + +We're happy to announce Lotus X.Y.Z... + +## 🗺 What's left for release + + + +## 🚢 Estimated shipping date + + + +## 🔦 Highlights + +< top highlights for this release notes > + +## Changelog + +< changelog generated by bin/mkreleaselog > + +## ✅ Release Checklist + +First steps: + + - [ ] Fork a new branch (`release/vX.Y.Z`) from `master` and make any further release related changes to this branch. If any "non-trivial" changes get added to the release, uncheck all the checkboxes and return to this stage. + - [ ] Prep the changelog + - [ ] Bump the version in `version.go` in the `master` branch to `vX.(Y+1).0-dev`. + - [ ] Follow the RC release process to cut the first RC. + +Prepping an RC: + +- [ ] version string in `build/version.go` has been updated (in the `release/vX.Y.Z` branch). +- [ ] tag commit with `vX.Y.Z-rcN` +- [ ] cut a pre-release on [github](https://github.com/filecoin-project/lotus/releases) + +Testing an RC: + +- [ ] **Stage 0 - Automated Testing** + - Automated Testing + - [ ] CI: Ensure that all tests are passing. + - [ ] Testground tests + +- [ ] **Stage 1 - Internal Testing** + - Upgrade our testnet infra + - [ ] 1 bootstrap node + - [ ] 1 miner + - [ ] Scratch nodes + - [ ] Wait 24 hours + - [ ] Remaining testnet infra + - Upgrade our mainnet infra + - [ ] Subset of development full archival nodes + - [ ]Subset of bootstrappers (1 per region) + - Report on new block validation time + - TODO: What other stats would we care about? + - If anything has worsened significantly, investigate + fix + - Confirm the following work (some combination of Testground / Calibnet / Mainnet / MinerX) + - [ ] Seal a sector + - [ ] make a deal + - [ ] Submit a PoSt + - [ ] (ideally) let a sector go faulty, and see it be recovered + +- [ ] **Stage 2 - Community Dev Testing** + - [ ] Inform MinerX / early testers + - [ ] Ask close ecosystem partners to test their projects with the upgrade + - [ ] Powergate + - TODO: List of partners + +- [ ] **Stage 3 - Community Prod Testing** + - [ ] Documentation + - [ ] Ensure that [CHANGELOG.md](https://github.com/filecoin-project/lotus/blob/master/CHANGELOG.md) is up to date + - [ ] TODO: Other docs checks? + - [ ] Invite the wider community through (link to the release issue): + - [ ] TODO: How should we announce this? + +- [ ] **Stage 4 - Release** + - [ ] Final preparation + - [ ] Verify that version string in [`version.go`](https://github.com/ipfs/go-ipfs/tree/master/version.go) has been updated. + - [ ] Ensure that [CHANGELOG.md](https://github.com/filecoin-project/lotus/blob/master/CHANGELOG.md) is up to date + - [ ] Ensure that [README.md](https://github.com/filecoin-project/lotus/blob/master/README.md) is up to date + - [ ] Merge `release-vX.Y.Z` into the `releases` branch. + - [ ] Tag this merge commit (on the `releases` branch) with `vX.Y.Z`. + - [ ] Cut the release on Github. + - [ ] Final announcements + - [ ] Update network.filecoin.io + - [ ] TODO: What / where else? + +- [ ] **Post-Release** + - [ ] Merge the `releases` branch back into `master`, ignoring the changes to `version.go` (keep the `-dev` version from master). + - [ ] Create an issue using this release issue template for the _next_ release. + +## ❤️ Contributors + +< list generated by bin/mkreleaselog > + +Would you like to contribute to Lotus and don't know how? Well, there are a few places you can get started: + +- TODO + +## ⁉️ Do you have questions? + +TODO \ No newline at end of file diff --git a/scripts/mkreleaselog b/scripts/mkreleaselog new file mode 100644 index 000000000..0588adaee --- /dev/null +++ b/scripts/mkreleaselog @@ -0,0 +1,233 @@ +#!/bin/zsh +#set -x +set -euo pipefail +export GO111MODULE=on +export GOPATH="$(go env GOPATH)" + +alias jq="jq --unbuffered" + +AUTHORS=( + # orgs + ipfs + ipld + libp2p + multiformats + filecoin-project + ipfs-shipyard + + # Authors of personal repos used by go-ipfs that should be mentioned in the + # release notes. + whyrusleeping + Kubuxu + jbenet + Stebalien + marten-seemann + hsanjuan + lucas-clemente + warpfork +) + +[[ -n "${REPO_FILTER+x}" ]] || REPO_FILTER="github.com/(${$(printf "|%s" "${AUTHORS[@]}"):1})" + +[[ -n "${IGNORED_FILES+x}" ]] || IGNORED_FILES='^\(\.gx\|package\.json\|\.travis\.yml\|go.mod\|go\.sum|\.github|\.circleci\)$' + +NL=$'\n' + +ROOT_DIR="$(git rev-parse --show-toplevel)" + +msg() { + echo "$*" >&2 +} + +statlog() { + local rpath="$GOPATH/src/$1" + local start="${2:-}" + local end="${3:-HEAD}" + local mailmap_file="$rpath/.mailmap" + if ! [[ -e "$mailmap_file" ]]; then + mailmap_file="$ROOT_DIR/.mailmap" + fi + + git -C "$rpath" -c mailmap.file="$mailmap_file" log --use-mailmap --shortstat --no-merges --pretty="tformat:%H%n%aN%n%aE" "$start..$end" | while + read hash + read name + read email + read _ # empty line + read changes + do + changed=0 + insertions=0 + deletions=0 + while read count event; do + if [[ "$event" =~ ^file ]]; then + changed=$count + elif [[ "$event" =~ ^insertion ]]; then + insertions=$count + elif [[ "$event" =~ ^deletion ]]; then + deletions=$count + else + echo "unknown event $event" >&2 + exit 1 + fi + done<<<"${changes//,/$NL}" + + jq -n \ + --arg "hash" "$hash" \ + --arg "name" "$name" \ + --arg "email" "$email" \ + --argjson "changed" "$changed" \ + --argjson "insertions" "$insertions" \ + --argjson "deletions" "$deletions" \ + '{Commit: $hash, Author: $name, Email: $email, Files: $changed, Insertions: $insertions, Deletions: $deletions}' + done +} + +# Returns a stream of deps changed between $1 and $2. +dep_changes() { + { + <"$1" + <"$2" + } | jq -s 'JOIN(INDEX(.[0][]; .Path); .[1][]; .Path; {Path: .[0].Path, Old: (.[1] | del(.Path)), New: (.[0] | del(.Path))}) | select(.New.Version != .Old.Version)' +} + +# resolve_commits resolves a git ref for each version. +resolve_commits() { + jq '. + {Ref: (.Version|capture("^((?.*)\\+incompatible|v.*-(0\\.)?[0-9]{14}-(?[a-f0-9]{12})|(?v.*))$") | .ref1 // .ref2 // .ref3)}' +} + +pr_link() { + local repo="$1" + local prnum="$2" + local ghname="${repo##github.com/}" + printf -- "[%s#%s](https://%s/pull/%s)" "$ghname" "$prnum" "$repo" "$prnum" +} + +# Generate a release log for a range of commits in a single repo. +release_log() { + setopt local_options BASH_REMATCH + + local repo="$1" + local start="$2" + local end="${3:-HEAD}" + local dir="$GOPATH/src/$repo" + + local commit pr + git -C "$dir" log \ + --format='tformat:%H %s' \ + --first-parent \ + "$start..$end" | + while read commit subject; do + # Skip gx-only PRs. + git -C "$dir" diff-tree --no-commit-id --name-only "$commit^" "$commit" | + grep -v "${IGNORED_FILES}" >/dev/null || continue + + if [[ "$subject" =~ '^Merge pull request #([0-9]+) from' ]]; then + local prnum="${BASH_REMATCH[2]}" + local desc="$(git -C "$dir" show --summary --format='tformat:%b' "$commit" | head -1)" + printf -- "- %s (%s)\n" "$desc" "$(pr_link "$repo" "$prnum")" + elif [[ "$subject" =~ '\(#([0-9]+)\)$' ]]; then + local prnum="${BASH_REMATCH[2]}" + printf -- "- %s (%s)\n" "$subject" "$(pr_link "$repo" "$prnum")" + else + printf -- "- %s\n" "$subject" + fi + done +} + +indent() { + sed -e 's/^/ /' +} + +mod_deps() { + go list -json -m all | jq 'select(.Version != null)' +} + +ensure() { + local repo="$1" + local commit="$2" + local rpath="$GOPATH/src/$repo" + if [[ ! -d "$rpath" ]]; then + msg "Cloning $repo..." + git clone "http://$repo" "$rpath" >&2 + fi + + if ! git -C "$rpath" rev-parse --verify "$commit" >/dev/null; then + msg "Fetching $repo..." + git -C "$rpath" fetch --all >&2 + fi + + git -C "$rpath" rev-parse --verify "$commit" >/dev/null || return 1 +} + +statsummary() { + jq -s 'group_by(.Author)[] | {Author: .[0].Author, Commits: (. | length), Insertions: (map(.Insertions) | add), Deletions: (map(.Deletions) | add), Files: (map(.Files) | add)}' | + jq '. + {Lines: (.Deletions + .Insertions)}' +} + +recursive_release_log() { + local start="${1:-$(git tag -l | sort -V | grep -v -- '-rc' | grep 'v'| tail -n1)}" + local end="${2:-$(git rev-parse HEAD)}" + local repo_root="$(git rev-parse --show-toplevel)" + local package="$(cd "$repo_root" && go list)" + + if ! [[ "${GOPATH}/${package}" != "${repo_root}" ]]; then + echo "This script requires the target package and all dependencies to live in a GOPATH." + return 1 + fi + + ( + local result=0 + local workspace="$(mktemp -d)" + trap "$(printf 'rm -rf "%q"' "$workspace")" INT TERM EXIT + cd "$workspace" + + echo "Computing old deps..." >&2 + git -C "$repo_root" show "$start:go.mod" >go.mod + mod_deps | resolve_commits | jq -s > old_deps.json + + echo "Computing new deps..." >&2 + git -C "$repo_root" show "$end:go.mod" >go.mod + mod_deps | resolve_commits | jq -s > new_deps.json + + rm -f go.mod go.sum + + printf -- "Generating Changelog for %s %s..%s\n" "$package" "$start" "$end" >&2 + + printf -- "- %s:\n" "$package" + release_log "$package" "$start" "$end" | indent + + + statlog "$package" "$start" "$end" > statlog.json + + dep_changes old_deps.json new_deps.json | + jq --arg filter "$REPO_FILTER" 'select(.Path | match($filter))' | + # Compute changelogs + jq -r '"\(.Path) \(.New.Version) \(.New.Ref) \(.Old.Version) \(.Old.Ref // "")"' | + while read repo new new_ref old old_ref; do + if ! ensure "$repo" "$new_ref"; then + result=1 + local changelog="failed to fetch repo" + else + statlog "$repo" "$old_ref" "$new_ref" >> statlog.json + local changelog="$(release_log "$repo" "$old_ref" "$new_ref")" + fi + if [[ -n "$changelog" ]]; then + printf -- "- %s (%s -> %s):\n" "$repo" "$old" "$new" + echo "$changelog" | indent + fi + done + + echo + echo "Contributors" + echo + + echo "| Contributor | Commits | Lines ± | Files Changed |" + echo "|-------------|---------|---------|---------------|" + statsummary Date: Thu, 8 Apr 2021 00:28:24 -0400 Subject: [PATCH 02/41] Update documentation/misc/RELEASE_ISSUE_TEMPLATE.md Co-authored-by: Jennifer <42981373+jennijuju@users.noreply.github.com> --- documentation/misc/RELEASE_ISSUE_TEMPLATE.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/documentation/misc/RELEASE_ISSUE_TEMPLATE.md b/documentation/misc/RELEASE_ISSUE_TEMPLATE.md index 76b61bab3..af1e4ffc5 100644 --- a/documentation/misc/RELEASE_ISSUE_TEMPLATE.md +++ b/documentation/misc/RELEASE_ISSUE_TEMPLATE.md @@ -72,7 +72,8 @@ Testing an RC: - [ ] Ensure that [CHANGELOG.md](https://github.com/filecoin-project/lotus/blob/master/CHANGELOG.md) is up to date - [ ] TODO: Other docs checks? - [ ] Invite the wider community through (link to the release issue): - - [ ] TODO: How should we announce this? + - [ ] Create a lotus disucssion, example [here](https://github.com/filecoin-project/lotus/discussions/5595) + - [ ] Link the disucssion in #fil-lotus on Filecoin slack - [ ] **Stage 4 - Release** - [ ] Final preparation @@ -100,4 +101,4 @@ Would you like to contribute to Lotus and don't know how? Well, there are a few ## ⁉️ Do you have questions? -TODO \ No newline at end of file +TODO From 536d267cbba925532ad009402656ceed0116bd3f Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Thu, 8 Apr 2021 01:34:53 -0400 Subject: [PATCH 03/41] Update documentation/misc/RELEASE_ISSUE_TEMPLATE.md Co-authored-by: Jennifer <42981373+jennijuju@users.noreply.github.com> --- documentation/misc/RELEASE_ISSUE_TEMPLATE.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/documentation/misc/RELEASE_ISSUE_TEMPLATE.md b/documentation/misc/RELEASE_ISSUE_TEMPLATE.md index af1e4ffc5..f33123e1c 100644 --- a/documentation/misc/RELEASE_ISSUE_TEMPLATE.md +++ b/documentation/misc/RELEASE_ISSUE_TEMPLATE.md @@ -85,7 +85,8 @@ Testing an RC: - [ ] Cut the release on Github. - [ ] Final announcements - [ ] Update network.filecoin.io - - [ ] TODO: What / where else? + - [ ] Add a comment when the final release is tagged, example [here](https://github.com/filecoin-project/lotus/discussions/5905#discussioncomment-571752) + - [ ] repost in #fil-lotus in filecoin slack - [ ] **Post-Release** - [ ] Merge the `releases` branch back into `master`, ignoring the changes to `version.go` (keep the `-dev` version from master). From b6949aa4ba88ba1148ff320ea028d43d8e77d226 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Thu, 8 Apr 2021 01:36:24 -0400 Subject: [PATCH 04/41] Incorporate feedback into release issue template --- documentation/misc/RELEASE_ISSUE_TEMPLATE.md | 47 +++++++++++--------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/documentation/misc/RELEASE_ISSUE_TEMPLATE.md b/documentation/misc/RELEASE_ISSUE_TEMPLATE.md index f33123e1c..d9e02efba 100644 --- a/documentation/misc/RELEASE_ISSUE_TEMPLATE.md +++ b/documentation/misc/RELEASE_ISSUE_TEMPLATE.md @@ -16,24 +16,19 @@ We're happy to announce Lotus X.Y.Z... < top highlights for this release notes > -## Changelog - -< changelog generated by bin/mkreleaselog > - ## ✅ Release Checklist First steps: - [ ] Fork a new branch (`release/vX.Y.Z`) from `master` and make any further release related changes to this branch. If any "non-trivial" changes get added to the release, uncheck all the checkboxes and return to this stage. - - [ ] Prep the changelog + - [ ] Prep the changelog using `bin/mkreleaselog`, and add it to `CHANGELOG.md` - [ ] Bump the version in `version.go` in the `master` branch to `vX.(Y+1).0-dev`. - - [ ] Follow the RC release process to cut the first RC. Prepping an RC: - [ ] version string in `build/version.go` has been updated (in the `release/vX.Y.Z` branch). - [ ] tag commit with `vX.Y.Z-rcN` -- [ ] cut a pre-release on [github](https://github.com/filecoin-project/lotus/releases) +- [ ] cut a pre-release [here](https://github.com/filecoin-project/lotus/releases/new?prerelease=true) Testing an RC: @@ -47,30 +42,42 @@ Testing an RC: - [ ] 1 bootstrap node - [ ] 1 miner - [ ] Scratch nodes - - [ ] Wait 24 hours + - [ ] Wait 24 hours, confirm nodes stay in sync - [ ] Remaining testnet infra - Upgrade our mainnet infra - [ ] Subset of development full archival nodes - - [ ]Subset of bootstrappers (1 per region) - - Report on new block validation time - - TODO: What other stats would we care about? - - If anything has worsened significantly, investigate + fix - - Confirm the following work (some combination of Testground / Calibnet / Mainnet / MinerX) + - [ ] Subset of bootstrappers (1 per region) + - [ ] Confirm nodes stay in sync + - Metrics report + - Block validation time + - Memory / CPU usage + - Number of goroutines + - IPLD block read latency + - Bandwidth usage + - [ ] If anything has worsened significantly, investigate + fix + - Confirm the following work (some combination of Testground / Calibnet / Mainnet / beta users) - [ ] Seal a sector - [ ] make a deal - [ ] Submit a PoSt - - [ ] (ideally) let a sector go faulty, and see it be recovered + - [ ] (optional) let a sector go faulty, and see it be recovered - [ ] **Stage 2 - Community Dev Testing** - - [ ] Inform MinerX / early testers - - [ ] Ask close ecosystem partners to test their projects with the upgrade + - [ ] Inform beta miners (@lotus-early-testers-miner in Filecoin Slack #fil-lotus) + - [ ] Ask close ecosystem partners to test their projects (@lotus-early-testers-eco-dev in Filecoin slack #fil-lotus) - [ ] Powergate - - TODO: List of partners + - [ ] Glif + - [ ] Zondax + - [ ] Stats dashboard + - [ ] Community dashboards + - [ ] Infura + - [ ] Sentinel + - [ ] Protofire + - [ ] Fleek - [ ] **Stage 3 - Community Prod Testing** - [ ] Documentation - [ ] Ensure that [CHANGELOG.md](https://github.com/filecoin-project/lotus/blob/master/CHANGELOG.md) is up to date - - [ ] TODO: Other docs checks? + - [ ] Check if any [config](https://docs.filecoin.io/get-started/lotus/configuration-and-advanced-usage/#configuration) updates are needed - [ ] Invite the wider community through (link to the release issue): - [ ] Create a lotus disucssion, example [here](https://github.com/filecoin-project/lotus/discussions/5595) - [ ] Link the disucssion in #fil-lotus on Filecoin slack @@ -82,7 +89,7 @@ Testing an RC: - [ ] Ensure that [README.md](https://github.com/filecoin-project/lotus/blob/master/README.md) is up to date - [ ] Merge `release-vX.Y.Z` into the `releases` branch. - [ ] Tag this merge commit (on the `releases` branch) with `vX.Y.Z`. - - [ ] Cut the release on Github. + - [ ] Cut the release [here](https://github.com/filecoin-project/lotus/releases/new?prerelease=true&target=releases). - [ ] Final announcements - [ ] Update network.filecoin.io - [ ] Add a comment when the final release is tagged, example [here](https://github.com/filecoin-project/lotus/discussions/5905#discussioncomment-571752) @@ -102,4 +109,4 @@ Would you like to contribute to Lotus and don't know how? Well, there are a few ## ⁉️ Do you have questions? -TODO +Leave a comment [here]() if you have any questions. From 0bc94b554c550f9482070da942fe2492a6bad505 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 13 Apr 2021 12:02:10 +0200 Subject: [PATCH 05/41] Refer to scripts/mkreleaselog --- documentation/misc/RELEASE_ISSUE_TEMPLATE.md | 4 ++-- scripts/mkreleaselog | 0 2 files changed, 2 insertions(+), 2 deletions(-) mode change 100644 => 100755 scripts/mkreleaselog diff --git a/documentation/misc/RELEASE_ISSUE_TEMPLATE.md b/documentation/misc/RELEASE_ISSUE_TEMPLATE.md index d9e02efba..7692058cb 100644 --- a/documentation/misc/RELEASE_ISSUE_TEMPLATE.md +++ b/documentation/misc/RELEASE_ISSUE_TEMPLATE.md @@ -21,7 +21,7 @@ We're happy to announce Lotus X.Y.Z... First steps: - [ ] Fork a new branch (`release/vX.Y.Z`) from `master` and make any further release related changes to this branch. If any "non-trivial" changes get added to the release, uncheck all the checkboxes and return to this stage. - - [ ] Prep the changelog using `bin/mkreleaselog`, and add it to `CHANGELOG.md` + - [ ] Prep the changelog using `scripts/mkreleaselog`, and add it to `CHANGELOG.md` - [ ] Bump the version in `version.go` in the `master` branch to `vX.(Y+1).0-dev`. Prepping an RC: @@ -101,7 +101,7 @@ Testing an RC: ## ❤️ Contributors -< list generated by bin/mkreleaselog > +< list generated by scripts/mkreleaselog > Would you like to contribute to Lotus and don't know how? Well, there are a few places you can get started: diff --git a/scripts/mkreleaselog b/scripts/mkreleaselog old mode 100644 new mode 100755 From 34aa267100584632db98c90ed26822e608a0e78d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 13 Apr 2021 12:20:30 +0200 Subject: [PATCH 06/41] Make mkreleaselog work a bit more --- scripts/mkreleaselog | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/mkreleaselog b/scripts/mkreleaselog index 0588adaee..3c30a3195 100755 --- a/scripts/mkreleaselog +++ b/scripts/mkreleaselog @@ -1,5 +1,5 @@ #!/bin/zsh -#set -x +set -x set -euo pipefail export GO111MODULE=on export GOPATH="$(go env GOPATH)" @@ -139,6 +139,7 @@ indent() { } mod_deps() { + go mod download go list -json -m all | jq 'select(.Version != null)' } @@ -178,9 +179,13 @@ recursive_release_log() { ( local result=0 local workspace="$(mktemp -d)" - trap "$(printf 'rm -rf "%q"' "$workspace")" INT TERM EXIT + #trap "$(printf 'rm -rf "%q"' "$workspace")" INT TERM EXIT cd "$workspace" + mkdir extern + ln -s "$repo_root"/extern/filecoin-ffi extern/filecoin-ffi + ln -s "$repo_root"/extern/test-vectors extern/test-vectors + echo "Computing old deps..." >&2 git -C "$repo_root" show "$start:go.mod" >go.mod mod_deps | resolve_commits | jq -s > old_deps.json From 26d14a6262434d04504da38e857d861d1129d0db Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Wed, 21 Apr 2021 12:55:14 -0700 Subject: [PATCH 07/41] fix: partially support v2+ go modules This patch supports branch-based v2 modules, but not directory-based (i.e., not vN modules with `vN/` directories). --- scripts/mkreleaselog | 46 +++++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/scripts/mkreleaselog b/scripts/mkreleaselog index 3c30a3195..ffc3d935e 100755 --- a/scripts/mkreleaselog +++ b/scripts/mkreleaselog @@ -40,7 +40,8 @@ msg() { } statlog() { - local rpath="$GOPATH/src/$1" + local module="$1" + local rpath="$GOPATH/src/$(strip_version "$module")" local start="${2:-}" local end="${3:-HEAD}" local mailmap_file="$rpath/.mailmap" @@ -106,9 +107,10 @@ pr_link() { release_log() { setopt local_options BASH_REMATCH - local repo="$1" + local module="$1" local start="$2" local end="${3:-HEAD}" + local repo="$(strip_version "$1")" local dir="$GOPATH/src/$repo" local commit pr @@ -139,12 +141,11 @@ indent() { } mod_deps() { - go mod download - go list -json -m all | jq 'select(.Version != null)' + go list -mod=mod -json -m all | jq 'select(.Version != null)' } ensure() { - local repo="$1" + local repo="$(strip_version "$1")" local commit="$2" local rpath="$GOPATH/src/$repo" if [[ ! -d "$rpath" ]]; then @@ -165,21 +166,30 @@ statsummary() { jq '. + {Lines: (.Deletions + .Insertions)}' } +strip_version() { + local repo="$1" + if [[ "$repo" =~ '.*/v[0-9]+$' ]]; then + repo="$(dirname "$repo")" + fi + echo "$repo" +} + recursive_release_log() { local start="${1:-$(git tag -l | sort -V | grep -v -- '-rc' | grep 'v'| tail -n1)}" local end="${2:-$(git rev-parse HEAD)}" local repo_root="$(git rev-parse --show-toplevel)" - local package="$(cd "$repo_root" && go list)" + local module="$(go list -m)" + local dir="$(go list -m -f '{{.Dir}}')" - if ! [[ "${GOPATH}/${package}" != "${repo_root}" ]]; then - echo "This script requires the target package and all dependencies to live in a GOPATH." + if [[ "${GOPATH}/${module}" -ef "${dir}" ]]; then + echo "This script requires the target module and all dependencies to live in a GOPATH." return 1 fi ( local result=0 local workspace="$(mktemp -d)" - #trap "$(printf 'rm -rf "%q"' "$workspace")" INT TERM EXIT + trap "$(printf 'rm -rf "%q"' "$workspace")" INT TERM EXIT cd "$workspace" mkdir extern @@ -196,28 +206,28 @@ recursive_release_log() { rm -f go.mod go.sum - printf -- "Generating Changelog for %s %s..%s\n" "$package" "$start" "$end" >&2 + printf -- "Generating Changelog for %s %s..%s\n" "$module" "$start" "$end" >&2 - printf -- "- %s:\n" "$package" - release_log "$package" "$start" "$end" | indent + printf -- "- %s:\n" "$module" + release_log "$module" "$start" "$end" | indent - statlog "$package" "$start" "$end" > statlog.json + statlog "$module" "$start" "$end" > statlog.json dep_changes old_deps.json new_deps.json | jq --arg filter "$REPO_FILTER" 'select(.Path | match($filter))' | # Compute changelogs jq -r '"\(.Path) \(.New.Version) \(.New.Ref) \(.Old.Version) \(.Old.Ref // "")"' | - while read repo new new_ref old old_ref; do - if ! ensure "$repo" "$new_ref"; then + while read module new new_ref old old_ref; do + if ! ensure "$module" "$new_ref"; then result=1 local changelog="failed to fetch repo" else - statlog "$repo" "$old_ref" "$new_ref" >> statlog.json - local changelog="$(release_log "$repo" "$old_ref" "$new_ref")" + statlog "$module" "$old_ref" "$new_ref" >> statlog.json + local changelog="$(release_log "$module" "$old_ref" "$new_ref")" fi if [[ -n "$changelog" ]]; then - printf -- "- %s (%s -> %s):\n" "$repo" "$old" "$new" + printf -- "- %s (%s -> %s):\n" "$module" "$old" "$new" echo "$changelog" | indent fi done From edc6a63e938d699bc86dcf808e179d908f37a93d Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Sat, 1 May 2021 00:15:45 -0400 Subject: [PATCH 08/41] Add a shed util to count miners by post type --- cmd/lotus-shed/main.go | 1 + cmd/lotus-shed/miner-types.go | 123 ++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 cmd/lotus-shed/miner-types.go diff --git a/cmd/lotus-shed/main.go b/cmd/lotus-shed/main.go index 3aa667459..99f533dde 100644 --- a/cmd/lotus-shed/main.go +++ b/cmd/lotus-shed/main.go @@ -55,6 +55,7 @@ func main() { cidCmd, blockmsgidCmd, signaturesCmd, + minerTypesCmd, } app := &cli.App{ diff --git a/cmd/lotus-shed/miner-types.go b/cmd/lotus-shed/miner-types.go new file mode 100644 index 000000000..bee478195 --- /dev/null +++ b/cmd/lotus-shed/miner-types.go @@ -0,0 +1,123 @@ +package main + +import ( + "context" + "fmt" + "io" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/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/extern/sector-storage/ffiwrapper" + "github.com/filecoin-project/lotus/node/repo" + builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" + "github.com/filecoin-project/specs-actors/v4/actors/util/adt" + "github.com/ipfs/go-cid" + cbor "github.com/ipfs/go-ipld-cbor" + "github.com/urfave/cli/v2" + "golang.org/x/xerrors" +) + +var minerTypesCmd = &cli.Command{ + Name: "miner-types", + Usage: "Scrape state to report on how many miners of each WindowPoStProofType exist", Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "repo", + Value: "~/.lotus", + }, + }, + Action: func(cctx *cli.Context) error { + ctx := context.TODO() + + if !cctx.Args().Present() { + return fmt.Errorf("must pass state root") + } + + sroot, err := cid.Decode(cctx.Args().First()) + if err != nil { + return fmt.Errorf("failed to parse input: %w", err) + } + + fsrepo, err := repo.NewFS(cctx.String("repo")) + if err != nil { + return err + } + + lkrepo, err := fsrepo.Lock(repo.FullNode) + if err != nil { + return err + } + + defer lkrepo.Close() //nolint:errcheck + + bs, err := lkrepo.Blockstore(ctx, repo.UniversalBlockstore) + if err != nil { + return fmt.Errorf("failed to open blockstore: %w", err) + } + + defer func() { + if c, ok := bs.(io.Closer); ok { + if err := c.Close(); err != nil { + log.Warnf("failed to close blockstore: %s", err) + } + } + }() + + mds, err := lkrepo.Datastore(context.Background(), "/metadata") + if err != nil { + return err + } + + cs := store.NewChainStore(bs, bs, mds, vm.Syscalls(ffiwrapper.ProofVerifier), nil) + defer cs.Close() //nolint:errcheck + + cst := cbor.NewCborStore(bs) + store := adt.WrapStore(ctx, cst) + + tree, err := state.LoadStateTree(cst, sroot) + if err != nil { + return err + } + + typeMap := make(map[abi.RegisteredPoStProof]int64) + + err = tree.ForEach(func(addr address.Address, act *types.Actor) error { + if act.Code == builtin4.StorageMinerActorCodeID { + ms, err := miner.Load(store, act) + if err != nil { + return err + } + + mi, err := ms.Info() + if err != nil { + return err + } + + if mi.WindowPoStProofType < abi.RegisteredPoStProof_StackedDrgWindow32GiBV1 { + fmt.Println(addr) + } + + c, f := typeMap[mi.WindowPoStProofType] + if !f { + typeMap[mi.WindowPoStProofType] = 1 + } else { + typeMap[mi.WindowPoStProofType] = c + 1 + } + } + return nil + }) + if err != nil { + return xerrors.Errorf("failed to loop over actors: %w", err) + } + + for k, v := range typeMap { + fmt.Println("Type:", k, " Count: ", v) + } + + return nil + }, +} From b14c467fb485e4ee7f5500dddeb85dc724331e7d Mon Sep 17 00:00:00 2001 From: Jennifer <42981373+jennijuju@users.noreply.github.com> Date: Wed, 5 May 2021 14:28:48 -0400 Subject: [PATCH 09/41] Update documentation/misc/RELEASE_ISSUE_TEMPLATE.md --- documentation/misc/RELEASE_ISSUE_TEMPLATE.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/documentation/misc/RELEASE_ISSUE_TEMPLATE.md b/documentation/misc/RELEASE_ISSUE_TEMPLATE.md index 7692058cb..36eecbbee 100644 --- a/documentation/misc/RELEASE_ISSUE_TEMPLATE.md +++ b/documentation/misc/RELEASE_ISSUE_TEMPLATE.md @@ -4,7 +4,9 @@ We're happy to announce Lotus X.Y.Z... -## 🗺 What's left for release +## 🗺 Must-dos for the release + +## 🌟 Nice-to-haves for the release From 72134ff458534d1740f3e27a8fc4b54b0f28df6b Mon Sep 17 00:00:00 2001 From: Peter Rabbitson Date: Tue, 4 May 2021 18:18:26 +0200 Subject: [PATCH 10/41] Add a mining-heartbeat INFO line at every epoch --- chain/gen/gen.go | 9 +++++++++ miner/miner.go | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/chain/gen/gen.go b/chain/gen/gen.go index d06c755fa..a6bf1b2b1 100644 --- a/chain/gen/gen.go +++ b/chain/gen/gen.go @@ -3,6 +3,7 @@ package gen import ( "bytes" "context" + "encoding/base64" "fmt" "io" "io/ioutil" @@ -610,6 +611,8 @@ func (wpp *wppProvider) ComputeProof(context.Context, []proof2.SectorInfo, abi.P return ValidWpostForTesting, nil } +var b64 = base64.URLEncoding.WithPadding(base64.NoPadding) + func IsRoundWinner(ctx context.Context, ts *types.TipSet, round abi.ChainEpoch, miner address.Address, brand types.BeaconEntry, mbi *api.MiningBaseInfo, a MiningCheckAPI) (*types.ElectionProof, error) { @@ -631,6 +634,12 @@ func IsRoundWinner(ctx context.Context, ts *types.TipSet, round abi.ChainEpoch, ep := &types.ElectionProof{VRFProof: vrfout} j := ep.ComputeWinCount(mbi.MinerPower, mbi.NetworkPower) ep.WinCount = j + + log.Infow("completed winAttemptVRF", + "VRFb64", b64.EncodeToString(vrfout), + "winCount", j, + ) + if j < 1 { return nil, nil } diff --git a/miner/miner.go b/miner/miner.go index e7e012d7c..15adfb993 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -13,6 +13,7 @@ import ( proof2 "github.com/filecoin-project/specs-actors/v2/actors/runtime/proof" + "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/gen/slashfilter" "github.com/filecoin-project/go-address" @@ -425,8 +426,37 @@ func (m *Miner) mineOne(ctx context.Context, base *MiningBase) (*types.BlockMsg, return nil, xerrors.Errorf("failed to get mining base info: %w", err) } if mbi == nil { + log.Warnf("mineOne: unexpectedly nil MiningBaseInfo for round %d, off tipset %d/%s", round, base.TipSet.Height(), base.TipSet.Key().String()) return nil, nil } + + // always write out a log from this point out + var winner *types.ElectionProof + lookBack := make(chan int64) + + // figure this out in the background, instead of slowing down the main loop + go func() { + lb := int64(-1) + if netVer, err := m.api.StateNetworkVersion(ctx, base.TipSet.Key()); err == nil { + lb = int64(policy.GetWinningPoStSectorSetLookback(netVer)) + } + lookBack <- lb + }() + + defer func() { + + log.Infow( + "completed mineOne", + "forRound", int64(round), + "baseEpoch", int64(base.TipSet.Height()), + "lookbackEpochs", <-lookBack, + "networkPowerAtLookback", mbi.NetworkPower.String(), + "minerPowerAtLookback", mbi.MinerPower.String(), + "isEligible", mbi.EligibleForMining, + "isWinner", (winner != nil), + ) + }() + if !mbi.EligibleForMining { // slashed or just have no power yet return nil, nil @@ -453,7 +483,7 @@ func (m *Miner) mineOne(ctx context.Context, base *MiningBase) (*types.BlockMsg, return nil, xerrors.Errorf("scratching ticket failed: %w", err) } - winner, err := gen.IsRoundWinner(ctx, base.TipSet, round, m.address, rbase, mbi, m.api) + winner, err = gen.IsRoundWinner(ctx, base.TipSet, round, m.address, rbase, mbi, m.api) if err != nil { return nil, xerrors.Errorf("failed to check if we win next round: %w", err) } @@ -505,7 +535,7 @@ func (m *Miner) mineOne(ctx context.Context, base *MiningBase) (*types.BlockMsg, for i, header := range base.TipSet.Blocks() { parentMiners[i] = header.Miner } - log.Infow("mined new block", "cid", b.Cid(), "height", b.Header.Height, "miner", b.Header.Miner, "parents", parentMiners, "took", dur) + log.Infow("mined new block", "cid", b.Cid(), "height", int64(b.Header.Height), "miner", b.Header.Miner, "parents", parentMiners, "parentTipset", base.TipSet.Key().String(), "took", dur) if dur > time.Second*time.Duration(build.BlockDelaySecs) { log.Warnw("CAUTION: block production took longer than the block delay. Your computer may not be fast enough to keep up", "tMinerBaseInfo ", tMBI.Sub(start), From b1db3fee78ca1ab9f6d6c000ae6000317e178cbb Mon Sep 17 00:00:00 2001 From: Peter Rabbitson Date: Wed, 5 May 2021 20:39:16 +0200 Subject: [PATCH 11/41] Log more ComputeVRF() inputs as per Why's request --- chain/gen/gen.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/chain/gen/gen.go b/chain/gen/gen.go index a6bf1b2b1..b4e04424c 100644 --- a/chain/gen/gen.go +++ b/chain/gen/gen.go @@ -636,7 +636,10 @@ func IsRoundWinner(ctx context.Context, ts *types.TipSet, round abi.ChainEpoch, ep.WinCount = j log.Infow("completed winAttemptVRF", - "VRFb64", b64.EncodeToString(vrfout), + "beaconRound", brand.Round, + "beaconDataB64", b64.EncodeToString(brand.Data), + "electionRandB64", b64.EncodeToString(electionRand), + "vrfB64", b64.EncodeToString(vrfout), "winCount", j, ) From de60229957e038c2a20c2463165a624177c38104 Mon Sep 17 00:00:00 2001 From: Peter Rabbitson Date: Wed, 5 May 2021 22:54:01 +0200 Subject: [PATCH 12/41] mining lookback is effectively a constant - make it so --- chain/actors/policy/policy.go | 1 + miner/miner.go | 14 +------------- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/chain/actors/policy/policy.go b/chain/actors/policy/policy.go index f210b8d94..07f489b11 100644 --- a/chain/actors/policy/policy.go +++ b/chain/actors/policy/policy.go @@ -187,6 +187,7 @@ func GetWinningPoStSectorSetLookback(nwVer network.Version) abi.ChainEpoch { return 10 } + // NOTE: if this ever changes, adjust it in a (*Miner).mineOne() logline as well return ChainFinality } diff --git a/miner/miner.go b/miner/miner.go index 15adfb993..a77e1c18b 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -432,24 +432,12 @@ func (m *Miner) mineOne(ctx context.Context, base *MiningBase) (*types.BlockMsg, // always write out a log from this point out var winner *types.ElectionProof - lookBack := make(chan int64) - - // figure this out in the background, instead of slowing down the main loop - go func() { - lb := int64(-1) - if netVer, err := m.api.StateNetworkVersion(ctx, base.TipSet.Key()); err == nil { - lb = int64(policy.GetWinningPoStSectorSetLookback(netVer)) - } - lookBack <- lb - }() - defer func() { - log.Infow( "completed mineOne", "forRound", int64(round), "baseEpoch", int64(base.TipSet.Height()), - "lookbackEpochs", <-lookBack, + "lookbackEpochs", int64(policy.ChainFinality), // hardcoded as it is unlikely to change again: https://github.com/filecoin-project/lotus/blob/v1.8.0/chain/actors/policy/policy.go#L180-L186 "networkPowerAtLookback", mbi.NetworkPower.String(), "minerPowerAtLookback", mbi.MinerPower.String(), "isEligible", mbi.EligibleForMining, From e1185dd5b5397961ef647cfc6aaf9a7685d99889 Mon Sep 17 00:00:00 2001 From: ZenGround0 Date: Thu, 22 Apr 2021 23:03:53 -0400 Subject: [PATCH 13/41] cron-wc --- cmd/lotus-shed/cron-count.go | 99 ++++++++++++++++++++++++++++++++++++ cmd/lotus-shed/main.go | 1 + 2 files changed, 100 insertions(+) create mode 100644 cmd/lotus-shed/cron-count.go diff --git a/cmd/lotus-shed/cron-count.go b/cmd/lotus-shed/cron-count.go new file mode 100644 index 000000000..91e7ab313 --- /dev/null +++ b/cmd/lotus-shed/cron-count.go @@ -0,0 +1,99 @@ +package main + +import ( + "fmt" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/lotus/build" + lcli "github.com/filecoin-project/lotus/cli" + "github.com/urfave/cli/v2" + "golang.org/x/xerrors" +) + +var cronWcCmd = &cli.Command{ + Name: "cron-wc", + Description: "cron stats", + Subcommands: []*cli.Command{ + minerDeadlineCronCountCmd, + }, +} + +var minerDeadlineCronCountCmd = &cli.Command{ + Name: "deadline", + Description: "list all addresses of miners with active deadline crons", + Action: func(c *cli.Context) error { + return countDeadlineCrons(c) + }, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "tipset", + Usage: "specify tipset state to search on (pass comma separated array of cids)", + }, + }, +} + +func findDeadlineCrons(c *cli.Context) (map[address.Address]struct{}, error) { + api, acloser, err := lcli.GetFullNodeAPI(c) + if err != nil { + return nil, err + } + defer acloser() + ctx := lcli.ReqContext(c) + + ts, err := lcli.LoadTipSet(ctx, c, api) + if err != nil { + return nil, err + } + if ts == nil { + ts, err = api.ChainHead(ctx) + if err != nil { + return nil, err + } + } + + mAddrs, err := api.StateListMiners(ctx, ts.Key()) + if err != nil { + return nil, err + } + activeMiners := make(map[address.Address]struct{}) + for _, mAddr := range mAddrs { + // All miners have active cron before v4. + // v4 upgrade epoch is last epoch running v3 epoch and api.StateReadState reads + // parent state, so v4 state isn't read until upgrade epoch + 2 + if ts.Height() <= build.UpgradeActorsV4Height+1 { + activeMiners[mAddr] = struct{}{} + continue + } + st, err := api.StateReadState(ctx, mAddr, ts.Key()) + if err != nil { + return nil, err + } + minerState, ok := st.State.(map[string]interface{}) + if !ok { + return nil, xerrors.Errorf("internal error: failed to cast miner state to expected map type") + } + + activeDlineIface, ok := minerState["DeadlineCronActive"] + if !ok { + return nil, xerrors.Errorf("miner %s had no deadline state, is this a v3 state root?", mAddr) + } + active := activeDlineIface.(bool) + if active { + activeMiners[mAddr] = struct{}{} + } + } + + return activeMiners, nil +} + +func countDeadlineCrons(c *cli.Context) error { + activeMiners, err := findDeadlineCrons(c) + if err != nil { + return err + } + for addr, _ := range activeMiners { + fmt.Printf("%s\n", addr) + } + + return nil +} diff --git a/cmd/lotus-shed/main.go b/cmd/lotus-shed/main.go index 3aa667459..daf878c73 100644 --- a/cmd/lotus-shed/main.go +++ b/cmd/lotus-shed/main.go @@ -20,6 +20,7 @@ func main() { base32Cmd, base16Cmd, bitFieldCmd, + cronWcCmd, frozenMinersCmd, keyinfoCmd, jwtCmd, From 5351db9da723adafc02e8dfd861e6153ad74b8c6 Mon Sep 17 00:00:00 2001 From: ZenGround0 Date: Thu, 6 May 2021 10:17:25 -0400 Subject: [PATCH 14/41] Lint --- cmd/lotus-shed/cron-count.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/lotus-shed/cron-count.go b/cmd/lotus-shed/cron-count.go index 91e7ab313..79fd6ec42 100644 --- a/cmd/lotus-shed/cron-count.go +++ b/cmd/lotus-shed/cron-count.go @@ -91,7 +91,7 @@ func countDeadlineCrons(c *cli.Context) error { if err != nil { return err } - for addr, _ := range activeMiners { + for addr := range activeMiners { fmt.Printf("%s\n", addr) } From 25b4ffd09b4e7a4bd478a558c6a3b8b48af4a3b8 Mon Sep 17 00:00:00 2001 From: Jennifer <42981373+jennijuju@users.noreply.github.com> Date: Fri, 7 May 2021 00:06:46 -0400 Subject: [PATCH 15/41] Update documentation/misc/RELEASE_ISSUE_TEMPLATE.md --- documentation/misc/RELEASE_ISSUE_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/misc/RELEASE_ISSUE_TEMPLATE.md b/documentation/misc/RELEASE_ISSUE_TEMPLATE.md index 36eecbbee..fcde01876 100644 --- a/documentation/misc/RELEASE_ISSUE_TEMPLATE.md +++ b/documentation/misc/RELEASE_ISSUE_TEMPLATE.md @@ -93,7 +93,7 @@ Testing an RC: - [ ] Tag this merge commit (on the `releases` branch) with `vX.Y.Z`. - [ ] Cut the release [here](https://github.com/filecoin-project/lotus/releases/new?prerelease=true&target=releases). - [ ] Final announcements - - [ ] Update network.filecoin.io + - [ ] Update network.filecoin.io for mainnet, calib and nerpa. - [ ] Add a comment when the final release is tagged, example [here](https://github.com/filecoin-project/lotus/discussions/5905#discussioncomment-571752) - [ ] repost in #fil-lotus in filecoin slack From fac9994c02e34ca1f59787706b9b70389e8852dc Mon Sep 17 00:00:00 2001 From: Jennifer <42981373+jennijuju@users.noreply.github.com> Date: Fri, 7 May 2021 00:08:23 -0400 Subject: [PATCH 16/41] Update documentation/misc/RELEASE_ISSUE_TEMPLATE.md --- documentation/misc/RELEASE_ISSUE_TEMPLATE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/documentation/misc/RELEASE_ISSUE_TEMPLATE.md b/documentation/misc/RELEASE_ISSUE_TEMPLATE.md index fcde01876..12618bd4c 100644 --- a/documentation/misc/RELEASE_ISSUE_TEMPLATE.md +++ b/documentation/misc/RELEASE_ISSUE_TEMPLATE.md @@ -96,6 +96,7 @@ Testing an RC: - [ ] Update network.filecoin.io for mainnet, calib and nerpa. - [ ] Add a comment when the final release is tagged, example [here](https://github.com/filecoin-project/lotus/discussions/5905#discussioncomment-571752) - [ ] repost in #fil-lotus in filecoin slack + - [ ] Inform node provides (Protofire, Digital Ocean..) - [ ] **Post-Release** - [ ] Merge the `releases` branch back into `master`, ignoring the changes to `version.go` (keep the `-dev` version from master). From e3948c851fe2fa19f7b39c24bb3aa279500b723b Mon Sep 17 00:00:00 2001 From: Jennifer <42981373+jennijuju@users.noreply.github.com> Date: Fri, 7 May 2021 00:09:11 -0400 Subject: [PATCH 17/41] Update documentation/misc/RELEASE_ISSUE_TEMPLATE.md --- documentation/misc/RELEASE_ISSUE_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/misc/RELEASE_ISSUE_TEMPLATE.md b/documentation/misc/RELEASE_ISSUE_TEMPLATE.md index 12618bd4c..df5e8d1c5 100644 --- a/documentation/misc/RELEASE_ISSUE_TEMPLATE.md +++ b/documentation/misc/RELEASE_ISSUE_TEMPLATE.md @@ -63,7 +63,7 @@ Testing an RC: - [ ] Submit a PoSt - [ ] (optional) let a sector go faulty, and see it be recovered -- [ ] **Stage 2 - Community Dev Testing** +- [ ] **Stage 2 - Community Testing** - [ ] Inform beta miners (@lotus-early-testers-miner in Filecoin Slack #fil-lotus) - [ ] Ask close ecosystem partners to test their projects (@lotus-early-testers-eco-dev in Filecoin slack #fil-lotus) - [ ] Powergate From ed61642b3ab68755d485ed04559514db5cc2cae5 Mon Sep 17 00:00:00 2001 From: vyzo Date: Wed, 10 Mar 2021 19:22:35 +0200 Subject: [PATCH 18/41] implement NodeStatus API Signed-off-by: Jakub Sztandera --- api/api_full.go | 5 ++ api/mocks/mock_full.go | 15 ++++ api/proxy_gen.go | 10 +++ api/types.go | 21 +++++ build/openrpc/full.json.gz | Bin 22483 -> 22681 bytes build/openrpc/miner.json.gz | Bin 7848 -> 7849 bytes build/openrpc/worker.json.gz | Bin 2577 -> 2579 bytes cli/cmd.go | 1 + cli/status.go | 60 ++++++++++++++ documentation/en/api-v1-unstable-methods.md | 36 +++++++++ documentation/en/cli-lotus.md | 19 +++++ node/impl/full.go | 82 +++++++++++++++++++- node/modules/lp2p/pubsub.go | 19 +++-- 13 files changed, 262 insertions(+), 6 deletions(-) create mode 100644 cli/status.go diff --git a/api/api_full.go b/api/api_full.go index a90b0c89f..8631ec4b7 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -664,6 +664,11 @@ type FullNode interface { 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 + // 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 diff --git a/api/mocks/mock_full.go b/api/mocks/mock_full.go index 4336a56f9..ede04fa20 100644 --- a/api/mocks/mock_full.go +++ b/api/mocks/mock_full.go @@ -1692,6 +1692,21 @@ func (mr *MockFullNodeMockRecorder) NetPubsubScores(arg0 interface{}) *gomock.Ca return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetPubsubScores", reflect.TypeOf((*MockFullNode)(nil).NetPubsubScores), 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() diff --git a/api/proxy_gen.go b/api/proxy_gen.go index bfaaade94..b743a2ddb 100644 --- a/api/proxy_gen.go +++ b/api/proxy_gen.go @@ -287,6 +287,8 @@ type FullNodeStruct struct { MsigSwapPropose func(p0 context.Context, p1 address.Address, p2 address.Address, p3 address.Address, p4 address.Address) (cid.Cid, error) `perm:"sign"` + 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"` @@ -1715,6 +1717,14 @@ func (s *FullNodeStub) MsigSwapPropose(p0 context.Context, p1 address.Address, p return *new(cid.Cid), xerrors.New("method not supported") } +func (s *FullNodeStruct) NodeStatus(p0 context.Context, p1 bool) (NodeStatus, error) { + return s.Internal.NodeStatus(p0, p1) +} + +func (s *FullNodeStub) NodeStatus(p0 context.Context, p1 bool) (NodeStatus, error) { + return *new(NodeStatus), xerrors.New("method not supported") +} + func (s *FullNodeStruct) PaychAllocateLane(p0 context.Context, p1 address.Address) (uint64, error) { return s.Internal.PaychAllocateLane(p0, p1) } diff --git a/api/types.go b/api/types.go index 6417ce756..bbcfa5c20 100644 --- a/api/types.go +++ b/api/types.go @@ -116,3 +116,24 @@ type ConnMgrInfo struct { 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 +} diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index df2b87f1330c334cb1a830105c124265d0b69486..7763e4af8ba241fea0cb4d20a1fc61790c2646f4 100644 GIT binary patch delta 20255 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 diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index a3407e7caa660f07c67af1ac5ff9477f29bdd95e..501b238282ef3d11fb1e356a867206a563054b2b 100644 GIT binary patch delta 6894 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 z_catK≫HXDx^sAZ>UX73c+MO9^WL|RLlm8Q1X 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 diff --git a/cli/cmd.go b/cli/cmd.go index 6ecd236f4..09bf5c461 100644 --- a/cli/cmd.go +++ b/cli/cmd.go @@ -81,6 +81,7 @@ var Commands = []*cli.Command{ WithCategory("developer", FetchParamCmd), WithCategory("network", NetCmd), WithCategory("network", SyncCmd), + WithCategory("status", StatusCmd), PprofCmd, VersionCmd, } diff --git a/cli/status.go b/cli/status.go new file mode 100644 index 000000000..75f91196a --- /dev/null +++ b/cli/status.go @@ -0,0 +1,60 @@ +package cli + +import ( + "fmt" + + "github.com/urfave/cli/v2" + + "github.com/filecoin-project/lotus/build" +) + +var StatusCmd = &cli.Command{ + Name: "status", + Usage: "Check node status", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "chain", + Usage: "include chain health status", + }, + }, + + Action: func(cctx *cli.Context) error { + apic, closer, err := GetFullNodeAPIV1(cctx) + if err != nil { + return err + } + defer closer() + ctx := ReqContext(cctx) + + inclChainStatus := cctx.Bool("chain") + + status, err := apic.NodeStatus(ctx, inclChainStatus) + if err != nil { + return err + } + + fmt.Printf("Sync Epoch: %d\n", status.SyncStatus.Epoch) + fmt.Printf("Epochs Behind: %d\n", status.SyncStatus.Behind) + fmt.Printf("Peers to Publish Messages: %d\n", status.PeerStatus.PeersToPublishMsgs) + fmt.Printf("Peers to Publish Blocks: %d\n", status.PeerStatus.PeersToPublishBlocks) + + if inclChainStatus && status.SyncStatus.Epoch > uint64(build.Finality) { + var ok100, okFin string + if status.ChainStatus.BlocksPerTipsetLast100 >= 4.75 { + ok100 = "[OK]" + } else { + ok100 = "[UNHEALTHY]" + } + if status.ChainStatus.BlocksPerTipsetLastFinality >= 4.75 { + okFin = "[OK]" + } else { + okFin = "[UNHEALTHY]" + } + + fmt.Printf("Blocks per TipSet in last 100 epochs: %f %s\n", status.ChainStatus.BlocksPerTipsetLast100, ok100) + fmt.Printf("Blocks per TipSet in last finality: %f %s\n", status.ChainStatus.BlocksPerTipsetLastFinality, okFin) + } + + return nil + }, +} diff --git a/documentation/en/api-v1-unstable-methods.md b/documentation/en/api-v1-unstable-methods.md index 261c0d51b..a0ee6fcf3 100644 --- a/documentation/en/api-v1-unstable-methods.md +++ b/documentation/en/api-v1-unstable-methods.md @@ -126,6 +126,8 @@ * [NetPeerInfo](#NetPeerInfo) * [NetPeers](#NetPeers) * [NetPubsubScores](#NetPubsubScores) +* [Node](#Node) + * [NodeStatus](#NodeStatus) * [Paych](#Paych) * [PaychAllocateLane](#PaychAllocateLane) * [PaychAvailableFunds](#PaychAvailableFunds) @@ -3000,6 +3002,40 @@ Inputs: `null` Response: `null` +## Node +These methods are general node management and status commands + + +### NodeStatus +There are not yet any comments for this method. + +Perms: read + +Inputs: +```json +[ + true +] +``` + +Response: +```json +{ + "SyncStatus": { + "Epoch": 42, + "Behind": 42 + }, + "PeerStatus": { + "PeersToPublishMsgs": 123, + "PeersToPublishBlocks": 123 + }, + "ChainStatus": { + "BlocksPerTipsetLast100": 12.3, + "BlocksPerTipsetLastFinality": 12.3 + } +} +``` + ## Paych The Paych methods are for interacting with and managing payment channels diff --git a/documentation/en/cli-lotus.md b/documentation/en/cli-lotus.md index ebd3300f0..a3ac4d487 100644 --- a/documentation/en/cli-lotus.md +++ b/documentation/en/cli-lotus.md @@ -31,6 +31,8 @@ COMMANDS: NETWORK: net Manage P2P Network sync Inspect or interact with the chain syncer + STATUS: + status Check node status GLOBAL OPTIONS: --help, -h show help (default: false) @@ -2671,3 +2673,20 @@ OPTIONS: --help, -h show help (default: false) ``` + +## lotus status +``` +NAME: + lotus status - Check node status + +USAGE: + lotus status [command options] [arguments...] + +CATEGORY: + STATUS + +OPTIONS: + --chain include chain health status (default: false) + --help, -h show help (default: false) + +``` diff --git a/node/impl/full.go b/node/impl/full.go index add40917c..50fd09cdf 100644 --- a/node/impl/full.go +++ b/node/impl/full.go @@ -2,16 +2,21 @@ package impl import ( "context" + "time" + + "github.com/libp2p/go-libp2p-core/peer" logging "github.com/ipfs/go-log/v2" "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/node/impl/client" "github.com/filecoin-project/lotus/node/impl/common" "github.com/filecoin-project/lotus/node/impl/full" "github.com/filecoin-project/lotus/node/impl/market" "github.com/filecoin-project/lotus/node/impl/paych" "github.com/filecoin-project/lotus/node/modules/dtypes" + "github.com/filecoin-project/lotus/node/modules/lp2p" ) var log = logging.Logger("node") @@ -30,11 +35,86 @@ type FullNodeAPI struct { full.SyncAPI full.BeaconAPI - DS dtypes.MetadataDS + DS dtypes.MetadataDS + NetworkName dtypes.NetworkName } func (n *FullNodeAPI) CreateBackup(ctx context.Context, fpath string) error { return backup(n.DS, fpath) } +func (n *FullNodeAPI) NodeStatus(ctx context.Context, inclChainStatus bool) (status api.NodeStatus, err error) { + curTs, err := n.ChainHead(ctx) + if err != nil { + return status, err + } + + status.SyncStatus.Epoch = uint64(curTs.Height()) + timestamp := time.Unix(int64(curTs.MinTimestamp()), 0) + delta := time.Since(timestamp).Seconds() + status.SyncStatus.Behind = uint64(delta / 30) + + // get peers in the messages and blocks topics + peersMsgs := make(map[peer.ID]struct{}) + peersBlocks := make(map[peer.ID]struct{}) + + for _, p := range n.PubSub.ListPeers(build.MessagesTopic(n.NetworkName)) { + peersMsgs[p] = struct{}{} + } + + for _, p := range n.PubSub.ListPeers(build.BlocksTopic(n.NetworkName)) { + peersBlocks[p] = struct{}{} + } + + // get scores for all connected and recent peers + scores, err := n.NetPubsubScores(ctx) + if err != nil { + return status, err + } + + for _, score := range scores { + if score.Score.Score > lp2p.PublishScoreThreshold { + _, inMsgs := peersMsgs[score.ID] + if inMsgs { + status.PeerStatus.PeersToPublishMsgs++ + } + + _, inBlocks := peersBlocks[score.ID] + if inBlocks { + status.PeerStatus.PeersToPublishBlocks++ + } + } + } + + if inclChainStatus && status.SyncStatus.Epoch > uint64(build.Finality) { + blockCnt := 0 + ts := curTs + + for i := 0; i < 100; i++ { + blockCnt += len(ts.Blocks()) + tsk := ts.Parents() + ts, err = n.ChainGetTipSet(ctx, tsk) + if err != nil { + return status, err + } + } + + status.ChainStatus.BlocksPerTipsetLast100 = float64(blockCnt) / 100 + + for i := 100; i < int(build.Finality); i++ { + blockCnt += len(ts.Blocks()) + tsk := ts.Parents() + ts, err = n.ChainGetTipSet(ctx, tsk) + if err != nil { + return status, err + } + } + + status.ChainStatus.BlocksPerTipsetLastFinality = float64(blockCnt) / float64(build.Finality) + + } + + return status, nil +} + var _ api.FullNode = &FullNodeAPI{} diff --git a/node/modules/lp2p/pubsub.go b/node/modules/lp2p/pubsub.go index 748167d95..32b85daf3 100644 --- a/node/modules/lp2p/pubsub.go +++ b/node/modules/lp2p/pubsub.go @@ -36,6 +36,15 @@ func init() { pubsub.GossipSubHistoryLength = 10 pubsub.GossipSubGossipFactor = 0.1 } + +const ( + GossipScoreThreshold = -500 + PublishScoreThreshold = -1000 + GraylistScoreThreshold = -2500 + AcceptPXScoreThreshold = 1000 + OpportunisticGraftScoreThreshold = 3.5 +) + func ScoreKeeper() *dtypes.ScoreKeeper { return new(dtypes.ScoreKeeper) } @@ -256,11 +265,11 @@ func GossipSub(in GossipIn) (service *pubsub.PubSub, err error) { Topics: topicParams, }, &pubsub.PeerScoreThresholds{ - GossipThreshold: -500, - PublishThreshold: -1000, - GraylistThreshold: -2500, - AcceptPXThreshold: 1000, - OpportunisticGraftThreshold: 3.5, + GossipThreshold: GossipScoreThreshold, + PublishThreshold: PublishScoreThreshold, + GraylistThreshold: GraylistScoreThreshold, + AcceptPXThreshold: AcceptPXScoreThreshold, + OpportunisticGraftThreshold: OpportunisticGraftScoreThreshold, }, ), pubsub.WithPeerScoreInspect(in.Sk.Update, 10*time.Second), From 91e774063e5d30934108c59876e9c0a35e6df404 Mon Sep 17 00:00:00 2001 From: vyzo Date: Fri, 12 Mar 2021 17:10:12 +0200 Subject: [PATCH 19/41] implement MessagePool.CheckMessages Signed-off-by: Jakub Sztandera --- api/api_full.go | 5 + api/checkstatuscode_string.go | 35 +++ api/docgen/docgen.go | 3 + api/mocks/mock_full.go | 30 +++ api/proxy_gen.go | 20 ++ api/types.go | 32 +++ build/tools.go | 1 + chain/messagepool/check.go | 374 +++++++++++++++++++++++++++++ documentation/en/api-v0-methods.md | 32 +++ go.mod | 1 + node/impl/full/mpool.go | 8 + 11 files changed, 541 insertions(+) create mode 100644 api/checkstatuscode_string.go create mode 100644 chain/messagepool/check.go diff --git a/api/api_full.go b/api/api_full.go index 8631ec4b7..3b69ca5e6 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -252,6 +252,11 @@ type FullNode interface { // 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, []*types.Message) ([][]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 + // 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 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/docgen/docgen.go b/api/docgen/docgen.go index 8357ff9b5..4f9bc637e 100644 --- a/api/docgen/docgen.go +++ b/api/docgen/docgen.go @@ -261,6 +261,9 @@ func init() { }, "methods": []interface{}{}}, ) + + addExample(api.CheckStatusCode(0)) + addExample(map[string]interface{}{"abc": 123}) } func GetAPIType(name, pkg string) (i interface{}, t, permStruct, commonPermStruct reflect.Type) { diff --git a/api/mocks/mock_full.go b/api/mocks/mock_full.go index ede04fa20..891a3637f 100644 --- a/api/mocks/mock_full.go +++ b/api/mocks/mock_full.go @@ -1068,6 +1068,36 @@ func (mr *MockFullNodeMockRecorder) MpoolBatchPushUntrusted(arg0, arg1 interface 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 []*types.Message) ([][]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) +} + // MpoolClear mocks base method func (m *MockFullNode) MpoolClear(arg0 context.Context, arg1 bool) error { m.ctrl.T.Helper() diff --git a/api/proxy_gen.go b/api/proxy_gen.go index b743a2ddb..08a9c0dd8 100644 --- a/api/proxy_gen.go +++ b/api/proxy_gen.go @@ -235,6 +235,10 @@ type FullNodeStruct struct { MpoolBatchPushUntrusted func(p0 context.Context, p1 []*types.SignedMessage) ([]cid.Cid, error) `perm:"write"` + MpoolCheckMessages func(p0 context.Context, p1 []*types.Message) ([][]MessageCheckStatus, error) `perm:"read"` + + MpoolCheckPendingMessages func(p0 context.Context, p1 address.Address) ([][]MessageCheckStatus, error) `perm:"read"` + MpoolClear func(p0 context.Context, p1 bool) error `perm:"write"` MpoolGetConfig func(p0 context.Context) (*types.MpoolConfig, error) `perm:"read"` @@ -1509,6 +1513,22 @@ func (s *FullNodeStub) MpoolBatchPushUntrusted(p0 context.Context, p1 []*types.S return *new([]cid.Cid), xerrors.New("method not supported") } +func (s *FullNodeStruct) MpoolCheckMessages(p0 context.Context, p1 []*types.Message) ([][]MessageCheckStatus, error) { + return s.Internal.MpoolCheckMessages(p0, p1) +} + +func (s *FullNodeStub) MpoolCheckMessages(p0 context.Context, p1 []*types.Message) ([][]MessageCheckStatus, error) { + return *new([][]MessageCheckStatus), xerrors.New("method not supported") +} + +func (s *FullNodeStruct) MpoolCheckPendingMessages(p0 context.Context, p1 address.Address) ([][]MessageCheckStatus, error) { + return s.Internal.MpoolCheckPendingMessages(p0, p1) +} + +func (s *FullNodeStub) MpoolCheckPendingMessages(p0 context.Context, p1 address.Address) ([][]MessageCheckStatus, error) { + return *new([][]MessageCheckStatus), xerrors.New("method not supported") +} + func (s *FullNodeStruct) MpoolClear(p0 context.Context, p1 bool) error { return s.Internal.MpoolClear(p0, p1) } diff --git a/api/types.go b/api/types.go index bbcfa5c20..ae8bbe958 100644 --- a/api/types.go +++ b/api/types.go @@ -137,3 +137,35 @@ 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 +} diff --git a/build/tools.go b/build/tools.go index ad45397bb..57b6e7d1f 100644 --- a/build/tools.go +++ b/build/tools.go @@ -6,4 +6,5 @@ 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/chain/messagepool/check.go b/chain/messagepool/check.go new file mode 100644 index 000000000..9a8e32248 --- /dev/null +++ b/chain/messagepool/check.go @@ -0,0 +1,374 @@ +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/build" + "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(msgs []*types.Message) ([][]api.MessageCheckStatus, error) { + return mp.checkMessages(msgs, false) +} + +// CheckPendingMessages performs a set of logical sets for all messages pending from a given actor +func (mp *MessagePool) CheckPendingMessages(from address.Address) ([][]api.MessageCheckStatus, error) { + var msgs []*types.Message + mp.lk.Lock() + mset, ok := mp.pending[from] + if ok { + for _, sm := range mset.msgs { + msgs = append(msgs, &sm.Message) + } + } + mp.lk.Unlock() + + 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(msgs, true) +} + +func (mp *MessagePool) checkMessages(msgs []*types.Message, interned bool) (result [][]api.MessageCheckStatus, err error) { + mp.curTsLk.Lock() + curTs := mp.curTs + mp.curTsLk.Unlock() + + epoch := curTs.Height() + + 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.Lock() + mset, ok := mp.pending[m.From] + 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.Unlock() + + check.OK = true + check.Hint = map[string]interface{}{ + "nonce": st.nextNonce, + } + } else { + mp.lk.Unlock() + + stateNonce, err := mp.getStateNonce(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 + } + } + + 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(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) > 32*1024-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, + }, + } + + if err := m.ValidForBlockInclusion(0, build.NewestNetworkVersion); 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, + }, + }, + } + + 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) + if !check.OK { + goto checkState + } + + // 8. Base Fee upper bound + check = api.MessageCheckStatus{ + Cid: m.Cid(), + CheckStatus: api.CheckStatus{ + Code: api.CheckStatusMessageBaseFeeUpperBound, + Hint: map[string]interface{}{ + "baseFeeUpperBound": baseFeeUpperBound, + }, + }, + } + + if m.GasFeeCap.LessThan(baseFeeUpperBound) { + check.OK = false + 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 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/documentation/en/api-v0-methods.md b/documentation/en/api-v0-methods.md index 3c5356a56..2b75212c2 100644 --- a/documentation/en/api-v0-methods.md +++ b/documentation/en/api-v0-methods.md @@ -82,6 +82,8 @@ * [MpoolBatchPush](#MpoolBatchPush) * [MpoolBatchPushMessage](#MpoolBatchPushMessage) * [MpoolBatchPushUntrusted](#MpoolBatchPushUntrusted) + * [MpoolCheckMessages](#MpoolCheckMessages) + * [MpoolCheckPendingMessages](#MpoolCheckPendingMessages) * [MpoolClear](#MpoolClear) * [MpoolGetConfig](#MpoolGetConfig) * [MpoolGetNonce](#MpoolGetNonce) @@ -1994,6 +1996,36 @@ Inputs: Response: `null` +### MpoolCheckMessages +MpoolCheckMessages performs logical checks on a batch of messages + + +Perms: read + +Inputs: +```json +[ + null +] +``` + +Response: `null` + +### MpoolCheckPendingMessages +MpoolCheckPendingMessages performs logical checks for all pending messages from a given address + + +Perms: read + +Inputs: +```json +[ + "f01234" +] +``` + +Response: `null` + ### MpoolClear MpoolClear clears pending messages from the mpool diff --git a/go.mod b/go.mod index 9d5accbf1..07afe91d3 100644 --- a/go.mod +++ b/go.mod @@ -150,6 +150,7 @@ require ( golang.org/x/sync v0.0.0-20201207232520-09787c993a3a golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 golang.org/x/time v0.0.0-20191024005414-555d28b269f0 + golang.org/x/tools v0.0.0-20201112185108-eeaa07dd7696 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/cheggaaa/pb.v1 v1.0.28 diff --git a/node/impl/full/mpool.go b/node/impl/full/mpool.go index 31c8bc4f7..4916af894 100644 --- a/node/impl/full/mpool.go +++ b/node/impl/full/mpool.go @@ -225,6 +225,14 @@ func (a *MpoolAPI) MpoolBatchPushMessage(ctx context.Context, msgs []*types.Mess return smsgs, nil } +func (a *MpoolAPI) MpoolCheckMessages(ctx context.Context, msgs []*types.Message) ([][]api.MessageCheckStatus, error) { + return a.Mpool.CheckMessages(msgs) +} + +func (a *MpoolAPI) MpoolCheckPendingMessages(ctx context.Context, from address.Address) ([][]api.MessageCheckStatus, error) { + return a.Mpool.CheckPendingMessages(from) +} + func (a *MpoolAPI) MpoolGetNonce(ctx context.Context, addr address.Address) (uint64, error) { return a.Mpool.GetNonce(ctx, addr, types.EmptyTSK) } From d782250abaa89b047e415e28a050230950b6ebb2 Mon Sep 17 00:00:00 2001 From: vyzo Date: Tue, 23 Mar 2021 16:55:38 +0200 Subject: [PATCH 20/41] implement MessagePool.CheckReplaceMessages Signed-off-by: Jakub Sztandera --- api/api_full.go | 2 ++ api/mocks/mock_full.go | 15 ++++++++++ api/proxy_gen.go | 10 +++++++ chain/messagepool/check.go | 45 ++++++++++++++++++++++++++++++ documentation/en/api-v0-methods.md | 16 +++++++++++ node/impl/full/mpool.go | 4 +++ 6 files changed, 92 insertions(+) diff --git a/api/api_full.go b/api/api_full.go index 3b69ca5e6..148f4ac92 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -256,6 +256,8 @@ type FullNode interface { MpoolCheckMessages(context.Context, []*types.Message) ([][]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. diff --git a/api/mocks/mock_full.go b/api/mocks/mock_full.go index 891a3637f..ee89a1d23 100644 --- a/api/mocks/mock_full.go +++ b/api/mocks/mock_full.go @@ -1098,6 +1098,21 @@ func (mr *MockFullNodeMockRecorder) MpoolCheckPendingMessages(arg0, arg1 interfa 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() diff --git a/api/proxy_gen.go b/api/proxy_gen.go index 08a9c0dd8..dfb9f3731 100644 --- a/api/proxy_gen.go +++ b/api/proxy_gen.go @@ -239,6 +239,8 @@ type FullNodeStruct struct { 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"` @@ -1529,6 +1531,14 @@ func (s *FullNodeStub) MpoolCheckPendingMessages(p0 context.Context, p1 address. return *new([][]MessageCheckStatus), xerrors.New("method not supported") } +func (s *FullNodeStruct) MpoolCheckReplaceMessages(p0 context.Context, p1 []*types.Message) ([][]MessageCheckStatus, error) { + return s.Internal.MpoolCheckReplaceMessages(p0, p1) +} + +func (s *FullNodeStub) MpoolCheckReplaceMessages(p0 context.Context, p1 []*types.Message) ([][]MessageCheckStatus, error) { + return *new([][]MessageCheckStatus), xerrors.New("method not supported") +} + func (s *FullNodeStruct) MpoolClear(p0 context.Context, p1 bool) error { return s.Internal.MpoolClear(p0, p1) } diff --git a/chain/messagepool/check.go b/chain/messagepool/check.go index 9a8e32248..5b0761a62 100644 --- a/chain/messagepool/check.go +++ b/chain/messagepool/check.go @@ -46,6 +46,51 @@ func (mp *MessagePool) CheckPendingMessages(from address.Address) ([][]api.Messa return mp.checkMessages(msgs, true) } +// CheckReplaceMessages performs a set of logical checks for related messages while performing a +// replacement. +func (mp *MessagePool) CheckReplaceMessages(replace []*types.Message) ([][]api.MessageCheckStatus, error) { + msgMap := make(map[address.Address]map[uint64]*types.Message) + count := 0 + + mp.lk.Lock() + for _, m := range replace { + mmap, ok := msgMap[m.From] + if !ok { + mmap = make(map[uint64]*types.Message) + msgMap[m.From] = mmap + mset, ok := mp.pending[m.From] + 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.Unlock() + + 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(msgs, true) +} + func (mp *MessagePool) checkMessages(msgs []*types.Message, interned bool) (result [][]api.MessageCheckStatus, err error) { mp.curTsLk.Lock() curTs := mp.curTs diff --git a/documentation/en/api-v0-methods.md b/documentation/en/api-v0-methods.md index 2b75212c2..e90ba7d6a 100644 --- a/documentation/en/api-v0-methods.md +++ b/documentation/en/api-v0-methods.md @@ -84,6 +84,7 @@ * [MpoolBatchPushUntrusted](#MpoolBatchPushUntrusted) * [MpoolCheckMessages](#MpoolCheckMessages) * [MpoolCheckPendingMessages](#MpoolCheckPendingMessages) + * [MpoolCheckReplaceMessages](#MpoolCheckReplaceMessages) * [MpoolClear](#MpoolClear) * [MpoolGetConfig](#MpoolGetConfig) * [MpoolGetNonce](#MpoolGetNonce) @@ -2026,6 +2027,21 @@ Inputs: Response: `null` +### MpoolCheckReplaceMessages +MpoolCheckMessages performs logical checks on pending messages with replacement + + +Perms: read + +Inputs: +```json +[ + null +] +``` + +Response: `null` + ### MpoolClear MpoolClear clears pending messages from the mpool diff --git a/node/impl/full/mpool.go b/node/impl/full/mpool.go index 4916af894..099fb45b8 100644 --- a/node/impl/full/mpool.go +++ b/node/impl/full/mpool.go @@ -233,6 +233,10 @@ func (a *MpoolAPI) MpoolCheckPendingMessages(ctx context.Context, from address.A return a.Mpool.CheckPendingMessages(from) } +func (a *MpoolAPI) MpoolCheckReplaceMessages(ctx context.Context, msgs []*types.Message) ([][]api.MessageCheckStatus, error) { + return a.Mpool.CheckReplaceMessages(msgs) +} + func (a *MpoolAPI) MpoolGetNonce(ctx context.Context, addr address.Address) (uint64, error) { return a.Mpool.GetNonce(ctx, addr, types.EmptyTSK) } From 86e90dc6f11e9e5cade15a6a1e81552fd5450d01 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Wed, 24 Mar 2021 15:12:35 +0100 Subject: [PATCH 21/41] Message sending UI Signed-off-by: Jakub Sztandera --- chain/messagepool/check.go | 5 +- cli/cmd.go | 2 +- cli/send.go | 24 ++-- cli/send_test.go | 15 +-- cli/sending_ui.go | 235 +++++++++++++++++++++++++++++++++++++ cli/services.go | 146 +++++++++++++++-------- cli/services_send_test.go | 85 +++----------- cli/servicesmock_test.go | 64 ++++++++-- go.mod | 4 +- go.sum | 18 ++- 10 files changed, 445 insertions(+), 153 deletions(-) create mode 100644 cli/sending_ui.go diff --git a/chain/messagepool/check.go b/chain/messagepool/check.go index 5b0761a62..6800b0ef4 100644 --- a/chain/messagepool/check.go +++ b/chain/messagepool/check.go @@ -331,6 +331,7 @@ func (mp *MessagePool) checkMessages(msgs []*types.Message, interned bool) (resu Code: api.CheckStatusMessageBaseFeeLowerBound, Hint: map[string]interface{}{ "baseFeeLowerBound": baseFeeLowerBound, + "baseFee": baseFee, }, }, } @@ -343,9 +344,6 @@ func (mp *MessagePool) checkMessages(msgs []*types.Message, interned bool) (resu } result[i] = append(result[i], check) - if !check.OK { - goto checkState - } // 8. Base Fee upper bound check = api.MessageCheckStatus{ @@ -354,6 +352,7 @@ func (mp *MessagePool) checkMessages(msgs []*types.Message, interned bool) (resu Code: api.CheckStatusMessageBaseFeeUpperBound, Hint: map[string]interface{}{ "baseFeeUpperBound": baseFeeUpperBound, + "baseFee": baseFee, }, }, } diff --git a/cli/cmd.go b/cli/cmd.go index 09bf5c461..fad9ee668 100644 --- a/cli/cmd.go +++ b/cli/cmd.go @@ -34,7 +34,7 @@ func GetFullNodeServices(ctx *cli.Context) (ServicesAPI, error) { return tn.(ServicesAPI), nil } - api, c, err := GetFullNodeAPI(ctx) + api, c, err := GetFullNodeAPIV1(ctx) if err != nil { return nil, err } diff --git a/cli/send.go b/cli/send.go index daf73ccad..4056a2d61 100644 --- a/cli/send.go +++ b/cli/send.go @@ -2,7 +2,6 @@ package cli import ( "encoding/hex" - "errors" "fmt" "github.com/urfave/cli/v2" @@ -137,23 +136,30 @@ var sendCmd = &cli.Command{ params.Params = decparams } - params.Force = cctx.Bool("force") - if cctx.IsSet("nonce") { n := cctx.Uint64("nonce") params.Nonce = &n } - msgCid, err := srv.Send(ctx, params) - + proto, err := srv.MessageForSend(ctx, params) if err != nil { - if errors.Is(err, ErrSendBalanceTooLow) { - return fmt.Errorf("--force must be specified for this action to have an effect; you have been warned: %w", err) + return xerrors.Errorf("creating message prototype: %w", err) + } + msg, checks, err := srv.PublishMessage(ctx, proto, cctx.Bool("force")) + if xerrors.Is(err, ErrCheckFailed) { + proto, err = resolveChecks(ctx, srv, cctx.App.Writer, proto, checks, true) + if err != nil { + return xerrors.Errorf("from UI: %w", err) } - return xerrors.Errorf("executing send: %w", err) + + msg, _, err = srv.PublishMessage(ctx, proto, true) } - fmt.Fprintf(cctx.App.Writer, "%s\n", msgCid) + if err != nil { + return xerrors.Errorf("publishing message: %w", err) + } + + fmt.Fprintf(cctx.App.Writer, "%s\n", msg.Cid()) return nil }, } diff --git a/cli/send_test.go b/cli/send_test.go index ff258346a..b16e3c57e 100644 --- a/cli/send_test.go +++ b/cli/send_test.go @@ -1,18 +1,6 @@ package cli -import ( - "bytes" - "errors" - "testing" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-state-types/abi" - types "github.com/filecoin-project/lotus/chain/types" - gomock "github.com/golang/mock/gomock" - cid "github.com/ipfs/go-cid" - "github.com/stretchr/testify/assert" - ucli "github.com/urfave/cli/v2" -) +/* var arbtCid = (&types.Message{ From: mustAddr(address.NewIDAddress(2)), @@ -126,3 +114,4 @@ func TestSendCLI(t *testing.T) { }) } +*/ diff --git a/cli/sending_ui.go b/cli/sending_ui.go new file mode 100644 index 000000000..c05f67a97 --- /dev/null +++ b/cli/sending_ui.go @@ -0,0 +1,235 @@ +package cli + +import ( + "context" + "errors" + "fmt" + "io" + "strings" + + "github.com/Kubuxu/imtui" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/lotus/api" + types "github.com/filecoin-project/lotus/chain/types" + "github.com/gdamore/tcell/v2" + cid "github.com/ipfs/go-cid" +) + +var interactiveSolves = map[api.CheckStatusCode]bool{ + api.CheckStatusMessageBaseFee: true, + api.CheckStatusMessageBaseFeeLowerBound: true, + api.CheckStatusMessageBaseFeeUpperBound: true, +} + +func baseFeeFromHints(hint map[string]interface{}) big.Int { + bHint, ok := hint["baseFee"] + if !ok { + return big.Zero() + } + bHintS, ok := bHint.(string) + if !ok { + return big.Zero() + } + + var err error + baseFee, err := big.FromString(bHintS) + if err != nil { + return big.Zero() + } + return baseFee +} + +func resolveChecks(ctx context.Context, s ServicesAPI, printer io.Writer, + proto *types.Message, checkGroups [][]api.MessageCheckStatus, + interactive bool) (*types.Message, error) { + + fmt.Fprintf(printer, "Following checks have failed:\n") + printChecks(printer, checkGroups, proto.Cid()) + if !interactive { + return nil, ErrCheckFailed + } + + if interactive { + if feeCapBad, baseFee := isFeeCapProblem(checkGroups, proto.Cid()); feeCapBad { + fmt.Fprintf(printer, "Fee of the message can be adjusted\n") + if askUser(printer, "Do you wish to do that? [Yes/no]: ", true) { + var err error + proto, err = runFeeCapAdjustmentUI(proto, baseFee) + if err != nil { + return nil, err + } + } + checks, err := s.RunChecksForPrototype(ctx, proto) + if err != nil { + return nil, err + } + fmt.Fprintf(printer, "Following checks still failed:\n") + printChecks(printer, checks, proto.Cid()) + } + + if !askUser(printer, "Do you wish to send this message? [yes/No]: ", false) { + return nil, ErrAbortedByUser + } + } + return proto, nil +} + +var ErrAbortedByUser = errors.New("aborted by user") + +func printChecks(printer io.Writer, checkGroups [][]api.MessageCheckStatus, protoCid cid.Cid) { + for _, checks := range checkGroups { + for _, c := range checks { + if c.OK { + continue + } + aboutProto := c.Cid.Equals(protoCid) + msgName := "current" + if !aboutProto { + msgName = c.Cid.String() + } + fmt.Fprintf(printer, "%s message failed a check: %s\n", msgName, c.Err) + } + } +} + +func askUser(printer io.Writer, q string, def bool) bool { + var resp string + fmt.Fprint(printer, q) + fmt.Scanln(&resp) + resp = strings.ToLower(resp) + if len(resp) == 0 { + return def + } + return resp[0] == 'y' +} + +func isFeeCapProblem(checkGroups [][]api.MessageCheckStatus, protoCid cid.Cid) (bool, big.Int) { + baseFee := big.Zero() + yes := false + for _, checks := range checkGroups { + for _, c := range checks { + if c.OK { + continue + } + aboutProto := c.Cid.Equals(protoCid) + if aboutProto && interactiveSolves[c.Code] { + yes = true + if baseFee.IsZero() { + baseFee = baseFeeFromHints(c.Hint) + } + } + } + } + + return yes, baseFee +} + +func runFeeCapAdjustmentUI(proto *types.Message, baseFee abi.TokenAmount) (*types.Message, error) { + t, err := imtui.NewTui() + if err != nil { + return nil, err + } + + maxFee := big.Mul(proto.GasFeeCap, big.NewInt(proto.GasLimit)) + send := false + t.SetScene(ui(baseFee, proto.GasLimit, &maxFee, &send)) + + err = t.Run() + if err != nil { + return nil, err + } + if !send { + return nil, fmt.Errorf("aborted by user") + } + + proto.GasFeeCap = big.Div(maxFee, big.NewInt(proto.GasLimit)) + + return proto, nil +} + +func ui(baseFee abi.TokenAmount, gasLimit int64, maxFee *abi.TokenAmount, send *bool) func(*imtui.Tui) error { + orignalMaxFee := *maxFee + required := big.Mul(baseFee, big.NewInt(gasLimit)) + safe := big.Mul(required, big.NewInt(10)) + + price := fmt.Sprintf("%s", types.FIL(*maxFee).Unitless()) + + return func(t *imtui.Tui) error { + if t.CurrentKey != nil { + if t.CurrentKey.Key() == tcell.KeyRune { + pF, err := types.ParseFIL(price) + switch t.CurrentKey.Rune() { + case 's', 'S': + price = types.FIL(safe).Unitless() + case '+': + if err == nil { + p := big.Mul(big.Int(pF), types.NewInt(11)) + p = big.Div(p, types.NewInt(10)) + price = fmt.Sprintf("%s", types.FIL(p).Unitless()) + } + case '-': + if err == nil { + p := big.Mul(big.Int(pF), types.NewInt(10)) + p = big.Div(p, types.NewInt(11)) + price = fmt.Sprintf("%s", types.FIL(p).Unitless()) + } + default: + } + } + + if t.CurrentKey.Key() == tcell.KeyEnter { + *send = true + return imtui.ErrNormalExit + } + } + + defS := tcell.StyleDefault + + row := 0 + t.Label(0, row, "Fee of the message is too low.", defS) + row++ + + t.Label(0, row, fmt.Sprintf("Your configured maximum fee is: %s FIL", + types.FIL(orignalMaxFee).Unitless()), defS) + row++ + t.Label(0, row, fmt.Sprintf("Required maximum fee for the message: %s FIL", + types.FIL(required).Unitless()), defS) + row++ + w := t.Label(0, row, fmt.Sprintf("Safe maximum fee for the message: %s FIL", + types.FIL(safe).Unitless()), defS) + t.Label(w, row, " Press S to use it", defS) + row++ + + w = t.Label(0, row, "Current Maximum Fee: ", defS) + + w += t.EditFieldFiltered(w, row, 14, &price, imtui.FilterDecimal, defS.Foreground(tcell.ColorWhite).Background(tcell.ColorBlack)) + + w += t.Label(w, row, " FIL", defS) + + pF, err := types.ParseFIL(price) + *maxFee = abi.TokenAmount(pF) + if err != nil { + w += t.Label(w, row, " invalid price", defS.Foreground(tcell.ColorMaroon).Bold(true)) + } else if maxFee.GreaterThanEqual(safe) { + w += t.Label(w, row, " SAFE", defS.Foreground(tcell.ColorDarkGreen).Bold(true)) + } else if maxFee.GreaterThanEqual(required) { + w += t.Label(w, row, " low", defS.Foreground(tcell.ColorYellow).Bold(true)) + over := big.Div(big.Mul(*maxFee, big.NewInt(100)), required) + w += t.Label(w, row, + fmt.Sprintf(" %.1fx over the minimum", float64(over.Int64())/100.0), defS) + } else { + w += t.Label(w, row, " too low", defS.Foreground(tcell.ColorRed).Bold(true)) + } + row += 2 + + t.Label(0, row, fmt.Sprintf("Current Base Fee is: %s", types.FIL(baseFee)), defS) + row++ + t.Label(0, row, fmt.Sprintf("Resulting FeeCap is: %s", + types.FIL(big.Div(*maxFee, big.NewInt(gasLimit)))), defS) + row++ + t.Label(0, row, "You can use '+' and '-' to adjust the fee.", defS) + + return nil + } +} diff --git a/cli/services.go b/cli/services.go index 3de0b567b..6fc4afe58 100644 --- a/cli/services.go +++ b/cli/services.go @@ -4,14 +4,14 @@ import ( "bytes" "context" "encoding/json" - "errors" "fmt" "reflect" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-jsonrpc" "github.com/filecoin-project/go-state-types/abi" - "github.com/filecoin-project/lotus/api/v0api" + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/stmgr" types "github.com/filecoin-project/lotus/chain/types" cid "github.com/ipfs/go-cid" @@ -22,12 +22,23 @@ import ( //go:generate go run github.com/golang/mock/mockgen -destination=servicesmock_test.go -package=cli -self_package github.com/filecoin-project/lotus/cli . ServicesAPI type ServicesAPI interface { - // Sends executes a send given SendParams - Send(ctx context.Context, params SendParams) (cid.Cid, error) + GetBaseFee(ctx context.Context) (abi.TokenAmount, error) + + // MessageForSend creates a prototype of a message based on SendParams + MessageForSend(ctx context.Context, params SendParams) (*types.Message, error) + // DecodeTypedParamsFromJSON takes in information needed to identify a method and converts JSON // parameters to bytes of their CBOR encoding DecodeTypedParamsFromJSON(ctx context.Context, to address.Address, method abi.MethodNum, paramstr string) ([]byte, error) + RunChecksForPrototype(ctx context.Context, prototype *types.Message) ([][]api.MessageCheckStatus, error) + + // PublishMessage takes in a message prototype and publishes it + // before publishing the message, it runs checks on the node, message and mpool to verify that + // message is valid and won't be stuck. + // if `force` is true, it skips the checks + PublishMessage(ctx context.Context, prototype *types.Message, interactive bool) (*types.SignedMessage, [][]api.MessageCheckStatus, error) + // Close ends the session of services and disconnects from RPC, using Services after Close is called // most likely will result in an error // Should not be called concurrently @@ -35,7 +46,7 @@ type ServicesAPI interface { } type ServicesImpl struct { - api v0api.FullNode + api api.FullNode closer jsonrpc.ClientCloser } @@ -48,6 +59,16 @@ func (s *ServicesImpl) Close() error { return nil } +func (s *ServicesImpl) GetBaseFee(ctx context.Context) (abi.TokenAmount, error) { + // not used but useful + + ts, err := s.api.ChainHead(ctx) + if err != nil { + return big.Zero(), xerrors.Errorf("getting head: %w", err) + } + return ts.MinTicketBlock().ParentBaseFee, nil +} + func (s *ServicesImpl) DecodeTypedParamsFromJSON(ctx context.Context, to address.Address, method abi.MethodNum, paramstr string) ([]byte, error) { act, err := s.api.StateGetActor(ctx, to, types.EmptyTSK) if err != nil { @@ -72,6 +93,76 @@ func (s *ServicesImpl) DecodeTypedParamsFromJSON(ctx context.Context, to address return buf.Bytes(), nil } +type CheckInfo struct { + MessageTie cid.Cid + CurrentMessageTie bool + + Check api.MessageCheckStatus +} + +var ErrCheckFailed = fmt.Errorf("check has failed") + +func (s *ServicesImpl) RunChecksForPrototype(ctx context.Context, prototype *types.Message) ([][]api.MessageCheckStatus, error) { + var outChecks [][]api.MessageCheckStatus + checks, err := s.api.MpoolCheckMessages(ctx, []*types.Message{prototype}) + if err != nil { + return nil, xerrors.Errorf("message check: %w", err) + } + outChecks = append(outChecks, checks...) + + checks, err = s.api.MpoolCheckPendingMessages(ctx, prototype.From) + if err != nil { + return nil, xerrors.Errorf("pending mpool check: %w", err) + } + outChecks = append(outChecks, checks...) + + return outChecks, nil +} + +// PublishMessage modifies prototype to include gas estimation +// Errors with ErrCheckFailed if any of the checks fail +// First group of checks is related to the message prototype +func (s *ServicesImpl) PublishMessage(ctx context.Context, + prototype *types.Message, force bool) (*types.SignedMessage, [][]api.MessageCheckStatus, error) { + + gasedMsg, err := s.api.GasEstimateMessageGas(ctx, prototype, nil, types.EmptyTSK) + if err != nil { + return nil, nil, xerrors.Errorf("estimating gas: %w", err) + } + *prototype = *gasedMsg + + if !force { + checks, err := s.RunChecksForPrototype(ctx, prototype) + if err != nil { + return nil, nil, xerrors.Errorf("running checks: %w", err) + } + if len(checks) != 0 { + return nil, checks, ErrCheckFailed + } + } + + //TODO: message prototype needs to have "IsNonceSet" + if prototype.Nonce != 0 { + sm, err := s.api.WalletSignMessage(ctx, prototype.From, prototype) + if err != nil { + return nil, nil, err + } + + _, err = s.api.MpoolPush(ctx, sm) + if err != nil { + return nil, nil, err + } + return sm, nil, nil + } + + sm, err := s.api.MpoolPushMessage(ctx, prototype, nil) + if err != nil { + return nil, nil, err + } + + return sm, nil, nil +} + type SendParams struct { To address.Address From address.Address @@ -84,21 +175,13 @@ type SendParams struct { Nonce *uint64 Method abi.MethodNum Params []byte - - Force bool } -// This is specialised Send for Send command -// There might be room for generic Send that other commands can use to send their messages -// We will see - -var ErrSendBalanceTooLow = errors.New("balance too low") - -func (s *ServicesImpl) Send(ctx context.Context, params SendParams) (cid.Cid, error) { +func (s *ServicesImpl) MessageForSend(ctx context.Context, params SendParams) (*types.Message, error) { if params.From == address.Undef { defaddr, err := s.api.WalletDefaultAddress(ctx) if err != nil { - return cid.Undef, err + return nil, err } params.From = defaddr } @@ -127,40 +210,9 @@ func (s *ServicesImpl) Send(ctx context.Context, params SendParams) (cid.Cid, er } else { msg.GasLimit = 0 } - - if !params.Force { - // Funds insufficient check - fromBalance, err := s.api.WalletBalance(ctx, msg.From) - if err != nil { - return cid.Undef, err - } - totalCost := types.BigAdd(types.BigMul(msg.GasFeeCap, types.NewInt(uint64(msg.GasLimit))), msg.Value) - - if fromBalance.LessThan(totalCost) { - return cid.Undef, xerrors.Errorf("From balance %s less than total cost %s: %w", types.FIL(fromBalance), types.FIL(totalCost), ErrSendBalanceTooLow) - - } - } - if params.Nonce != nil { msg.Nonce = *params.Nonce - sm, err := s.api.WalletSignMessage(ctx, params.From, msg) - if err != nil { - return cid.Undef, err - } - - _, err = s.api.MpoolPush(ctx, sm) - if err != nil { - return cid.Undef, err - } - - return sm.Cid(), nil } - sm, err := s.api.MpoolPushMessage(ctx, msg, nil) - if err != nil { - return cid.Undef, err - } - - return sm.Cid(), nil + return msg, nil } diff --git a/cli/services_send_test.go b/cli/services_send_test.go index 713e81b2a..faa052e0c 100644 --- a/cli/services_send_test.go +++ b/cli/services_send_test.go @@ -151,47 +151,12 @@ func TestSendService(t *testing.T) { t.Run("happy", func(t *testing.T) { params := params - srvcs, mockApi := setupMockSrvcs(t) + srvcs, _ := setupMockSrvcs(t) defer srvcs.Close() //nolint:errcheck - msgCid, sign := makeMessageSigner() - gomock.InOrder( - mockApi.EXPECT().WalletBalance(ctxM, params.From).Return(types.NewInt(balance), nil), - mockApi.EXPECT().MpoolPushMessage(ctxM, MessageMatcher(params), nil).DoAndReturn(sign), - ) - c, err := srvcs.Send(ctx, params) + proto, err := srvcs.MessageForSend(ctx, params) assert.NoError(t, err) - assert.Equal(t, *msgCid, c) - }) - - t.Run("balance-too-low", func(t *testing.T) { - params := params - srvcs, mockApi := setupMockSrvcs(t) - defer srvcs.Close() //nolint:errcheck - gomock.InOrder( - mockApi.EXPECT().WalletBalance(ctxM, a1).Return(types.NewInt(balance-200), nil), - // no MpoolPushMessage - ) - - c, err := srvcs.Send(ctx, params) - assert.Equal(t, c, cid.Undef) - assert.ErrorIs(t, err, ErrSendBalanceTooLow) - }) - - t.Run("force", func(t *testing.T) { - params := params - params.Force = true - srvcs, mockApi := setupMockSrvcs(t) - defer srvcs.Close() //nolint:errcheck - msgCid, sign := makeMessageSigner() - gomock.InOrder( - mockApi.EXPECT().WalletBalance(ctxM, a1).Return(types.NewInt(balance-200), nil).AnyTimes(), - mockApi.EXPECT().MpoolPushMessage(ctxM, MessageMatcher(params), nil).DoAndReturn(sign), - ) - - c, err := srvcs.Send(ctx, params) - assert.NoError(t, err) - assert.Equal(t, *msgCid, c) + assert.True(t, MessageMatcher(params).Matches(proto)) }) t.Run("default-from", func(t *testing.T) { @@ -202,16 +167,14 @@ func TestSendService(t *testing.T) { srvcs, mockApi := setupMockSrvcs(t) defer srvcs.Close() //nolint:errcheck - msgCid, sign := makeMessageSigner() + gomock.InOrder( mockApi.EXPECT().WalletDefaultAddress(ctxM).Return(a1, nil), - mockApi.EXPECT().WalletBalance(ctxM, a1).Return(types.NewInt(balance), nil), - mockApi.EXPECT().MpoolPushMessage(ctxM, mm, nil).DoAndReturn(sign), ) - c, err := srvcs.Send(ctx, params) + proto, err := srvcs.MessageForSend(ctx, params) assert.NoError(t, err) - assert.Equal(t, *msgCid, c) + assert.True(t, mm.Matches(proto)) }) t.Run("set-nonce", func(t *testing.T) { @@ -220,26 +183,12 @@ func TestSendService(t *testing.T) { params.Nonce = &n mm := MessageMatcher(params) - srvcs, mockApi := setupMockSrvcs(t) + srvcs, _ := setupMockSrvcs(t) defer srvcs.Close() //nolint:errcheck - _, _ = mm, mockApi - var sm *types.SignedMessage - gomock.InOrder( - mockApi.EXPECT().WalletBalance(ctxM, a1).Return(types.NewInt(balance), nil), - mockApi.EXPECT().WalletSignMessage(ctxM, a1, mm).DoAndReturn( - func(_ context.Context, _ address.Address, msg *types.Message) (*types.SignedMessage, error) { - sm = fakeSign(msg) - - // now we expect MpoolPush with that SignedMessage - mockApi.EXPECT().MpoolPush(ctxM, sm).Return(sm.Cid(), nil) - return sm, nil - }), - ) - - c, err := srvcs.Send(ctx, params) + proto, err := srvcs.MessageForSend(ctx, params) assert.NoError(t, err) - assert.Equal(t, sm.Cid(), c) + assert.True(t, mm.Matches(proto)) }) t.Run("gas-params", func(t *testing.T) { @@ -251,16 +200,14 @@ func TestSendService(t *testing.T) { gp := big.NewInt(10) params.GasPremium = &gp - srvcs, mockApi := setupMockSrvcs(t) - defer srvcs.Close() //nolint:errcheck - msgCid, sign := makeMessageSigner() - gomock.InOrder( - mockApi.EXPECT().WalletBalance(ctxM, params.From).Return(types.NewInt(balance), nil), - mockApi.EXPECT().MpoolPushMessage(ctxM, MessageMatcher(params), nil).DoAndReturn(sign), - ) + mm := MessageMatcher(params) - c, err := srvcs.Send(ctx, params) + srvcs, _ := setupMockSrvcs(t) + defer srvcs.Close() //nolint:errcheck + + proto, err := srvcs.MessageForSend(ctx, params) assert.NoError(t, err) - assert.Equal(t, *msgCid, c) + assert.True(t, mm.Matches(proto)) + }) } diff --git a/cli/servicesmock_test.go b/cli/servicesmock_test.go index 48f1a95ec..4d1f589cd 100644 --- a/cli/servicesmock_test.go +++ b/cli/servicesmock_test.go @@ -8,8 +8,10 @@ import ( context "context" go_address "github.com/filecoin-project/go-address" abi "github.com/filecoin-project/go-state-types/abi" + big "github.com/filecoin-project/go-state-types/big" + api "github.com/filecoin-project/lotus/api" + types "github.com/filecoin-project/lotus/chain/types" gomock "github.com/golang/mock/gomock" - go_cid "github.com/ipfs/go-cid" reflect "reflect" ) @@ -65,17 +67,63 @@ func (mr *MockServicesAPIMockRecorder) DecodeTypedParamsFromJSON(arg0, arg1, arg return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecodeTypedParamsFromJSON", reflect.TypeOf((*MockServicesAPI)(nil).DecodeTypedParamsFromJSON), arg0, arg1, arg2, arg3) } -// Send mocks base method -func (m *MockServicesAPI) Send(arg0 context.Context, arg1 SendParams) (go_cid.Cid, error) { +// GetBaseFee mocks base method +func (m *MockServicesAPI) GetBaseFee(arg0 context.Context) (big.Int, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Send", arg0, arg1) - ret0, _ := ret[0].(go_cid.Cid) + ret := m.ctrl.Call(m, "GetBaseFee", arg0) + ret0, _ := ret[0].(big.Int) ret1, _ := ret[1].(error) return ret0, ret1 } -// Send indicates an expected call of Send -func (mr *MockServicesAPIMockRecorder) Send(arg0, arg1 interface{}) *gomock.Call { +// GetBaseFee indicates an expected call of GetBaseFee +func (mr *MockServicesAPIMockRecorder) GetBaseFee(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Send", reflect.TypeOf((*MockServicesAPI)(nil).Send), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBaseFee", reflect.TypeOf((*MockServicesAPI)(nil).GetBaseFee), arg0) +} + +// MessageForSend mocks base method +func (m *MockServicesAPI) MessageForSend(arg0 context.Context, arg1 SendParams) (*types.Message, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MessageForSend", arg0, arg1) + ret0, _ := ret[0].(*types.Message) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MessageForSend indicates an expected call of MessageForSend +func (mr *MockServicesAPIMockRecorder) MessageForSend(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MessageForSend", reflect.TypeOf((*MockServicesAPI)(nil).MessageForSend), arg0, arg1) +} + +// PublishMessage mocks base method +func (m *MockServicesAPI) PublishMessage(arg0 context.Context, arg1 *types.Message, arg2 bool) (*types.SignedMessage, [][]api.MessageCheckStatus, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PublishMessage", arg0, arg1, arg2) + ret0, _ := ret[0].(*types.SignedMessage) + ret1, _ := ret[1].([][]api.MessageCheckStatus) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// PublishMessage indicates an expected call of PublishMessage +func (mr *MockServicesAPIMockRecorder) PublishMessage(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PublishMessage", reflect.TypeOf((*MockServicesAPI)(nil).PublishMessage), arg0, arg1, arg2) +} + +// RunChecksForPrototype mocks base method +func (m *MockServicesAPI) RunChecksForPrototype(arg0 context.Context, arg1 *types.Message) ([][]api.MessageCheckStatus, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RunChecksForPrototype", arg0, arg1) + ret0, _ := ret[0].([][]api.MessageCheckStatus) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// RunChecksForPrototype indicates an expected call of RunChecksForPrototype +func (mr *MockServicesAPIMockRecorder) RunChecksForPrototype(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RunChecksForPrototype", reflect.TypeOf((*MockServicesAPI)(nil).RunChecksForPrototype), arg0, arg1) } diff --git a/go.mod b/go.mod index 07afe91d3..e1fe8c764 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/BurntSushi/toml v0.3.1 github.com/GeertJohan/go.rice v1.0.0 github.com/Gurpartap/async v0.0.0-20180927173644-4f7f499dd9ee + github.com/Kubuxu/imtui v0.0.0-20210323145256-9fdaecfdf6b7 github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 @@ -50,6 +51,7 @@ require ( github.com/filecoin-project/specs-storage v0.1.1-0.20201105051918-5188d9774506 github.com/filecoin-project/test-vectors/schema v0.0.5 github.com/gbrlsnchs/jwt/v3 v3.0.0-beta.1 + github.com/gdamore/tcell/v2 v2.2.0 github.com/go-kit/kit v0.10.0 github.com/go-ole/go-ole v1.2.4 // indirect github.com/golang/mock v1.4.4 @@ -86,7 +88,7 @@ require ( github.com/ipfs/go-ipfs-util v0.0.2 github.com/ipfs/go-ipld-cbor v0.0.5 github.com/ipfs/go-ipld-format v0.2.0 - github.com/ipfs/go-log/v2 v2.1.2-0.20200626104915-0016c0b4b3e4 + github.com/ipfs/go-log/v2 v2.1.2 github.com/ipfs/go-merkledag v0.3.2 github.com/ipfs/go-metrics-interface v0.0.1 github.com/ipfs/go-metrics-prometheus v0.0.2 diff --git a/go.sum b/go.sum index 54c5da743..bfb498886 100644 --- a/go.sum +++ b/go.sum @@ -45,6 +45,8 @@ github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETF github.com/Masterminds/glide v0.13.2/go.mod h1:STyF5vcenH/rUqTEv+/hBXlSTo7KYwg2oc2f4tzPWic= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= +github.com/Kubuxu/imtui v0.0.0-20210323145256-9fdaecfdf6b7 h1:oaKenk0p5Pg7k2YRflJtiai4weJN+VsABO3zSaUVU6w= +github.com/Kubuxu/imtui v0.0.0-20210323145256-9fdaecfdf6b7/go.mod h1:WUmMvh9wMtqj1Xhf1hf3kp9RvL+y6odtdYxpyZjb90U= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= @@ -330,6 +332,10 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/gbrlsnchs/jwt/v3 v3.0.0-beta.1 h1:EzDjxMg43q1tA2c0MV3tNbaontnHLplHyFF6M5KiVP0= github.com/gbrlsnchs/jwt/v3 v3.0.0-beta.1/go.mod h1:0eHX/BVySxPc6SE2mZRoppGq7qcEagxdmQnA3dzork8= +github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= +github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= +github.com/gdamore/tcell/v2 v2.2.0 h1:vSyEgKwraXPSOkvCk7IwOSyX+Pv3V2cV9CikJMXg4U4= +github.com/gdamore/tcell/v2 v2.2.0/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU= github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghemawat/stream v0.0.0-20171120220530-696b145b53b9/go.mod h1:106OIgooyS7OzLDOpUGgm9fA3bQENb/cFSyyBmMoJDs= @@ -672,8 +678,9 @@ github.com/ipfs/go-log/v2 v2.0.3/go.mod h1:O7P1lJt27vWHhOwQmcFEvlmo49ry2VY2+JfBW github.com/ipfs/go-log/v2 v2.0.5/go.mod h1:eZs4Xt4ZUJQFM3DlanGhy7TkwwawCZcSByscwkWG+dw= github.com/ipfs/go-log/v2 v2.0.8/go.mod h1:eZs4Xt4ZUJQFM3DlanGhy7TkwwawCZcSByscwkWG+dw= github.com/ipfs/go-log/v2 v2.1.1/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHntrv9KM= -github.com/ipfs/go-log/v2 v2.1.2-0.20200626104915-0016c0b4b3e4 h1:3bijxqzQ1O9yg7gd7Aqk80oaEvsJ+uXw0zSvi2qR3Jw= github.com/ipfs/go-log/v2 v2.1.2-0.20200626104915-0016c0b4b3e4/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHntrv9KM= +github.com/ipfs/go-log/v2 v2.1.2 h1:a0dRiL098zY23vay1h3dimx6y94XchEUyt5h0l4VvQU= +github.com/ipfs/go-log/v2 v2.1.2/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHntrv9KM= github.com/ipfs/go-merkledag v0.0.3/go.mod h1:Oc5kIXLHokkE1hWGMBHw+oxehkAaTOqtEb7Zbh6BhLA= github.com/ipfs/go-merkledag v0.0.6/go.mod h1:QYPdnlvkOg7GnQRofu9XZimC5ZW5Wi3bKys/4GQQfto= github.com/ipfs/go-merkledag v0.2.3/go.mod h1:SQiXrtSts3KGNmgOzMICy5c0POOpUNQLvB3ClKnBAlk= @@ -1110,6 +1117,8 @@ github.com/lucas-clemente/quic-go v0.11.2/go.mod h1:PpMmPfPKO9nKJ/psF49ESTAGQSdf github.com/lucas-clemente/quic-go v0.16.0/go.mod h1:I0+fcNTdb9eS1ZcjQZbDVPGchJ86chcIxPALn9lEJqE= github.com/lucas-clemente/quic-go v0.18.1 h1:DMR7guC0NtVS8zNZR3IO7NARZvZygkSC56GGtC6cyys= github.com/lucas-clemente/quic-go v0.18.1/go.mod h1:yXttHsSNxQi8AWijC/vLP+OJczXqzHSOcJrM5ITUlCg= +github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= +github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lufia/iostat v1.1.0/go.mod h1:rEPNA0xXgjHQjuI5Cy05sLlS2oRcSlWHRLrvh/AQ+Pg= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= @@ -1143,8 +1152,9 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg= +github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-xmlrpc v0.0.3/go.mod h1:mqc2dz7tP5x5BKlCahN/n+hs7OSZKJkS9JsHNBRlrxA= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -1369,6 +1379,8 @@ github.com/raulk/go-watchdog v1.0.1/go.mod h1:lzSbAl5sh4rtI8tYHU01BWIDzgzqaQLj6R github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -1814,6 +1826,8 @@ golang.org/x/sys v0.0.0-20200926100807-9d91bd62050c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M= +golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From e2d0047a2aef2f209bf1af0518d7201e7c5f37b1 Mon Sep 17 00:00:00 2001 From: vyzo Date: Sat, 27 Mar 2021 16:35:46 +0200 Subject: [PATCH 22/41] introduce message prototypes This introduces message prototypes to applicable API endpoints, which allows us to invert control of message sending and give the user a chance to intervene with an interactive ui. Signed-off-by: Jakub Sztandera --- api/api_full.go | 31 ++- api/mocks/mock_full.go | 48 ++-- api/proxy_gen.go | 96 ++++---- api/types.go | 7 + api/v0api/v1_wrapper.go | 127 ++++++++++ build/openrpc/full.json.gz | Bin 22681 -> 23192 bytes build/openrpc/miner.json.gz | Bin 7849 -> 7847 bytes build/openrpc/worker.json.gz | Bin 2579 -> 2579 bytes cli/init_test.go | 9 + cli/multisig.go | 247 ++++++++++++++----- cli/send.go | 4 +- cli/sending_ui.go | 18 +- cli/services.go | 60 +++-- cli/services_send_test.go | 37 +-- cli/servicesmock_test.go | 22 +- cmd/lotus-gateway/endtoend_test.go | 35 ++- cmd/lotus-shed/verifreg.go | 19 +- documentation/en/api-v0-methods.md | 48 ---- documentation/en/api-v1-unstable-methods.md | 252 +++++++++++++++++++- node/impl/full/multisig.go | 115 ++++----- 20 files changed, 852 insertions(+), 323 deletions(-) create mode 100644 cli/init_test.go diff --git a/api/api_full.go b/api/api_full.go index 148f4ac92..e8e8dcb2e 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -586,15 +586,16 @@ type FullNode interface { // 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 + 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) (cid.Cid, error) //perm:sign + 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) (cid.Cid, error) //perm:sign + 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 @@ -602,43 +603,49 @@ type FullNode interface { // 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 + 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, types.BigInt, address.Address, uint64, []byte) (cid.Cid, error) //perm:sign + MsigCancel(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) (cid.Cid, error) //perm:sign + 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) (cid.Cid, error) //perm:sign + 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) (cid.Cid, error) //perm:sign + 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) (cid.Cid, error) //perm:sign + 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) (cid.Cid, error) //perm:sign + 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) (cid.Cid, error) //perm:sign + 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) (cid.Cid, error) //perm:sign + 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 diff --git a/api/mocks/mock_full.go b/api/mocks/mock_full.go index ee89a1d23..a14336537 100644 --- a/api/mocks/mock_full.go +++ b/api/mocks/mock_full.go @@ -1262,10 +1262,10 @@ func (mr *MockFullNodeMockRecorder) MpoolSub(arg0 interface{}) *gomock.Call { } // 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) { +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].(cid.Cid) + ret0, _ := ret[0].(*api.MessagePrototype) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -1277,10 +1277,10 @@ func (mr *MockFullNodeMockRecorder) MsigAddApprove(arg0, arg1, arg2, arg3, arg4, } // 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) { +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].(cid.Cid) + ret0, _ := ret[0].(*api.MessagePrototype) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -1292,10 +1292,10 @@ func (mr *MockFullNodeMockRecorder) MsigAddCancel(arg0, arg1, arg2, arg3, arg4, } // MsigAddPropose mocks base method -func (m *MockFullNode) MsigAddPropose(arg0 context.Context, arg1, arg2, arg3 address.Address, arg4 bool) (cid.Cid, error) { +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].(cid.Cid) + ret0, _ := ret[0].(*api.MessagePrototype) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -1307,10 +1307,10 @@ func (mr *MockFullNodeMockRecorder) 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) { +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].(cid.Cid) + ret0, _ := ret[0].(*api.MessagePrototype) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -1322,10 +1322,10 @@ func (mr *MockFullNodeMockRecorder) MsigApprove(arg0, arg1, arg2, arg3 interface } // 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) { +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].(cid.Cid) + ret0, _ := ret[0].(*api.MessagePrototype) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -1337,10 +1337,10 @@ func (mr *MockFullNodeMockRecorder) MsigApproveTxnHash(arg0, arg1, arg2, arg3, a } // 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) { +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) (*api.MessagePrototype, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "MsigCancel", arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) - ret0, _ := ret[0].(cid.Cid) + ret0, _ := ret[0].(*api.MessagePrototype) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -1352,10 +1352,10 @@ func (mr *MockFullNodeMockRecorder) MsigCancel(arg0, arg1, arg2, arg3, arg4, arg } // 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) { +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].(cid.Cid) + ret0, _ := ret[0].(*api.MessagePrototype) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -1427,10 +1427,10 @@ func (mr *MockFullNodeMockRecorder) MsigGetVestingSchedule(arg0, arg1, arg2 inte } // 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) { +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].(cid.Cid) + ret0, _ := ret[0].(*api.MessagePrototype) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -1442,10 +1442,10 @@ func (mr *MockFullNodeMockRecorder) MsigPropose(arg0, arg1, arg2, arg3, arg4, ar } // MsigRemoveSigner mocks base method -func (m *MockFullNode) MsigRemoveSigner(arg0 context.Context, arg1, arg2, arg3 address.Address, arg4 bool) (cid.Cid, error) { +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].(cid.Cid) + ret0, _ := ret[0].(*api.MessagePrototype) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -1457,10 +1457,10 @@ func (mr *MockFullNodeMockRecorder) MsigRemoveSigner(arg0, arg1, arg2, arg3, arg } // 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) { +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].(cid.Cid) + ret0, _ := ret[0].(*api.MessagePrototype) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -1472,10 +1472,10 @@ func (mr *MockFullNodeMockRecorder) MsigSwapApprove(arg0, arg1, arg2, arg3, arg4 } // MsigSwapCancel mocks base method -func (m *MockFullNode) MsigSwapCancel(arg0 context.Context, arg1, arg2 address.Address, arg3 uint64, arg4, arg5 address.Address) (cid.Cid, error) { +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].(cid.Cid) + ret0, _ := ret[0].(*api.MessagePrototype) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -1487,10 +1487,10 @@ func (mr *MockFullNodeMockRecorder) MsigSwapCancel(arg0, arg1, arg2, arg3, arg4, } // MsigSwapPropose mocks base method -func (m *MockFullNode) MsigSwapPropose(arg0 context.Context, arg1, arg2, arg3, arg4 address.Address) (cid.Cid, error) { +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].(cid.Cid) + ret0, _ := ret[0].(*api.MessagePrototype) ret1, _ := ret[1].(error) return ret0, ret1 } diff --git a/api/proxy_gen.go b/api/proxy_gen.go index dfb9f3731..f285f1ce6 100644 --- a/api/proxy_gen.go +++ b/api/proxy_gen.go @@ -261,19 +261,19 @@ type FullNodeStruct struct { 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) (cid.Cid, error) `perm:"sign"` + 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) (cid.Cid, 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) (cid.Cid, 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) (cid.Cid, 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) (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) (*MessagePrototype, 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"` + MsigCancel 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) (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) (*MessagePrototype, error) `perm:"sign"` MsigGetAvailableBalance func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (types.BigInt, error) `perm:"read"` @@ -283,15 +283,15 @@ type FullNodeStruct struct { 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) (cid.Cid, error) `perm:"sign"` + 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) (cid.Cid, 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) (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) (*MessagePrototype, 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"` + 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) (cid.Cid, error) `perm:"sign"` + MsigSwapPropose func(p0 context.Context, p1 address.Address, p2 address.Address, p3 address.Address, p4 address.Address) (*MessagePrototype, error) `perm:"sign"` NodeStatus func(p0 context.Context, p1 bool) (NodeStatus, error) `perm:"read"` @@ -1619,60 +1619,60 @@ func (s *FullNodeStub) MpoolSub(p0 context.Context) (<-chan MpoolUpdate, error) return nil, xerrors.New("method not supported") } -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) { +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) { 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), xerrors.New("method not supported") +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, xerrors.New("method not supported") } -func (s *FullNodeStruct) MsigAddCancel(p0 context.Context, p1 address.Address, p2 address.Address, p3 uint64, p4 address.Address, p5 bool) (cid.Cid, error) { +func (s *FullNodeStruct) MsigAddCancel(p0 context.Context, p1 address.Address, p2 address.Address, p3 uint64, p4 address.Address, p5 bool) (*MessagePrototype, error) { 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), xerrors.New("method not supported") +func (s *FullNodeStub) MsigAddCancel(p0 context.Context, p1 address.Address, p2 address.Address, p3 uint64, p4 address.Address, p5 bool) (*MessagePrototype, error) { + return nil, xerrors.New("method not supported") } -func (s *FullNodeStruct) MsigAddPropose(p0 context.Context, p1 address.Address, p2 address.Address, p3 address.Address, p4 bool) (cid.Cid, error) { +func (s *FullNodeStruct) MsigAddPropose(p0 context.Context, p1 address.Address, p2 address.Address, p3 address.Address, p4 bool) (*MessagePrototype, error) { 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), xerrors.New("method not supported") +func (s *FullNodeStub) MsigAddPropose(p0 context.Context, p1 address.Address, p2 address.Address, p3 address.Address, p4 bool) (*MessagePrototype, error) { + return nil, xerrors.New("method not supported") } -func (s *FullNodeStruct) MsigApprove(p0 context.Context, p1 address.Address, p2 uint64, p3 address.Address) (cid.Cid, error) { +func (s *FullNodeStruct) MsigApprove(p0 context.Context, p1 address.Address, p2 uint64, p3 address.Address) (*MessagePrototype, error) { 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), xerrors.New("method not supported") +func (s *FullNodeStub) MsigApprove(p0 context.Context, p1 address.Address, p2 uint64, p3 address.Address) (*MessagePrototype, error) { + return nil, xerrors.New("method not supported") } -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) { +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) { 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), xerrors.New("method not supported") +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, xerrors.New("method not supported") } -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) { +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) (*MessagePrototype, error) { 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), xerrors.New("method not supported") +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) (*MessagePrototype, error) { + return nil, xerrors.New("method not supported") } -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) { +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) { 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), xerrors.New("method not supported") +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, xerrors.New("method not supported") } func (s *FullNodeStruct) MsigGetAvailableBalance(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (types.BigInt, error) { @@ -1707,44 +1707,44 @@ func (s *FullNodeStub) MsigGetVestingSchedule(p0 context.Context, p1 address.Add return *new(MsigVesting), xerrors.New("method not supported") } -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) { +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) { 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), xerrors.New("method not supported") +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, xerrors.New("method not supported") } -func (s *FullNodeStruct) MsigRemoveSigner(p0 context.Context, p1 address.Address, p2 address.Address, p3 address.Address, p4 bool) (cid.Cid, error) { +func (s *FullNodeStruct) MsigRemoveSigner(p0 context.Context, p1 address.Address, p2 address.Address, p3 address.Address, p4 bool) (*MessagePrototype, error) { 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), xerrors.New("method not supported") +func (s *FullNodeStub) MsigRemoveSigner(p0 context.Context, p1 address.Address, p2 address.Address, p3 address.Address, p4 bool) (*MessagePrototype, error) { + return nil, xerrors.New("method not supported") } -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) { +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) { 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), xerrors.New("method not supported") +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, xerrors.New("method not supported") } -func (s *FullNodeStruct) MsigSwapCancel(p0 context.Context, p1 address.Address, p2 address.Address, p3 uint64, p4 address.Address, p5 address.Address) (cid.Cid, error) { +func (s *FullNodeStruct) MsigSwapCancel(p0 context.Context, p1 address.Address, p2 address.Address, p3 uint64, p4 address.Address, p5 address.Address) (*MessagePrototype, error) { 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), xerrors.New("method not supported") +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, xerrors.New("method not supported") } -func (s *FullNodeStruct) MsigSwapPropose(p0 context.Context, p1 address.Address, p2 address.Address, p3 address.Address, p4 address.Address) (cid.Cid, error) { +func (s *FullNodeStruct) MsigSwapPropose(p0 context.Context, p1 address.Address, p2 address.Address, p3 address.Address, p4 address.Address) (*MessagePrototype, error) { 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), xerrors.New("method not supported") +func (s *FullNodeStub) MsigSwapPropose(p0 context.Context, p1 address.Address, p2 address.Address, p3 address.Address, p4 address.Address) (*MessagePrototype, error) { + return nil, xerrors.New("method not supported") } func (s *FullNodeStruct) NodeStatus(p0 context.Context, p1 bool) (NodeStatus, error) { diff --git a/api/types.go b/api/types.go index ae8bbe958..83de131a2 100644 --- a/api/types.go +++ b/api/types.go @@ -5,6 +5,8 @@ import ( "fmt" "time" + "github.com/filecoin-project/lotus/chain/types" + datatransfer "github.com/filecoin-project/go-data-transfer" "github.com/filecoin-project/go-state-types/abi" "github.com/ipfs/go-cid" @@ -169,3 +171,8 @@ type MessageCheckStatus struct { Cid cid.Cid CheckStatus } + +type MessagePrototype struct { + Message types.Message + ValidNonce bool +} diff --git a/api/v0api/v1_wrapper.go b/api/v0api/v1_wrapper.go index e977c6b67..ff4474fe5 100644 --- a/api/v0api/v1_wrapper.go +++ b/api/v0api/v1_wrapper.go @@ -3,7 +3,9 @@ package v0api import ( "context" + "github.com/filecoin-project/go-address" "github.com/filecoin-project/lotus/chain/types" + "golang.org/x/xerrors" "github.com/ipfs/go-cid" @@ -57,4 +59,129 @@ func (w *WrapperV1Full) Version(ctx context.Context) (api.APIVersion, error) { 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.MsigCancel(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) +} + var _ FullNode = &WrapperV1Full{} diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index 7763e4af8ba241fea0cb4d20a1fc61790c2646f4..cb781bec9169de220365f7d8cfbac0850a948f70 100644 GIT binary patch literal 23192 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 Npp+;_KoSPZe*qz{wEF-6 literal 22681 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 BZlVAH diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index 501b238282ef3d11fb1e356a867206a563054b2b..0d52dac870e8b5a1854f566261221e5616a6839f 100644 GIT binary patch delta 7234 zcmV-I9KGYIJ*PdeIiQlg(m@B1IT1BDxhe+x!96-{!N} zH)HIfbLu#l@v(cAinM^+1#7s^mDd!vSM{`@CD9BgTNzuT zZ{l|MUsYx5+r(E)k750_VhEw(Lzd1Ic_}i#iN^r@0D`DUY}oxl|Mlrff7m}B-mV50 zlzis0Gt8Eq^96r0@5Uaz=RjCWauO7jax=y4Yc$8af5;ls2^)Kq&RW3jfzP+TBpXce zHR8|vfTQnA@WO%a=hghnDZKTWhe@s?U+)_43U88=->%GM4|i*fTb^B4qv)kt&6pT1 z^x#IsZ7bAapL3L0(kQsR$klqu3W66iKVhH@gWpfB;453|lzqz3tzrgm#M1%` zJA-%7g^Wx6IKdq4pJLWr(BQ$DMZ=T41FL^e2MR3q5M<~LH1C#fIV_G5P+_tB&MzgP zD)BT)NbP>c3j$O8@ByI~0#o^n5({~NEfDZsWOB5z0RL=&555g<7U&C+k!27}s1Miz zGEqf*&Lf8pkxQbtBm|Q=_=?$rzyCt6e!uxR`RmQu`L7?xf4w=Iy!~r@cJnhp*A?w(hXDXhN*gr_&~-S)r?Xpnzb@i zgHhjwo%#wFCow}G$vyIfwOubuWz(&Rt(3$Y*4hng4Rj3kKeobHSyXFcD+Pb)hN*VL zRIfK#>6ea6J$3;ZEYCtK8mhpIiLLfIwnSW9D@c>3yOX$g(9is3%FH-(lGZu4fX5la zM}+wR0w3A4BUxsO$y|OY!orjaWC0veYlBM$un%U`bC3n5j{t%{Wb$GQKyXeiWP%r8 z7uZ|?Oe}1Q;1mST(q`D?Z;yXmXxS?%fAy`!@nawMkJ+^E-yz5S4}JKryL_aM`=6;ZLyxomV21n4$K&M^4SM$jVCS!liBJpy|ixnPq zsSoXf=>g|9MuZj5q?7f|scl1sJdI{k6y6f+txL^?-r~v?oFY48vASi9F)~r9bv{v3 zBuwe9@7!~HrT9?8npHm6gh7uL_+Wu#jXi9lG4i6C#aaz*Gr__ciJw`CAQJ`|D#(Uc z&}ScAivwJdG0K#;qF+D8hWyeT9i1rA$pY---wl~6uKN(K3mg{Xn| zSwy!4{wsrU9r<%jMV$ru12Q91M%IBz%*m6W2OodKO#!AfFVqua+Dx~84ql_)@bBU9 z@9CykR-6-RT_VgOTqg%-lW$n~fLM4_OeMt%^&_y(nq)QhZSeO{nRnRPJ3prXiSr1vj}gU zO`CuC-L~R)jc8u=hJA&bDwhk-Au*AC0U5mUAn|7zT%#kIDq<~lDANuLUtWl$ z3>P7)MBgKJ=HFGoCP^TB$?vD&Xaz@gfAAj}%lURB~x6|*nj1PbC zA^l7q4-p<{(=u+bgM0=ZmzU^vx-H}FgNu2mf}a!g-`}V)8uXr81|I{IU+$YB&wl8& z43RE4GX72M&YzPv3zPh^K>z!%e;H2&55NkBTA)VQRh=||0wm zA|hu-r(>~M)+xTmVZ}O@nIjTsoC1FnELp46?&_9N_;8YqDId=n*k3Ec8xd?qOAM6} zK2Avi^M8%b1|$Q{XYqhcxrGh|Emh)YywLdq5`uD*{?#pPDGM~C zj~Nl=>{8p^6z~T6{Y!!whKzbyY2UoS1d$*aKK=Fs@_jf*nfdy^vjy_TC{Z~1l86?V=>2Q`W zwtn97Z_vq}xvjRV+DWkVH9gu@7`9M(0n}PPFkPC9Os+lr4FN;d8HzEiExq);Mx*+! zuQb7C?^m<;E2Cz!_p90aRXq@RU{J3*qH%?)m&BwTwpfLHCnYr%{3m}GPXw)Ex**-o zmb8MRr|RRwGfHk(Uz}OiyCt#wb8Hny&a-=nZ8){jJL*Nz*K9XnrC?H2TxY98fgfEewX=Bopt3$emhwayExbRE?tY*slWH(+qtKzQ;$~`V-5QQ zRm4i7;>3)RfH%ispuBEF6QpOQ-mMLdox zMCMFWSN=Ym`G^&X)adm>XMLjV6l5ELRgzEw;Zm$*AzKW*;EY|vuO&U(sw*hPj2gl}ZWDWWm2tz)T=kbbX| zb{7DvywW#t=*Gyy)DljtF)})xPWMRsbJOXJ#6N!;Yh{hmaVPp;!KAxN(Edy#XiWg9 z2>|Uw07%QyND8{v$Irl#Z^n#z4rFrY7&>CuK^x4I!$^NiJyqS+5vYyuJJqAx($q3l zoJJ;UHmR+swI40N@KklvhWzr^$mjA>)5g~fYCGHcn%%bU*4K>M05XOgm-&EE;K}#R zKGD+m?hC;lD@h{PH4mc|?cZ)`{fo z@8%5XiYknHs2xK7Ok9Q_gr1>U|7nx$0-T+O+vQd)js42kN2^jOlzE|WEyJiNnQ=vH0*B~kJU`|KQBoaxK0aHq*s zyd*D^U~@sw`x!dV< zT1L39@U);&xY`DtYkui(T|6`cNp+>!wcxHZORj^KV(ugfmAlKG4?Ke=YM(>f4l;kc z#4_y4<8}Msr)0UeQV=#SuBF<~Px4wd@`-wP$de>$%gI_FtEMzeqP;2#xsV&ejLwPt z|53)j!v7lug-E&(kz1v)!ksT|4t%(j1iQyA!@(r>Yu%BVl-Z}VUCFP0SnZ~4l?3bS zGDz&u_`2QlqARndFsaI{SYn3tn`i+SEeEs1w^rnLPEhXgpew(>lOjxpA{u zGcK(W`yJIiiBcE5Lri36l~sqLej#00TJwcX?c})TMzuAnEl;&IAlra!0J5q(JOkvy z1lstYDxP%~p&h~QdYcXYHu&4%Z-c-4guki_XeESwp!eZT@D;!}MrAk1n{0ouf!zjn z8`y1Ncb{Nab-A#_uWJM=hub3>g&=oIsa-A19&a~4&fcJJgT6aL-`#A*Zi35=0?<+b z8<$(Jk&U2_mZ$$k}@i%S4!oKJxXVFX`5c7Z5nN}kJ?6Xd823=MRS;n=6K_>?nInHgz9l` zR)k8b8&s#0lju#Oc^b{Lhnfd9u1=$Y4qXGCY+T_^Ky_7bXGN%#29keBbdD{*M)lJ; zK8^a>OZBtjc1H~up%O&!V7sP+6&A9fy?E$r@RxSGAOHIW4$o2 zmX7NK91sJ(d=K~pTT2^xz-N%5+D_B?ewS>;zG$jf$#)t6sD>=rrE>Pne#%9+qgbGl zK=>Z9w+}8d8L~KYT`WBqFhm(4*+4P-tTl!m)DDZ}i^^vDDw%)TPz%fv^TjQJD0mBa zm&8eJ5cr}hU{nZJe}##0mQ^38$MR!?ubmC zEFCAB!^ct474r(7WedpotfQ~?b4xt-DI=-P{`A zoi-QDRbXw^oyc1#CEMwpz0&Q*?w`+4D8aaFxa;UoFS5DoecCSG}QF#k++=S2)Q*{G* za;vBb{4Kc-8?XO;E~Zl>L|6T;>bBH~uj=qfCYwEy^5PPwsv#pQx5VEEx1^DxLy;oY zjiLePaTb3G;-XcgC`P7M^KgxDt{}yVa|;hl5ip@`BMS(XBds+|z+1;=^&krxGo5%lcG#)kXB1ZQKIAaWBbNKHheR*{KQ&6Ag??8;Y4xK^Rl z1YU5rhRnHEVHCiXCqxODLS`<&40}F%+2Xay7Xp8mj5?5EE_{!X(}r2XS!*Qf$C4=3 zjg@P>_|9JXL(#k z$Oc($qzMZm*HgjJ3{|D?J`>Zc$4&dgwYODK?qZoJ%AkyKqb zxW<3VV??ZK7UDW7)un1hu8&eu))LR4IPe0Qg22W;19S%5;7u5XSc#y|wciv8LE~m5 zSCs@kus{a};#gS(lb<2uKDWX*RHe3N?M?blEKlH|E@A6;RhJaXi4!*hRuCvrKVORE zjOa;HNHSY|_)7q7n>$MLCon`M8QMe>>cbGO%Tb$f%`{n?bbr>v^FFEGXz zmt-OwEev=tVJsn*5Z+*h+4xUAI0; za$+Nidn$<=qYnNxnpKiOUO!uDIMDqpnd+9aDYTJwjb@s%7LeV_y|&pMOML`oF`a*u z?$cqX(}^W_UtWl;?$wp(-Ogb6`sBFNimt8`zn=vExsdVCF-}-b?VpU2*Sl})8uk)5 z)+sM+Kdf6t`#seiY5{+9z+P4oJV`+}&=;L?{t~t9C85UVF)xf^WWN$mPVY!?e2zjdj( z;HcT{bOmb3{mGlkL?;w{@aOR^B_3QO<_Xjs^jgLZ4U_hQ?|TJ(_SW;(x3GV;j4K*Z zkcT0N!3|r+naS|IT-THxX&G-HT#U;TELtEZ+ z9X$J;++YW-=tIwb42NB8Gf=#@I`AJ%Z_yp)0V9;D7ED&rs`2lbqdHT1DqzRDH%YB; zgbJoj=d;H-NT>W7(-i#x5!ZjGHc|$RU(PD|6%#9yrh4NhhXV)s3_7mf$ZtGoZrlN? zv`1EArPEFlTZ(dL02kj35!mN{OfiAp<6B}<5hWcNQ|P1PL9Cmr+7V>wnRgL7ZNVB0 zP`CgS1Q@MDJ;3(u%3&5`1Zpjl8$;!EU_*{+zG&E?;qjEMo{FK$p_zaCs@pt63py~R zs@VIIgxc4RMS^ZIVY;?`L{@Fo2fqL&Gs?~Sx&pNsz0UN)(`9r_)6fbXb$gv0r@98I zIy8n>U5^;B%}3X&mZ}m^DCEB;;H45?Z#2pE7dXqHT- zic{Eh!VB`l)?|>2NqbLbHD7XkeY7qiG+RN2l<(cQPAH;cGZFTE@rgt0-6&+TwqMUd#9ZAHoZa#qV<{ z&gl#plKCMv)%^2WXk7{^;X9-lB13}Mk$Kx?)=J6*_tW0NxLQ( zSoi2G=t2{-M-f~-yNB3@Q{MB_mhoSA?}A78Iza|BQYRie6QERgg!$Y*D^P0cFWBqEZ3WSE3_-iw<@ z7YVhpGTA5iA0$peUX>q1wzv^#oG(7#X6dCI2Rq%t?{F@1a?o3INxEBcNs`mEwa)hk z{nw`_{bB!jSi>;Vht{T9O4Fzfwo%n-&t`FQMp8~m)1`k(R_KpsR2){OQBQTpS1<<` z$h4t{f&>35Kz@Qyjvo=6$D2$7PoR8CD-41>2^}9}y(@8m=cKj{@?wa3Xrkp$l-`b@ z2jkjDVk!>8EN02>EYG|~4e^Zh^Pjz!r00-|dv8rR*ic0?4W9+^pg|XOAhY&*hF%~okR@RD!4ia76 zscG3ji_Wb=&jVUEBIL7-unx_OR@4R-Go0QcVwTUV_bJt^yj6 z*Uz=*UxF8TlX48i?_2 zq#-YmX={Sb#v=x5*(sXYJH>WURaqU8){a^xgfLlRdtU&V30{Kx1J2$mXUF+KtXULS}Yp(BQXm&ar63iWl>#DrDOv>_& z_gK|?4M=R;fqgNhR5uv9>Qdv&Hok1*%dT`*kRk9r(gyYwAys$EeWKnS^3EwCTmZ@d zWP(DaZRhKh)V5cmBw)z4<;5d}S_m-8yCC0C@KqG`!S(pO#=XAHX7$~(u!U$;czQIP&EV1L49z;TVdoej6eTi*=Sar%dTMbqNDMGejyuEd6n2h|yHo3E@EX28 znwp*A5u9~i&$?#s7`?VuN0;SLyn%oI9T#2%?N|4mSDeMgF>bH87QbGSPY^R>)Db}x zj~OGQ*XebRy4|C0@1}D)>h(sQlRu4?@iX;V@YxsCXU0~RU001{M0w_GsP|wo z2R>X%g5Bem;b0Q`wVrITnxDNf`qBZO*jjYN4f^Ni?dRYkX8vZ(sOLb=))s$c=!jtl zWgSl_g`~t8iihS>s%}1%*f?R3RZ%yhzK*_G2W3%FPv3-X>_FjU_A*D|sOr?GaK0HL z$1Z`U60${Hg)Tia5Ju#T7mq$Rp>2$MCxhY1$!X{KbTDY~SCg;MHAdaua5y;abce%X zM?79dW_sPh$za$&9t>N?`S^dMZ;XxyE#tlT>$3shk3WKLzkg!fav^z3;M86T#0TVj z^pW)e6QO#-pYTEaeD5x0NGFhsbMf04b)IrmPLbrjfTq@Av|;D7Qu(6JX6E??Gf|fc zOw3=A(TZ8o8SDF?q<%_af{nDIfN?I;tRT6uE=3pTH_Z-2?7GXGtXW Q2LJ&7|C@$z=;D0=07z*TkN^Mx delta 7236 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 S{(k@f0RR7}{E`;peE|UZq(4jm diff --git a/build/openrpc/worker.json.gz b/build/openrpc/worker.json.gz index dcb410ce0056baaef6a154a896b1246547580e6a..5f024f24c2b314b106ed981c82c1a08907ed8b3e 100644 GIT binary patch literal 2579 zcmV+u3hebCiwFP!00000|Li?&bJ{xAe?_C`OLNBo0whiO)+gE9%}ig|8pEY|QcC z2`j2xzyth|t3aa5;Td{AyucQ|2O%kEA;i&9t8?hUJ)T?G12yH2#Dniom=RBd{z+Z1 zIc2T)jx(fS1GcaO(iYTL2F2~|?Sx;malmZ!MxsByad8LYN{PT$2WSEI#_u4TGX;;T zb!Psxkbi`v;(|<|bp^KK;u6Ld^1JB!4H?naSLPGP0fXR*{sas8Jyi5fKgM-V6gfa| z^hY`-Hnh%&>t38&2!!C`06kr^Yb8Jhf+5+uUL0-=|2G&}UUq~Vf9U#FJYKgz6YW@S=i>ksx6 zJfim*9*O6>U@%^s>!weXfxv^KPN!qx&w)46pWe+aJQSRd|C;%L2dDEJ3lql)$s+Dh za36%C8qb)x5-jW|niiNV07OJA8Ic)6EUZET7N6%!64&>g$1QyH18jxUld6E=`vSww z+`4yXVJ50nT%Z-y_xCQ7%2lbc)1-Rfp`tAJxm?HLPP_r}6hLBtf98KlQDYlWyp z1g%W+`vT@h@9mn!|(k&z2dMPDI;%5fDA(3=tG?m7#;M38T=D_G_{Y1JfTrW7@tuc5T=@+xKxj(U5~HbfY4D~emNv+8a4M5&F}4S**|dyYPgFF za?AOOjZ^pvox&pa+@8m@0e30u^<@RA6xU%1#HDP46GTZR!j{eWa2YW!N0-O%p*B&C z8`rpT`{u^=P7AC!WyGaFA=-QqK}|H-9_htjSyx+iX+_2(r>9tb;PS@4F_nWQ#x@u1(ebi>sH@#7Dd zAVJ(~hS3dY!>`i(??d=Mt!d{KMSDrv(}J@j=YGCITm__turiYOA?g|HmJAK6x*)Yi zWrj#CTS)-3=#4t)^^g1c>fjT`CS!yoDyfKh=FK1sBYL$M66>w+O68*pejnw;rO$i}2fOzt(!^O)RyW zvD9;i;Th@JVj*O|(>vWxE#%87>1<-dCN|u+*zmOAeBu&ZAQHYZ38Z=<$t3 zX98D1+%xWZRAss6dHF~{R`d4^Qxf+w%uO-#xr@TPTPxdal`I)QU>kdQcFKbQYcIMT-(@ zOs3F<15D8|O>*+`Nbi3TA|zJsc#YE1inpZJic<`OC*0NwlZ`mgUs4AC(SKH6zGE{-w zq!-l0driFelAYS4f|LEFKbJM%Wu4k?_3BF98o7~Q0`*INXt&8>?wauIg72B-vX|$N zHHN4$M0;n53d(A3&Xl#6A z<6n`D?-o?fq%2~ccykF{FXUXB+ein}3?h&zeY6c+qUBm(nYd%@Q&^qDK9rbPcmC6b^NWs_^> zkr`6t4iG_Oct8?t&K!vpM_P%J2~nUwC?fBWW>hC|H&{pZd)}OF3bj7=j*pAZw2TM# z-f_aKzg1a24P6{)2+AhwI6s4{nEf{vEQHC7a*u!}UkKXdqnodf6$`T3e-U|iZWLBU zHcUVkvLFywYcUxPV;-n^mL1bLx6g_h`pUme+rIC#?J#_5cN^CH71lf{NXFgj;mcRSmv0M#vBjQLeSRf|%MZvr;(yO>{EZBupXTerv?TU?=3MkZhu1-; z(*tB)7Ww7yYYpLzZ>G>^^u3EB<_<^r8$?8Hv^(Yf~6|o?JLBh`mhp zVx1Qw5|DM@ugiNS;<=yc$~_D}MmBX?XVj^YF;CB-xi8S9&P%<8_Y&W4*xR4v+Oq4?CJ3cghMmpNIBOJtW1V{-EqA|7>x=?xn#$0&d zpwSEw^l$TnkQO57j5}bXHxE;4PZ4Df)l<}Cc!8^^t-n3=iG2r7JcmBvFr?su#uCh--Ht`#w>R$l`Rgr;WxLg)`HZzk1&F7rB2O7EDzLO|min>TmK>#8G p5Hn{j5{ZDZ@Py_UneDZuN=`Z_cs{?G|2F^t|NpL25vosm008}t0%8CF delta 2573 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 diff --git a/cli/init_test.go b/cli/init_test.go new file mode 100644 index 000000000..8c343bcfa --- /dev/null +++ b/cli/init_test.go @@ -0,0 +1,9 @@ +package cli + +import ( + logging "github.com/ipfs/go-log/v2" +) + +func init() { + logging.SetLogLevel("watchdog", "ERROR") +} diff --git a/cli/multisig.go b/cli/multisig.go index f6caa6ee0..6ec0dcd5d 100644 --- a/cli/multisig.go +++ b/cli/multisig.go @@ -95,11 +95,13 @@ var msigCreateCmd = &cli.Command{ return ShowHelp(cctx, fmt.Errorf("multisigs must have at least one signer")) } - api, closer, err := GetFullNodeAPI(cctx) + srv, err := GetFullNodeServices(cctx) if err != nil { return err } - defer closer() + defer srv.Close() //nolint:errcheck + + api := srv.FullNodeAPI() ctx := ReqContext(cctx) var addrs []address.Address @@ -146,13 +148,20 @@ var msigCreateCmd = &cli.Command{ gp := types.NewInt(1) - msgCid, err := api.MsigCreate(ctx, required, addrs, d, intVal, sendAddr, gp) + proto, err := api.MsigCreate(ctx, required, addrs, d, intVal, sendAddr, gp) if err != nil { return err } + sm, _, err := srv.PublishMessage(ctx, proto, true) + if err != nil { + return err + } + + msgCid := sm.Cid() + // wait for it to get mined into a block - wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence"))) + wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true) if err != nil { return err } @@ -364,11 +373,13 @@ var msigProposeCmd = &cli.Command{ return ShowHelp(cctx, fmt.Errorf("must either pass three or five arguments")) } - api, closer, err := GetFullNodeAPI(cctx) + srv, err := GetFullNodeServices(cctx) if err != nil { return err } - defer closer() + defer srv.Close() //nolint:errcheck + + api := srv.FullNodeAPI() ctx := ReqContext(cctx) msig, err := address.NewFromString(cctx.Args().Get(0)) @@ -426,14 +437,21 @@ var msigProposeCmd = &cli.Command{ return fmt.Errorf("actor %s is not a multisig actor", msig) } - msgCid, err := api.MsigPropose(ctx, msig, dest, types.BigInt(value), from, method, params) + proto, err := api.MsigPropose(ctx, msig, dest, types.BigInt(value), from, method, params) if err != nil { return err } + sm, _, err := srv.PublishMessage(ctx, proto, true) + if err != nil { + return err + } + + msgCid := sm.Cid() + fmt.Println("send proposal in message: ", msgCid) - wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence"))) + wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true) if err != nil { return err } @@ -481,11 +499,13 @@ var msigApproveCmd = &cli.Command{ return ShowHelp(cctx, fmt.Errorf("usage: msig approve [ ]")) } - api, closer, err := GetFullNodeAPI(cctx) + srv, err := GetFullNodeServices(cctx) if err != nil { return err } - defer closer() + defer srv.Close() //nolint:errcheck + + api := srv.FullNodeAPI() ctx := ReqContext(cctx) msig, err := address.NewFromString(cctx.Args().Get(0)) @@ -515,10 +535,17 @@ var msigApproveCmd = &cli.Command{ var msgCid cid.Cid if cctx.Args().Len() == 2 { - msgCid, err = api.MsigApprove(ctx, msig, txid, from) + proto, err := api.MsigApprove(ctx, msig, txid, from) if err != nil { return err } + + sm, _, err := srv.PublishMessage(ctx, proto, true) + if err != nil { + return err + } + + msgCid = sm.Cid() } else { proposer, err := address.NewFromString(cctx.Args().Get(2)) if err != nil { @@ -558,15 +585,22 @@ var msigApproveCmd = &cli.Command{ params = p } - msgCid, err = api.MsigApproveTxnHash(ctx, msig, txid, proposer, dest, types.BigInt(value), from, method, params) + proto, err := api.MsigApproveTxnHash(ctx, msig, txid, proposer, dest, types.BigInt(value), from, method, params) if err != nil { return err } + + sm, _, err := srv.PublishMessage(ctx, proto, true) + if err != nil { + return err + } + + msgCid = sm.Cid() } fmt.Println("sent approval in message: ", msgCid) - wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence"))) + wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true) if err != nil { return err } @@ -598,11 +632,13 @@ var msigRemoveProposeCmd = &cli.Command{ return ShowHelp(cctx, fmt.Errorf("must pass multisig address and signer address")) } - api, closer, err := GetFullNodeAPI(cctx) + srv, err := GetFullNodeServices(cctx) if err != nil { return err } - defer closer() + defer srv.Close() //nolint:errcheck + + api := srv.FullNodeAPI() ctx := ReqContext(cctx) msig, err := address.NewFromString(cctx.Args().Get(0)) @@ -630,14 +666,21 @@ var msigRemoveProposeCmd = &cli.Command{ from = defaddr } - msgCid, err := api.MsigRemoveSigner(ctx, msig, from, addr, cctx.Bool("decrease-threshold")) + proto, err := api.MsigRemoveSigner(ctx, msig, from, addr, cctx.Bool("decrease-threshold")) if err != nil { return err } + sm, _, err := srv.PublishMessage(ctx, proto, true) + if err != nil { + return err + } + + msgCid := sm.Cid() + fmt.Println("sent remove proposal in message: ", msgCid) - wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence"))) + wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true) if err != nil { return err } @@ -676,11 +719,13 @@ var msigAddProposeCmd = &cli.Command{ return ShowHelp(cctx, fmt.Errorf("must pass multisig address and signer address")) } - api, closer, err := GetFullNodeAPI(cctx) + srv, err := GetFullNodeServices(cctx) if err != nil { return err } - defer closer() + defer srv.Close() //nolint:errcheck + + api := srv.FullNodeAPI() ctx := ReqContext(cctx) msig, err := address.NewFromString(cctx.Args().Get(0)) @@ -708,14 +753,21 @@ var msigAddProposeCmd = &cli.Command{ from = defaddr } - msgCid, err := api.MsigAddPropose(ctx, msig, from, addr, cctx.Bool("increase-threshold")) + proto, err := api.MsigAddPropose(ctx, msig, from, addr, cctx.Bool("increase-threshold")) if err != nil { return err } + sm, _, err := srv.PublishMessage(ctx, proto, true) + if err != nil { + return err + } + + msgCid := sm.Cid() + fmt.Fprintln(cctx.App.Writer, "sent add proposal in message: ", msgCid) - wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence"))) + wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true) if err != nil { return err } @@ -743,11 +795,13 @@ var msigAddApproveCmd = &cli.Command{ return ShowHelp(cctx, fmt.Errorf("must pass multisig address, proposer address, transaction id, new signer address, whether to increase threshold")) } - api, closer, err := GetFullNodeAPI(cctx) + srv, err := GetFullNodeServices(cctx) if err != nil { return err } - defer closer() + defer srv.Close() //nolint:errcheck + + api := srv.FullNodeAPI() ctx := ReqContext(cctx) msig, err := address.NewFromString(cctx.Args().Get(0)) @@ -790,14 +844,21 @@ var msigAddApproveCmd = &cli.Command{ from = defaddr } - msgCid, err := api.MsigAddApprove(ctx, msig, from, txid, prop, newAdd, inc) + proto, err := api.MsigAddApprove(ctx, msig, from, txid, prop, newAdd, inc) if err != nil { return err } + sm, _, err := srv.PublishMessage(ctx, proto, true) + if err != nil { + return err + } + + msgCid := sm.Cid() + fmt.Println("sent add approval in message: ", msgCid) - wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence"))) + wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true) if err != nil { return err } @@ -825,11 +886,13 @@ var msigAddCancelCmd = &cli.Command{ return ShowHelp(cctx, fmt.Errorf("must pass multisig address, transaction id, new signer address, whether to increase threshold")) } - api, closer, err := GetFullNodeAPI(cctx) + srv, err := GetFullNodeServices(cctx) if err != nil { return err } - defer closer() + defer srv.Close() //nolint:errcheck + + api := srv.FullNodeAPI() ctx := ReqContext(cctx) msig, err := address.NewFromString(cctx.Args().Get(0)) @@ -867,14 +930,21 @@ var msigAddCancelCmd = &cli.Command{ from = defaddr } - msgCid, err := api.MsigAddCancel(ctx, msig, from, txid, newAdd, inc) + proto, err := api.MsigAddCancel(ctx, msig, from, txid, newAdd, inc) if err != nil { return err } + sm, _, err := srv.PublishMessage(ctx, proto, true) + if err != nil { + return err + } + + msgCid := sm.Cid() + fmt.Println("sent add cancellation in message: ", msgCid) - wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence"))) + wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true) if err != nil { return err } @@ -902,11 +972,13 @@ var msigSwapProposeCmd = &cli.Command{ return ShowHelp(cctx, fmt.Errorf("must pass multisig address, old signer address, new signer address")) } - api, closer, err := GetFullNodeAPI(cctx) + srv, err := GetFullNodeServices(cctx) if err != nil { return err } - defer closer() + defer srv.Close() //nolint:errcheck + + api := srv.FullNodeAPI() ctx := ReqContext(cctx) msig, err := address.NewFromString(cctx.Args().Get(0)) @@ -939,14 +1011,21 @@ var msigSwapProposeCmd = &cli.Command{ from = defaddr } - msgCid, err := api.MsigSwapPropose(ctx, msig, from, oldAdd, newAdd) + proto, err := api.MsigSwapPropose(ctx, msig, from, oldAdd, newAdd) if err != nil { return err } + sm, _, err := srv.PublishMessage(ctx, proto, true) + if err != nil { + return err + } + + msgCid := sm.Cid() + fmt.Println("sent swap proposal in message: ", msgCid) - wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence"))) + wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true) if err != nil { return err } @@ -974,11 +1053,13 @@ var msigSwapApproveCmd = &cli.Command{ return ShowHelp(cctx, fmt.Errorf("must pass multisig address, proposer address, transaction id, old signer address, new signer address")) } - api, closer, err := GetFullNodeAPI(cctx) + srv, err := GetFullNodeServices(cctx) if err != nil { return err } - defer closer() + defer srv.Close() //nolint:errcheck + + api := srv.FullNodeAPI() ctx := ReqContext(cctx) msig, err := address.NewFromString(cctx.Args().Get(0)) @@ -1021,14 +1102,21 @@ var msigSwapApproveCmd = &cli.Command{ from = defaddr } - msgCid, err := api.MsigSwapApprove(ctx, msig, from, txid, prop, oldAdd, newAdd) + proto, err := api.MsigSwapApprove(ctx, msig, from, txid, prop, oldAdd, newAdd) if err != nil { return err } + sm, _, err := srv.PublishMessage(ctx, proto, true) + if err != nil { + return err + } + + msgCid := sm.Cid() + fmt.Println("sent swap approval in message: ", msgCid) - wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence"))) + wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true) if err != nil { return err } @@ -1056,11 +1144,13 @@ var msigSwapCancelCmd = &cli.Command{ return ShowHelp(cctx, fmt.Errorf("must pass multisig address, transaction id, old signer address, new signer address")) } - api, closer, err := GetFullNodeAPI(cctx) + srv, err := GetFullNodeServices(cctx) if err != nil { return err } - defer closer() + defer srv.Close() //nolint:errcheck + + api := srv.FullNodeAPI() ctx := ReqContext(cctx) msig, err := address.NewFromString(cctx.Args().Get(0)) @@ -1098,14 +1188,21 @@ var msigSwapCancelCmd = &cli.Command{ from = defaddr } - msgCid, err := api.MsigSwapCancel(ctx, msig, from, txid, oldAdd, newAdd) + proto, err := api.MsigSwapCancel(ctx, msig, from, txid, oldAdd, newAdd) if err != nil { return err } + sm, _, err := srv.PublishMessage(ctx, proto, true) + if err != nil { + return err + } + + msgCid := sm.Cid() + fmt.Println("sent swap cancellation in message: ", msgCid) - wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence"))) + wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true) if err != nil { return err } @@ -1133,11 +1230,13 @@ var msigLockProposeCmd = &cli.Command{ return ShowHelp(cctx, fmt.Errorf("must pass multisig address, start epoch, unlock duration, and amount")) } - api, closer, err := GetFullNodeAPI(cctx) + srv, err := GetFullNodeServices(cctx) if err != nil { return err } - defer closer() + defer srv.Close() //nolint:errcheck + + api := srv.FullNodeAPI() ctx := ReqContext(cctx) msig, err := address.NewFromString(cctx.Args().Get(0)) @@ -1185,14 +1284,21 @@ var msigLockProposeCmd = &cli.Command{ return actErr } - msgCid, err := api.MsigPropose(ctx, msig, msig, big.Zero(), from, uint64(multisig.Methods.LockBalance), params) + proto, err := api.MsigPropose(ctx, msig, msig, big.Zero(), from, uint64(multisig.Methods.LockBalance), params) if err != nil { return err } + sm, _, err := srv.PublishMessage(ctx, proto, true) + if err != nil { + return err + } + + msgCid := sm.Cid() + fmt.Println("sent lock proposal in message: ", msgCid) - wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence"))) + wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true) if err != nil { return err } @@ -1220,11 +1326,13 @@ var msigLockApproveCmd = &cli.Command{ return ShowHelp(cctx, fmt.Errorf("must pass multisig address, proposer address, tx id, start epoch, unlock duration, and amount")) } - api, closer, err := GetFullNodeAPI(cctx) + srv, err := GetFullNodeServices(cctx) if err != nil { return err } - defer closer() + defer srv.Close() //nolint:errcheck + + api := srv.FullNodeAPI() ctx := ReqContext(cctx) msig, err := address.NewFromString(cctx.Args().Get(0)) @@ -1282,14 +1390,21 @@ var msigLockApproveCmd = &cli.Command{ return actErr } - msgCid, err := api.MsigApproveTxnHash(ctx, msig, txid, prop, msig, big.Zero(), from, uint64(multisig.Methods.LockBalance), params) + proto, err := api.MsigApproveTxnHash(ctx, msig, txid, prop, msig, big.Zero(), from, uint64(multisig.Methods.LockBalance), params) if err != nil { return err } + sm, _, err := srv.PublishMessage(ctx, proto, true) + if err != nil { + return err + } + + msgCid := sm.Cid() + fmt.Println("sent lock approval in message: ", msgCid) - wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence"))) + wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true) if err != nil { return err } @@ -1317,11 +1432,13 @@ var msigLockCancelCmd = &cli.Command{ return ShowHelp(cctx, fmt.Errorf("must pass multisig address, tx id, start epoch, unlock duration, and amount")) } - api, closer, err := GetFullNodeAPI(cctx) + srv, err := GetFullNodeServices(cctx) if err != nil { return err } - defer closer() + defer srv.Close() //nolint:errcheck + + api := srv.FullNodeAPI() ctx := ReqContext(cctx) msig, err := address.NewFromString(cctx.Args().Get(0)) @@ -1374,14 +1491,21 @@ var msigLockCancelCmd = &cli.Command{ return actErr } - msgCid, err := api.MsigCancel(ctx, msig, txid, msig, big.Zero(), from, uint64(multisig.Methods.LockBalance), params) + proto, err := api.MsigCancel(ctx, msig, txid, msig, big.Zero(), from, uint64(multisig.Methods.LockBalance), params) if err != nil { return err } + sm, _, err := srv.PublishMessage(ctx, proto, true) + if err != nil { + return err + } + + msgCid := sm.Cid() + fmt.Println("sent lock cancellation in message: ", msgCid) - wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence"))) + wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true) if err != nil { return err } @@ -1471,11 +1595,13 @@ var msigProposeThresholdCmd = &cli.Command{ return ShowHelp(cctx, fmt.Errorf("must pass multisig address and new threshold value")) } - api, closer, err := GetFullNodeAPI(cctx) + srv, err := GetFullNodeServices(cctx) if err != nil { return err } - defer closer() + defer srv.Close() //nolint:errcheck + + api := srv.FullNodeAPI() ctx := ReqContext(cctx) msig, err := address.NewFromString(cctx.Args().Get(0)) @@ -1511,14 +1637,21 @@ var msigProposeThresholdCmd = &cli.Command{ return actErr } - msgCid, err := api.MsigPropose(ctx, msig, msig, types.NewInt(0), from, uint64(multisig.Methods.ChangeNumApprovalsThreshold), params) + proto, err := api.MsigPropose(ctx, msig, msig, types.NewInt(0), from, uint64(multisig.Methods.ChangeNumApprovalsThreshold), params) if err != nil { return fmt.Errorf("failed to propose change of threshold: %w", err) } + sm, _, err := srv.PublishMessage(ctx, proto, true) + if err != nil { + return err + } + + msgCid := sm.Cid() + fmt.Println("sent change threshold proposal in message: ", msgCid) - wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence"))) + wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true) if err != nil { return err } diff --git a/cli/send.go b/cli/send.go index 4056a2d61..9efed458a 100644 --- a/cli/send.go +++ b/cli/send.go @@ -147,12 +147,12 @@ var sendCmd = &cli.Command{ } msg, checks, err := srv.PublishMessage(ctx, proto, cctx.Bool("force")) if xerrors.Is(err, ErrCheckFailed) { - proto, err = resolveChecks(ctx, srv, cctx.App.Writer, proto, checks, true) + proto, err := resolveChecks(ctx, srv, cctx.App.Writer, proto, checks, true) if err != nil { return xerrors.Errorf("from UI: %w", err) } - msg, _, err = srv.PublishMessage(ctx, proto, true) + msg, _, err = srv.PublishMessage(ctx, proto, true) //nolint } if err != nil { diff --git a/cli/sending_ui.go b/cli/sending_ui.go index c05f67a97..d58e93c74 100644 --- a/cli/sending_ui.go +++ b/cli/sending_ui.go @@ -41,17 +41,17 @@ func baseFeeFromHints(hint map[string]interface{}) big.Int { } func resolveChecks(ctx context.Context, s ServicesAPI, printer io.Writer, - proto *types.Message, checkGroups [][]api.MessageCheckStatus, - interactive bool) (*types.Message, error) { + proto *api.MessagePrototype, checkGroups [][]api.MessageCheckStatus, + interactive bool) (*api.MessagePrototype, error) { fmt.Fprintf(printer, "Following checks have failed:\n") - printChecks(printer, checkGroups, proto.Cid()) + printChecks(printer, checkGroups, proto.Message.Cid()) if !interactive { return nil, ErrCheckFailed } if interactive { - if feeCapBad, baseFee := isFeeCapProblem(checkGroups, proto.Cid()); feeCapBad { + if feeCapBad, baseFee := isFeeCapProblem(checkGroups, proto.Message.Cid()); feeCapBad { fmt.Fprintf(printer, "Fee of the message can be adjusted\n") if askUser(printer, "Do you wish to do that? [Yes/no]: ", true) { var err error @@ -65,7 +65,7 @@ func resolveChecks(ctx context.Context, s ServicesAPI, printer io.Writer, return nil, err } fmt.Fprintf(printer, "Following checks still failed:\n") - printChecks(printer, checks, proto.Cid()) + printChecks(printer, checks, proto.Message.Cid()) } if !askUser(printer, "Do you wish to send this message? [yes/No]: ", false) { @@ -125,15 +125,15 @@ func isFeeCapProblem(checkGroups [][]api.MessageCheckStatus, protoCid cid.Cid) ( return yes, baseFee } -func runFeeCapAdjustmentUI(proto *types.Message, baseFee abi.TokenAmount) (*types.Message, error) { +func runFeeCapAdjustmentUI(proto *api.MessagePrototype, baseFee abi.TokenAmount) (*api.MessagePrototype, error) { t, err := imtui.NewTui() if err != nil { return nil, err } - maxFee := big.Mul(proto.GasFeeCap, big.NewInt(proto.GasLimit)) + maxFee := big.Mul(proto.Message.GasFeeCap, big.NewInt(proto.Message.GasLimit)) send := false - t.SetScene(ui(baseFee, proto.GasLimit, &maxFee, &send)) + t.SetScene(ui(baseFee, proto.Message.GasLimit, &maxFee, &send)) err = t.Run() if err != nil { @@ -143,7 +143,7 @@ func runFeeCapAdjustmentUI(proto *types.Message, baseFee abi.TokenAmount) (*type return nil, fmt.Errorf("aborted by user") } - proto.GasFeeCap = big.Div(maxFee, big.NewInt(proto.GasLimit)) + proto.Message.GasFeeCap = big.Div(maxFee, big.NewInt(proto.Message.GasLimit)) return proto, nil } diff --git a/cli/services.go b/cli/services.go index 6fc4afe58..82d95397b 100644 --- a/cli/services.go +++ b/cli/services.go @@ -22,22 +22,24 @@ import ( //go:generate go run github.com/golang/mock/mockgen -destination=servicesmock_test.go -package=cli -self_package github.com/filecoin-project/lotus/cli . ServicesAPI type ServicesAPI interface { + FullNodeAPI() api.FullNode + GetBaseFee(ctx context.Context) (abi.TokenAmount, error) // MessageForSend creates a prototype of a message based on SendParams - MessageForSend(ctx context.Context, params SendParams) (*types.Message, error) + MessageForSend(ctx context.Context, params SendParams) (*api.MessagePrototype, error) // DecodeTypedParamsFromJSON takes in information needed to identify a method and converts JSON // parameters to bytes of their CBOR encoding DecodeTypedParamsFromJSON(ctx context.Context, to address.Address, method abi.MethodNum, paramstr string) ([]byte, error) - RunChecksForPrototype(ctx context.Context, prototype *types.Message) ([][]api.MessageCheckStatus, error) + RunChecksForPrototype(ctx context.Context, prototype *api.MessagePrototype) ([][]api.MessageCheckStatus, error) // PublishMessage takes in a message prototype and publishes it // before publishing the message, it runs checks on the node, message and mpool to verify that // message is valid and won't be stuck. // if `force` is true, it skips the checks - PublishMessage(ctx context.Context, prototype *types.Message, interactive bool) (*types.SignedMessage, [][]api.MessageCheckStatus, error) + PublishMessage(ctx context.Context, prototype *api.MessagePrototype, interactive bool) (*types.SignedMessage, [][]api.MessageCheckStatus, error) // Close ends the session of services and disconnects from RPC, using Services after Close is called // most likely will result in an error @@ -50,6 +52,10 @@ type ServicesImpl struct { closer jsonrpc.ClientCloser } +func (s *ServicesImpl) FullNodeAPI() api.FullNode { + return s.api +} + func (s *ServicesImpl) Close() error { if s.closer == nil { return xerrors.Errorf("Services already closed") @@ -102,15 +108,24 @@ type CheckInfo struct { var ErrCheckFailed = fmt.Errorf("check has failed") -func (s *ServicesImpl) RunChecksForPrototype(ctx context.Context, prototype *types.Message) ([][]api.MessageCheckStatus, error) { +func (s *ServicesImpl) RunChecksForPrototype(ctx context.Context, prototype *api.MessagePrototype) ([][]api.MessageCheckStatus, error) { + if !prototype.ValidNonce { + nonce, err := s.api.MpoolGetNonce(ctx, prototype.Message.From) + if err != nil { + return nil, xerrors.Errorf("mpool get nonce: %w", err) + } + prototype.Message.Nonce = nonce + prototype.ValidNonce = true + } + var outChecks [][]api.MessageCheckStatus - checks, err := s.api.MpoolCheckMessages(ctx, []*types.Message{prototype}) + checks, err := s.api.MpoolCheckMessages(ctx, []*types.Message{&prototype.Message}) if err != nil { return nil, xerrors.Errorf("message check: %w", err) } outChecks = append(outChecks, checks...) - checks, err = s.api.MpoolCheckPendingMessages(ctx, prototype.From) + checks, err = s.api.MpoolCheckPendingMessages(ctx, prototype.Message.From) if err != nil { return nil, xerrors.Errorf("pending mpool check: %w", err) } @@ -123,27 +138,30 @@ func (s *ServicesImpl) RunChecksForPrototype(ctx context.Context, prototype *typ // Errors with ErrCheckFailed if any of the checks fail // First group of checks is related to the message prototype func (s *ServicesImpl) PublishMessage(ctx context.Context, - prototype *types.Message, force bool) (*types.SignedMessage, [][]api.MessageCheckStatus, error) { + prototype *api.MessagePrototype, force bool) (*types.SignedMessage, [][]api.MessageCheckStatus, error) { - gasedMsg, err := s.api.GasEstimateMessageGas(ctx, prototype, nil, types.EmptyTSK) + gasedMsg, err := s.api.GasEstimateMessageGas(ctx, &prototype.Message, nil, types.EmptyTSK) if err != nil { return nil, nil, xerrors.Errorf("estimating gas: %w", err) } - *prototype = *gasedMsg + prototype.Message = *gasedMsg if !force { checks, err := s.RunChecksForPrototype(ctx, prototype) if err != nil { return nil, nil, xerrors.Errorf("running checks: %w", err) } - if len(checks) != 0 { - return nil, checks, ErrCheckFailed + for _, chks := range checks { + for _, c := range chks { + if !c.OK { + return nil, checks, ErrCheckFailed + } + } } } - //TODO: message prototype needs to have "IsNonceSet" - if prototype.Nonce != 0 { - sm, err := s.api.WalletSignMessage(ctx, prototype.From, prototype) + if prototype.ValidNonce { + sm, err := s.api.WalletSignMessage(ctx, prototype.Message.From, &prototype.Message) if err != nil { return nil, nil, err } @@ -155,7 +173,7 @@ func (s *ServicesImpl) PublishMessage(ctx context.Context, return sm, nil, nil } - sm, err := s.api.MpoolPushMessage(ctx, prototype, nil) + sm, err := s.api.MpoolPushMessage(ctx, &prototype.Message, nil) if err != nil { return nil, nil, err } @@ -177,7 +195,7 @@ type SendParams struct { Params []byte } -func (s *ServicesImpl) MessageForSend(ctx context.Context, params SendParams) (*types.Message, error) { +func (s *ServicesImpl) MessageForSend(ctx context.Context, params SendParams) (*api.MessagePrototype, error) { if params.From == address.Undef { defaddr, err := s.api.WalletDefaultAddress(ctx) if err != nil { @@ -186,7 +204,7 @@ func (s *ServicesImpl) MessageForSend(ctx context.Context, params SendParams) (* params.From = defaddr } - msg := &types.Message{ + msg := types.Message{ From: params.From, To: params.To, Value: params.Val, @@ -210,9 +228,15 @@ func (s *ServicesImpl) MessageForSend(ctx context.Context, params SendParams) (* } else { msg.GasLimit = 0 } + validNonce := false if params.Nonce != nil { msg.Nonce = *params.Nonce + validNonce = true } - return msg, nil + prototype := &api.MessagePrototype{ + Message: msg, + ValidNonce: validNonce, + } + return prototype, nil } diff --git a/cli/services_send_test.go b/cli/services_send_test.go index faa052e0c..3437e90d9 100644 --- a/cli/services_send_test.go +++ b/cli/services_send_test.go @@ -7,12 +7,10 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/big" - "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/lotus/api" mocks "github.com/filecoin-project/lotus/api/v0api/v0mocks" types "github.com/filecoin-project/lotus/chain/types" gomock "github.com/golang/mock/gomock" - cid "github.com/ipfs/go-cid" "github.com/stretchr/testify/assert" ) @@ -61,22 +59,23 @@ func setupMockSrvcs(t *testing.T) (*ServicesImpl, *mocks.MockFullNode) { return srvcs, mockApi } -func fakeSign(msg *types.Message) *types.SignedMessage { - return &types.SignedMessage{ - Message: *msg, - Signature: crypto.Signature{Type: crypto.SigTypeSecp256k1, Data: make([]byte, 32)}, - } -} +// linter doesn't like dead code, so these are commented out. +// func fakeSign(msg *types.Message) *types.SignedMessage { +// return &types.SignedMessage{ +// Message: *msg, +// Signature: crypto.Signature{Type: crypto.SigTypeSecp256k1, Data: make([]byte, 32)}, +// } +// } -func makeMessageSigner() (*cid.Cid, interface{}) { - smCid := cid.Undef - return &smCid, - func(_ context.Context, msg *types.Message, _ *api.MessageSendSpec) (*types.SignedMessage, error) { - sm := fakeSign(msg) - smCid = sm.Cid() - return sm, nil - } -} +// func makeMessageSigner() (*cid.Cid, interface{}) { +// smCid := cid.Undef +// return &smCid, +// func(_ context.Context, msg *types.Message, _ *api.MessageSendSpec) (*types.SignedMessage, error) { +// sm := fakeSign(msg) +// smCid = sm.Cid() +// return sm, nil +// } +// } type MessageMatcher SendParams @@ -84,11 +83,13 @@ var _ gomock.Matcher = MessageMatcher{} // Matches returns whether x is a match. func (mm MessageMatcher) Matches(x interface{}) bool { - m, ok := x.(*types.Message) + proto, ok := x.(*api.MessagePrototype) if !ok { return false } + m := &proto.Message + if mm.From != address.Undef && mm.From != m.From { return false } diff --git a/cli/servicesmock_test.go b/cli/servicesmock_test.go index 4d1f589cd..0a353c153 100644 --- a/cli/servicesmock_test.go +++ b/cli/servicesmock_test.go @@ -67,6 +67,20 @@ func (mr *MockServicesAPIMockRecorder) DecodeTypedParamsFromJSON(arg0, arg1, arg return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecodeTypedParamsFromJSON", reflect.TypeOf((*MockServicesAPI)(nil).DecodeTypedParamsFromJSON), arg0, arg1, arg2, arg3) } +// FullNodeAPI mocks base method +func (m *MockServicesAPI) FullNodeAPI() api.FullNode { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FullNodeAPI") + ret0, _ := ret[0].(api.FullNode) + return ret0 +} + +// FullNodeAPI indicates an expected call of FullNodeAPI +func (mr *MockServicesAPIMockRecorder) FullNodeAPI() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FullNodeAPI", reflect.TypeOf((*MockServicesAPI)(nil).FullNodeAPI)) +} + // GetBaseFee mocks base method func (m *MockServicesAPI) GetBaseFee(arg0 context.Context) (big.Int, error) { m.ctrl.T.Helper() @@ -83,10 +97,10 @@ func (mr *MockServicesAPIMockRecorder) GetBaseFee(arg0 interface{}) *gomock.Call } // MessageForSend mocks base method -func (m *MockServicesAPI) MessageForSend(arg0 context.Context, arg1 SendParams) (*types.Message, error) { +func (m *MockServicesAPI) MessageForSend(arg0 context.Context, arg1 SendParams) (*api.MessagePrototype, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "MessageForSend", arg0, arg1) - ret0, _ := ret[0].(*types.Message) + ret0, _ := ret[0].(*api.MessagePrototype) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -98,7 +112,7 @@ func (mr *MockServicesAPIMockRecorder) MessageForSend(arg0, arg1 interface{}) *g } // PublishMessage mocks base method -func (m *MockServicesAPI) PublishMessage(arg0 context.Context, arg1 *types.Message, arg2 bool) (*types.SignedMessage, [][]api.MessageCheckStatus, error) { +func (m *MockServicesAPI) PublishMessage(arg0 context.Context, arg1 *api.MessagePrototype, arg2 bool) (*types.SignedMessage, [][]api.MessageCheckStatus, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "PublishMessage", arg0, arg1, arg2) ret0, _ := ret[0].(*types.SignedMessage) @@ -114,7 +128,7 @@ func (mr *MockServicesAPIMockRecorder) PublishMessage(arg0, arg1, arg2 interface } // RunChecksForPrototype mocks base method -func (m *MockServicesAPI) RunChecksForPrototype(arg0 context.Context, arg1 *types.Message) ([][]api.MessageCheckStatus, error) { +func (m *MockServicesAPI) RunChecksForPrototype(arg0 context.Context, arg1 *api.MessagePrototype) ([][]api.MessageCheckStatus, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "RunChecksForPrototype", arg0, arg1) ret0, _ := ret[0].([][]api.MessageCheckStatus) diff --git a/cmd/lotus-gateway/endtoend_test.go b/cmd/lotus-gateway/endtoend_test.go index 084218b24..fa1004df3 100644 --- a/cmd/lotus-gateway/endtoend_test.go +++ b/cmd/lotus-gateway/endtoend_test.go @@ -18,6 +18,8 @@ import ( "github.com/stretchr/testify/require" "golang.org/x/xerrors" + "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" @@ -102,7 +104,28 @@ func TestWalletMsig(t *testing.T) { // Create an msig with three of the addresses and threshold of two sigs msigAddrs := walletAddrs[:3] amt := types.NewInt(1000) - addProposal, err := lite.MsigCreate(ctx, 2, msigAddrs, abi.ChainEpoch(50), amt, liteWalletAddr, types.NewInt(0)) + proto, err := lite.MsigCreate(ctx, 2, msigAddrs, abi.ChainEpoch(50), amt, liteWalletAddr, types.NewInt(0)) + require.NoError(t, err) + + doSend := func(proto *api.MessagePrototype) (cid.Cid, error) { + if proto.ValidNonce { + sm, err := lite.WalletSignMessage(ctx, proto.Message.From, &proto.Message) + if err != nil { + return cid.Undef, err + } + + return lite.MpoolPush(ctx, sm) + } + + sm, err := lite.MpoolPushMessage(ctx, &proto.Message, nil) + if err != nil { + return cid.Undef, err + } + + return sm.Cid(), nil + } + + addProposal, err := doSend(proto) require.NoError(t, err) res, err := lite.StateWaitMsg(ctx, addProposal, 1, api.LookbackNoLimit, true) @@ -122,7 +145,10 @@ func TestWalletMsig(t *testing.T) { require.Less(t, msigBalance.Int64(), amt.Int64()) // Propose to add a new address to the msig - addProposal, err = lite.MsigAddPropose(ctx, msig, walletAddrs[0], walletAddrs[3], false) + proto, err = lite.MsigAddPropose(ctx, msig, walletAddrs[0], walletAddrs[3], false) + require.NoError(t, err) + + addProposal, err = doSend(proto) require.NoError(t, err) res, err = lite.StateWaitMsg(ctx, addProposal, 1, api.LookbackNoLimit, true) @@ -136,7 +162,10 @@ func TestWalletMsig(t *testing.T) { // Approve proposal (proposer is first (implicit) signer, approver is // second signer txnID := uint64(proposeReturn.TxnID) - approval1, err := lite.MsigAddApprove(ctx, msig, walletAddrs[1], txnID, walletAddrs[0], walletAddrs[3], false) + proto, err = lite.MsigAddApprove(ctx, msig, walletAddrs[1], txnID, walletAddrs[0], walletAddrs[3], false) + require.NoError(t, err) + + approval1, err := doSend(proto) require.NoError(t, err) res, err = lite.StateWaitMsg(ctx, approval1, 1, api.LookbackNoLimit, true) diff --git a/cmd/lotus-shed/verifreg.go b/cmd/lotus-shed/verifreg.go index 426827ad2..1b0b57ca0 100644 --- a/cmd/lotus-shed/verifreg.go +++ b/cmd/lotus-shed/verifreg.go @@ -67,11 +67,13 @@ var verifRegAddVerifierCmd = &cli.Command{ return err } - api, closer, err := lcli.GetFullNodeAPI(cctx) + srv, err := lcli.GetFullNodeServices(cctx) if err != nil { return err } - defer closer() + defer srv.Close() //nolint:errcheck + + api := srv.FullNodeAPI() ctx := lcli.ReqContext(cctx) vrk, err := api.StateVerifiedRegistryRootKey(ctx, types.EmptyTSK) @@ -79,14 +81,21 @@ var verifRegAddVerifierCmd = &cli.Command{ return err } - smsg, err := api.MsigPropose(ctx, vrk, verifreg.Address, big.Zero(), sender, uint64(verifreg.Methods.AddVerifier), params) + proto, err := api.MsigPropose(ctx, vrk, verifreg.Address, big.Zero(), sender, uint64(verifreg.Methods.AddVerifier), params) if err != nil { return err } - fmt.Printf("message sent, now waiting on cid: %s\n", smsg) + sm, _, err := srv.PublishMessage(ctx, proto, false) + if err != nil { + return err + } - mwait, err := api.StateWaitMsg(ctx, smsg, build.MessageConfidence) + msgCid := sm.Cid() + + fmt.Printf("message sent, now waiting on cid: %s\n", msgCid) + + mwait, err := api.StateWaitMsg(ctx, msgCid, build.MessageConfidence) if err != nil { return err } diff --git a/documentation/en/api-v0-methods.md b/documentation/en/api-v0-methods.md index e90ba7d6a..3c5356a56 100644 --- a/documentation/en/api-v0-methods.md +++ b/documentation/en/api-v0-methods.md @@ -82,9 +82,6 @@ * [MpoolBatchPush](#MpoolBatchPush) * [MpoolBatchPushMessage](#MpoolBatchPushMessage) * [MpoolBatchPushUntrusted](#MpoolBatchPushUntrusted) - * [MpoolCheckMessages](#MpoolCheckMessages) - * [MpoolCheckPendingMessages](#MpoolCheckPendingMessages) - * [MpoolCheckReplaceMessages](#MpoolCheckReplaceMessages) * [MpoolClear](#MpoolClear) * [MpoolGetConfig](#MpoolGetConfig) * [MpoolGetNonce](#MpoolGetNonce) @@ -1997,51 +1994,6 @@ Inputs: Response: `null` -### MpoolCheckMessages -MpoolCheckMessages performs logical checks on a batch of messages - - -Perms: read - -Inputs: -```json -[ - null -] -``` - -Response: `null` - -### MpoolCheckPendingMessages -MpoolCheckPendingMessages performs logical checks for all pending messages from a given address - - -Perms: read - -Inputs: -```json -[ - "f01234" -] -``` - -Response: `null` - -### MpoolCheckReplaceMessages -MpoolCheckMessages performs logical checks on pending messages with replacement - - -Perms: read - -Inputs: -```json -[ - null -] -``` - -Response: `null` - ### MpoolClear MpoolClear clears pending messages from the mpool diff --git a/documentation/en/api-v1-unstable-methods.md b/documentation/en/api-v1-unstable-methods.md index a0ee6fcf3..83c2d6d41 100644 --- a/documentation/en/api-v1-unstable-methods.md +++ b/documentation/en/api-v1-unstable-methods.md @@ -82,6 +82,9 @@ * [MpoolBatchPush](#MpoolBatchPush) * [MpoolBatchPushMessage](#MpoolBatchPushMessage) * [MpoolBatchPushUntrusted](#MpoolBatchPushUntrusted) + * [MpoolCheckMessages](#MpoolCheckMessages) + * [MpoolCheckPendingMessages](#MpoolCheckPendingMessages) + * [MpoolCheckReplaceMessages](#MpoolCheckReplaceMessages) * [MpoolClear](#MpoolClear) * [MpoolGetConfig](#MpoolGetConfig) * [MpoolGetNonce](#MpoolGetNonce) @@ -1993,6 +1996,51 @@ Inputs: Response: `null` +### MpoolCheckMessages +MpoolCheckMessages performs logical checks on a batch of messages + + +Perms: read + +Inputs: +```json +[ + null +] +``` + +Response: `null` + +### MpoolCheckPendingMessages +MpoolCheckPendingMessages performs logical checks for all pending messages from a given address + + +Perms: read + +Inputs: +```json +[ + "f01234" +] +``` + +Response: `null` + +### MpoolCheckReplaceMessages +MpoolCheckReplaceMessages performs logical checks on pending messages with replacement + + +Perms: read + +Inputs: +```json +[ + null +] +``` + +Response: `null` + ### MpoolClear MpoolClear clears pending messages from the mpool @@ -2326,7 +2374,22 @@ Inputs: Response: ```json { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + "Message": { + "Version": 42, + "To": "f01234", + "From": "f01234", + "Nonce": 42, + "Value": "0", + "GasLimit": 9, + "GasFeeCap": "0", + "GasPremium": "0", + "Method": 1, + "Params": "Ynl0ZSBhcnJheQ==", + "CID": { + "/": "bafy2bzacebbpdegvr3i4cosewthysg5xkxpqfn2wfcz6mv2hmoktwbdxkax4s" + } + }, + "ValidNonce": true } ``` @@ -2352,7 +2415,22 @@ Inputs: Response: ```json { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + "Message": { + "Version": 42, + "To": "f01234", + "From": "f01234", + "Nonce": 42, + "Value": "0", + "GasLimit": 9, + "GasFeeCap": "0", + "GasPremium": "0", + "Method": 1, + "Params": "Ynl0ZSBhcnJheQ==", + "CID": { + "/": "bafy2bzacebbpdegvr3i4cosewthysg5xkxpqfn2wfcz6mv2hmoktwbdxkax4s" + } + }, + "ValidNonce": true } ``` @@ -2377,7 +2455,22 @@ Inputs: Response: ```json { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + "Message": { + "Version": 42, + "To": "f01234", + "From": "f01234", + "Nonce": 42, + "Value": "0", + "GasLimit": 9, + "GasFeeCap": "0", + "GasPremium": "0", + "Method": 1, + "Params": "Ynl0ZSBhcnJheQ==", + "CID": { + "/": "bafy2bzacebbpdegvr3i4cosewthysg5xkxpqfn2wfcz6mv2hmoktwbdxkax4s" + } + }, + "ValidNonce": true } ``` @@ -2400,7 +2493,22 @@ Inputs: Response: ```json { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + "Message": { + "Version": 42, + "To": "f01234", + "From": "f01234", + "Nonce": 42, + "Value": "0", + "GasLimit": 9, + "GasFeeCap": "0", + "GasPremium": "0", + "Method": 1, + "Params": "Ynl0ZSBhcnJheQ==", + "CID": { + "/": "bafy2bzacebbpdegvr3i4cosewthysg5xkxpqfn2wfcz6mv2hmoktwbdxkax4s" + } + }, + "ValidNonce": true } ``` @@ -2432,7 +2540,22 @@ Inputs: Response: ```json { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + "Message": { + "Version": 42, + "To": "f01234", + "From": "f01234", + "Nonce": 42, + "Value": "0", + "GasLimit": 9, + "GasFeeCap": "0", + "GasPremium": "0", + "Method": 1, + "Params": "Ynl0ZSBhcnJheQ==", + "CID": { + "/": "bafy2bzacebbpdegvr3i4cosewthysg5xkxpqfn2wfcz6mv2hmoktwbdxkax4s" + } + }, + "ValidNonce": true } ``` @@ -2460,7 +2583,22 @@ Inputs: Response: ```json { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + "Message": { + "Version": 42, + "To": "f01234", + "From": "f01234", + "Nonce": 42, + "Value": "0", + "GasLimit": 9, + "GasFeeCap": "0", + "GasPremium": "0", + "Method": 1, + "Params": "Ynl0ZSBhcnJheQ==", + "CID": { + "/": "bafy2bzacebbpdegvr3i4cosewthysg5xkxpqfn2wfcz6mv2hmoktwbdxkax4s" + } + }, + "ValidNonce": true } ``` @@ -2487,7 +2625,22 @@ Inputs: Response: ```json { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + "Message": { + "Version": 42, + "To": "f01234", + "From": "f01234", + "Nonce": 42, + "Value": "0", + "GasLimit": 9, + "GasFeeCap": "0", + "GasPremium": "0", + "Method": 1, + "Params": "Ynl0ZSBhcnJheQ==", + "CID": { + "/": "bafy2bzacebbpdegvr3i4cosewthysg5xkxpqfn2wfcz6mv2hmoktwbdxkax4s" + } + }, + "ValidNonce": true } ``` @@ -2624,7 +2777,22 @@ Inputs: Response: ```json { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + "Message": { + "Version": 42, + "To": "f01234", + "From": "f01234", + "Nonce": 42, + "Value": "0", + "GasLimit": 9, + "GasFeeCap": "0", + "GasPremium": "0", + "Method": 1, + "Params": "Ynl0ZSBhcnJheQ==", + "CID": { + "/": "bafy2bzacebbpdegvr3i4cosewthysg5xkxpqfn2wfcz6mv2hmoktwbdxkax4s" + } + }, + "ValidNonce": true } ``` @@ -2651,7 +2819,22 @@ Inputs: Response: ```json { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + "Message": { + "Version": 42, + "To": "f01234", + "From": "f01234", + "Nonce": 42, + "Value": "0", + "GasLimit": 9, + "GasFeeCap": "0", + "GasPremium": "0", + "Method": 1, + "Params": "Ynl0ZSBhcnJheQ==", + "CID": { + "/": "bafy2bzacebbpdegvr3i4cosewthysg5xkxpqfn2wfcz6mv2hmoktwbdxkax4s" + } + }, + "ValidNonce": true } ``` @@ -2678,7 +2861,22 @@ Inputs: Response: ```json { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + "Message": { + "Version": 42, + "To": "f01234", + "From": "f01234", + "Nonce": 42, + "Value": "0", + "GasLimit": 9, + "GasFeeCap": "0", + "GasPremium": "0", + "Method": 1, + "Params": "Ynl0ZSBhcnJheQ==", + "CID": { + "/": "bafy2bzacebbpdegvr3i4cosewthysg5xkxpqfn2wfcz6mv2hmoktwbdxkax4s" + } + }, + "ValidNonce": true } ``` @@ -2704,7 +2902,22 @@ Inputs: Response: ```json { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + "Message": { + "Version": 42, + "To": "f01234", + "From": "f01234", + "Nonce": 42, + "Value": "0", + "GasLimit": 9, + "GasFeeCap": "0", + "GasPremium": "0", + "Method": 1, + "Params": "Ynl0ZSBhcnJheQ==", + "CID": { + "/": "bafy2bzacebbpdegvr3i4cosewthysg5xkxpqfn2wfcz6mv2hmoktwbdxkax4s" + } + }, + "ValidNonce": true } ``` @@ -2729,7 +2942,22 @@ Inputs: Response: ```json { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + "Message": { + "Version": 42, + "To": "f01234", + "From": "f01234", + "Nonce": 42, + "Value": "0", + "GasLimit": 9, + "GasFeeCap": "0", + "GasPremium": "0", + "Method": 1, + "Params": "Ynl0ZSBhcnJheQ==", + "CID": { + "/": "bafy2bzacebbpdegvr3i4cosewthysg5xkxpqfn2wfcz6mv2hmoktwbdxkax4s" + } + }, + "ValidNonce": true } ``` diff --git a/node/impl/full/multisig.go b/node/impl/full/multisig.go index 9c5f683c4..e44509d7c 100644 --- a/node/impl/full/multisig.go +++ b/node/impl/full/multisig.go @@ -14,7 +14,6 @@ import ( multisig2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/multisig" - "github.com/ipfs/go-cid" "go.uber.org/fx" "golang.org/x/xerrors" ) @@ -37,134 +36,129 @@ func (a *MsigAPI) messageBuilder(ctx context.Context, from address.Address) (mul // TODO: remove gp (gasPrice) from arguments // TODO: Add "vesting start" to arguments. -func (a *MsigAPI) MsigCreate(ctx context.Context, req uint64, addrs []address.Address, duration abi.ChainEpoch, val types.BigInt, src address.Address, gp types.BigInt) (cid.Cid, error) { +func (a *MsigAPI) MsigCreate(ctx context.Context, req uint64, addrs []address.Address, duration abi.ChainEpoch, val types.BigInt, src address.Address, gp types.BigInt) (*api.MessagePrototype, error) { mb, err := a.messageBuilder(ctx, src) if err != nil { - return cid.Undef, err + return nil, err } msg, err := mb.Create(addrs, req, 0, duration, val) if err != nil { - return cid.Undef, err + return nil, err } - // send the message out to the network - smsg, err := a.MpoolAPI.MpoolPushMessage(ctx, msg, nil) - if err != nil { - return cid.Undef, err - } - - return smsg.Cid(), nil + return &api.MessagePrototype{ + Message: *msg, + ValidNonce: false, + }, nil } -func (a *MsigAPI) MsigPropose(ctx context.Context, msig address.Address, to address.Address, amt types.BigInt, src address.Address, method uint64, params []byte) (cid.Cid, error) { +func (a *MsigAPI) MsigPropose(ctx context.Context, msig address.Address, to address.Address, amt types.BigInt, src address.Address, method uint64, params []byte) (*api.MessagePrototype, error) { mb, err := a.messageBuilder(ctx, src) if err != nil { - return cid.Undef, err + return nil, err } msg, err := mb.Propose(msig, to, amt, abi.MethodNum(method), params) if err != nil { - return cid.Undef, xerrors.Errorf("failed to create proposal: %w", err) + return nil, xerrors.Errorf("failed to create proposal: %w", err) } - smsg, err := a.MpoolAPI.MpoolPushMessage(ctx, msg, nil) - if err != nil { - return cid.Undef, xerrors.Errorf("failed to push message: %w", err) - } - - return smsg.Cid(), nil + return &api.MessagePrototype{ + Message: *msg, + ValidNonce: false, + }, nil } -func (a *MsigAPI) MsigAddPropose(ctx context.Context, msig address.Address, src address.Address, newAdd address.Address, inc bool) (cid.Cid, error) { +func (a *MsigAPI) MsigAddPropose(ctx context.Context, msig address.Address, src address.Address, newAdd address.Address, inc bool) (*api.MessagePrototype, error) { enc, actErr := serializeAddParams(newAdd, inc) if actErr != nil { - return cid.Undef, actErr + return nil, actErr } return a.MsigPropose(ctx, msig, msig, big.Zero(), src, uint64(multisig.Methods.AddSigner), enc) } -func (a *MsigAPI) MsigAddApprove(ctx context.Context, msig address.Address, src address.Address, txID uint64, proposer address.Address, newAdd address.Address, inc bool) (cid.Cid, error) { +func (a *MsigAPI) MsigAddApprove(ctx context.Context, msig address.Address, src address.Address, txID uint64, proposer address.Address, newAdd address.Address, inc bool) (*api.MessagePrototype, error) { enc, actErr := serializeAddParams(newAdd, inc) if actErr != nil { - return cid.Undef, actErr + return nil, actErr } return a.MsigApproveTxnHash(ctx, msig, txID, proposer, msig, big.Zero(), src, uint64(multisig.Methods.AddSigner), enc) } -func (a *MsigAPI) MsigAddCancel(ctx context.Context, msig address.Address, src address.Address, txID uint64, newAdd address.Address, inc bool) (cid.Cid, error) { +func (a *MsigAPI) MsigAddCancel(ctx context.Context, msig address.Address, src address.Address, txID uint64, newAdd address.Address, inc bool) (*api.MessagePrototype, error) { enc, actErr := serializeAddParams(newAdd, inc) if actErr != nil { - return cid.Undef, actErr + return nil, actErr } return a.MsigCancel(ctx, msig, txID, msig, big.Zero(), src, uint64(multisig.Methods.AddSigner), enc) } -func (a *MsigAPI) MsigSwapPropose(ctx context.Context, msig address.Address, src address.Address, oldAdd address.Address, newAdd address.Address) (cid.Cid, error) { +func (a *MsigAPI) MsigSwapPropose(ctx context.Context, msig address.Address, src address.Address, oldAdd address.Address, newAdd address.Address) (*api.MessagePrototype, error) { enc, actErr := serializeSwapParams(oldAdd, newAdd) if actErr != nil { - return cid.Undef, actErr + return nil, actErr } return a.MsigPropose(ctx, msig, msig, big.Zero(), src, uint64(multisig.Methods.SwapSigner), enc) } -func (a *MsigAPI) MsigSwapApprove(ctx context.Context, msig address.Address, src address.Address, txID uint64, proposer address.Address, oldAdd address.Address, newAdd address.Address) (cid.Cid, error) { +func (a *MsigAPI) MsigSwapApprove(ctx context.Context, msig address.Address, src address.Address, txID uint64, proposer address.Address, oldAdd address.Address, newAdd address.Address) (*api.MessagePrototype, error) { enc, actErr := serializeSwapParams(oldAdd, newAdd) if actErr != nil { - return cid.Undef, actErr + return nil, actErr } return a.MsigApproveTxnHash(ctx, msig, txID, proposer, msig, big.Zero(), src, uint64(multisig.Methods.SwapSigner), enc) } -func (a *MsigAPI) MsigSwapCancel(ctx context.Context, msig address.Address, src address.Address, txID uint64, oldAdd address.Address, newAdd address.Address) (cid.Cid, error) { +func (a *MsigAPI) MsigSwapCancel(ctx context.Context, msig address.Address, src address.Address, txID uint64, oldAdd address.Address, newAdd address.Address) (*api.MessagePrototype, error) { enc, actErr := serializeSwapParams(oldAdd, newAdd) if actErr != nil { - return cid.Undef, actErr + return nil, actErr } return a.MsigCancel(ctx, msig, txID, msig, big.Zero(), src, uint64(multisig.Methods.SwapSigner), enc) } -func (a *MsigAPI) MsigApprove(ctx context.Context, msig address.Address, txID uint64, src address.Address) (cid.Cid, error) { +func (a *MsigAPI) MsigApprove(ctx context.Context, msig address.Address, txID uint64, src address.Address) (*api.MessagePrototype, error) { return a.msigApproveOrCancelSimple(ctx, api.MsigApprove, msig, txID, src) } -func (a *MsigAPI) 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) { +func (a *MsigAPI) 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) (*api.MessagePrototype, error) { return a.msigApproveOrCancelTxnHash(ctx, api.MsigApprove, msig, txID, proposer, to, amt, src, method, params) } -func (a *MsigAPI) 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) { +func (a *MsigAPI) MsigCancel(ctx context.Context, msig address.Address, txID uint64, to address.Address, amt types.BigInt, src address.Address, method uint64, params []byte) (*api.MessagePrototype, error) { return a.msigApproveOrCancelTxnHash(ctx, api.MsigCancel, msig, txID, src, to, amt, src, method, params) } -func (a *MsigAPI) MsigRemoveSigner(ctx context.Context, msig address.Address, proposer address.Address, toRemove address.Address, decrease bool) (cid.Cid, error) { +func (a *MsigAPI) MsigRemoveSigner(ctx context.Context, msig address.Address, proposer address.Address, toRemove address.Address, decrease bool) (*api.MessagePrototype, error) { enc, actErr := serializeRemoveParams(toRemove, decrease) if actErr != nil { - return cid.Undef, actErr + return nil, actErr } return a.MsigPropose(ctx, msig, msig, types.NewInt(0), proposer, uint64(multisig.Methods.RemoveSigner), enc) } -func (a *MsigAPI) msigApproveOrCancelSimple(ctx context.Context, operation api.MsigProposeResponse, msig address.Address, txID uint64, src address.Address) (cid.Cid, error) { +func (a *MsigAPI) msigApproveOrCancelSimple(ctx context.Context, operation api.MsigProposeResponse, msig address.Address, txID uint64, src address.Address) (*api.MessagePrototype, error) { if msig == address.Undef { - return cid.Undef, xerrors.Errorf("must provide multisig address") + return nil, xerrors.Errorf("must provide multisig address") } if src == address.Undef { - return cid.Undef, xerrors.Errorf("must provide source address") + return nil, xerrors.Errorf("must provide source address") } mb, err := a.messageBuilder(ctx, src) if err != nil { - return cid.Undef, err + return nil, err } var msg *types.Message @@ -174,34 +168,31 @@ func (a *MsigAPI) msigApproveOrCancelSimple(ctx context.Context, operation api.M case api.MsigCancel: msg, err = mb.Cancel(msig, txID, nil) default: - return cid.Undef, xerrors.Errorf("Invalid operation for msigApproveOrCancel") + return nil, xerrors.Errorf("Invalid operation for msigApproveOrCancel") } if err != nil { - return cid.Undef, err + return nil, err } - smsg, err := a.MpoolAPI.MpoolPushMessage(ctx, msg, nil) - if err != nil { - return cid.Undef, err - } - - return smsg.Cid(), nil - + return &api.MessagePrototype{ + Message: *msg, + ValidNonce: false, + }, nil } -func (a *MsigAPI) msigApproveOrCancelTxnHash(ctx context.Context, operation api.MsigProposeResponse, msig address.Address, txID uint64, proposer address.Address, to address.Address, amt types.BigInt, src address.Address, method uint64, params []byte) (cid.Cid, error) { +func (a *MsigAPI) msigApproveOrCancelTxnHash(ctx context.Context, operation api.MsigProposeResponse, msig address.Address, txID uint64, proposer address.Address, to address.Address, amt types.BigInt, src address.Address, method uint64, params []byte) (*api.MessagePrototype, error) { if msig == address.Undef { - return cid.Undef, xerrors.Errorf("must provide multisig address") + return nil, xerrors.Errorf("must provide multisig address") } if src == address.Undef { - return cid.Undef, xerrors.Errorf("must provide source address") + return nil, xerrors.Errorf("must provide source address") } if proposer.Protocol() != address.ID { proposerID, err := a.StateAPI.StateLookupID(ctx, proposer, types.EmptyTSK) if err != nil { - return cid.Undef, err + return nil, err } proposer = proposerID } @@ -216,7 +207,7 @@ func (a *MsigAPI) msigApproveOrCancelTxnHash(ctx context.Context, operation api. mb, err := a.messageBuilder(ctx, src) if err != nil { - return cid.Undef, err + return nil, err } var msg *types.Message @@ -226,18 +217,16 @@ func (a *MsigAPI) msigApproveOrCancelTxnHash(ctx context.Context, operation api. case api.MsigCancel: msg, err = mb.Cancel(msig, txID, &p) default: - return cid.Undef, xerrors.Errorf("Invalid operation for msigApproveOrCancel") + return nil, xerrors.Errorf("Invalid operation for msigApproveOrCancel") } if err != nil { - return cid.Undef, err + return nil, err } - smsg, err := a.MpoolAPI.MpoolPushMessage(ctx, msg, nil) - if err != nil { - return cid.Undef, err - } - - return smsg.Cid(), nil + return &api.MessagePrototype{ + Message: *msg, + ValidNonce: false, + }, nil } func serializeAddParams(new address.Address, inc bool) ([]byte, error) { From 87df73a455fac8bab767a85af279200ab7093ff1 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Thu, 1 Apr 2021 13:19:00 +0200 Subject: [PATCH 23/41] Fix get nonce check Signed-off-by: Jakub Sztandera --- chain/messagepool/check.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/chain/messagepool/check.go b/chain/messagepool/check.go index 6800b0ef4..38273481a 100644 --- a/chain/messagepool/check.go +++ b/chain/messagepool/check.go @@ -163,6 +163,8 @@ func (mp *MessagePool) checkMessages(msgs []*types.Message, interned bool) (resu st = &actorState{nextNonce: stateNonce, requiredFunds: new(stdbig.Int)} state[m.From] = st } + } else { + check.OK = true } result[i] = append(result[i], check) From 7535c5bb53ca6b53e13f1274bdd660db532d799d Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Thu, 25 Mar 2021 15:24:09 +0100 Subject: [PATCH 24/41] Add `mpool manage` command Signed-off-by: Jakub Sztandera --- chain/messagepool/check.go | 2 +- cli/mpool.go | 1 + cli/mpool_manage.go | 356 +++++++++++++++++++++++++++++++++++++ cli/send.go | 20 +-- cli/send_test.go | 102 +++-------- cli/sending_ui.go | 71 +++++--- cli/services.go | 45 ++++- cli/services_send_test.go | 31 ++-- cli/servicesmock_test.go | 46 +++++ cmd/lotus/main.go | 14 ++ go.mod | 3 +- go.sum | 4 +- 12 files changed, 566 insertions(+), 129 deletions(-) create mode 100644 cli/mpool_manage.go diff --git a/chain/messagepool/check.go b/chain/messagepool/check.go index 38273481a..7cb99ff15 100644 --- a/chain/messagepool/check.go +++ b/chain/messagepool/check.go @@ -360,7 +360,7 @@ func (mp *MessagePool) checkMessages(msgs []*types.Message, interned bool) (resu } if m.GasFeeCap.LessThan(baseFeeUpperBound) { - check.OK = false + 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 diff --git a/cli/mpool.go b/cli/mpool.go index 025a2fc3f..b128ccc15 100644 --- a/cli/mpool.go +++ b/cli/mpool.go @@ -34,6 +34,7 @@ var MpoolCmd = &cli.Command{ MpoolFindCmd, MpoolConfig, MpoolGasPerfCmd, + mpoolManage, }, } diff --git a/cli/mpool_manage.go b/cli/mpool_manage.go new file mode 100644 index 000000000..1ca23e614 --- /dev/null +++ b/cli/mpool_manage.go @@ -0,0 +1,356 @@ +package cli + +import ( + "context" + "fmt" + "sort" + + "github.com/Kubuxu/imtui" + "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/actors/builtin" + "github.com/filecoin-project/lotus/chain/messagepool" + types "github.com/filecoin-project/lotus/chain/types" + "github.com/gdamore/tcell/v2" + cid "github.com/ipfs/go-cid" + "github.com/urfave/cli/v2" + "golang.org/x/xerrors" +) + +var mpoolManage = &cli.Command{ + Name: "manage", + Action: func(cctx *cli.Context) error { + srv, err := GetFullNodeServices(cctx) + if err != nil { + return err + } + defer srv.Close() + ctx := ReqContext(cctx) + + _, localAddr, err := srv.LocalAddresses(ctx) + + msgs, err := srv.MpoolPendingFilter(ctx, func(sm *types.SignedMessage) bool { + if sm.Message.From.Empty() { + return false + } + for _, a := range localAddr { + if a == sm.Message.From { + return true + } + } + return false + }, types.EmptyTSK) + if err != nil { + return err + } + + t, err := imtui.NewTui() + if err != nil { + panic(err) + } + + mm := &mmUI{ + ctx: ctx, + srv: srv, + addrs: localAddr, + messages: msgs, + } + sort.Slice(mm.addrs, func(i, j int) bool { + return mm.addrs[i].String() < mm.addrs[j].String() + }) + t.PushScene(mm.addrSelect()) + + err = t.Run() + + if err != nil { + panic(err) + } + + return nil + }, +} + +type mmUI struct { + ctx context.Context + srv ServicesAPI + addrs []address.Address + messages []*types.SignedMessage +} + +func (mm *mmUI) addrSelect() func(*imtui.Tui) error { + rows := [][]string{{"Address", "No. Messages"}} + mCount := map[address.Address]int{} + for _, sm := range mm.messages { + mCount[sm.Message.From]++ + } + for _, a := range mm.addrs { + rows = append(rows, []string{a.String(), fmt.Sprintf("%d", mCount[a])}) + } + + flex := []int{4, 1} + sel := 0 + scroll := 0 + return func(t *imtui.Tui) error { + if t.CurrentKey != nil && t.CurrentKey.Key() == tcell.KeyEnter { + if sel > 0 { + t.ReplaceScene(mm.messageLising(mm.addrs[sel-1])) + } + } + t.FlexTable(0, 0, 0, &sel, &scroll, rows, flex, true) + return nil + } +} + +func errUI(err error) func(*imtui.Tui) error { + return func(t *imtui.Tui) error { + return err + } +} + +type msgInfo struct { + sm *types.SignedMessage + checks []api.MessageCheckStatus +} + +func (mi *msgInfo) Row() []string { + cidStr := mi.sm.Cid().String() + failedChecks := 0 + for _, c := range mi.checks { + if !c.OK { + failedChecks++ + } + } + shortAddr := mi.sm.Message.To.String() + if len(shortAddr) > 16 { + shortAddr = "…" + shortAddr[len(shortAddr)-16:] + } + var fCk string + if failedChecks == 0 { + fCk = "[:green:]OK" + } else { + fCk = "[:orange:]" + fmt.Sprintf("%d", failedChecks) + } + return []string{"…" + cidStr[len(cidStr)-32:], shortAddr, + fmt.Sprintf("%d", mi.sm.Message.Nonce), types.FIL(mi.sm.Message.Value).String(), + fmt.Sprintf("%d", mi.sm.Message.Method), fCk} + +} + +func (mm *mmUI) messageLising(a address.Address) func(*imtui.Tui) error { + genMsgInfos := func() ([]msgInfo, error) { + msgs, err := mm.srv.MpoolPendingFilter(mm.ctx, func(sm *types.SignedMessage) bool { + if sm.Message.From.Empty() { + return false + } + if a == sm.Message.From { + return true + } + return false + }, types.EmptyTSK) + + if err != nil { + return nil, xerrors.Errorf("getting pending: %w", err) + } + + msgIdx := map[cid.Cid]*types.SignedMessage{} + for _, sm := range msgs { + if sm.Message.From == a { + msgIdx[sm.Message.Cid()] = sm + msgIdx[sm.Cid()] = sm + } + } + + checks, err := mm.srv.MpoolCheckPendingMessages(mm.ctx, a) + if err != nil { + return nil, xerrors.Errorf("checking pending: %w", err) + } + msgInfos := make([]msgInfo, 0, len(checks)) + for _, msgChecks := range checks { + failingChecks := []api.MessageCheckStatus{} + for _, c := range msgChecks { + if !c.OK { + failingChecks = append(failingChecks, c) + } + } + msgInfos = append(msgInfos, msgInfo{ + sm: msgIdx[msgChecks[0].Cid], + checks: failingChecks, + }) + } + return msgInfos, nil + } + + sel := 0 + scroll := 0 + + var msgInfos []msgInfo + var rows [][]string + flex := []int{3, 2, 1, 1, 1, 1} + refresh := true + + return func(t *imtui.Tui) error { + if refresh { + var err error + msgInfos, err = genMsgInfos() + if err != nil { + return xerrors.Errorf("getting msgInfos: %w", err) + } + + rows = [][]string{{"Message Cid", "To", "Nonce", "Value", "Method", "Checks"}} + for _, mi := range msgInfos { + rows = append(rows, mi.Row()) + } + refresh = false + } + + if t.CurrentKey != nil && t.CurrentKey.Key() == tcell.KeyEnter { + if sel > 0 { + t.PushScene(mm.messageDetail(msgInfos[sel-1])) + refresh = true + return nil + } + } + + t.Label(0, 0, fmt.Sprintf("Address: %s", a), tcell.StyleDefault) + t.FlexTable(1, 0, 0, &sel, &scroll, rows, flex, true) + return nil + } +} + +func (mm *mmUI) messageDetail(mi msgInfo) func(*imtui.Tui) error { + baseFee, err := mm.srv.GetBaseFee(mm.ctx) + if err != nil { + return errUI(err) + } + _ = baseFee + + m := mi.sm.Message + maxFee := big.Mul(m.GasFeeCap, big.NewInt(m.GasLimit)) + + issues := [][]string{} + for _, c := range mi.checks { + issues = append(issues, []string{c.Code.String(), c.Err}) + } + issuesFlex := []int{1, 3} + var sel, scroll int + + executeReprice := false + executeNoop := false + return func(t *imtui.Tui) error { + if executeReprice { + m.GasFeeCap = big.Div(maxFee, big.NewInt(m.GasLimit)) + m.GasPremium = messagepool.ComputeMinRBF(m.GasPremium) + m.GasFeeCap = big.Max(m.GasFeeCap, m.GasPremium) + + _, _, err := mm.srv.PublishMessage(mm.ctx, &api.MessagePrototype{ + Message: m, + ValidNonce: true, + }, true) + if err != nil { + return err + } + t.PopScene() + return nil + } + if executeNoop { + nop := types.Message{ + To: builtin.BurntFundsActorAddr, + From: m.From, + + Nonce: m.Nonce, + Value: big.Zero(), + } + + nop.GasPremium = messagepool.ComputeMinRBF(m.GasPremium) + + _, _, err := mm.srv.PublishMessage(mm.ctx, &api.MessagePrototype{ + Message: nop, + ValidNonce: true, + }, true) + + if err != nil { + return xerrors.Errorf("publishing noop message: %w", err) + } + + t.PopScene() + return nil + } + + if t.CurrentKey != nil { + if t.CurrentKey.Key() == tcell.KeyLeft { + t.PopScene() + return nil + } + if t.CurrentKey.Key() == tcell.KeyRune { + switch t.CurrentKey.Rune() { + case 'R', 'r': + t.PushScene(feeUI(baseFee, m.GasLimit, &maxFee, &executeReprice)) + return nil + case 'N', 'n': + t.PushScene(confirmationScene( + &executeNoop, + "Are you sure you want to cancel the message by", + "replacing it with a message with no effects?")) + return nil + } + } + } + + row := 0 + defS := tcell.StyleDefault + display := func(f string, args ...interface{}) { + t.Label(0, row, fmt.Sprintf(f, args...), defS) + row++ + } + + display("Message CID: %s", m.Cid()) + display("Signed Message CID: %s", mi.sm.Cid()) + row++ + display("From: %s", m.From) + display("To: %s", m.To) + row++ + display("Nonce: %d", m.Nonce) + display("Value: %s", types.FIL(m.Value)) + row++ + display("GasLimit: %d", m.GasLimit) + display("GasPremium: %s", types.FIL(m.GasPremium).Short()) + display("GasFeeCap %s", types.FIL(m.GasFeeCap).Short()) + row++ + display("Press R to reprice this message") + display("Press N to replace this message with no-operation message") + row++ + + t.FlexTable(row, 0, 0, &sel, &scroll, issues, issuesFlex, false) + + return nil + } +} + +func confirmationScene(yes *bool, ask ...string) func(*imtui.Tui) error { + return func(t *imtui.Tui) error { + row := 0 + defS := tcell.StyleDefault + display := func(f string, args ...interface{}) { + t.Label(0, row, fmt.Sprintf(f, args...), defS) + row++ + } + + for _, a := range ask { + display(a) + } + row++ + display("Enter to confirm") + display("Esc to cancel") + + if t.CurrentKey != nil { + if t.CurrentKey.Key() == tcell.KeyEnter { + *yes = true + t.PopScene() + return nil + } + } + + return nil + } +} diff --git a/cli/send.go b/cli/send.go index 9efed458a..0e53d18c1 100644 --- a/cli/send.go +++ b/cli/send.go @@ -58,10 +58,14 @@ var sendCmd = &cli.Command{ }, &cli.BoolFlag{ Name: "force", - Usage: "must be specified for the action to take effect if maybe SysErrInsufficientFunds etc", + Usage: "Deprecated: use global 'force-send'", }, }, Action: func(cctx *cli.Context) error { + if cctx.IsSet("force") { + fmt.Println("'force' flag is deprecated, use global flag 'force-send'") + } + if cctx.Args().Len() != 2 { return ShowHelp(cctx, fmt.Errorf("'send' expects two arguments, target and amount")) } @@ -145,21 +149,13 @@ var sendCmd = &cli.Command{ if err != nil { return xerrors.Errorf("creating message prototype: %w", err) } - msg, checks, err := srv.PublishMessage(ctx, proto, cctx.Bool("force")) - if xerrors.Is(err, ErrCheckFailed) { - proto, err := resolveChecks(ctx, srv, cctx.App.Writer, proto, checks, true) - if err != nil { - return xerrors.Errorf("from UI: %w", err) - } - - msg, _, err = srv.PublishMessage(ctx, proto, true) //nolint - } + c, err := InteractiveSend(ctx, cctx, srv, proto) if err != nil { - return xerrors.Errorf("publishing message: %w", err) + return err } - fmt.Fprintf(cctx.App.Writer, "%s\n", msg.Cid()) + fmt.Fprintf(cctx.App.Writer, "%s\n", c) return nil }, } diff --git a/cli/send_test.go b/cli/send_test.go index b16e3c57e..5e7489c43 100644 --- a/cli/send_test.go +++ b/cli/send_test.go @@ -1,6 +1,17 @@ package cli -/* +import ( + "bytes" + "testing" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/api" + types "github.com/filecoin-project/lotus/chain/types" + gomock "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + ucli "github.com/urfave/cli/v2" +) var arbtCid = (&types.Message{ From: mustAddr(address.NewIDAddress(2)), @@ -37,81 +48,26 @@ func TestSendCLI(t *testing.T) { app, mockSrvcs, buf, done := newMockApp(t, sendCmd) defer done() - gomock.InOrder( - mockSrvcs.EXPECT().Send(gomock.Any(), SendParams{ - To: mustAddr(address.NewIDAddress(1)), - Val: oneFil, - }).Return(arbtCid, nil), - mockSrvcs.EXPECT().Close(), - ) - err := app.Run([]string{"lotus", "send", "t01", "1"}) - assert.NoError(t, err) - assert.EqualValues(t, arbtCid.String()+"\n", buf.String()) - }) - t.Run("ErrSendBalanceTooLow", func(t *testing.T) { - app, mockSrvcs, _, done := newMockApp(t, sendCmd) - defer done() - - gomock.InOrder( - mockSrvcs.EXPECT().Send(gomock.Any(), SendParams{ - To: mustAddr(address.NewIDAddress(1)), - Val: oneFil, - }).Return(cid.Undef, ErrSendBalanceTooLow), - mockSrvcs.EXPECT().Close(), - ) - err := app.Run([]string{"lotus", "send", "t01", "1"}) - assert.ErrorIs(t, err, ErrSendBalanceTooLow) - }) - t.Run("generic-err-is-forwarded", func(t *testing.T) { - app, mockSrvcs, _, done := newMockApp(t, sendCmd) - defer done() - - errMark := errors.New("something") - gomock.InOrder( - mockSrvcs.EXPECT().Send(gomock.Any(), SendParams{ - To: mustAddr(address.NewIDAddress(1)), - Val: oneFil, - }).Return(cid.Undef, errMark), - mockSrvcs.EXPECT().Close(), - ) - err := app.Run([]string{"lotus", "send", "t01", "1"}) - assert.ErrorIs(t, err, errMark) - }) - - t.Run("from-specific", func(t *testing.T) { - app, mockSrvcs, buf, done := newMockApp(t, sendCmd) - defer done() - - gomock.InOrder( - mockSrvcs.EXPECT().Send(gomock.Any(), SendParams{ - To: mustAddr(address.NewIDAddress(1)), - From: mustAddr(address.NewIDAddress(2)), - Val: oneFil, - }).Return(arbtCid, nil), - mockSrvcs.EXPECT().Close(), - ) - err := app.Run([]string{"lotus", "send", "--from=t02", "t01", "1"}) - assert.NoError(t, err) - assert.EqualValues(t, arbtCid.String()+"\n", buf.String()) - }) - - t.Run("nonce-specific", func(t *testing.T) { - app, mockSrvcs, buf, done := newMockApp(t, sendCmd) - defer done() - zero := uint64(0) - - gomock.InOrder( - mockSrvcs.EXPECT().Send(gomock.Any(), SendParams{ + arbtProto := &api.MessagePrototype{ + Message: types.Message{ + From: mustAddr(address.NewIDAddress(1)), To: mustAddr(address.NewIDAddress(1)), - Nonce: &zero, - Val: oneFil, - }).Return(arbtCid, nil), + Value: oneFil, + }, + } + sigMsg := fakeSign(&arbtProto.Message) + + gomock.InOrder( + mockSrvcs.EXPECT().MessageForSend(gomock.Any(), SendParams{ + To: mustAddr(address.NewIDAddress(1)), + Val: oneFil, + }).Return(arbtProto, nil), + mockSrvcs.EXPECT().PublishMessage(gomock.Any(), arbtProto, false). + Return(sigMsg, nil, nil), mockSrvcs.EXPECT().Close(), ) - err := app.Run([]string{"lotus", "send", "--nonce=0", "t01", "1"}) + err := app.Run([]string{"lotus", "send", "t01", "1"}) assert.NoError(t, err) - assert.EqualValues(t, arbtCid.String()+"\n", buf.String()) + assert.EqualValues(t, sigMsg.Cid().String()+"\n", buf.String()) }) - } -*/ diff --git a/cli/sending_ui.go b/cli/sending_ui.go index d58e93c74..4024b1f67 100644 --- a/cli/sending_ui.go +++ b/cli/sending_ui.go @@ -14,8 +14,35 @@ import ( types "github.com/filecoin-project/lotus/chain/types" "github.com/gdamore/tcell/v2" cid "github.com/ipfs/go-cid" + "github.com/urfave/cli/v2" + "golang.org/x/xerrors" ) +func InteractiveSend(ctx context.Context, cctx *cli.Context, srv ServicesAPI, + proto *api.MessagePrototype) (cid.Cid, error) { + + msg, checks, err := srv.PublishMessage(ctx, proto, cctx.Bool("force") || cctx.Bool("force-send")) + printer := cctx.App.Writer + if xerrors.Is(err, ErrCheckFailed) { + if !cctx.Bool("interactive") { + fmt.Fprintf(printer, "Following checks have failed:\n") + printChecks(printer, checks, proto.Message.Cid()) + } else { + proto, err = resolveChecks(ctx, srv, cctx.App.Writer, proto, checks) + if err != nil { + return cid.Undef, xerrors.Errorf("from UI: %w", err) + } + + msg, _, err = srv.PublishMessage(ctx, proto, true) + } + } + if err != nil { + return cid.Undef, xerrors.Errorf("publishing message: %w", err) + } + + return msg.Cid(), nil +} + var interactiveSolves = map[api.CheckStatusCode]bool{ api.CheckStatusMessageBaseFee: true, api.CheckStatusMessageBaseFeeLowerBound: true, @@ -42,35 +69,30 @@ func baseFeeFromHints(hint map[string]interface{}) big.Int { func resolveChecks(ctx context.Context, s ServicesAPI, printer io.Writer, proto *api.MessagePrototype, checkGroups [][]api.MessageCheckStatus, - interactive bool) (*api.MessagePrototype, error) { +) (*api.MessagePrototype, error) { fmt.Fprintf(printer, "Following checks have failed:\n") printChecks(printer, checkGroups, proto.Message.Cid()) - if !interactive { - return nil, ErrCheckFailed - } - if interactive { - if feeCapBad, baseFee := isFeeCapProblem(checkGroups, proto.Message.Cid()); feeCapBad { - fmt.Fprintf(printer, "Fee of the message can be adjusted\n") - if askUser(printer, "Do you wish to do that? [Yes/no]: ", true) { - var err error - proto, err = runFeeCapAdjustmentUI(proto, baseFee) - if err != nil { - return nil, err - } - } - checks, err := s.RunChecksForPrototype(ctx, proto) + if feeCapBad, baseFee := isFeeCapProblem(checkGroups, proto.Message.Cid()); feeCapBad { + fmt.Fprintf(printer, "Fee of the message can be adjusted\n") + if askUser(printer, "Do you wish to do that? [Yes/no]: ", true) { + var err error + proto, err = runFeeCapAdjustmentUI(proto, baseFee) if err != nil { return nil, err } - fmt.Fprintf(printer, "Following checks still failed:\n") - printChecks(printer, checks, proto.Message.Cid()) } + checks, err := s.RunChecksForPrototype(ctx, proto) + if err != nil { + return nil, err + } + fmt.Fprintf(printer, "Following checks still failed:\n") + printChecks(printer, checks, proto.Message.Cid()) + } - if !askUser(printer, "Do you wish to send this message? [yes/No]: ", false) { - return nil, ErrAbortedByUser - } + if !askUser(printer, "Do you wish to send this message? [yes/No]: ", false) { + return nil, ErrAbortedByUser } return proto, nil } @@ -88,7 +110,7 @@ func printChecks(printer io.Writer, checkGroups [][]api.MessageCheckStatus, prot if !aboutProto { msgName = c.Cid.String() } - fmt.Fprintf(printer, "%s message failed a check: %s\n", msgName, c.Err) + fmt.Fprintf(printer, "%s message failed a check %s: %s\n", msgName, c.Code, c.Err) } } } @@ -133,7 +155,7 @@ func runFeeCapAdjustmentUI(proto *api.MessagePrototype, baseFee abi.TokenAmount) maxFee := big.Mul(proto.Message.GasFeeCap, big.NewInt(proto.Message.GasLimit)) send := false - t.SetScene(ui(baseFee, proto.Message.GasLimit, &maxFee, &send)) + t.PushScene(feeUI(baseFee, proto.Message.GasLimit, &maxFee, &send)) err = t.Run() if err != nil { @@ -148,7 +170,7 @@ func runFeeCapAdjustmentUI(proto *api.MessagePrototype, baseFee abi.TokenAmount) return proto, nil } -func ui(baseFee abi.TokenAmount, gasLimit int64, maxFee *abi.TokenAmount, send *bool) func(*imtui.Tui) error { +func feeUI(baseFee abi.TokenAmount, gasLimit int64, maxFee *abi.TokenAmount, send *bool) func(*imtui.Tui) error { orignalMaxFee := *maxFee required := big.Mul(baseFee, big.NewInt(gasLimit)) safe := big.Mul(required, big.NewInt(10)) @@ -180,7 +202,8 @@ func ui(baseFee abi.TokenAmount, gasLimit int64, maxFee *abi.TokenAmount, send * if t.CurrentKey.Key() == tcell.KeyEnter { *send = true - return imtui.ErrNormalExit + t.PopScene() + return nil } } diff --git a/cli/services.go b/cli/services.go index 82d95397b..a69dab655 100644 --- a/cli/services.go +++ b/cli/services.go @@ -39,7 +39,12 @@ type ServicesAPI interface { // before publishing the message, it runs checks on the node, message and mpool to verify that // message is valid and won't be stuck. // if `force` is true, it skips the checks - PublishMessage(ctx context.Context, prototype *api.MessagePrototype, interactive bool) (*types.SignedMessage, [][]api.MessageCheckStatus, error) + PublishMessage(ctx context.Context, prototype *api.MessagePrototype, force bool) (*types.SignedMessage, [][]api.MessageCheckStatus, error) + + LocalAddresses(ctx context.Context) (address.Address, []address.Address, error) + + MpoolPendingFilter(ctx context.Context, filter func(*types.SignedMessage) bool, tsk types.TipSetKey) ([]*types.SignedMessage, error) + MpoolCheckPendingMessages(ctx context.Context, a address.Address) ([][]api.MessageCheckStatus, error) // Close ends the session of services and disconnects from RPC, using Services after Close is called // most likely will result in an error @@ -240,3 +245,41 @@ func (s *ServicesImpl) MessageForSend(ctx context.Context, params SendParams) (* } return prototype, nil } + +func (s *ServicesImpl) MpoolPendingFilter(ctx context.Context, filter func(*types.SignedMessage) bool, + tsk types.TipSetKey) ([]*types.SignedMessage, error) { + msgs, err := s.api.MpoolPending(ctx, types.EmptyTSK) + if err != nil { + return nil, xerrors.Errorf("getting pending messages: %w", err) + } + out := []*types.SignedMessage{} + for _, sm := range msgs { + if filter(sm) { + out = append(out, sm) + } + } + + return out, nil +} + +func (s *ServicesImpl) LocalAddresses(ctx context.Context) (address.Address, []address.Address, error) { + def, err := s.api.WalletDefaultAddress(ctx) + if err != nil { + return address.Undef, nil, xerrors.Errorf("getting default addr: %w", err) + } + + all, err := s.api.WalletList(ctx) + if err != nil { + return address.Undef, nil, xerrors.Errorf("getting list of addrs: %w", err) + } + + return def, all, nil +} + +func (s *ServicesImpl) MpoolCheckPendingMessages(ctx context.Context, a address.Address) ([][]api.MessageCheckStatus, error) { + checks, err := s.api.MpoolCheckPendingMessages(ctx, a) + if err != nil { + return nil, xerrors.Errorf("pending mpool check: %w", err) + } + return checks, nil +} diff --git a/cli/services_send_test.go b/cli/services_send_test.go index 3437e90d9..c6af9866a 100644 --- a/cli/services_send_test.go +++ b/cli/services_send_test.go @@ -7,6 +7,7 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/lotus/api" mocks "github.com/filecoin-project/lotus/api/v0api/v0mocks" types "github.com/filecoin-project/lotus/chain/types" @@ -60,22 +61,22 @@ func setupMockSrvcs(t *testing.T) (*ServicesImpl, *mocks.MockFullNode) { } // linter doesn't like dead code, so these are commented out. -// func fakeSign(msg *types.Message) *types.SignedMessage { -// return &types.SignedMessage{ -// Message: *msg, -// Signature: crypto.Signature{Type: crypto.SigTypeSecp256k1, Data: make([]byte, 32)}, -// } -// } +func fakeSign(msg *types.Message) *types.SignedMessage { + return &types.SignedMessage{ + Message: *msg, + Signature: crypto.Signature{Type: crypto.SigTypeSecp256k1, Data: make([]byte, 32)}, + } +} -// func makeMessageSigner() (*cid.Cid, interface{}) { -// smCid := cid.Undef -// return &smCid, -// func(_ context.Context, msg *types.Message, _ *api.MessageSendSpec) (*types.SignedMessage, error) { -// sm := fakeSign(msg) -// smCid = sm.Cid() -// return sm, nil -// } -// } +//func makeMessageSigner() (*cid.Cid, interface{}) { +//smCid := cid.Undef +//return &smCid, +//func(_ context.Context, msg *types.Message, _ *api.MessageSendSpec) (*types.SignedMessage, error) { +//sm := fakeSign(msg) +//smCid = sm.Cid() +//return sm, nil +//} +//} type MessageMatcher SendParams diff --git a/cli/servicesmock_test.go b/cli/servicesmock_test.go index 0a353c153..4bd4b79c9 100644 --- a/cli/servicesmock_test.go +++ b/cli/servicesmock_test.go @@ -96,6 +96,22 @@ func (mr *MockServicesAPIMockRecorder) GetBaseFee(arg0 interface{}) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBaseFee", reflect.TypeOf((*MockServicesAPI)(nil).GetBaseFee), arg0) } +// LocalAddresses mocks base method +func (m *MockServicesAPI) LocalAddresses(arg0 context.Context) (go_address.Address, []go_address.Address, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LocalAddresses", arg0) + ret0, _ := ret[0].(go_address.Address) + ret1, _ := ret[1].([]go_address.Address) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// LocalAddresses indicates an expected call of LocalAddresses +func (mr *MockServicesAPIMockRecorder) LocalAddresses(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LocalAddresses", reflect.TypeOf((*MockServicesAPI)(nil).LocalAddresses), arg0) +} + // MessageForSend mocks base method func (m *MockServicesAPI) MessageForSend(arg0 context.Context, arg1 SendParams) (*api.MessagePrototype, error) { m.ctrl.T.Helper() @@ -111,6 +127,36 @@ func (mr *MockServicesAPIMockRecorder) MessageForSend(arg0, arg1 interface{}) *g return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MessageForSend", reflect.TypeOf((*MockServicesAPI)(nil).MessageForSend), arg0, arg1) } +// MpoolCheckPendingMessages mocks base method +func (m *MockServicesAPI) MpoolCheckPendingMessages(arg0 context.Context, arg1 go_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 *MockServicesAPIMockRecorder) MpoolCheckPendingMessages(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MpoolCheckPendingMessages", reflect.TypeOf((*MockServicesAPI)(nil).MpoolCheckPendingMessages), arg0, arg1) +} + +// MpoolPendingFilter mocks base method +func (m *MockServicesAPI) MpoolPendingFilter(arg0 context.Context, arg1 func(*types.SignedMessage) bool, arg2 types.TipSetKey) ([]*types.SignedMessage, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MpoolPendingFilter", arg0, arg1, arg2) + ret0, _ := ret[0].([]*types.SignedMessage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MpoolPendingFilter indicates an expected call of MpoolPendingFilter +func (mr *MockServicesAPIMockRecorder) MpoolPendingFilter(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MpoolPendingFilter", reflect.TypeOf((*MockServicesAPI)(nil).MpoolPendingFilter), arg0, arg1, arg2) +} + // PublishMessage mocks base method func (m *MockServicesAPI) PublishMessage(arg0 context.Context, arg1 *api.MessagePrototype, arg2 bool) (*types.SignedMessage, [][]api.MessageCheckStatus, error) { m.ctrl.T.Helper() diff --git a/cmd/lotus/main.go b/cmd/lotus/main.go index af9c56735..c1dab8e94 100644 --- a/cmd/lotus/main.go +++ b/cmd/lotus/main.go @@ -2,7 +2,9 @@ package main import ( "context" + "os" + "github.com/mattn/go-isatty" "github.com/urfave/cli/v2" "go.opencensus.io/trace" @@ -52,6 +54,8 @@ func main() { ctx, span := trace.StartSpan(context.Background(), "/cli") defer span.End() + interactiveDef := isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd()) + app := &cli.App{ Name: "lotus", Usage: "Filecoin decentralized storage network client", @@ -64,10 +68,20 @@ func main() { Hidden: true, Value: "~/.lotus", // TODO: Consider XDG_DATA_HOME }, + &cli.BoolFlag{ + Name: "interactive", + Usage: "setting to false will disable interactive functionality of commands", + Value: interactiveDef, + }, + &cli.BoolFlag{ + Name: "force-send", + Usage: "if true, will ignore pre-send checks", + }, }, Commands: append(local, lcli.Commands...), } + app.Setup() app.Metadata["traceContext"] = ctx app.Metadata["repoType"] = repo.FullNode diff --git a/go.mod b/go.mod index e1fe8c764..384cac442 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/BurntSushi/toml v0.3.1 github.com/GeertJohan/go.rice v1.0.0 github.com/Gurpartap/async v0.0.0-20180927173644-4f7f499dd9ee - github.com/Kubuxu/imtui v0.0.0-20210323145256-9fdaecfdf6b7 + github.com/Kubuxu/imtui v0.0.0-20210401140320-41663d68d0fa github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d github.com/alecthomas/jsonschema v0.0.0-20200530073317-71f438968921 @@ -118,6 +118,7 @@ require ( github.com/libp2p/go-libp2p-yamux v0.4.1 github.com/libp2p/go-maddr-filter v0.1.0 github.com/mattn/go-colorable v0.1.6 // indirect + github.com/mattn/go-isatty v0.0.12 github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 github.com/mitchellh/go-homedir v1.1.0 github.com/multiformats/go-base32 v0.0.3 diff --git a/go.sum b/go.sum index bfb498886..ef3ac9678 100644 --- a/go.sum +++ b/go.sum @@ -42,11 +42,11 @@ github.com/Gurpartap/async v0.0.0-20180927173644-4f7f499dd9ee h1:8doiS7ib3zi6/K1 github.com/Gurpartap/async v0.0.0-20180927173644-4f7f499dd9ee/go.mod h1:W0GbEAA4uFNYOGG2cJpmFJ04E6SD1NLELPYZB57/7AY= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y= +github.com/Kubuxu/imtui v0.0.0-20210401140320-41663d68d0fa h1:1PPxEyGdIGVkX/kqMvLJ95a1dGS1Sz7tpNEgehEYYt0= +github.com/Kubuxu/imtui v0.0.0-20210401140320-41663d68d0fa/go.mod h1:WUmMvh9wMtqj1Xhf1hf3kp9RvL+y6odtdYxpyZjb90U= github.com/Masterminds/glide v0.13.2/go.mod h1:STyF5vcenH/rUqTEv+/hBXlSTo7KYwg2oc2f4tzPWic= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= -github.com/Kubuxu/imtui v0.0.0-20210323145256-9fdaecfdf6b7 h1:oaKenk0p5Pg7k2YRflJtiai4weJN+VsABO3zSaUVU6w= -github.com/Kubuxu/imtui v0.0.0-20210323145256-9fdaecfdf6b7/go.mod h1:WUmMvh9wMtqj1Xhf1hf3kp9RvL+y6odtdYxpyZjb90U= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= From 3d8f641310d545e31e7d4c82025c08d4822c45bf Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Fri, 2 Apr 2021 15:43:28 +0200 Subject: [PATCH 25/41] Plug in InteractiveSend to all adopted commands Signed-off-by: Jakub Sztandera --- cli/chain.go | 30 +++++++++++++---------- cli/mpool_manage.go | 6 ++++- cli/multisig.go | 58 ++++++++++++++++++++++----------------------- cli/send.go | 4 ++-- cli/send_test.go | 6 ----- cli/sending_ui.go | 8 +++---- 6 files changed, 57 insertions(+), 55 deletions(-) diff --git a/cli/chain.go b/cli/chain.go index cc2fe50ec..019b2e91f 100644 --- a/cli/chain.go +++ b/cli/chain.go @@ -31,6 +31,7 @@ import ( cbg "github.com/whyrusleeping/cbor-gen" "golang.org/x/xerrors" + "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" @@ -1116,11 +1117,12 @@ var SlashConsensusFault = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { - api, closer, err := GetFullNodeAPI(cctx) + srv, err := GetFullNodeServices(cctx) if err != nil { return err } - defer closer() + defer srv.Close() + a := srv.FullNodeAPI() ctx := ReqContext(cctx) c1, err := cid.Parse(cctx.Args().Get(0)) @@ -1128,7 +1130,7 @@ var SlashConsensusFault = &cli.Command{ return xerrors.Errorf("parsing cid 1: %w", err) } - b1, err := api.ChainGetBlock(ctx, c1) + b1, err := a.ChainGetBlock(ctx, c1) if err != nil { return xerrors.Errorf("getting block 1: %w", err) } @@ -1138,7 +1140,7 @@ var SlashConsensusFault = &cli.Command{ return xerrors.Errorf("parsing cid 2: %w", err) } - b2, err := api.ChainGetBlock(ctx, c2) + b2, err := a.ChainGetBlock(ctx, c2) if err != nil { return xerrors.Errorf("getting block 2: %w", err) } @@ -1149,7 +1151,7 @@ var SlashConsensusFault = &cli.Command{ var fromAddr address.Address if from := cctx.String("from"); from == "" { - defaddr, err := api.WalletDefaultAddress(ctx) + defaddr, err := a.WalletDefaultAddress(ctx) if err != nil { return err } @@ -1185,7 +1187,7 @@ var SlashConsensusFault = &cli.Command{ return xerrors.Errorf("parsing cid extra: %w", err) } - bExtra, err := api.ChainGetBlock(ctx, cExtra) + bExtra, err := a.ChainGetBlock(ctx, cExtra) if err != nil { return xerrors.Errorf("getting block extra: %w", err) } @@ -1203,15 +1205,17 @@ var SlashConsensusFault = &cli.Command{ return err } - msg := &types.Message{ - To: b2.Miner, - From: fromAddr, - Value: types.NewInt(0), - Method: builtin.MethodsMiner.ReportConsensusFault, - Params: enc, + proto := &api.MessagePrototype{ + Message: types.Message{ + To: b2.Miner, + From: fromAddr, + Value: types.NewInt(0), + Method: builtin.MethodsMiner.ReportConsensusFault, + Params: enc, + }, } - smsg, err := api.MpoolPushMessage(ctx, msg, nil) + smsg, err := InteractiveSend(ctx, cctx, srv, proto) if err != nil { return err } diff --git a/cli/mpool_manage.go b/cli/mpool_manage.go index 1ca23e614..164a05842 100644 --- a/cli/mpool_manage.go +++ b/cli/mpool_manage.go @@ -25,10 +25,14 @@ var mpoolManage = &cli.Command{ if err != nil { return err } - defer srv.Close() + defer srv.Close() //nolint:errcheck + ctx := ReqContext(cctx) _, localAddr, err := srv.LocalAddresses(ctx) + if err != nil { + return xerrors.Errorf("getting local addresses: %w", err) + } msgs, err := srv.MpoolPendingFilter(ctx, func(sm *types.SignedMessage) bool { if sm.Message.From.Empty() { diff --git a/cli/multisig.go b/cli/multisig.go index 6ec0dcd5d..0baed5a93 100644 --- a/cli/multisig.go +++ b/cli/multisig.go @@ -153,7 +153,7 @@ var msigCreateCmd = &cli.Command{ return err } - sm, _, err := srv.PublishMessage(ctx, proto, true) + sm, err := InteractiveSend(ctx, cctx, srv, proto) if err != nil { return err } @@ -161,7 +161,7 @@ var msigCreateCmd = &cli.Command{ msgCid := sm.Cid() // wait for it to get mined into a block - wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true) + wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence"))) if err != nil { return err } @@ -442,7 +442,7 @@ var msigProposeCmd = &cli.Command{ return err } - sm, _, err := srv.PublishMessage(ctx, proto, true) + sm, err := InteractiveSend(ctx, cctx, srv, proto) if err != nil { return err } @@ -451,7 +451,7 @@ var msigProposeCmd = &cli.Command{ fmt.Println("send proposal in message: ", msgCid) - wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true) + wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence"))) if err != nil { return err } @@ -540,7 +540,7 @@ var msigApproveCmd = &cli.Command{ return err } - sm, _, err := srv.PublishMessage(ctx, proto, true) + sm, err := InteractiveSend(ctx, cctx, srv, proto) if err != nil { return err } @@ -590,7 +590,7 @@ var msigApproveCmd = &cli.Command{ return err } - sm, _, err := srv.PublishMessage(ctx, proto, true) + sm, err := InteractiveSend(ctx, cctx, srv, proto) if err != nil { return err } @@ -600,7 +600,7 @@ var msigApproveCmd = &cli.Command{ fmt.Println("sent approval in message: ", msgCid) - wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true) + wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence"))) if err != nil { return err } @@ -671,7 +671,7 @@ var msigRemoveProposeCmd = &cli.Command{ return err } - sm, _, err := srv.PublishMessage(ctx, proto, true) + sm, err := InteractiveSend(ctx, cctx, srv, proto) if err != nil { return err } @@ -680,7 +680,7 @@ var msigRemoveProposeCmd = &cli.Command{ fmt.Println("sent remove proposal in message: ", msgCid) - wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true) + wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence"))) if err != nil { return err } @@ -758,7 +758,7 @@ var msigAddProposeCmd = &cli.Command{ return err } - sm, _, err := srv.PublishMessage(ctx, proto, true) + sm, err := InteractiveSend(ctx, cctx, srv, proto) if err != nil { return err } @@ -767,7 +767,7 @@ var msigAddProposeCmd = &cli.Command{ fmt.Fprintln(cctx.App.Writer, "sent add proposal in message: ", msgCid) - wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true) + wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence"))) if err != nil { return err } @@ -849,7 +849,7 @@ var msigAddApproveCmd = &cli.Command{ return err } - sm, _, err := srv.PublishMessage(ctx, proto, true) + sm, err := InteractiveSend(ctx, cctx, srv, proto) if err != nil { return err } @@ -858,7 +858,7 @@ var msigAddApproveCmd = &cli.Command{ fmt.Println("sent add approval in message: ", msgCid) - wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true) + wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence"))) if err != nil { return err } @@ -935,7 +935,7 @@ var msigAddCancelCmd = &cli.Command{ return err } - sm, _, err := srv.PublishMessage(ctx, proto, true) + sm, err := InteractiveSend(ctx, cctx, srv, proto) if err != nil { return err } @@ -944,7 +944,7 @@ var msigAddCancelCmd = &cli.Command{ fmt.Println("sent add cancellation in message: ", msgCid) - wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true) + wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence"))) if err != nil { return err } @@ -1016,7 +1016,7 @@ var msigSwapProposeCmd = &cli.Command{ return err } - sm, _, err := srv.PublishMessage(ctx, proto, true) + sm, err := InteractiveSend(ctx, cctx, srv, proto) if err != nil { return err } @@ -1025,7 +1025,7 @@ var msigSwapProposeCmd = &cli.Command{ fmt.Println("sent swap proposal in message: ", msgCid) - wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true) + wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence"))) if err != nil { return err } @@ -1107,7 +1107,7 @@ var msigSwapApproveCmd = &cli.Command{ return err } - sm, _, err := srv.PublishMessage(ctx, proto, true) + sm, err := InteractiveSend(ctx, cctx, srv, proto) if err != nil { return err } @@ -1116,7 +1116,7 @@ var msigSwapApproveCmd = &cli.Command{ fmt.Println("sent swap approval in message: ", msgCid) - wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true) + wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence"))) if err != nil { return err } @@ -1193,7 +1193,7 @@ var msigSwapCancelCmd = &cli.Command{ return err } - sm, _, err := srv.PublishMessage(ctx, proto, true) + sm, err := InteractiveSend(ctx, cctx, srv, proto) if err != nil { return err } @@ -1202,7 +1202,7 @@ var msigSwapCancelCmd = &cli.Command{ fmt.Println("sent swap cancellation in message: ", msgCid) - wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true) + wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence"))) if err != nil { return err } @@ -1289,7 +1289,7 @@ var msigLockProposeCmd = &cli.Command{ return err } - sm, _, err := srv.PublishMessage(ctx, proto, true) + sm, err := InteractiveSend(ctx, cctx, srv, proto) if err != nil { return err } @@ -1298,7 +1298,7 @@ var msigLockProposeCmd = &cli.Command{ fmt.Println("sent lock proposal in message: ", msgCid) - wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true) + wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence"))) if err != nil { return err } @@ -1395,7 +1395,7 @@ var msigLockApproveCmd = &cli.Command{ return err } - sm, _, err := srv.PublishMessage(ctx, proto, true) + sm, err := InteractiveSend(ctx, cctx, srv, proto) if err != nil { return err } @@ -1404,7 +1404,7 @@ var msigLockApproveCmd = &cli.Command{ fmt.Println("sent lock approval in message: ", msgCid) - wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true) + wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence"))) if err != nil { return err } @@ -1496,7 +1496,7 @@ var msigLockCancelCmd = &cli.Command{ return err } - sm, _, err := srv.PublishMessage(ctx, proto, true) + sm, err := InteractiveSend(ctx, cctx, srv, proto) if err != nil { return err } @@ -1505,7 +1505,7 @@ var msigLockCancelCmd = &cli.Command{ fmt.Println("sent lock cancellation in message: ", msgCid) - wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true) + wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence"))) if err != nil { return err } @@ -1642,7 +1642,7 @@ var msigProposeThresholdCmd = &cli.Command{ return fmt.Errorf("failed to propose change of threshold: %w", err) } - sm, _, err := srv.PublishMessage(ctx, proto, true) + sm, err := InteractiveSend(ctx, cctx, srv, proto) if err != nil { return err } @@ -1651,7 +1651,7 @@ var msigProposeThresholdCmd = &cli.Command{ fmt.Println("sent change threshold proposal in message: ", msgCid) - wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true) + wait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence"))) if err != nil { return err } diff --git a/cli/send.go b/cli/send.go index 0e53d18c1..a5200d3b8 100644 --- a/cli/send.go +++ b/cli/send.go @@ -150,12 +150,12 @@ var sendCmd = &cli.Command{ return xerrors.Errorf("creating message prototype: %w", err) } - c, err := InteractiveSend(ctx, cctx, srv, proto) + sm, err := InteractiveSend(ctx, cctx, srv, proto) if err != nil { return err } - fmt.Fprintf(cctx.App.Writer, "%s\n", c) + fmt.Fprintf(cctx.App.Writer, "%s\n", sm.Cid()) return nil }, } diff --git a/cli/send_test.go b/cli/send_test.go index 5e7489c43..52eafda67 100644 --- a/cli/send_test.go +++ b/cli/send_test.go @@ -13,12 +13,6 @@ import ( ucli "github.com/urfave/cli/v2" ) -var arbtCid = (&types.Message{ - From: mustAddr(address.NewIDAddress(2)), - To: mustAddr(address.NewIDAddress(1)), - Value: types.NewInt(1000), -}).Cid() - func mustAddr(a address.Address, err error) address.Address { if err != nil { panic(err) diff --git a/cli/sending_ui.go b/cli/sending_ui.go index 4024b1f67..881aa3aac 100644 --- a/cli/sending_ui.go +++ b/cli/sending_ui.go @@ -19,7 +19,7 @@ import ( ) func InteractiveSend(ctx context.Context, cctx *cli.Context, srv ServicesAPI, - proto *api.MessagePrototype) (cid.Cid, error) { + proto *api.MessagePrototype) (*types.SignedMessage, error) { msg, checks, err := srv.PublishMessage(ctx, proto, cctx.Bool("force") || cctx.Bool("force-send")) printer := cctx.App.Writer @@ -30,17 +30,17 @@ func InteractiveSend(ctx context.Context, cctx *cli.Context, srv ServicesAPI, } else { proto, err = resolveChecks(ctx, srv, cctx.App.Writer, proto, checks) if err != nil { - return cid.Undef, xerrors.Errorf("from UI: %w", err) + return nil, xerrors.Errorf("from UI: %w", err) } msg, _, err = srv.PublishMessage(ctx, proto, true) } } if err != nil { - return cid.Undef, xerrors.Errorf("publishing message: %w", err) + return nil, xerrors.Errorf("publishing message: %w", err) } - return msg.Cid(), nil + return msg, nil } var interactiveSolves = map[api.CheckStatusCode]bool{ From 8d75da1629c8d1c67a192ec3ba1447db7802ec80 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Fri, 2 Apr 2021 16:12:34 +0200 Subject: [PATCH 26/41] Use MessagePrototype for check API Signed-off-by: Jakub Sztandera --- api/api_full.go | 2 +- api/mocks/mock_full.go | 2 +- api/proxy_gen.go | 6 +++--- build/openrpc/full.json.gz | Bin 23192 -> 23212 bytes chain/messagepool/check.go | 20 ++++++++++++++------ cli/multisig.go | 28 ++++++++++++++-------------- cli/services.go | 11 +---------- documentation/en/cli-lotus.md | 8 +++++++- node/impl/full/mpool.go | 4 ++-- 9 files changed, 43 insertions(+), 38 deletions(-) diff --git a/api/api_full.go b/api/api_full.go index e8e8dcb2e..fed9cece5 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -253,7 +253,7 @@ type FullNode interface { MpoolBatchPushMessage(context.Context, []*types.Message, *MessageSendSpec) ([]*types.SignedMessage, error) //perm:sign // MpoolCheckMessages performs logical checks on a batch of messages - MpoolCheckMessages(context.Context, []*types.Message) ([][]MessageCheckStatus, error) //perm:read + 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 diff --git a/api/mocks/mock_full.go b/api/mocks/mock_full.go index a14336537..6feee64f7 100644 --- a/api/mocks/mock_full.go +++ b/api/mocks/mock_full.go @@ -1069,7 +1069,7 @@ func (mr *MockFullNodeMockRecorder) MpoolBatchPushUntrusted(arg0, arg1 interface } // MpoolCheckMessages mocks base method -func (m *MockFullNode) MpoolCheckMessages(arg0 context.Context, arg1 []*types.Message) ([][]api.MessageCheckStatus, error) { +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) diff --git a/api/proxy_gen.go b/api/proxy_gen.go index f285f1ce6..2d4d41503 100644 --- a/api/proxy_gen.go +++ b/api/proxy_gen.go @@ -235,7 +235,7 @@ type FullNodeStruct struct { MpoolBatchPushUntrusted func(p0 context.Context, p1 []*types.SignedMessage) ([]cid.Cid, error) `perm:"write"` - MpoolCheckMessages func(p0 context.Context, p1 []*types.Message) ([][]MessageCheckStatus, error) `perm:"read"` + MpoolCheckMessages func(p0 context.Context, p1 []*MessagePrototype) ([][]MessageCheckStatus, error) `perm:"read"` MpoolCheckPendingMessages func(p0 context.Context, p1 address.Address) ([][]MessageCheckStatus, error) `perm:"read"` @@ -1515,11 +1515,11 @@ func (s *FullNodeStub) MpoolBatchPushUntrusted(p0 context.Context, p1 []*types.S return *new([]cid.Cid), xerrors.New("method not supported") } -func (s *FullNodeStruct) MpoolCheckMessages(p0 context.Context, p1 []*types.Message) ([][]MessageCheckStatus, error) { +func (s *FullNodeStruct) MpoolCheckMessages(p0 context.Context, p1 []*MessagePrototype) ([][]MessageCheckStatus, error) { return s.Internal.MpoolCheckMessages(p0, p1) } -func (s *FullNodeStub) MpoolCheckMessages(p0 context.Context, p1 []*types.Message) ([][]MessageCheckStatus, error) { +func (s *FullNodeStub) MpoolCheckMessages(p0 context.Context, p1 []*MessagePrototype) ([][]MessageCheckStatus, error) { return *new([][]MessageCheckStatus), xerrors.New("method not supported") } diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index cb781bec9169de220365f7d8cfbac0850a948f70..d628685e57299f0100424b40692559317a058007 100644 GIT binary patch delta 22540 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 delta 22540 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∓_Ll)(Lu^CHRc+S#AeRy(7V#7w# zHreWOt>`%C5TA8ey6CcT3O;ycv7>mV;yW9;8iER>1OVd%bFznM^sbi(_MnRh-emz@ zQ=lQpw>TlZQCrt-pg**)kwTWwKw%!O?PMeeyHZ@YtXYxLn_1Bqil?yd$?Yncl+#q- zJlEOG4~TXT)YWndTzuA_SgS2sv>fiyhosZgvb=u8)zj#R-WRG7Fe|0~`%{aa#B*1r6{Jj27u&#m|}Z&BXyag~ypMXC6Y;%YAD zY^sN={bhdyvbZ!2U{j=t&C@SleSTJx+w;#H?yh z4q?3^6ba@5D;WhS?@vd;k=A17TI5m`S8TFF$ns8wHFSU#k^?kABot#EF?t-%>80#@ zt`#c3@rVaREu`Fa(v^%J{OC!crWLIc$@?>mRZ23%T`R1Wt|=29 zyKA#$2ft@tdH`ywyVGXbG1B4%I%}`urUIz+b8y|Yx8=GSi_jHOv$}JjOS%HiBTcyP z4T>QBE`%BeqjCXLr8cM$2I|xGW^f6usa6KbbXP+I?H$3&h`5OsZB}$Rn52 z+MS}a76x8Ah>dlsVrqCrRVJlV33*nDRW3x(o@`$1hdJ5xgMAoBu!B$DX~c*qZhqA; zfJ>(%XA_pr`5#kB;tMQnE_W}BM_q1jFxC!VO;W0PYVURCNMs8!U(!FWWEM9Xn)w6y zqEtw?_2+&25`UgM*kJ3v$hTZwjkSZjA0m*-dP_CFxS@`ce+Zu}mju_ckm_}x_Dd^7 z{5&~%WysJ#UsxwCrcAsLmrYGuhwsJ_bE1U{)$)|et1~Kn#g9du%h?I}sy?8t<=xSC z-O^+EJ5Ada+D4b62xbFv|CS}^vd&IX%->|s1GmtAILqX^dY=C`nAi4PREpJ zFdvL@EO^cI^H(FOl2!B$PIlFM@f_`ddV*WCLF_>+I)p*RUt3=tzTBmeA67cKf5Ml@ zfWiD5SyaqaETdr_53P8=$$owcU^+on`MOIwR1d5@TFwi7n5A$0V%!Q^w)xi|y9V{@ ziAZw5`NXjk^`&ZyPQyDRM!4NzzIp2+A`G^u?lfuD9Y|a3*kN$r=`QJ6HK85o>=31M zGYygEXK17iEn7w43H7@%AN0~KzF=+v91w+CABELl~#l_IJ<^|7DcHymMa(L@tH@lLSL*X4B6 z^fqE;t};Ai>U!9NZfb>U*+HljF*ic0qNJn)u=)BQDm~xr^Qxa4fGzI@Qemuf34xo) zUn`@L++V0Y{MBE)l07=UZr8m&eO~+GWMa~F#Yypoq;z-mXdzB+|7!PF>7=Xk@0Y-fZaL%V-*Gxd<^7ef)Y_wCJh^-nk@`z>9QX(Jxzzy)1iiIVh(x} zdd;pwYzXl>&2Wo?IiFo|UZ+}SBbwOR2ZL%O{bVEu(9opq`yMoE2x0bdzuS!JQEHFkeDHG2!TrYJVXt!rxS& zrt;0g!Pb@(MgM1~Ue0G!<()=J@>Ek}KMz6w!7w;Yo@a?y0;gNb`@j#e%Ln8ABV?I! zhn|e}1*ROJd5F`yU@?z7qm>BD4dE%?I2q=Z8@%)1TKFfQQ?&v2ljA+JuMPy2O3H?| zaEAZSrs((3^xd_ztNoayc-hOV++k-@gu+Uuz}ZiIXSI~6aUp$w^H=eq94)MKe=+bA z3RY9af%tpntr=?3pdfjujdm(X?}6lXyPfo_b;kA+#YeO{v0-X$zTX(!)btoms#@4dnzsVv}v0c`9c?k1joS5S-`G78V{=pF=jv!|hamu?FdfV3Nt6g%-ZEtES zjaLHM)xQ(Vl>uWC_?x(T9 Date: Fri, 2 Apr 2021 17:25:29 +0200 Subject: [PATCH 27/41] Print more details on test fail Signed-off-by: Jakub Sztandera --- cli/chain.go | 3 ++- cli/test/mockcli.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cli/chain.go b/cli/chain.go index 019b2e91f..6651a7764 100644 --- a/cli/chain.go +++ b/cli/chain.go @@ -1121,7 +1121,8 @@ var SlashConsensusFault = &cli.Command{ if err != nil { return err } - defer srv.Close() + defer srv.Close() //nolint:errcheck + a := srv.FullNodeAPI() ctx := ReqContext(cctx) diff --git a/cli/test/mockcli.go b/cli/test/mockcli.go index e8eb78f1b..aef7808c8 100644 --- a/cli/test/mockcli.go +++ b/cli/test/mockcli.go @@ -56,7 +56,7 @@ type MockCLIClient struct { func (c *MockCLIClient) RunCmd(input ...string) string { out, err := c.RunCmdRaw(input...) - require.NoError(c.t, err) + require.NoError(c.t, err, "output:\n%s", out) return out } From cbfb4770fdfa290e4242b13fd210a83e8735f744 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Tue, 6 Apr 2021 18:50:49 +0200 Subject: [PATCH 28/41] Add function to display nanoFIL Signed-off-by: Jakub Sztandera --- chain/types/fil.go | 9 +++++++++ cli/sending_ui.go | 10 ++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/chain/types/fil.go b/chain/types/fil.go index 223ed3c50..7438410c8 100644 --- a/chain/types/fil.go +++ b/chain/types/fil.go @@ -46,6 +46,15 @@ func (f FIL) Short() string { 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': diff --git a/cli/sending_ui.go b/cli/sending_ui.go index 881aa3aac..a70abefb9 100644 --- a/cli/sending_ui.go +++ b/cli/sending_ui.go @@ -11,6 +11,7 @@ import ( "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/build" types "github.com/filecoin-project/lotus/chain/types" "github.com/gdamore/tcell/v2" cid "github.com/ipfs/go-cid" @@ -44,6 +45,7 @@ func InteractiveSend(ctx context.Context, cctx *cli.Context, srv ServicesAPI, } var interactiveSolves = map[api.CheckStatusCode]bool{ + api.CheckStatusMessageMinBaseFee: true, api.CheckStatusMessageBaseFee: true, api.CheckStatusMessageBaseFeeLowerBound: true, api.CheckStatusMessageBaseFeeUpperBound: true, @@ -143,6 +145,10 @@ func isFeeCapProblem(checkGroups [][]api.MessageCheckStatus, protoCid cid.Cid) ( } } } + if baseFee.IsZero() { + // this will only be the case if failing check is: MessageMinBaseFee + baseFee = big.NewInt(build.MinimumBaseFee) + } return yes, baseFee } @@ -246,10 +252,10 @@ func feeUI(baseFee abi.TokenAmount, gasLimit int64, maxFee *abi.TokenAmount, sen } row += 2 - t.Label(0, row, fmt.Sprintf("Current Base Fee is: %s", types.FIL(baseFee)), defS) + t.Label(0, row, fmt.Sprintf("Current Base Fee is: %s", types.FIL(baseFee).Nano()), defS) row++ t.Label(0, row, fmt.Sprintf("Resulting FeeCap is: %s", - types.FIL(big.Div(*maxFee, big.NewInt(gasLimit)))), defS) + types.FIL(big.Div(*maxFee, big.NewInt(gasLimit))).Nano()), defS) row++ t.Label(0, row, "You can use '+' and '-' to adjust the fee.", defS) From 61dbd443b86f88d1e8a6a457f0e741fcbb04b6e5 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Thu, 6 May 2021 16:37:46 +0200 Subject: [PATCH 29/41] Fix tests and verifreg Signed-off-by: Jakub Sztandera --- cli/services_send_test.go | 2 +- cmd/lotus-shed/verifreg.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/services_send_test.go b/cli/services_send_test.go index c6af9866a..b7ed78f80 100644 --- a/cli/services_send_test.go +++ b/cli/services_send_test.go @@ -9,7 +9,7 @@ import ( "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/lotus/api" - mocks "github.com/filecoin-project/lotus/api/v0api/v0mocks" + mocks "github.com/filecoin-project/lotus/api/mocks" types "github.com/filecoin-project/lotus/chain/types" gomock "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" diff --git a/cmd/lotus-shed/verifreg.go b/cmd/lotus-shed/verifreg.go index 1b0b57ca0..52e1aa1d6 100644 --- a/cmd/lotus-shed/verifreg.go +++ b/cmd/lotus-shed/verifreg.go @@ -95,7 +95,7 @@ var verifRegAddVerifierCmd = &cli.Command{ fmt.Printf("message sent, now waiting on cid: %s\n", msgCid) - mwait, err := api.StateWaitMsg(ctx, msgCid, build.MessageConfidence) + mwait, err := api.StateWaitMsg(ctx, msgCid, uint64(cctx.Int("confidence")), build.Finality, true) if err != nil { return err } From d777680449daf261a7a1109a2d50dc97583bfd6c Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Fri, 7 May 2021 15:20:37 +0200 Subject: [PATCH 30/41] Fix mpool.GetActor for lite node Signed-off-by: Jakub Sztandera --- chain/messagepool/messagepool.go | 7 +++++++ chain/messagepool/provider.go | 20 ++++++++++++++++++++ chain/messagesigner/messagesigner.go | 1 + chain/messagesigner/messagesigner_test.go | 3 +++ node/builder.go | 2 ++ node/modules/chain.go | 4 +--- node/modules/mpoolnonceapi.go | 9 +++++++++ 7 files changed, 43 insertions(+), 3 deletions(-) diff --git a/chain/messagepool/messagepool.go b/chain/messagepool/messagepool.go index 40d0c4eaf..fb5f0acba 100644 --- a/chain/messagepool/messagepool.go +++ b/chain/messagepool/messagepool.go @@ -805,6 +805,13 @@ func (mp *MessagePool) GetNonce(_ context.Context, addr address.Address, _ types return mp.getNonceLocked(addr, mp.curTs) } +// 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.Lock() + defer mp.curTsLk.Unlock() + return mp.api.GetActorAfter(addr, mp.curTs) +} + func (mp *MessagePool) getNonceLocked(addr address.Address, curTs *types.TipSet) (uint64, error) { stateNonce, err := mp.getStateNonce(addr, curTs) // sanity check if err != nil { diff --git a/chain/messagepool/provider.go b/chain/messagepool/provider.go index 5a6c751bc..1c64cdcdf 100644 --- a/chain/messagepool/provider.go +++ b/chain/messagepool/provider.go @@ -9,6 +9,7 @@ import ( "golang.org/x/xerrors" "github.com/filecoin-project/go-address" + "github.com/filecoin-project/lotus/chain/messagesigner" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" @@ -35,12 +36,18 @@ type Provider interface { type mpoolProvider struct { sm *stmgr.StateManager ps *pubsub.PubSub + + lite messagesigner.MpoolNonceAPI } func NewProvider(sm *stmgr.StateManager, ps *pubsub.PubSub) Provider { return &mpoolProvider{sm: sm, ps: ps} } +func NewProviderLite(sm *stmgr.StateManager, ps *pubsub.PubSub, noncer messagesigner.MpoolNonceAPI) Provider { + return &mpoolProvider{sm: sm, ps: ps, lite: noncer} +} + func (mpp *mpoolProvider) SubscribeHeadChanges(cb func(rev, app []*types.TipSet) error) *types.TipSet { mpp.sm.ChainStore().SubscribeHeadChanges( store.WrapHeadChangeCoalescer( @@ -61,6 +68,19 @@ func (mpp *mpoolProvider) PubSubPublish(k string, v []byte) error { } func (mpp *mpoolProvider) GetActorAfter(addr address.Address, ts *types.TipSet) (*types.Actor, error) { + if mpp.lite != nil { + 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 + } + stcid, _, err := mpp.sm.TipSetState(context.TODO(), ts) if err != nil { return nil, xerrors.Errorf("computing tipset state for GetActor: %w", err) diff --git a/chain/messagesigner/messagesigner.go b/chain/messagesigner/messagesigner.go index c91f75632..063d1aa7d 100644 --- a/chain/messagesigner/messagesigner.go +++ b/chain/messagesigner/messagesigner.go @@ -24,6 +24,7 @@ var log = logging.Logger("messagesigner") type MpoolNonceAPI interface { GetNonce(context.Context, address.Address, types.TipSetKey) (uint64, error) + GetActor(context.Context, address.Address, types.TipSetKey) (*types.Actor, error) } // MessageSigner keeps track of nonces per address, and increments the nonce diff --git a/chain/messagesigner/messagesigner_test.go b/chain/messagesigner/messagesigner_test.go index 7bba5b3e9..90d16b7ff 100644 --- a/chain/messagesigner/messagesigner_test.go +++ b/chain/messagesigner/messagesigner_test.go @@ -41,6 +41,9 @@ func (mp *mockMpool) GetNonce(_ context.Context, addr address.Address, _ types.T 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() diff --git a/node/builder.go b/node/builder.go index c884b169b..34be610f5 100644 --- a/node/builder.go +++ b/node/builder.go @@ -333,6 +333,7 @@ var ChainNode = Options( // Lite node API ApplyIf(isLiteNode, + Override(new(messagepool.Provider), messagepool.NewProviderLite), Override(new(messagesigner.MpoolNonceAPI), From(new(modules.MpoolNonceAPI))), Override(new(full.ChainModuleAPI), From(new(api.Gateway))), Override(new(full.GasModuleAPI), From(new(api.Gateway))), @@ -343,6 +344,7 @@ var ChainNode = Options( // Full node API / service startup ApplyIf(isFullNode, + Override(new(messagepool.Provider), messagepool.NewProvider), Override(new(messagesigner.MpoolNonceAPI), From(new(*messagepool.MessagePool))), Override(new(full.ChainModuleAPI), From(new(full.ChainModule))), Override(new(full.GasModuleAPI), From(new(full.GasModule))), diff --git a/node/modules/chain.go b/node/modules/chain.go index ffdf3aa3a..954322948 100644 --- a/node/modules/chain.go +++ b/node/modules/chain.go @@ -9,7 +9,6 @@ import ( "github.com/ipfs/go-blockservice" "github.com/libp2p/go-libp2p-core/host" "github.com/libp2p/go-libp2p-core/routing" - pubsub "github.com/libp2p/go-libp2p-pubsub" "go.uber.org/fx" "golang.org/x/xerrors" @@ -59,8 +58,7 @@ func ChainBlockService(bs dtypes.ExposedBlockstore, rem dtypes.ChainBitswap) dty return blockservice.New(bs, rem) } -func MessagePool(lc fx.Lifecycle, sm *stmgr.StateManager, ps *pubsub.PubSub, ds dtypes.MetadataDS, nn dtypes.NetworkName, j journal.Journal) (*messagepool.MessagePool, error) { - mpp := messagepool.NewProvider(sm, ps) +func MessagePool(lc fx.Lifecycle, mpp messagepool.Provider, ds dtypes.MetadataDS, nn dtypes.NetworkName, j journal.Journal) (*messagepool.MessagePool, error) { mp, err := messagepool.New(mpp, ds, nn, j) if err != nil { return nil, xerrors.Errorf("constructing mpool: %w", err) diff --git a/node/modules/mpoolnonceapi.go b/node/modules/mpoolnonceapi.go index 61b38e821..3b30d24cf 100644 --- a/node/modules/mpoolnonceapi.go +++ b/node/modules/mpoolnonceapi.go @@ -96,4 +96,13 @@ func (a *MpoolNonceAPI) GetNonce(ctx context.Context, addr address.Address, tsk return highestNonce, nil } +func (a *MpoolNonceAPI) GetActor(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*types.Actor, error) { + act, err := a.StateModule.StateGetActor(ctx, addr, tsk) + if err != nil { + return nil, xerrors.Errorf("calling StateGetActor: %w", err) + } + + return act, nil +} + var _ messagesigner.MpoolNonceAPI = (*MpoolNonceAPI)(nil) From 18cbdcfc819fba9077561f87a972497e0064905b Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Fri, 7 May 2021 16:38:40 +0200 Subject: [PATCH 31/41] Disable checks API on Lotus Lite Signed-off-by: Jakub Sztandera --- api/test/util.go | 2 +- chain/messagepool/check.go | 3 +++ chain/messagepool/messagepool.go | 4 ++-- chain/messagepool/messagepool_test.go | 3 +++ chain/messagepool/provider.go | 7 ++++++- cli/test/util.go | 2 ++ cmd/lotus-gateway/endtoend_test.go | 2 +- node/impl/full/state.go | 2 +- node/modules/mpoolnonceapi.go | 2 +- 9 files changed, 20 insertions(+), 7 deletions(-) diff --git a/api/test/util.go b/api/test/util.go index f571b48da..219dcf9ed 100644 --- a/api/test/util.go +++ b/api/test/util.go @@ -29,7 +29,7 @@ func SendFunds(ctx context.Context, t *testing.T, sender TestNode, addr address. if err != nil { t.Fatal(err) } - res, err := sender.StateWaitMsg(ctx, sm.Cid(), 1, lapi.LookbackNoLimit, true) + res, err := sender.StateWaitMsg(ctx, sm.Cid(), 3, lapi.LookbackNoLimit, true) if err != nil { t.Fatal(err) } diff --git a/chain/messagepool/check.go b/chain/messagepool/check.go index 71389d9e2..8d6463691 100644 --- a/chain/messagepool/check.go +++ b/chain/messagepool/check.go @@ -100,6 +100,9 @@ func (mp *MessagePool) CheckReplaceMessages(replace []*types.Message) ([][]api.M // 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(msgs []*types.Message, interned bool, flexibleNonces []bool) (result [][]api.MessageCheckStatus, err error) { + if mp.api.IsLite() { + return nil, nil + } mp.curTsLk.Lock() curTs := mp.curTs mp.curTsLk.Unlock() diff --git a/chain/messagepool/messagepool.go b/chain/messagepool/messagepool.go index fb5f0acba..93dce1df0 100644 --- a/chain/messagepool/messagepool.go +++ b/chain/messagepool/messagepool.go @@ -832,8 +832,8 @@ 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) { - act, err := mp.api.GetActorAfter(addr, curTs) +func (mp *MessagePool) getStateNonce(addr address.Address, ts *types.TipSet) (uint64, error) { + act, err := mp.api.GetActorAfter(addr, ts) if err != nil { return 0, err } diff --git a/chain/messagepool/messagepool_test.go b/chain/messagepool/messagepool_test.go index 8e4f16a30..925ee438c 100644 --- a/chain/messagepool/messagepool_test.go +++ b/chain/messagepool/messagepool_test.go @@ -103,6 +103,9 @@ func (tma *testMpoolAPI) SubscribeHeadChanges(cb func(rev, app []*types.TipSet) func (tma *testMpoolAPI) PutMessage(m types.ChainMsg) (cid.Cid, error) { return cid.Undef, nil } +func (tma *testMpoolAPI) IsLite() bool { + return false +} func (tma *testMpoolAPI) PubSubPublish(string, []byte) error { tma.published++ diff --git a/chain/messagepool/provider.go b/chain/messagepool/provider.go index 1c64cdcdf..565691004 100644 --- a/chain/messagepool/provider.go +++ b/chain/messagepool/provider.go @@ -31,6 +31,7 @@ type Provider interface { MessagesForTipset(*types.TipSet) ([]types.ChainMsg, error) LoadTipSet(tsk types.TipSetKey) (*types.TipSet, error) ChainComputeBaseFee(ctx context.Context, ts *types.TipSet) (types.BigInt, error) + IsLite() bool } type mpoolProvider struct { @@ -48,6 +49,10 @@ func NewProviderLite(sm *stmgr.StateManager, ps *pubsub.PubSub, noncer messagesi return &mpoolProvider{sm: sm, ps: ps, lite: noncer} } +func (mpp *mpoolProvider) IsLite() bool { + return mpp.lite != nil +} + func (mpp *mpoolProvider) SubscribeHeadChanges(cb func(rev, app []*types.TipSet) error) *types.TipSet { mpp.sm.ChainStore().SubscribeHeadChanges( store.WrapHeadChangeCoalescer( @@ -68,7 +73,7 @@ func (mpp *mpoolProvider) PubSubPublish(k string, v []byte) error { } func (mpp *mpoolProvider) GetActorAfter(addr address.Address, ts *types.TipSet) (*types.Actor, error) { - if mpp.lite != nil { + if mpp.IsLite() { n, err := mpp.lite.GetNonce(context.TODO(), addr, ts.Key()) if err != nil { return nil, xerrors.Errorf("getting nonce over lite: %w", err) diff --git a/cli/test/util.go b/cli/test/util.go index e3930dc83..d7959b9d5 100644 --- a/cli/test/util.go +++ b/cli/test/util.go @@ -9,4 +9,6 @@ func QuietMiningLogs() { _ = log.SetLogLevel("sub", "ERROR") _ = log.SetLogLevel("storageminer", "ERROR") _ = log.SetLogLevel("pubsub", "ERROR") + _ = log.SetLogLevel("gen", "ERROR") + _ = log.SetLogLevel("dht/RtRefreshManager", "ERROR") } diff --git a/cmd/lotus-gateway/endtoend_test.go b/cmd/lotus-gateway/endtoend_test.go index fa1004df3..3fae221c3 100644 --- a/cmd/lotus-gateway/endtoend_test.go +++ b/cmd/lotus-gateway/endtoend_test.go @@ -333,7 +333,7 @@ func sendFunds(ctx context.Context, fromNode test.TestNode, fromAddr address.Add return err } - res, err := fromNode.StateWaitMsg(ctx, sm.Cid(), 1, api.LookbackNoLimit, true) + res, err := fromNode.StateWaitMsg(ctx, sm.Cid(), 3, api.LookbackNoLimit, true) if err != nil { return err } diff --git a/node/impl/full/state.go b/node/impl/full/state.go index 59fdd8e2e..b3639c5e0 100644 --- a/node/impl/full/state.go +++ b/node/impl/full/state.go @@ -426,7 +426,7 @@ func (a *StateAPI) StateReplay(ctx context.Context, tsk types.TipSetKey, mc cid. }, nil } -func (m *StateModule) StateGetActor(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*types.Actor, error) { +func (m *StateModule) StateGetActor(ctx context.Context, actor address.Address, tsk types.TipSetKey) (a *types.Actor, err error) { ts, err := m.Chain.GetTipSetFromKey(tsk) if err != nil { return nil, xerrors.Errorf("loading tipset %s: %w", tsk, err) diff --git a/node/modules/mpoolnonceapi.go b/node/modules/mpoolnonceapi.go index 3b30d24cf..67f512960 100644 --- a/node/modules/mpoolnonceapi.go +++ b/node/modules/mpoolnonceapi.go @@ -63,7 +63,7 @@ func (a *MpoolNonceAPI) GetNonce(ctx context.Context, addr address.Address, tsk act, err := a.StateModule.StateGetActor(ctx, keyAddr, ts.Key()) if err != nil { if strings.Contains(err.Error(), types.ErrActorNotFound.Error()) { - return 0, types.ErrActorNotFound + return 0, xerrors.Errorf("getting actor converted: %w", types.ErrActorNotFound) } return 0, xerrors.Errorf("getting actor: %w", err) } From 0db070779f45903ba8138a86918edf0383af7e35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Mon, 26 Apr 2021 16:48:20 +0200 Subject: [PATCH 32/41] wip actor wrapper codegen --- chain/actors/agen/main.go | 87 +++++++++++++++++++ .../actors/builtin/account/actor.go.template | 37 ++++++++ .../actors/builtin/account/state.go.template | 30 +++++++ 3 files changed, 154 insertions(+) create mode 100644 chain/actors/agen/main.go create mode 100644 chain/actors/builtin/account/actor.go.template create mode 100644 chain/actors/builtin/account/state.go.template diff --git a/chain/actors/agen/main.go b/chain/actors/agen/main.go new file mode 100644 index 000000000..702d309ac --- /dev/null +++ b/chain/actors/agen/main.go @@ -0,0 +1,87 @@ +package main + +import ( + "bytes" + "fmt" + "golang.org/x/xerrors" + "io/ioutil" + "path/filepath" + "text/template" +) + +func main() { + if err := run(); err != nil { + fmt.Println(err) + return + } +} + +func run() error { + versions := []int{0, 2, 3, 4} + + versionImports := map[int]string{ + 0: "/", + 2: "/v2/", + 3: "/v3/", + 4: "/v4/", + } + + actors := map[string][]int{ + "account": versions, + } + + for act, versions := range actors { + actDir := filepath.Join("chain/actors/builtin", act) + + { + af, err := ioutil.ReadFile(filepath.Join(actDir, "state.go.template")) + if err != nil { + 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": versionImports[version], + }) + if err != nil { + return err + } + + if err := ioutil.WriteFile(filepath.Join(actDir, fmt.Sprintf("v%d.go", version)), b.Bytes(), 0666); err != nil { + return err + } + } + } + + { + af, err := ioutil.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 versionImports[v] }, + }).Parse(string(af))) + + var b bytes.Buffer + + err = tpl.Execute(&b, map[string]interface{}{ + "versions": versions, + }) + if err != nil { + return err + } + + if err := ioutil.WriteFile(filepath.Join(actDir, fmt.Sprintf("%s.go", act)), b.Bytes(), 0666); err != nil { + return err + } + } + } + + return nil +} diff --git a/chain/actors/builtin/account/actor.go.template b/chain/actors/builtin/account/actor.go.template new file mode 100644 index 000000000..860dadc1c --- /dev/null +++ b/chain/actors/builtin/account/actor.go.template @@ -0,0 +1,37 @@ +package account + +import ( + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "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/actors/builtin" + "github.com/filecoin-project/lotus/chain/types" +{{range .versions}} + builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin"{{end}} +) + +func init() { +{{range .versions}} builtin.RegisterActorState(builtin{{.}}.AccountActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { + return load{{.}}(store, root) + }) +{{end}}} + +var Methods = builtin4.MethodsAccount + +func Load(store adt.Store, act *types.Actor) (State, error) { + switch act.Code { +{{range .versions}} case builtin{{.}}.AccountActorCodeID: + return load{{.}}(store, act.Head) +{{end}} } + return nil, xerrors.Errorf("unknown actor code %s", act.Code) +} + +type State interface { + cbor.Marshaler + + PubkeyAddress() (address.Address, error) +} diff --git a/chain/actors/builtin/account/state.go.template b/chain/actors/builtin/account/state.go.template new file mode 100644 index 000000000..65d874c80 --- /dev/null +++ b/chain/actors/builtin/account/state.go.template @@ -0,0 +1,30 @@ +package account + +import ( + "github.com/filecoin-project/go-address" + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/lotus/chain/actors/adt" + + account{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/builtin/account" +) + +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 +} + +type state{{.v}} struct { + account{{.v}}.State + store adt.Store +} + +func (s *state{{.v}}) PubkeyAddress() (address.Address, error) { + return s.Address, nil +} From 1a84bd584207c5a58dd4f196d7d5495df41601db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 27 Apr 2021 20:48:32 +0200 Subject: [PATCH 33/41] chain actors: codegen templates for all actors --- chain/actors/agen/main.go | 130 +++-- chain/actors/builtin/cron/actor.go.template | 10 + chain/actors/builtin/init/actor.go.template | 56 +++ chain/actors/builtin/init/state.go.template | 86 ++++ chain/actors/builtin/market/actor.go.template | 138 ++++++ chain/actors/builtin/market/market.go | 5 +- chain/actors/builtin/market/state.go.template | 209 ++++++++ chain/actors/builtin/market/v0.go | 3 +- chain/actors/builtin/market/v2.go | 32 +- chain/actors/builtin/market/v3.go | 56 +-- chain/actors/builtin/market/v4.go | 48 +- chain/actors/builtin/miner/actor.go.template | 265 +++++++++++ chain/actors/builtin/miner/miner.go | 9 +- chain/actors/builtin/miner/state.go.template | 446 ++++++++++++++++++ chain/actors/builtin/miner/v0.go | 1 + chain/actors/builtin/miner/v3.go | 22 +- chain/actors/builtin/miner/v4.go | 22 +- .../actors/builtin/multisig/actor.go.template | 113 +++++ chain/actors/builtin/multisig/message.go | 79 ---- .../builtin/multisig/message.go.template | 143 ++++++ .../multisig/{state.go => multisig.go} | 70 ++- .../actors/builtin/multisig/state.go.template | 95 ++++ .../builtin/multisig/{state0.go => v0.go} | 6 +- .../builtin/multisig/{state2.go => v2.go} | 3 +- .../builtin/multisig/{state3.go => v3.go} | 4 +- .../builtin/multisig/{state4.go => v4.go} | 6 +- chain/actors/builtin/paych/actor.go.template | 103 ++++ chain/actors/builtin/paych/message.go | 36 -- .../actors/builtin/paych/message.go.template | 74 +++ .../builtin/paych/{state.go => paych.go} | 29 +- chain/actors/builtin/paych/state.go.template | 104 ++++ .../actors/builtin/paych/{state0.go => v0.go} | 0 .../actors/builtin/paych/{state2.go => v2.go} | 0 .../actors/builtin/paych/{state3.go => v3.go} | 0 .../actors/builtin/paych/{state4.go => v4.go} | 0 chain/actors/builtin/power/actor.go.template | 74 +++ chain/actors/builtin/power/power.go | 2 +- chain/actors/builtin/power/state.go.template | 149 ++++++ chain/actors/builtin/power/v0.go | 11 +- chain/actors/builtin/power/v2.go | 6 +- chain/actors/builtin/power/v3.go | 4 +- chain/actors/builtin/power/v4.go | 4 +- chain/actors/builtin/reward/actor.go.template | 56 +++ chain/actors/builtin/reward/reward.go | 3 +- chain/actors/builtin/reward/state.go.template | 99 ++++ chain/actors/builtin/reward/v0.go | 6 +- .../actors/builtin/verifreg/actor.go.template | 46 ++ .../actors/builtin/verifreg/state.go.template | 58 +++ chain/actors/builtin/verifreg/verifreg.go | 1 + 49 files changed, 2647 insertions(+), 275 deletions(-) create mode 100644 chain/actors/builtin/cron/actor.go.template create mode 100644 chain/actors/builtin/init/actor.go.template create mode 100644 chain/actors/builtin/init/state.go.template create mode 100644 chain/actors/builtin/market/actor.go.template create mode 100644 chain/actors/builtin/market/state.go.template create mode 100644 chain/actors/builtin/miner/actor.go.template create mode 100644 chain/actors/builtin/miner/state.go.template create mode 100644 chain/actors/builtin/multisig/actor.go.template delete mode 100644 chain/actors/builtin/multisig/message.go create mode 100644 chain/actors/builtin/multisig/message.go.template rename chain/actors/builtin/multisig/{state.go => multisig.go} (51%) create mode 100644 chain/actors/builtin/multisig/state.go.template rename chain/actors/builtin/multisig/{state0.go => v0.go} (95%) rename chain/actors/builtin/multisig/{state2.go => v2.go} (99%) rename chain/actors/builtin/multisig/{state3.go => v3.go} (96%) rename chain/actors/builtin/multisig/{state4.go => v4.go} (93%) create mode 100644 chain/actors/builtin/paych/actor.go.template delete mode 100644 chain/actors/builtin/paych/message.go create mode 100644 chain/actors/builtin/paych/message.go.template rename chain/actors/builtin/paych/{state.go => paych.go} (80%) create mode 100644 chain/actors/builtin/paych/state.go.template rename chain/actors/builtin/paych/{state0.go => v0.go} (100%) rename chain/actors/builtin/paych/{state2.go => v2.go} (100%) rename chain/actors/builtin/paych/{state3.go => v3.go} (100%) rename chain/actors/builtin/paych/{state4.go => v4.go} (100%) create mode 100644 chain/actors/builtin/power/actor.go.template create mode 100644 chain/actors/builtin/power/state.go.template create mode 100644 chain/actors/builtin/reward/actor.go.template create mode 100644 chain/actors/builtin/reward/state.go.template create mode 100644 chain/actors/builtin/verifreg/actor.go.template create mode 100644 chain/actors/builtin/verifreg/state.go.template diff --git a/chain/actors/agen/main.go b/chain/actors/agen/main.go index 702d309ac..0d73a064e 100644 --- a/chain/actors/agen/main.go +++ b/chain/actors/agen/main.go @@ -5,10 +5,35 @@ import ( "fmt" "golang.org/x/xerrors" "io/ioutil" + "os" "path/filepath" "text/template" ) +var latestVersion = 4 + +var versions = []int{0, 2, 3, latestVersion} + +var versionImports = map[int]string{ + 0: "/", + 2: "/v2/", + 3: "/v3/", + latestVersion: "/v4/", +} + +var actors = map[string][]int{ + "account": versions, + "cron": versions, + "init": versions, + "market": versions, + "miner": versions, + "multisig": versions, + "paych": versions, + "power": versions, + "reward": versions, + "verifreg": versions, +} + func main() { if err := run(); err != nil { fmt.Println(err) @@ -17,45 +42,15 @@ func main() { } func run() error { - versions := []int{0, 2, 3, 4} - - versionImports := map[int]string{ - 0: "/", - 2: "/v2/", - 3: "/v3/", - 4: "/v4/", - } - - actors := map[string][]int{ - "account": versions, - } - for act, versions := range actors { actDir := filepath.Join("chain/actors/builtin", act) - { - af, err := ioutil.ReadFile(filepath.Join(actDir, "state.go.template")) - if err != nil { - return xerrors.Errorf("loading state adapter template: %w", err) - } + if err := generateState(actDir); err != nil { + return 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": versionImports[version], - }) - if err != nil { - return err - } - - if err := ioutil.WriteFile(filepath.Join(actDir, fmt.Sprintf("v%d.go", version)), b.Bytes(), 0666); err != nil { - return err - } - } + if err := generateMessages(actDir); err != nil { + return err } { @@ -71,7 +66,8 @@ func run() error { var b bytes.Buffer err = tpl.Execute(&b, map[string]interface{}{ - "versions": versions, + "versions": versions, + "latestVersion": latestVersion, }) if err != nil { return err @@ -85,3 +81,65 @@ func run() error { return nil } + +func generateState(actDir string) error { + af, err := ioutil.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": versionImports[version], + }) + if err != nil { + return err + } + + if err := ioutil.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 := ioutil.ReadFile(filepath.Join(actDir, "message.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": versionImports[version], + }) + if err != nil { + return err + } + + if err := ioutil.WriteFile(filepath.Join(actDir, fmt.Sprintf("message%d.go", version)), b.Bytes(), 0666); err != nil { + return err + } + } + + return nil +} diff --git a/chain/actors/builtin/cron/actor.go.template b/chain/actors/builtin/cron/actor.go.template new file mode 100644 index 000000000..6b7449617 --- /dev/null +++ b/chain/actors/builtin/cron/actor.go.template @@ -0,0 +1,10 @@ +package cron + +import ( + builtin{{.latestVersion}} "github.com/filecoin-project/specs-actors{{import .latestVersion}}actors/builtin" +) + +var ( + Address = builtin{{.latestVersion}}.CronActorAddr + Methods = builtin{{.latestVersion}}.MethodsCron +) diff --git a/chain/actors/builtin/init/actor.go.template b/chain/actors/builtin/init/actor.go.template new file mode 100644 index 000000000..0c9a6c702 --- /dev/null +++ b/chain/actors/builtin/init/actor.go.template @@ -0,0 +1,56 @@ +package init + +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/cbor" + "github.com/ipfs/go-cid" + + "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/lotus/node/modules/dtypes" +{{range .versions}} + builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin"{{end}} +) + +func init() { +{{range .versions}} builtin.RegisterActorState(builtin{{.}}.InitActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { + return load{{.}}(store, root) + }) +{{end}}} + +var ( + Address = builtin{{.latestVersion}}.InitActorAddr + Methods = builtin{{.latestVersion}}.MethodsInit +) + +func Load(store adt.Store, act *types.Actor) (State, error) { + switch act.Code { +{{range .versions}} case builtin{{.}}.InitActorCodeID: + return load{{.}}(store, act.Head) +{{end}} } + return nil, xerrors.Errorf("unknown actor code %s", act.Code) +} + +type State interface { + cbor.Marshaler + + 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 + + addressMap() (adt.Map, error) +} diff --git a/chain/actors/builtin/init/state.go.template b/chain/actors/builtin/init/state.go.template new file mode 100644 index 000000000..3fd7aaaa7 --- /dev/null +++ b/chain/actors/builtin/init/state.go.template @@ -0,0 +1,86 @@ +package init + +import ( + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" +{{if (ge .v 3)}} builtin{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/builtin" +{{end}} "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/node/modules/dtypes" + + init{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/builtin/init" + adt{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/util/adt" +) + +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 +} + +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}}) 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}}) addressMap() (adt.Map, error) { + return adt{{.v}}.AsMap(s.store, s.AddressMap{{if (ge .v 3)}}, builtin{{.v}}.DefaultHamtBitwidth{{end}}) +} diff --git a/chain/actors/builtin/market/actor.go.template b/chain/actors/builtin/market/actor.go.template new file mode 100644 index 000000000..8c16ffb91 --- /dev/null +++ b/chain/actors/builtin/market/actor.go.template @@ -0,0 +1,138 @@ +package market + +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/cbor" + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + + market0 "github.com/filecoin-project/specs-actors/actors/builtin/market" +{{range .versions}} + builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin"{{end}} + + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/types" +) + +func init() { +{{range .versions}} builtin.RegisterActorState(builtin{{.}}.StorageMarketActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { + return load{{.}}(store, root) + }) +{{end}}} + +var ( + Address = builtin{{.latestVersion}}.StorageMarketActorAddr + Methods = builtin{{.latestVersion}}.MethodsMarket +) + +func Load(store adt.Store, act *types.Actor) (State, error) { + switch act.Code { +{{range .versions}} case builtin{{.}}.StorageMarketActorCodeID: + return load{{.}}(store, act.Head) +{{end}} } + return nil, xerrors.Errorf("unknown actor code %s", act.Code) +} + +type State interface { + cbor.Marshaler + 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) +} + +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 DealProposal) error) error + Get(id abi.DealID) (*DealProposal, bool, error) + + array() adt.Array + decode(*cbg.Deferred) (*DealProposal, error) +} + +type PublishStorageDealsParams = market0.PublishStorageDealsParams +type PublishStorageDealsReturn = market0.PublishStorageDealsReturn +type VerifyDealsForActivationParams = market0.VerifyDealsForActivationParams +type WithdrawBalanceParams = market0.WithdrawBalanceParams + +type ClientDealProposal = market0.ClientDealProposal + +type DealState struct { + SectorStartEpoch abi.ChainEpoch // -1 if not yet included in proven sector + LastUpdatedEpoch abi.ChainEpoch // -1 if deal state never updated + SlashEpoch abi.ChainEpoch // -1 if deal never slashed +} + +type DealProposal struct { + PieceCID cid.Cid + PieceSize abi.PaddedPieceSize + VerifiedDeal bool + Client address.Address + Provider address.Address + Label string + StartEpoch abi.ChainEpoch + EndEpoch abi.ChainEpoch + StoragePricePerEpoch abi.TokenAmount + ProviderCollateral abi.TokenAmount + ClientCollateral abi.TokenAmount +} + +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 DealProposal +} + +func EmptyDealState() *DealState { + return &DealState{ + SectorStartEpoch: -1, + SlashEpoch: -1, + LastUpdatedEpoch: -1, + } +} diff --git a/chain/actors/builtin/market/market.go b/chain/actors/builtin/market/market.go index 33729bdf9..16c44339b 100644 --- a/chain/actors/builtin/market/market.go +++ b/chain/actors/builtin/market/market.go @@ -10,8 +10,9 @@ import ( "github.com/ipfs/go-cid" cbg "github.com/whyrusleeping/cbor-gen" - builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" market0 "github.com/filecoin-project/specs-actors/actors/builtin/market" + + 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" @@ -41,7 +42,7 @@ var ( Methods = builtin4.MethodsMarket ) -func Load(store adt.Store, act *types.Actor) (st State, err error) { +func Load(store adt.Store, act *types.Actor) (State, error) { switch act.Code { case builtin0.StorageMarketActorCodeID: return load0(store, act.Head) diff --git a/chain/actors/builtin/market/state.go.template b/chain/actors/builtin/market/state.go.template new file mode 100644 index 000000000..a55743ce5 --- /dev/null +++ b/chain/actors/builtin/market/state.go.template @@ -0,0 +1,209 @@ +package market + +import ( + "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/adt" + "github.com/filecoin-project/lotus/chain/types" + + market{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/builtin/market" + adt{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/util/adt" +) + +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 +} + +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 { + return (DealState)(v{{.v}}) +} + +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 := fromV{{.v}}DealProposal(proposal{{.v}}) + 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 { + return cb(abi.DealID(idx), fromV{{.v}}DealProposal(dp{{.v}})) + }) +} + +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 := fromV{{.v}}DealProposal(dp{{.v}}) + return &dp, nil +} + +func (s *dealProposals{{.v}}) array() adt.Array { + return s.Array +} + +func fromV{{.v}}DealProposal(v{{.v}} market{{.v}}.DealProposal) DealProposal { + return (DealProposal)(v{{.v}}) +} diff --git a/chain/actors/builtin/market/v0.go b/chain/actors/builtin/market/v0.go index f3b885995..175c0a2ea 100644 --- a/chain/actors/builtin/market/v0.go +++ b/chain/actors/builtin/market/v0.go @@ -102,7 +102,8 @@ func (s *state0) LockedTable() (BalanceTable, error) { func (s *state0) VerifyDealsForActivation( minerAddr address.Address, deals []abi.DealID, currEpoch, sectorExpiry abi.ChainEpoch, ) (weight, verifiedWeight abi.DealWeight, err error) { - return market0.ValidateDealsForActivation(&s.State, s.store, deals, minerAddr, sectorExpiry, currEpoch) + w, vw, err := market0.ValidateDealsForActivation(&s.State, s.store, deals, minerAddr, sectorExpiry, currEpoch) + return w, vw, err } func (s *state0) NextID() (abi.DealID, error) { diff --git a/chain/actors/builtin/market/v2.go b/chain/actors/builtin/market/v2.go index 1ce051c38..dafae8feb 100644 --- a/chain/actors/builtin/market/v2.go +++ b/chain/actors/builtin/market/v2.go @@ -144,18 +144,18 @@ func (s *dealStates2) Get(dealID abi.DealID) (*DealState, bool, error) { } func (s *dealStates2) ForEach(cb func(dealID abi.DealID, ds DealState) error) error { - var ds1 market2.DealState - return s.Array.ForEach(&ds1, func(idx int64) error { - return cb(abi.DealID(idx), fromV2DealState(ds1)) + 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 ds1 market2.DealState - if err := ds1.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + var ds2 market2.DealState + if err := ds2.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { return nil, err } - ds := fromV2DealState(ds1) + ds := fromV2DealState(ds2) return &ds, nil } @@ -163,8 +163,8 @@ func (s *dealStates2) array() adt.Array { return s.Array } -func fromV2DealState(v1 market2.DealState) DealState { - return (DealState)(v1) +func fromV2DealState(v2 market2.DealState) DealState { + return (DealState)(v2) } type dealProposals2 struct { @@ -185,18 +185,18 @@ func (s *dealProposals2) Get(dealID abi.DealID) (*DealProposal, bool, error) { } func (s *dealProposals2) ForEach(cb func(dealID abi.DealID, dp DealProposal) error) error { - var dp1 market2.DealProposal - return s.Array.ForEach(&dp1, func(idx int64) error { - return cb(abi.DealID(idx), fromV2DealProposal(dp1)) + var dp2 market2.DealProposal + return s.Array.ForEach(&dp2, func(idx int64) error { + return cb(abi.DealID(idx), fromV2DealProposal(dp2)) }) } func (s *dealProposals2) decode(val *cbg.Deferred) (*DealProposal, error) { - var dp1 market2.DealProposal - if err := dp1.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + var dp2 market2.DealProposal + if err := dp2.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { return nil, err } - dp := fromV2DealProposal(dp1) + dp := fromV2DealProposal(dp2) return &dp, nil } @@ -204,6 +204,6 @@ func (s *dealProposals2) array() adt.Array { return s.Array } -func fromV2DealProposal(v1 market2.DealProposal) DealProposal { - return (DealProposal)(v1) +func fromV2DealProposal(v2 market2.DealProposal) DealProposal { + return (DealProposal)(v2) } diff --git a/chain/actors/builtin/market/v3.go b/chain/actors/builtin/market/v3.go index 15251985b..dec8d6e25 100644 --- a/chain/actors/builtin/market/v3.go +++ b/chain/actors/builtin/market/v3.go @@ -38,23 +38,23 @@ func (s *state3) TotalLocked() (abi.TokenAmount, error) { } func (s *state3) BalancesChanged(otherState State) (bool, error) { - otherState2, ok := otherState.(*state3) + 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(otherState2.State.EscrowTable) || !s.State.LockedTable.Equals(otherState2.State.LockedTable), 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) { - otherState2, ok := otherState.(*state3) + 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(otherState2.State.States), nil + return !s.State.States.Equals(otherState3.State.States), nil } func (s *state3) States() (DealStates, error) { @@ -66,13 +66,13 @@ func (s *state3) States() (DealStates, error) { } func (s *state3) ProposalsChanged(otherState State) (bool, error) { - otherState2, ok := otherState.(*state3) + 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(otherState2.State.Proposals), nil + return !s.State.Proposals.Equals(otherState3.State.Proposals), nil } func (s *state3) Proposals() (DealProposals, error) { @@ -131,31 +131,31 @@ type dealStates3 struct { } func (s *dealStates3) Get(dealID abi.DealID) (*DealState, bool, error) { - var deal2 market3.DealState - found, err := s.Array.Get(uint64(dealID), &deal2) + 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(deal2) + deal := fromV3DealState(deal3) return &deal, true, nil } func (s *dealStates3) ForEach(cb func(dealID abi.DealID, ds DealState) error) error { - var ds1 market3.DealState - return s.Array.ForEach(&ds1, func(idx int64) error { - return cb(abi.DealID(idx), fromV3DealState(ds1)) + 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 ds1 market3.DealState - if err := ds1.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + var ds3 market3.DealState + if err := ds3.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { return nil, err } - ds := fromV3DealState(ds1) + ds := fromV3DealState(ds3) return &ds, nil } @@ -163,8 +163,8 @@ func (s *dealStates3) array() adt.Array { return s.Array } -func fromV3DealState(v1 market3.DealState) DealState { - return (DealState)(v1) +func fromV3DealState(v3 market3.DealState) DealState { + return (DealState)(v3) } type dealProposals3 struct { @@ -172,31 +172,31 @@ type dealProposals3 struct { } func (s *dealProposals3) Get(dealID abi.DealID) (*DealProposal, bool, error) { - var proposal2 market3.DealProposal - found, err := s.Array.Get(uint64(dealID), &proposal2) + 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 := fromV3DealProposal(proposal2) + proposal := fromV3DealProposal(proposal3) return &proposal, true, nil } func (s *dealProposals3) ForEach(cb func(dealID abi.DealID, dp DealProposal) error) error { - var dp1 market3.DealProposal - return s.Array.ForEach(&dp1, func(idx int64) error { - return cb(abi.DealID(idx), fromV3DealProposal(dp1)) + var dp3 market3.DealProposal + return s.Array.ForEach(&dp3, func(idx int64) error { + return cb(abi.DealID(idx), fromV3DealProposal(dp3)) }) } func (s *dealProposals3) decode(val *cbg.Deferred) (*DealProposal, error) { - var dp1 market3.DealProposal - if err := dp1.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + var dp3 market3.DealProposal + if err := dp3.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { return nil, err } - dp := fromV3DealProposal(dp1) + dp := fromV3DealProposal(dp3) return &dp, nil } @@ -204,6 +204,6 @@ func (s *dealProposals3) array() adt.Array { return s.Array } -func fromV3DealProposal(v1 market3.DealProposal) DealProposal { - return (DealProposal)(v1) +func fromV3DealProposal(v3 market3.DealProposal) DealProposal { + return (DealProposal)(v3) } diff --git a/chain/actors/builtin/market/v4.go b/chain/actors/builtin/market/v4.go index dede98d1a..22514395c 100644 --- a/chain/actors/builtin/market/v4.go +++ b/chain/actors/builtin/market/v4.go @@ -38,23 +38,23 @@ func (s *state4) TotalLocked() (abi.TokenAmount, error) { } func (s *state4) BalancesChanged(otherState State) (bool, error) { - otherState2, ok := otherState.(*state4) + 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(otherState2.State.EscrowTable) || !s.State.LockedTable.Equals(otherState2.State.LockedTable), 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) { - otherState2, ok := otherState.(*state4) + 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(otherState2.State.States), nil + return !s.State.States.Equals(otherState4.State.States), nil } func (s *state4) States() (DealStates, error) { @@ -66,13 +66,13 @@ func (s *state4) States() (DealStates, error) { } func (s *state4) ProposalsChanged(otherState State) (bool, error) { - otherState2, ok := otherState.(*state4) + 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(otherState2.State.Proposals), nil + return !s.State.Proposals.Equals(otherState4.State.Proposals), nil } func (s *state4) Proposals() (DealProposals, error) { @@ -131,31 +131,31 @@ type dealStates4 struct { } func (s *dealStates4) Get(dealID abi.DealID) (*DealState, bool, error) { - var deal2 market4.DealState - found, err := s.Array.Get(uint64(dealID), &deal2) + 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(deal2) + deal := fromV4DealState(deal4) return &deal, true, nil } func (s *dealStates4) ForEach(cb func(dealID abi.DealID, ds DealState) error) error { - var ds1 market4.DealState - return s.Array.ForEach(&ds1, func(idx int64) error { - return cb(abi.DealID(idx), fromV4DealState(ds1)) + 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 ds1 market4.DealState - if err := ds1.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + var ds4 market4.DealState + if err := ds4.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { return nil, err } - ds := fromV4DealState(ds1) + ds := fromV4DealState(ds4) return &ds, nil } @@ -172,31 +172,31 @@ type dealProposals4 struct { } func (s *dealProposals4) Get(dealID abi.DealID) (*DealProposal, bool, error) { - var proposal2 market4.DealProposal - found, err := s.Array.Get(uint64(dealID), &proposal2) + 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 := fromV4DealProposal(proposal2) + proposal := fromV4DealProposal(proposal4) return &proposal, true, nil } func (s *dealProposals4) ForEach(cb func(dealID abi.DealID, dp DealProposal) error) error { - var dp1 market4.DealProposal - return s.Array.ForEach(&dp1, func(idx int64) error { - return cb(abi.DealID(idx), fromV4DealProposal(dp1)) + var dp4 market4.DealProposal + return s.Array.ForEach(&dp4, func(idx int64) error { + return cb(abi.DealID(idx), fromV4DealProposal(dp4)) }) } func (s *dealProposals4) decode(val *cbg.Deferred) (*DealProposal, error) { - var dp1 market4.DealProposal - if err := dp1.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + var dp4 market4.DealProposal + if err := dp4.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { return nil, err } - dp := fromV4DealProposal(dp1) + dp := fromV4DealProposal(dp4) return &dp, nil } diff --git a/chain/actors/builtin/miner/actor.go.template b/chain/actors/builtin/miner/actor.go.template new file mode 100644 index 000000000..4265af7dc --- /dev/null +++ b/chain/actors/builtin/miner/actor.go.template @@ -0,0 +1,265 @@ +package miner + +import ( + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/network" + "github.com/ipfs/go-cid" + "github.com/libp2p/go-libp2p-core/peer" + cbg "github.com/whyrusleeping/cbor-gen" + "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/cbor" + "github.com/filecoin-project/go-state-types/dline" + + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/types" + + miner0 "github.com/filecoin-project/specs-actors/actors/builtin/miner" + miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/miner" + miner3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/miner" +{{range .versions}} + builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin"{{end}} +) + +func init() { +{{range .versions}} builtin.RegisterActorState(builtin{{.}}.StorageMinerActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { + return load{{.}}(store, root) + }) +{{end}}} + +var Methods = builtin{{.latestVersion}}.MethodsMiner + +// Unchanged between v0, v2, v3, and v4 actors +var WPoStProvingPeriod = miner0.WPoStProvingPeriod +var WPoStPeriodDeadlines = miner0.WPoStPeriodDeadlines +var WPoStChallengeWindow = miner0.WPoStChallengeWindow +var WPoStChallengeLookback = miner0.WPoStChallengeLookback +var FaultDeclarationCutoff = miner0.FaultDeclarationCutoff + +const MinSectorExpiration = miner0.MinSectorExpiration + +// Not used / checked in v0 +// TODO: Abstract over network versions +var DeclarationsMax = miner2.DeclarationsMax +var AddressedSectorsMax = miner2.AddressedSectorsMax + +func Load(store adt.Store, act *types.Actor) (State, error) { + switch act.Code { +{{range .versions}} case builtin{{.}}.StorageMinerActorCodeID: + return load{{.}}(store, act.Head) +{{end}} } + return nil, xerrors.Errorf("unknown actor code %s", act.Code) +} + +type State interface { + cbor.Marshaler + + // 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) + + GetSector(abi.SectorNumber) (*SectorOnChainInfo, error) + FindSector(abi.SectorNumber) (*SectorLocation, error) + GetSectorExpiration(abi.SectorNumber) (*SectorExpiration, error) + GetPrecommittedSector(abi.SectorNumber) (*SectorPreCommitOnChainInfo, error) + LoadSectors(sectorNos *bitfield.BitField) ([]*SectorOnChainInfo, error) + NumLiveSectors() (uint64, error) + IsAllocated(abi.SectorNumber) (bool, 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) +} + +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() (bitfield.BitField, error) + FaultySectors() (bitfield.BitField, error) + RecoveringSectors() (bitfield.BitField, error) + LiveSectors() (bitfield.BitField, error) + ActiveSectors() (bitfield.BitField, error) +} + +type SectorOnChainInfo struct { + SectorNumber abi.SectorNumber + SealProof abi.RegisteredSealProof + SealedCID cid.Cid + DealIDs []abi.DealID + Activation abi.ChainEpoch + Expiration abi.ChainEpoch + DealWeight abi.DealWeight + VerifiedDealWeight abi.DealWeight + InitialPledge abi.TokenAmount + ExpectedDayReward abi.TokenAmount + ExpectedStoragePledge abi.TokenAmount +} + +type SectorPreCommitInfo = miner0.SectorPreCommitInfo + +type SectorPreCommitOnChainInfo struct { + Info SectorPreCommitInfo + PreCommitDeposit abi.TokenAmount + PreCommitEpoch abi.ChainEpoch + DealWeight abi.DealWeight + VerifiedDealWeight abi.DealWeight +} + +type PoStPartition = miner0.PoStPartition +type RecoveryDeclaration = miner0.RecoveryDeclaration +type FaultDeclaration = miner0.FaultDeclaration + +// Params +type DeclareFaultsParams = miner0.DeclareFaultsParams +type DeclareFaultsRecoveredParams = miner0.DeclareFaultsRecoveredParams +type SubmitWindowedPoStParams = miner0.SubmitWindowedPoStParams +type ProveCommitSectorParams = miner0.ProveCommitSectorParams +type DisputeWindowedPoStParams = miner3.DisputeWindowedPoStParams + +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: + return abi.RegisteredSealProof_StackedDrg2KiBV1_1, nil + case abi.RegisteredPoStProof_StackedDrgWindow8MiBV1: + return abi.RegisteredSealProof_StackedDrg8MiBV1_1, nil + case abi.RegisteredPoStProof_StackedDrgWindow512MiBV1: + return abi.RegisteredSealProof_StackedDrg512MiBV1_1, nil + case abi.RegisteredPoStProof_StackedDrgWindow32GiBV1: + return abi.RegisteredSealProof_StackedDrg32GiBV1_1, nil + case abi.RegisteredPoStProof_StackedDrgWindow64GiBV1: + 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: + return abi.RegisteredPoStProof_StackedDrgWinning2KiBV1, nil + case abi.RegisteredPoStProof_StackedDrgWindow8MiBV1: + return abi.RegisteredPoStProof_StackedDrgWinning8MiBV1, nil + case abi.RegisteredPoStProof_StackedDrgWindow512MiBV1: + return abi.RegisteredPoStProof_StackedDrgWinning512MiBV1, nil + case abi.RegisteredPoStProof_StackedDrgWindow32GiBV1: + return abi.RegisteredPoStProof_StackedDrgWinning32GiBV1, nil + case abi.RegisteredPoStProof_StackedDrgWindow64GiBV1: + return abi.RegisteredPoStProof_StackedDrgWinning64GiBV1, nil + default: + return -1, xerrors.Errorf("unknown proof type %d", proof) + } +} + +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 +} + +func (mi MinerInfo) IsController(addr address.Address) bool { + if addr == mi.Owner || addr == mi.Worker { + return true + } + + for _, ca := range mi.ControlAddresses { + if addr == ca { + return true + } + } + + return false +} + +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)) +} diff --git a/chain/actors/builtin/miner/miner.go b/chain/actors/builtin/miner/miner.go index 8fffcc8d6..ae49a6d06 100644 --- a/chain/actors/builtin/miner/miner.go +++ b/chain/actors/builtin/miner/miner.go @@ -18,12 +18,13 @@ import ( "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/types" - builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" miner0 "github.com/filecoin-project/specs-actors/actors/builtin/miner" - builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/miner" - builtin3 "github.com/filecoin-project/specs-actors/v3/actors/builtin" miner3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/miner" + + 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" ) @@ -58,7 +59,7 @@ const MinSectorExpiration = miner0.MinSectorExpiration var DeclarationsMax = miner2.DeclarationsMax var AddressedSectorsMax = miner2.AddressedSectorsMax -func Load(store adt.Store, act *types.Actor) (st State, err error) { +func Load(store adt.Store, act *types.Actor) (State, error) { switch act.Code { case builtin0.StorageMinerActorCodeID: return load0(store, act.Head) diff --git a/chain/actors/builtin/miner/state.go.template b/chain/actors/builtin/miner/state.go.template new file mode 100644 index 000000000..df94c942c --- /dev/null +++ b/chain/actors/builtin/miner/state.go.template @@ -0,0 +1,446 @@ +package miner + +import ( + "bytes" + "errors" +{{if (le .v 1)}} + "github.com/filecoin-project/go-state-types/big" +{{end}} + "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/dline" + "github.com/ipfs/go-cid" + "github.com/libp2p/go-libp2p-core/peer" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/lotus/chain/actors/adt" + +{{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" +) + +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 +} + +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 +} + +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. + // 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 *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}}) 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}}) IsAllocated(num abi.SectorNumber) (bool, error) { + var allocatedSectors bitfield.BitField + if err := s.store.Get(s.store.Context(), s.State.AllocatedSectors, &allocatedSectors); err != nil { + return false, err + } + + return allocatedSectors.IsSet(uint64(num)) +} + +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 + } + + var pid *peer.ID + if peerID, err := peer.IDFromBytes(info.PeerId); err == nil { + pid = &peerID + } +{{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, + + NewWorker: address.Undef, + WorkerChangeEpoch: -1, + + PeerId: pid, + 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 info.PendingWorkerKey != nil { + mi.NewWorker = info.PendingWorkerKey.NewWorker + mi.WorkerChangeEpoch = info.PendingWorkerKey.EffectiveAt + } + + 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 (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 fromV{{.v}}SectorOnChainInfo(v{{.v}} miner{{.v}}.SectorOnChainInfo) SectorOnChainInfo { + {{if (ge .v 2)}}return 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, + }{{else}}return (SectorOnChainInfo)(v0){{end}} +} + +func fromV{{.v}}SectorPreCommitOnChainInfo(v{{.v}} miner{{.v}}.SectorPreCommitOnChainInfo) SectorPreCommitOnChainInfo { + {{if (ge .v 2)}}return SectorPreCommitOnChainInfo{ + Info: (SectorPreCommitInfo)(v{{.v}}.Info), + PreCommitDeposit: v{{.v}}.PreCommitDeposit, + PreCommitEpoch: v{{.v}}.PreCommitEpoch, + DealWeight: v{{.v}}.DealWeight, + VerifiedDealWeight: v{{.v}}.VerifiedDealWeight, + }{{else}}return (SectorPreCommitOnChainInfo)(v0){{end}} +} diff --git a/chain/actors/builtin/miner/v0.go b/chain/actors/builtin/miner/v0.go index 4f02e313f..262c4870d 100644 --- a/chain/actors/builtin/miner/v0.go +++ b/chain/actors/builtin/miner/v0.go @@ -196,6 +196,7 @@ func (s *state0) GetPrecommittedSector(num abi.SectorNumber) (*SectorPreCommitOn } ret := fromV0SectorPreCommitOnChainInfo(*info) + return &ret, nil } diff --git a/chain/actors/builtin/miner/v3.go b/chain/actors/builtin/miner/v3.go index 5e058ed1f..4f03fdcc9 100644 --- a/chain/actors/builtin/miner/v3.go +++ b/chain/actors/builtin/miner/v3.go @@ -208,9 +208,9 @@ func (s *state3) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, err // If no sector numbers are specified, load all. if snos == nil { infos := make([]*SectorOnChainInfo, 0, sectors.Length()) - var info2 miner3.SectorOnChainInfo - if err := sectors.ForEach(&info2, func(_ int64) error { - info := fromV3SectorOnChainInfo(info2) + var info3 miner3.SectorOnChainInfo + if err := sectors.ForEach(&info3, func(_ int64) error { + info := fromV3SectorOnChainInfo(info3) infos = append(infos, &info) return nil }); err != nil { @@ -220,13 +220,13 @@ func (s *state3) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, err } // Otherwise, load selected. - infos2, err := sectors.Load(*snos) + infos3, err := sectors.Load(*snos) if err != nil { return nil, err } - infos := make([]*SectorOnChainInfo, len(infos2)) - for i, info2 := range infos2 { - info := fromV3SectorOnChainInfo(*info2) + infos := make([]*SectorOnChainInfo, len(infos3)) + for i, info3 := range infos3 { + info := fromV3SectorOnChainInfo(*info3) infos[i] = &info } return infos, nil @@ -268,13 +268,13 @@ func (s *state3) NumDeadlines() (uint64, error) { } func (s *state3) DeadlinesChanged(other State) (bool, error) { - other2, ok := other.(*state3) + other3, ok := other.(*state3) if !ok { // treat an upgrade as a change, always return true, nil } - return !s.State.Deadlines.Equals(other2.Deadlines), nil + return !s.State.Deadlines.Equals(other3.Deadlines), nil } func (s *state3) MinerInfoChanged(other State) (bool, error) { @@ -377,13 +377,13 @@ func (d *deadline3) ForEachPartition(cb func(uint64, Partition) error) error { } func (d *deadline3) PartitionsChanged(other Deadline) (bool, error) { - other2, ok := other.(*deadline3) + other3, ok := other.(*deadline3) if !ok { // treat an upgrade as a change, always return true, nil } - return !d.Deadline.Partitions.Equals(other2.Deadline.Partitions), nil + return !d.Deadline.Partitions.Equals(other3.Deadline.Partitions), nil } func (d *deadline3) PartitionsPoSted() (bitfield.BitField, error) { diff --git a/chain/actors/builtin/miner/v4.go b/chain/actors/builtin/miner/v4.go index b354dbc33..e277c0298 100644 --- a/chain/actors/builtin/miner/v4.go +++ b/chain/actors/builtin/miner/v4.go @@ -208,9 +208,9 @@ func (s *state4) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, err // If no sector numbers are specified, load all. if snos == nil { infos := make([]*SectorOnChainInfo, 0, sectors.Length()) - var info2 miner4.SectorOnChainInfo - if err := sectors.ForEach(&info2, func(_ int64) error { - info := fromV4SectorOnChainInfo(info2) + var info4 miner4.SectorOnChainInfo + if err := sectors.ForEach(&info4, func(_ int64) error { + info := fromV4SectorOnChainInfo(info4) infos = append(infos, &info) return nil }); err != nil { @@ -220,13 +220,13 @@ func (s *state4) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, err } // Otherwise, load selected. - infos2, err := sectors.Load(*snos) + infos4, err := sectors.Load(*snos) if err != nil { return nil, err } - infos := make([]*SectorOnChainInfo, len(infos2)) - for i, info2 := range infos2 { - info := fromV4SectorOnChainInfo(*info2) + infos := make([]*SectorOnChainInfo, len(infos4)) + for i, info4 := range infos4 { + info := fromV4SectorOnChainInfo(*info4) infos[i] = &info } return infos, nil @@ -268,13 +268,13 @@ func (s *state4) NumDeadlines() (uint64, error) { } func (s *state4) DeadlinesChanged(other State) (bool, error) { - other2, ok := other.(*state4) + other4, ok := other.(*state4) if !ok { // treat an upgrade as a change, always return true, nil } - return !s.State.Deadlines.Equals(other2.Deadlines), nil + return !s.State.Deadlines.Equals(other4.Deadlines), nil } func (s *state4) MinerInfoChanged(other State) (bool, error) { @@ -377,13 +377,13 @@ func (d *deadline4) ForEachPartition(cb func(uint64, Partition) error) error { } func (d *deadline4) PartitionsChanged(other Deadline) (bool, error) { - other2, ok := other.(*deadline4) + other4, ok := other.(*deadline4) if !ok { // treat an upgrade as a change, always return true, nil } - return !d.Deadline.Partitions.Equals(other2.Deadline.Partitions), nil + return !d.Deadline.Partitions.Equals(other4.Deadline.Partitions), nil } func (d *deadline4) PartitionsPoSted() (bitfield.BitField, error) { diff --git a/chain/actors/builtin/multisig/actor.go.template b/chain/actors/builtin/multisig/actor.go.template new file mode 100644 index 000000000..76aff2581 --- /dev/null +++ b/chain/actors/builtin/multisig/actor.go.template @@ -0,0 +1,113 @@ +package multisig + +import ( + "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" + "github.com/ipfs/go-cid" + + msig0 "github.com/filecoin-project/specs-actors/actors/builtin/multisig" + msig4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/multisig" +{{range .versions}} + builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin"{{end}} + + "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" +) + +func init() { +{{range .versions}} builtin.RegisterActorState(builtin{{.}}.MultisigActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { + return load{{.}}(store, root) + }) +{{end}}} + +func Load(store adt.Store, act *types.Actor) (State, error) { + switch act.Code { +{{range .versions}} case builtin{{.}}.MultisigActorCodeID: + return load{{.}}(store, act.Head) +{{end}} } + return nil, xerrors.Errorf("unknown actor code %s", act.Code) +} + +type State interface { + cbor.Marshaler + + 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) +} + +type Transaction = msig0.Transaction + +var Methods = builtin{{.latestVersion}}.MethodsMultisig + +func Message(version actors.Version, from address.Address) MessageBuilder { + switch version { +{{range .versions}} case actors.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 + +func txnParams(id uint64, data *ProposalHashData) ([]byte, error) { + params := msig{{.latestVersion}}.TxnIDParams{ID: msig4.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) +} diff --git a/chain/actors/builtin/multisig/message.go b/chain/actors/builtin/multisig/message.go deleted file mode 100644 index 096049002..000000000 --- a/chain/actors/builtin/multisig/message.go +++ /dev/null @@ -1,79 +0,0 @@ -package multisig - -import ( - "fmt" - - "github.com/minio/blake2b-simd" - "golang.org/x/xerrors" - - "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-state-types/abi" - - builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" - multisig4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/multisig" - - "github.com/filecoin-project/lotus/chain/actors" - "github.com/filecoin-project/lotus/chain/types" -) - -var Methods = builtin4.MethodsMultisig - -func Message(version actors.Version, from address.Address) MessageBuilder { - switch version { - case actors.Version0: - return message0{from} - case actors.Version2: - return message2{message0{from}} - case actors.Version3: - return message3{message0{from}} - case actors.Version4: - return message4{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 = multisig4.ProposalHashData -type ProposeReturn = multisig4.ProposeReturn -type ProposeParams = multisig4.ProposeParams - -func txnParams(id uint64, data *ProposalHashData) ([]byte, error) { - params := multisig4.TxnIDParams{ID: multisig4.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) -} diff --git a/chain/actors/builtin/multisig/message.go.template b/chain/actors/builtin/multisig/message.go.template new file mode 100644 index 000000000..dfd5c6fc0 --- /dev/null +++ b/chain/actors/builtin/multisig/message.go.template @@ -0,0 +1,143 @@ +package multisig + +import ( + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + + 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" + + "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 + } + + // 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, + } + + enc, actErr = actors.SerializeParams(execParams) + if actErr != nil { + return nil, actErr + } + + return &types.Message{ + To: init_.Address, + From: m.from, + Method: builtin{{.v}}.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/state.go b/chain/actors/builtin/multisig/multisig.go similarity index 51% rename from chain/actors/builtin/multisig/state.go rename to chain/actors/builtin/multisig/multisig.go index 0ce10d290..0cee644c5 100644 --- a/chain/actors/builtin/multisig/state.go +++ b/chain/actors/builtin/multisig/multisig.go @@ -1,6 +1,9 @@ package multisig import ( + "fmt" + + "github.com/minio/blake2b-simd" cbg "github.com/whyrusleeping/cbor-gen" "golang.org/x/xerrors" @@ -9,12 +12,15 @@ import ( "github.com/filecoin-project/go-state-types/cbor" "github.com/ipfs/go-cid" - builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" msig0 "github.com/filecoin-project/specs-actors/actors/builtin/multisig" + msig4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/multisig" + + 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" + "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" @@ -67,3 +73,65 @@ type State interface { } type Transaction = msig0.Transaction + +var Methods = builtin4.MethodsMultisig + +func Message(version actors.Version, from address.Address) MessageBuilder { + switch version { + case actors.Version0: + return message0{from} + case actors.Version2: + return message2{message0{from}} + case actors.Version3: + return message3{message0{from}} + case actors.Version4: + return message4{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 = msig4.ProposalHashData +type ProposeReturn = msig4.ProposeReturn +type ProposeParams = msig4.ProposeParams + +func txnParams(id uint64, data *ProposalHashData) ([]byte, error) { + params := msig4.TxnIDParams{ID: msig4.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) +} diff --git a/chain/actors/builtin/multisig/state.go.template b/chain/actors/builtin/multisig/state.go.template new file mode 100644 index 000000000..c62655646 --- /dev/null +++ b/chain/actors/builtin/multisig/state.go.template @@ -0,0 +1,95 @@ +package multisig + +import ( + "bytes" + "encoding/binary" + + adt{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/util/adt" + + "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" + +{{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" +) + +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 +} + +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 tx, nil +} diff --git a/chain/actors/builtin/multisig/state0.go b/chain/actors/builtin/multisig/v0.go similarity index 95% rename from chain/actors/builtin/multisig/state0.go rename to chain/actors/builtin/multisig/v0.go index 27dd5c413..20c1557b0 100644 --- a/chain/actors/builtin/multisig/state0.go +++ b/chain/actors/builtin/multisig/v0.go @@ -4,6 +4,8 @@ import ( "bytes" "encoding/binary" + adt0 "github.com/filecoin-project/specs-actors/actors/util/adt" + "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" "github.com/ipfs/go-cid" @@ -13,8 +15,6 @@ import ( "github.com/filecoin-project/lotus/chain/actors/adt" msig0 "github.com/filecoin-project/specs-actors/actors/builtin/multisig" - multisig0 "github.com/filecoin-project/specs-actors/actors/builtin/multisig" - adt0 "github.com/filecoin-project/specs-actors/actors/util/adt" ) var _ State = (*state0)(nil) @@ -86,7 +86,7 @@ func (s *state0) transactions() (adt.Map, error) { } func (s *state0) decodeTransaction(val *cbg.Deferred) (Transaction, error) { - var tx multisig0.Transaction + var tx msig0.Transaction if err := tx.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { return Transaction{}, err } diff --git a/chain/actors/builtin/multisig/state2.go b/chain/actors/builtin/multisig/v2.go similarity index 99% rename from chain/actors/builtin/multisig/state2.go rename to chain/actors/builtin/multisig/v2.go index d637abb91..ef317f903 100644 --- a/chain/actors/builtin/multisig/state2.go +++ b/chain/actors/builtin/multisig/v2.go @@ -4,6 +4,8 @@ import ( "bytes" "encoding/binary" + adt2 "github.com/filecoin-project/specs-actors/v2/actors/util/adt" + "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" "github.com/ipfs/go-cid" @@ -13,7 +15,6 @@ import ( "github.com/filecoin-project/lotus/chain/actors/adt" msig2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/multisig" - adt2 "github.com/filecoin-project/specs-actors/v2/actors/util/adt" ) var _ State = (*state2)(nil) diff --git a/chain/actors/builtin/multisig/state3.go b/chain/actors/builtin/multisig/v3.go similarity index 96% rename from chain/actors/builtin/multisig/state3.go rename to chain/actors/builtin/multisig/v3.go index a2eb1d909..1a7611440 100644 --- a/chain/actors/builtin/multisig/state3.go +++ b/chain/actors/builtin/multisig/v3.go @@ -74,12 +74,12 @@ func (s *state3) ForEachPendingTxn(cb func(id int64, txn Transaction) error) err } func (s *state3) PendingTxnChanged(other State) (bool, error) { - other2, ok := other.(*state3) + other3, ok := other.(*state3) if !ok { // treat an upgrade as a change, always return true, nil } - return !s.State.PendingTxns.Equals(other2.PendingTxns), nil + return !s.State.PendingTxns.Equals(other3.PendingTxns), nil } func (s *state3) transactions() (adt.Map, error) { diff --git a/chain/actors/builtin/multisig/state4.go b/chain/actors/builtin/multisig/v4.go similarity index 93% rename from chain/actors/builtin/multisig/state4.go rename to chain/actors/builtin/multisig/v4.go index 3475ad361..50c1e9620 100644 --- a/chain/actors/builtin/multisig/state4.go +++ b/chain/actors/builtin/multisig/v4.go @@ -69,17 +69,17 @@ func (s *state4) ForEachPendingTxn(cb func(id int64, txn Transaction) error) err if n <= 0 { return xerrors.Errorf("invalid pending transaction key: %v", key) } - return cb(txid, (Transaction)(out)) + return cb(txid, (Transaction)(out)) //nolint:unconvert }) } func (s *state4) PendingTxnChanged(other State) (bool, error) { - other2, ok := other.(*state4) + other4, ok := other.(*state4) if !ok { // treat an upgrade as a change, always return true, nil } - return !s.State.PendingTxns.Equals(other2.PendingTxns), nil + return !s.State.PendingTxns.Equals(other4.PendingTxns), nil } func (s *state4) transactions() (adt.Map, error) { diff --git a/chain/actors/builtin/paych/actor.go.template b/chain/actors/builtin/paych/actor.go.template new file mode 100644 index 000000000..bd0d6c03c --- /dev/null +++ b/chain/actors/builtin/paych/actor.go.template @@ -0,0 +1,103 @@ +package paych + +import ( + "encoding/base64" + "fmt" + + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + big "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/cbor" + "github.com/ipfs/go-cid" + ipldcbor "github.com/ipfs/go-ipld-cbor" + + paych0 "github.com/filecoin-project/specs-actors/actors/builtin/paych" +{{range .versions}} + builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin"{{end}} + + "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" +) + +func init() { +{{range .versions}} builtin.RegisterActorState(builtin{{.}}.PaymentChannelActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { + return load{{.}}(store, root) + }) +{{end}}} + +// Load returns an abstract copy of payment channel state, irregardless of actor version +func Load(store adt.Store, act *types.Actor) (State, error) { + switch act.Code { +{{range .versions}} case builtin{{.}}.PaymentChannelActorCodeID: + return load{{.}}(store, act.Head) +{{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 + // 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 +} + +// LaneState is an abstract copy of the state of a single lane +type LaneState interface { + Redeemed() (big.Int, error) + Nonce() (uint64, error) +} + +type SignedVoucher = paych0.SignedVoucher +type ModVerifyParams = paych0.ModVerifyParams + +// DecodeSignedVoucher decodes base64 encoded signed voucher. +func DecodeSignedVoucher(s string) (*SignedVoucher, error) { + data, err := base64.RawURLEncoding.DecodeString(s) + if err != nil { + return nil, err + } + + var sv SignedVoucher + if err := ipldcbor.DecodeInto(data, &sv); err != nil { + return nil, err + } + + return &sv, nil +} + +var Methods = builtin{{.latestVersion}}.MethodsPaych + +func Message(version actors.Version, from address.Address) MessageBuilder { + switch version { +{{range .versions}} case actors.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 *SignedVoucher, secret []byte) (*types.Message, error) + Settle(paych address.Address) (*types.Message, error) + Collect(paych address.Address) (*types.Message, error) +} diff --git a/chain/actors/builtin/paych/message.go b/chain/actors/builtin/paych/message.go deleted file mode 100644 index 6669cd227..000000000 --- a/chain/actors/builtin/paych/message.go +++ /dev/null @@ -1,36 +0,0 @@ -package paych - -import ( - "fmt" - - "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" - - builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" -) - -var Methods = builtin4.MethodsPaych - -func Message(version actors.Version, from address.Address) MessageBuilder { - switch version { - case actors.Version0: - return message0{from} - case actors.Version2: - return message2{from} - case actors.Version3: - return message3{from} - case actors.Version4: - return message4{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 *SignedVoucher, secret []byte) (*types.Message, error) - Settle(paych address.Address) (*types.Message, error) - Collect(paych address.Address) (*types.Message, error) -} diff --git a/chain/actors/builtin/paych/message.go.template b/chain/actors/builtin/paych/message.go.template new file mode 100644 index 000000000..4a5ea2331 --- /dev/null +++ b/chain/actors/builtin/paych/message.go.template @@ -0,0 +1,74 @@ +package paych + +import ( + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + + 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" + + "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) { + 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: builtin{{.v}}.PaymentChannelActorCodeID, + 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 *SignedVoucher, secret []byte) (*types.Message, error) { + params, aerr := actors.SerializeParams(&paych{{.v}}.UpdateChannelStateParams{ + Sv: *sv, + 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 +} + +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/state.go b/chain/actors/builtin/paych/paych.go similarity index 80% rename from chain/actors/builtin/paych/state.go rename to chain/actors/builtin/paych/paych.go index f28dc2f83..a62b2691f 100644 --- a/chain/actors/builtin/paych/state.go +++ b/chain/actors/builtin/paych/paych.go @@ -2,6 +2,7 @@ package paych import ( "encoding/base64" + "fmt" "golang.org/x/xerrors" @@ -12,12 +13,14 @@ import ( "github.com/ipfs/go-cid" ipldcbor "github.com/ipfs/go-ipld-cbor" - builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" paych0 "github.com/filecoin-project/specs-actors/actors/builtin/paych" + + 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" + "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" @@ -98,3 +101,27 @@ func DecodeSignedVoucher(s string) (*SignedVoucher, error) { return &sv, nil } + +var Methods = builtin4.MethodsPaych + +func Message(version actors.Version, from address.Address) MessageBuilder { + switch version { + case actors.Version0: + return message0{from} + case actors.Version2: + return message2{from} + case actors.Version3: + return message3{from} + case actors.Version4: + return message4{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 *SignedVoucher, secret []byte) (*types.Message, error) + Settle(paych address.Address) (*types.Message, error) + Collect(paych address.Address) (*types.Message, error) +} diff --git a/chain/actors/builtin/paych/state.go.template b/chain/actors/builtin/paych/state.go.template new file mode 100644 index 000000000..b4b575a3e --- /dev/null +++ b/chain/actors/builtin/paych/state.go.template @@ -0,0 +1,104 @@ +package paych + +import ( + "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" + + paych{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/builtin/paych" + adt{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/util/adt" +) + +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 +} + +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 +} + +// 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 +} diff --git a/chain/actors/builtin/paych/state0.go b/chain/actors/builtin/paych/v0.go similarity index 100% rename from chain/actors/builtin/paych/state0.go rename to chain/actors/builtin/paych/v0.go diff --git a/chain/actors/builtin/paych/state2.go b/chain/actors/builtin/paych/v2.go similarity index 100% rename from chain/actors/builtin/paych/state2.go rename to chain/actors/builtin/paych/v2.go diff --git a/chain/actors/builtin/paych/state3.go b/chain/actors/builtin/paych/v3.go similarity index 100% rename from chain/actors/builtin/paych/state3.go rename to chain/actors/builtin/paych/v3.go diff --git a/chain/actors/builtin/paych/state4.go b/chain/actors/builtin/paych/v4.go similarity index 100% rename from chain/actors/builtin/paych/state4.go rename to chain/actors/builtin/paych/v4.go diff --git a/chain/actors/builtin/power/actor.go.template b/chain/actors/builtin/power/actor.go.template new file mode 100644 index 000000000..56b591f7c --- /dev/null +++ b/chain/actors/builtin/power/actor.go.template @@ -0,0 +1,74 @@ +package power + +import ( + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/big" + "github.com/ipfs/go-cid" + 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" +{{range .versions}} + builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin"{{end}} +) + +func init() { +{{range .versions}} builtin.RegisterActorState(builtin{{.}}.StoragePowerActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { + return load{{.}}(store, root) + }) +{{end}}} + +var ( + Address = builtin{{.latestVersion}}.StoragePowerActorAddr + Methods = builtin{{.latestVersion}}.MethodsPower +) + +func Load(store adt.Store, act *types.Actor) (State, error) { + switch act.Code { +{{range .versions}} case builtin{{.}}.StoragePowerActorCodeID: + return load{{.}}(store, act.Head) +{{end}} } + return nil, xerrors.Errorf("unknown actor code %s", act.Code) +} + +type State interface { + cbor.Marshaler + + TotalLocked() (abi.TokenAmount, error) + TotalPower() (Claim, error) + TotalCommitted() (Claim, error) + TotalPowerSmoothed() (builtin.FilterEstimate, error) + + // 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) + + // 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), + } +} diff --git a/chain/actors/builtin/power/power.go b/chain/actors/builtin/power/power.go index 7e15275a5..56b6f7c95 100644 --- a/chain/actors/builtin/power/power.go +++ b/chain/actors/builtin/power/power.go @@ -40,7 +40,7 @@ var ( Methods = builtin4.MethodsPower ) -func Load(store adt.Store, act *types.Actor) (st State, err error) { +func Load(store adt.Store, act *types.Actor) (State, error) { switch act.Code { case builtin0.StoragePowerActorCodeID: return load0(store, act.Head) diff --git a/chain/actors/builtin/power/state.go.template b/chain/actors/builtin/power/state.go.template new file mode 100644 index 000000000..b71ac1c07 --- /dev/null +++ b/chain/actors/builtin/power/state.go.template @@ -0,0 +1,149 @@ +package power + +import ( + "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/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" + +{{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" +) + +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 +} + +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.FromV{{.v}}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}}) 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, + } +} diff --git a/chain/actors/builtin/power/v0.go b/chain/actors/builtin/power/v0.go index 7636b612b..91fad8c57 100644 --- a/chain/actors/builtin/power/v0.go +++ b/chain/actors/builtin/power/v0.go @@ -51,7 +51,7 @@ func (s *state0) TotalCommitted() (Claim, error) { } func (s *state0) MinerPower(addr address.Address) (Claim, bool, error) { - claims, err := adt0.AsMap(s.store, s.Claims) + claims, err := s.claims() if err != nil { return Claim{}, false, err } @@ -79,7 +79,7 @@ func (s *state0) MinerCounts() (uint64, uint64, error) { } func (s *state0) ListAllMiners() ([]address.Address, error) { - claims, err := adt0.AsMap(s.store, s.Claims) + claims, err := s.claims() if err != nil { return nil, err } @@ -101,7 +101,7 @@ func (s *state0) ListAllMiners() ([]address.Address, error) { } func (s *state0) ForEachClaim(cb func(miner address.Address, claim Claim) error) error { - claims, err := adt0.AsMap(s.store, s.Claims) + claims, err := s.claims() if err != nil { return err } @@ -141,5 +141,8 @@ func (s *state0) decodeClaim(val *cbg.Deferred) (Claim, error) { } func fromV0Claim(v0 power0.Claim) Claim { - return (Claim)(v0) + return Claim{ + RawBytePower: v0.RawBytePower, + QualityAdjPower: v0.QualityAdjPower, + } } diff --git a/chain/actors/builtin/power/v2.go b/chain/actors/builtin/power/v2.go index 012dc2a4f..313160a78 100644 --- a/chain/actors/builtin/power/v2.go +++ b/chain/actors/builtin/power/v2.go @@ -51,7 +51,7 @@ func (s *state2) TotalCommitted() (Claim, error) { } func (s *state2) MinerPower(addr address.Address) (Claim, bool, error) { - claims, err := adt2.AsMap(s.store, s.Claims) + claims, err := s.claims() if err != nil { return Claim{}, false, err } @@ -79,7 +79,7 @@ func (s *state2) MinerCounts() (uint64, uint64, error) { } func (s *state2) ListAllMiners() ([]address.Address, error) { - claims, err := adt2.AsMap(s.store, s.Claims) + claims, err := s.claims() if err != nil { return nil, err } @@ -101,7 +101,7 @@ func (s *state2) ListAllMiners() ([]address.Address, error) { } func (s *state2) ForEachClaim(cb func(miner address.Address, claim Claim) error) error { - claims, err := adt2.AsMap(s.store, s.Claims) + claims, err := s.claims() if err != nil { return err } diff --git a/chain/actors/builtin/power/v3.go b/chain/actors/builtin/power/v3.go index fd161dda5..174a091cd 100644 --- a/chain/actors/builtin/power/v3.go +++ b/chain/actors/builtin/power/v3.go @@ -121,12 +121,12 @@ func (s *state3) ForEachClaim(cb func(miner address.Address, claim Claim) error) } func (s *state3) ClaimsChanged(other State) (bool, error) { - other2, ok := other.(*state3) + other3, ok := other.(*state3) if !ok { // treat an upgrade as a change, always return true, nil } - return !s.State.Claims.Equals(other2.State.Claims), nil + return !s.State.Claims.Equals(other3.State.Claims), nil } func (s *state3) claims() (adt.Map, error) { diff --git a/chain/actors/builtin/power/v4.go b/chain/actors/builtin/power/v4.go index bae5d044e..95cb47a65 100644 --- a/chain/actors/builtin/power/v4.go +++ b/chain/actors/builtin/power/v4.go @@ -121,12 +121,12 @@ func (s *state4) ForEachClaim(cb func(miner address.Address, claim Claim) error) } func (s *state4) ClaimsChanged(other State) (bool, error) { - other2, ok := other.(*state4) + other4, ok := other.(*state4) if !ok { // treat an upgrade as a change, always return true, nil } - return !s.State.Claims.Equals(other2.State.Claims), nil + return !s.State.Claims.Equals(other4.State.Claims), nil } func (s *state4) claims() (adt.Map, error) { diff --git a/chain/actors/builtin/reward/actor.go.template b/chain/actors/builtin/reward/actor.go.template new file mode 100644 index 000000000..476ec6190 --- /dev/null +++ b/chain/actors/builtin/reward/actor.go.template @@ -0,0 +1,56 @@ +package reward + +import ( + "github.com/filecoin-project/go-state-types/abi" + reward0 "github.com/filecoin-project/specs-actors/actors/builtin/reward" + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-state-types/cbor" +{{range .versions}} + builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin"{{end}} + + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/types" +) + +func init() { +{{range .versions}} builtin.RegisterActorState(builtin{{.}}.RewardActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { + return load{{.}}(store, root) + }) +{{end}}} + +var ( + Address = builtin{{.latestVersion}}.RewardActorAddr + Methods = builtin{{.latestVersion}}.MethodsReward +) + +func Load(store adt.Store, act *types.Actor) (State, error) { + switch act.Code { +{{range .versions}} case builtin{{.}}.RewardActorCodeID: + return load{{.}}(store, act.Head) +{{end}} } + return nil, xerrors.Errorf("unknown actor code %s", act.Code) +} + +type State interface { + cbor.Marshaler + + 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) +} + +type AwardBlockRewardParams = reward0.AwardBlockRewardParams diff --git a/chain/actors/builtin/reward/reward.go b/chain/actors/builtin/reward/reward.go index cfcd68dd5..ac836f854 100644 --- a/chain/actors/builtin/reward/reward.go +++ b/chain/actors/builtin/reward/reward.go @@ -7,6 +7,7 @@ import ( "golang.org/x/xerrors" "github.com/filecoin-project/go-state-types/cbor" + 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" @@ -37,7 +38,7 @@ var ( Methods = builtin4.MethodsReward ) -func Load(store adt.Store, act *types.Actor) (st State, err error) { +func Load(store adt.Store, act *types.Actor) (State, error) { switch act.Code { case builtin0.RewardActorCodeID: return load0(store, act.Head) diff --git a/chain/actors/builtin/reward/state.go.template b/chain/actors/builtin/reward/state.go.template new file mode 100644 index 000000000..906b97837 --- /dev/null +++ b/chain/actors/builtin/reward/state.go.template @@ -0,0 +1,99 @@ +package reward + +import ( + "github.com/filecoin-project/go-state-types/abi" + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" + + 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" +) + +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 +} + +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.FromV0FilterEstimate(*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 +} diff --git a/chain/actors/builtin/reward/v0.go b/chain/actors/builtin/reward/v0.go index 6a6e6d12e..eba6f65d7 100644 --- a/chain/actors/builtin/reward/v0.go +++ b/chain/actors/builtin/reward/v0.go @@ -28,7 +28,7 @@ type state0 struct { store adt.Store } -func (s *state0) ThisEpochReward() (abi.StoragePower, error) { +func (s *state0) ThisEpochReward() (abi.TokenAmount, error) { return s.State.ThisEpochReward, nil } @@ -52,11 +52,11 @@ func (s *state0) EffectiveNetworkTime() (abi.ChainEpoch, error) { return s.State.EffectiveNetworkTime, nil } -func (s *state0) CumsumBaseline() (abi.StoragePower, error) { +func (s *state0) CumsumBaseline() (reward0.Spacetime, error) { return s.State.CumsumBaseline, nil } -func (s *state0) CumsumRealized() (abi.StoragePower, error) { +func (s *state0) CumsumRealized() (reward0.Spacetime, error) { return s.State.CumsumRealized, nil } diff --git a/chain/actors/builtin/verifreg/actor.go.template b/chain/actors/builtin/verifreg/actor.go.template new file mode 100644 index 000000000..d194a2c89 --- /dev/null +++ b/chain/actors/builtin/verifreg/actor.go.template @@ -0,0 +1,46 @@ +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" + + "github.com/filecoin-project/go-state-types/cbor" +{{range .versions}} + builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin"{{end}} + + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin" + "github.com/filecoin-project/lotus/chain/types" +) + +func init() { +{{range .versions}} builtin.RegisterActorState(builtin{{.}}.VerifiedRegistryActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { + return load{{.}}(store, root) + }) +{{end}}} + +var ( + Address = builtin{{.latestVersion}}.VerifiedRegistryActorAddr + Methods = builtin{{.latestVersion}}.MethodsVerifiedRegistry +) + +func Load(store adt.Store, act *types.Actor) (State, error) { + switch act.Code { +{{range .versions}} case builtin{{.}}.VerifiedRegistryActorCodeID: + return load{{.}}(store, act.Head) +{{end}} } + return nil, xerrors.Errorf("unknown actor code %s", act.Code) +} + +type State interface { + cbor.Marshaler + + RootKey() (address.Address, error) + VerifiedClientDataCap(address.Address) (bool, abi.StoragePower, error) + VerifierDataCap(address.Address) (bool, abi.StoragePower, error) + ForEachVerifier(func(addr address.Address, dcap abi.StoragePower) error) error + ForEachClient(func(addr address.Address, dcap abi.StoragePower) error) error +} diff --git a/chain/actors/builtin/verifreg/state.go.template b/chain/actors/builtin/verifreg/state.go.template new file mode 100644 index 000000000..244d20932 --- /dev/null +++ b/chain/actors/builtin/verifreg/state.go.template @@ -0,0 +1,58 @@ +package verifreg + +import ( + "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" + +{{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" +) + +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 +} + +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) { + return getDataCap(s.store, actors.Version{{.v}}, s.verifiedClients, addr) +} + +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}}) 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 { + return forEachCap(s.store, actors.Version{{.v}}, s.verifiedClients, cb) +} + +func (s *state{{.v}}) verifiedClients() (adt.Map, error) { + return adt{{.v}}.AsMap(s.store, s.VerifiedClients{{if (ge .v 3)}}, builtin{{.v}}.DefaultHamtBitwidth{{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}}) +} diff --git a/chain/actors/builtin/verifreg/verifreg.go b/chain/actors/builtin/verifreg/verifreg.go index 83ba0c6c5..88e8adfc1 100644 --- a/chain/actors/builtin/verifreg/verifreg.go +++ b/chain/actors/builtin/verifreg/verifreg.go @@ -8,6 +8,7 @@ import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/cbor" + 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" From 715b3dccecf22997891952561951f6efabcab028 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 28 Apr 2021 16:20:53 +0200 Subject: [PATCH 34/41] Fix error message --- chain/actors/agen/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chain/actors/agen/main.go b/chain/actors/agen/main.go index 0d73a064e..637361f17 100644 --- a/chain/actors/agen/main.go +++ b/chain/actors/agen/main.go @@ -120,7 +120,7 @@ func generateMessages(actDir string) error { return nil // skip } - return xerrors.Errorf("loading state adapter template: %w", err) + return xerrors.Errorf("loading message adapter template: %w", err) } for _, version := range versions { From c6cebb448fd3be4db082d400c26649eed69d9f45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 30 Apr 2021 17:51:24 +0200 Subject: [PATCH 35/41] Cleanup actor adapter templates --- Makefile | 10 ++++-- chain/actors/builtin/account/account.go | 12 +++++++ .../actors/builtin/account/actor.go.template | 12 ++++--- chain/actors/builtin/init/actor.go.template | 12 ++++--- chain/actors/builtin/init/init.go | 12 +++++++ chain/actors/builtin/init/state.go.template | 7 ++-- chain/actors/builtin/init/v3.go | 3 +- chain/actors/builtin/init/v4.go | 3 +- chain/actors/builtin/market/actor.go.template | 29 ++++++++++++++--- chain/actors/builtin/market/market.go | 14 +++++++- chain/actors/builtin/miner/actor.go.template | 15 ++++++--- chain/actors/builtin/miner/miner.go | 13 ++++++++ chain/actors/builtin/miner/state.go.template | 32 +++++++++++++------ chain/actors/builtin/miner/v0.go | 6 ++++ chain/actors/builtin/miner/v2.go | 6 ++++ chain/actors/builtin/miner/v3.go | 7 ++++ chain/actors/builtin/miner/v4.go | 7 ++++ .../actors/builtin/multisig/actor.go.template | 15 ++++++--- .../builtin/multisig/message.go.template | 7 ++-- chain/actors/builtin/multisig/multisig.go | 16 ++++++++++ .../actors/builtin/multisig/state.go.template | 6 ++-- chain/actors/builtin/multisig/v3.go | 1 + chain/actors/builtin/multisig/v4.go | 1 + chain/actors/builtin/paych/actor.go.template | 18 +++++++---- chain/actors/builtin/paych/paych.go | 17 ++++++++++ chain/actors/builtin/power/actor.go.template | 12 ++++--- chain/actors/builtin/power/power.go | 12 +++++++ chain/actors/builtin/power/state.go.template | 6 ++-- chain/actors/builtin/power/v3.go | 1 + chain/actors/builtin/power/v4.go | 1 + chain/actors/builtin/reward/actor.go.template | 12 ++++--- chain/actors/builtin/reward/reward.go | 12 +++++++ chain/actors/builtin/reward/state.go.template | 8 +++-- chain/actors/builtin/reward/v0.go | 2 ++ chain/actors/builtin/reward/v2.go | 2 ++ chain/actors/builtin/reward/v3.go | 2 ++ chain/actors/builtin/reward/v4.go | 2 ++ .../actors/builtin/verifreg/actor.go.template | 15 ++++++--- chain/actors/builtin/verifreg/verifreg.go | 13 ++++++++ 39 files changed, 315 insertions(+), 66 deletions(-) diff --git a/Makefile b/Makefile index 1ccce11ed..67d26a71b 100644 --- a/Makefile +++ b/Makefile @@ -333,6 +333,10 @@ type-gen: api-gen method-gen: api-gen (cd ./lotuspond/front/src/chain && go run ./methodgen.go) +actors-gen: + go run ./chain/actors/agen + go fmt ./... + api-gen: go run ./gen/api goimports -w api @@ -341,9 +345,9 @@ api-gen: docsgen: docsgen-md docsgen-openrpc -docsgen-md-bin: api-gen +docsgen-md-bin: api-gen actors-gen go build $(GOFLAGS) -o docgen-md ./api/docgen/cmd -docsgen-openrpc-bin: api-gen +docsgen-openrpc-bin: api-gen actors-gen go build $(GOFLAGS) -o docgen-openrpc ./api/docgen-openrpc/cmd docsgen-md: docsgen-md-full docsgen-md-storage docsgen-md-worker @@ -367,7 +371,7 @@ docsgen-openrpc-worker: docsgen-openrpc-bin .PHONY: docsgen docsgen-md-bin docsgen-openrpc-bin -gen: type-gen method-gen docsgen api-gen +gen: actors-gen type-gen method-gen docsgen api-gen @echo ">>> IF YOU'VE MODIFIED THE CLI, REMEMBER TO ALSO MAKE docsgen-cli" .PHONY: gen diff --git a/chain/actors/builtin/account/account.go b/chain/actors/builtin/account/account.go index 4d94b245a..8242e300d 100644 --- a/chain/actors/builtin/account/account.go +++ b/chain/actors/builtin/account/account.go @@ -12,21 +12,28 @@ import ( "github.com/filecoin-project/lotus/chain/types" 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" ) func init() { + builtin.RegisterActorState(builtin0.AccountActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load0(store, root) }) + builtin.RegisterActorState(builtin2.AccountActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load2(store, root) }) + builtin.RegisterActorState(builtin3.AccountActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load3(store, root) }) + builtin.RegisterActorState(builtin4.AccountActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load4(store, root) }) @@ -36,14 +43,19 @@ var Methods = builtin4.MethodsAccount func Load(store adt.Store, act *types.Actor) (State, error) { 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) + } return nil, xerrors.Errorf("unknown actor code %s", act.Code) } diff --git a/chain/actors/builtin/account/actor.go.template b/chain/actors/builtin/account/actor.go.template index 860dadc1c..f75af3dfb 100644 --- a/chain/actors/builtin/account/actor.go.template +++ b/chain/actors/builtin/account/actor.go.template @@ -11,11 +11,13 @@ import ( "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/types" {{range .versions}} - builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin"{{end}} + builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin" +{{end}} ) func init() { -{{range .versions}} builtin.RegisterActorState(builtin{{.}}.AccountActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { +{{range .versions}} + builtin.RegisterActorState(builtin{{.}}.AccountActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load{{.}}(store, root) }) {{end}}} @@ -24,9 +26,11 @@ var Methods = builtin4.MethodsAccount func Load(store adt.Store, act *types.Actor) (State, error) { switch act.Code { -{{range .versions}} case builtin{{.}}.AccountActorCodeID: +{{range .versions}} + case builtin{{.}}.AccountActorCodeID: return load{{.}}(store, act.Head) -{{end}} } +{{end}} + } return nil, xerrors.Errorf("unknown actor code %s", act.Code) } diff --git a/chain/actors/builtin/init/actor.go.template b/chain/actors/builtin/init/actor.go.template index 0c9a6c702..5b700cec8 100644 --- a/chain/actors/builtin/init/actor.go.template +++ b/chain/actors/builtin/init/actor.go.template @@ -13,11 +13,13 @@ import ( "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/node/modules/dtypes" {{range .versions}} - builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin"{{end}} + builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin" +{{end}} ) func init() { -{{range .versions}} builtin.RegisterActorState(builtin{{.}}.InitActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { +{{range .versions}} + builtin.RegisterActorState(builtin{{.}}.InitActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load{{.}}(store, root) }) {{end}}} @@ -29,9 +31,11 @@ var ( func Load(store adt.Store, act *types.Actor) (State, error) { switch act.Code { -{{range .versions}} case builtin{{.}}.InitActorCodeID: +{{range .versions}} + case builtin{{.}}.InitActorCodeID: return load{{.}}(store, act.Head) -{{end}} } +{{end}} + } return nil, xerrors.Errorf("unknown actor code %s", act.Code) } diff --git a/chain/actors/builtin/init/init.go b/chain/actors/builtin/init/init.go index 697148641..730d21fd8 100644 --- a/chain/actors/builtin/init/init.go +++ b/chain/actors/builtin/init/init.go @@ -14,21 +14,28 @@ import ( "github.com/filecoin-project/lotus/node/modules/dtypes" 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" ) func init() { + builtin.RegisterActorState(builtin0.InitActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load0(store, root) }) + builtin.RegisterActorState(builtin2.InitActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load2(store, root) }) + builtin.RegisterActorState(builtin3.InitActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load3(store, root) }) + builtin.RegisterActorState(builtin4.InitActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load4(store, root) }) @@ -41,14 +48,19 @@ var ( func Load(store adt.Store, act *types.Actor) (State, error) { 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) + } return nil, xerrors.Errorf("unknown actor code %s", act.Code) } diff --git a/chain/actors/builtin/init/state.go.template b/chain/actors/builtin/init/state.go.template index 3fd7aaaa7..95f052bda 100644 --- a/chain/actors/builtin/init/state.go.template +++ b/chain/actors/builtin/init/state.go.template @@ -3,14 +3,17 @@ package init import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" -{{if (ge .v 3)}} builtin{{.v}} "github.com/filecoin-project/specs-actors{{.import}}actors/builtin" -{{end}} "github.com/ipfs/go-cid" + "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/node/modules/dtypes" +{{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" ) diff --git a/chain/actors/builtin/init/v3.go b/chain/actors/builtin/init/v3.go index e586b3b11..eaa54dfd4 100644 --- a/chain/actors/builtin/init/v3.go +++ b/chain/actors/builtin/init/v3.go @@ -3,7 +3,6 @@ package init import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" - builtin3 "github.com/filecoin-project/specs-actors/v3/actors/builtin" "github.com/ipfs/go-cid" cbg "github.com/whyrusleeping/cbor-gen" "golang.org/x/xerrors" @@ -11,6 +10,8 @@ import ( "github.com/filecoin-project/lotus/chain/actors/adt" "github.com/filecoin-project/lotus/node/modules/dtypes" + 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" ) diff --git a/chain/actors/builtin/init/v4.go b/chain/actors/builtin/init/v4.go index a2be603a3..38749eed5 100644 --- a/chain/actors/builtin/init/v4.go +++ b/chain/actors/builtin/init/v4.go @@ -3,7 +3,6 @@ package init import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" - builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" "github.com/ipfs/go-cid" cbg "github.com/whyrusleeping/cbor-gen" "golang.org/x/xerrors" @@ -11,6 +10,8 @@ import ( "github.com/filecoin-project/lotus/chain/actors/adt" "github.com/filecoin-project/lotus/node/modules/dtypes" + 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" ) diff --git a/chain/actors/builtin/market/actor.go.template b/chain/actors/builtin/market/actor.go.template index 8c16ffb91..39cfe1be7 100644 --- a/chain/actors/builtin/market/actor.go.template +++ b/chain/actors/builtin/market/actor.go.template @@ -5,13 +5,15 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/cbor" "github.com/ipfs/go-cid" cbg "github.com/whyrusleeping/cbor-gen" market0 "github.com/filecoin-project/specs-actors/actors/builtin/market" {{range .versions}} - builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin"{{end}} + builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin" +{{end}} "github.com/filecoin-project/lotus/chain/actors/adt" "github.com/filecoin-project/lotus/chain/actors/builtin" @@ -19,7 +21,8 @@ import ( ) func init() { -{{range .versions}} builtin.RegisterActorState(builtin{{.}}.StorageMarketActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { +{{range .versions}} + builtin.RegisterActorState(builtin{{.}}.StorageMarketActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load{{.}}(store, root) }) {{end}}} @@ -31,9 +34,11 @@ var ( func Load(store adt.Store, act *types.Actor) (State, error) { switch act.Code { -{{range .versions}} case builtin{{.}}.StorageMarketActorCodeID: +{{range .versions}} + case builtin{{.}}.StorageMarketActorCodeID: return load{{.}}(store, act.Head) -{{end}} } +{{end}} + } return nil, xerrors.Errorf("unknown actor code %s", act.Code) } @@ -136,3 +141,19 @@ func EmptyDealState() *DealState { LastUpdatedEpoch: -1, } } + +// returns the earned fees and pending fees for a given deal +func (deal DealProposal) GetDealFees(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) +} diff --git a/chain/actors/builtin/market/market.go b/chain/actors/builtin/market/market.go index 16c44339b..adf7ce33d 100644 --- a/chain/actors/builtin/market/market.go +++ b/chain/actors/builtin/market/market.go @@ -1,11 +1,11 @@ package market import ( - "github.com/filecoin-project/go-state-types/big" "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" "github.com/ipfs/go-cid" cbg "github.com/whyrusleeping/cbor-gen" @@ -13,8 +13,11 @@ import ( market0 "github.com/filecoin-project/specs-actors/actors/builtin/market" 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" "github.com/filecoin-project/lotus/chain/actors/adt" @@ -23,15 +26,19 @@ import ( ) func init() { + builtin.RegisterActorState(builtin0.StorageMarketActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load0(store, root) }) + builtin.RegisterActorState(builtin2.StorageMarketActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load2(store, root) }) + builtin.RegisterActorState(builtin3.StorageMarketActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load3(store, root) }) + builtin.RegisterActorState(builtin4.StorageMarketActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load4(store, root) }) @@ -44,14 +51,19 @@ var ( func Load(store adt.Store, act *types.Actor) (State, error) { 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) + } return nil, xerrors.Errorf("unknown actor code %s", act.Code) } diff --git a/chain/actors/builtin/miner/actor.go.template b/chain/actors/builtin/miner/actor.go.template index 4265af7dc..4b3d8db5e 100644 --- a/chain/actors/builtin/miner/actor.go.template +++ b/chain/actors/builtin/miner/actor.go.template @@ -22,14 +22,17 @@ import ( miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/miner" miner3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/miner" {{range .versions}} - builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin"{{end}} + builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin" +{{end}} ) func init() { -{{range .versions}} builtin.RegisterActorState(builtin{{.}}.StorageMinerActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { +{{range .versions}} + builtin.RegisterActorState(builtin{{.}}.StorageMinerActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load{{.}}(store, root) }) -{{end}}} +{{end}} +} var Methods = builtin{{.latestVersion}}.MethodsMiner @@ -49,9 +52,11 @@ var AddressedSectorsMax = miner2.AddressedSectorsMax func Load(store adt.Store, act *types.Actor) (State, error) { switch act.Code { -{{range .versions}} case builtin{{.}}.StorageMinerActorCodeID: +{{range .versions}} + case builtin{{.}}.StorageMinerActorCodeID: return load{{.}}(store, act.Head) -{{end}} } +{{end}} +} return nil, xerrors.Errorf("unknown actor code %s", act.Code) } diff --git a/chain/actors/builtin/miner/miner.go b/chain/actors/builtin/miner/miner.go index ae49a6d06..a426e063b 100644 --- a/chain/actors/builtin/miner/miner.go +++ b/chain/actors/builtin/miner/miner.go @@ -23,24 +23,32 @@ import ( miner3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/miner" 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" ) func init() { + builtin.RegisterActorState(builtin0.StorageMinerActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load0(store, root) }) + builtin.RegisterActorState(builtin2.StorageMinerActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load2(store, root) }) + builtin.RegisterActorState(builtin3.StorageMinerActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load3(store, root) }) + builtin.RegisterActorState(builtin4.StorageMinerActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load4(store, root) }) + } var Methods = builtin4.MethodsMiner @@ -61,14 +69,19 @@ var AddressedSectorsMax = miner2.AddressedSectorsMax func Load(store adt.Store, act *types.Actor) (State, error) { 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) + } return nil, xerrors.Errorf("unknown actor code %s", act.Code) } diff --git a/chain/actors/builtin/miner/state.go.template b/chain/actors/builtin/miner/state.go.template index df94c942c..0769eea10 100644 --- a/chain/actors/builtin/miner/state.go.template +++ b/chain/actors/builtin/miner/state.go.template @@ -17,8 +17,10 @@ import ( "github.com/filecoin-project/lotus/chain/actors/adt" -{{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" +{{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" ) @@ -398,13 +400,17 @@ func (d *deadline{{.v}}) PartitionsPoSted() (bitfield.BitField, error) { } func (d *deadline{{.v}}) DisputableProofCount() (uint64, error) { -{{if (ge .v 3)}} ops, err := d.OptimisticProofsSnapshotArray(d.store) +{{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}} + return ops.Length(), nil +{{else}} + // field doesn't exist until v3 + return 0, nil +{{end}} } func (p *partition{{.v}}) AllSectors() (bitfield.BitField, error) { @@ -420,7 +426,8 @@ func (p *partition{{.v}}) RecoveringSectors() (bitfield.BitField, error) { } func fromV{{.v}}SectorOnChainInfo(v{{.v}} miner{{.v}}.SectorOnChainInfo) SectorOnChainInfo { - {{if (ge .v 2)}}return SectorOnChainInfo{ +{{if (ge .v 2)}} + return SectorOnChainInfo{ SectorNumber: v{{.v}}.SectorNumber, SealProof: v{{.v}}.SealProof, SealedCID: v{{.v}}.SealedCID, @@ -432,15 +439,22 @@ func fromV{{.v}}SectorOnChainInfo(v{{.v}} miner{{.v}}.SectorOnChainInfo) SectorO InitialPledge: v{{.v}}.InitialPledge, ExpectedDayReward: v{{.v}}.ExpectedDayReward, ExpectedStoragePledge: v{{.v}}.ExpectedStoragePledge, - }{{else}}return (SectorOnChainInfo)(v0){{end}} + } +{{else}} + return (SectorOnChainInfo)(v0) +{{end}} } func fromV{{.v}}SectorPreCommitOnChainInfo(v{{.v}} miner{{.v}}.SectorPreCommitOnChainInfo) SectorPreCommitOnChainInfo { - {{if (ge .v 2)}}return SectorPreCommitOnChainInfo{ +{{if (ge .v 2)}} + return SectorPreCommitOnChainInfo{ Info: (SectorPreCommitInfo)(v{{.v}}.Info), PreCommitDeposit: v{{.v}}.PreCommitDeposit, PreCommitEpoch: v{{.v}}.PreCommitEpoch, DealWeight: v{{.v}}.DealWeight, VerifiedDealWeight: v{{.v}}.VerifiedDealWeight, - }{{else}}return (SectorPreCommitOnChainInfo)(v0){{end}} + } +{{else}} + return (SectorPreCommitOnChainInfo)(v0) +{{end}} } diff --git a/chain/actors/builtin/miner/v0.go b/chain/actors/builtin/miner/v0.go index 262c4870d..2dc8ae23e 100644 --- a/chain/actors/builtin/miner/v0.go +++ b/chain/actors/builtin/miner/v0.go @@ -397,8 +397,10 @@ func (d *deadline0) PartitionsPoSted() (bitfield.BitField, error) { } func (d *deadline0) DisputableProofCount() (uint64, error) { + // field doesn't exist until v3 return 0, nil + } func (p *partition0) AllSectors() (bitfield.BitField, error) { @@ -414,9 +416,13 @@ func (p *partition0) RecoveringSectors() (bitfield.BitField, error) { } func fromV0SectorOnChainInfo(v0 miner0.SectorOnChainInfo) SectorOnChainInfo { + return (SectorOnChainInfo)(v0) + } func fromV0SectorPreCommitOnChainInfo(v0 miner0.SectorPreCommitOnChainInfo) SectorPreCommitOnChainInfo { + return (SectorPreCommitOnChainInfo)(v0) + } diff --git a/chain/actors/builtin/miner/v2.go b/chain/actors/builtin/miner/v2.go index 1720b619f..7564dd8b8 100644 --- a/chain/actors/builtin/miner/v2.go +++ b/chain/actors/builtin/miner/v2.go @@ -395,8 +395,10 @@ func (d *deadline2) PartitionsPoSted() (bitfield.BitField, error) { } func (d *deadline2) DisputableProofCount() (uint64, error) { + // field doesn't exist until v3 return 0, nil + } func (p *partition2) AllSectors() (bitfield.BitField, error) { @@ -412,6 +414,7 @@ func (p *partition2) RecoveringSectors() (bitfield.BitField, error) { } func fromV2SectorOnChainInfo(v2 miner2.SectorOnChainInfo) SectorOnChainInfo { + return SectorOnChainInfo{ SectorNumber: v2.SectorNumber, SealProof: v2.SealProof, @@ -425,9 +428,11 @@ func fromV2SectorOnChainInfo(v2 miner2.SectorOnChainInfo) SectorOnChainInfo { ExpectedDayReward: v2.ExpectedDayReward, ExpectedStoragePledge: v2.ExpectedStoragePledge, } + } func fromV2SectorPreCommitOnChainInfo(v2 miner2.SectorPreCommitOnChainInfo) SectorPreCommitOnChainInfo { + return SectorPreCommitOnChainInfo{ Info: (SectorPreCommitInfo)(v2.Info), PreCommitDeposit: v2.PreCommitDeposit, @@ -435,4 +440,5 @@ func fromV2SectorPreCommitOnChainInfo(v2 miner2.SectorPreCommitOnChainInfo) Sect DealWeight: v2.DealWeight, VerifiedDealWeight: v2.VerifiedDealWeight, } + } diff --git a/chain/actors/builtin/miner/v3.go b/chain/actors/builtin/miner/v3.go index 4f03fdcc9..72a080f73 100644 --- a/chain/actors/builtin/miner/v3.go +++ b/chain/actors/builtin/miner/v3.go @@ -16,6 +16,7 @@ import ( "github.com/filecoin-project/lotus/chain/actors/adt" 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" ) @@ -391,12 +392,14 @@ func (d *deadline3) PartitionsPoSted() (bitfield.BitField, error) { } 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) { @@ -412,6 +415,7 @@ func (p *partition3) RecoveringSectors() (bitfield.BitField, error) { } func fromV3SectorOnChainInfo(v3 miner3.SectorOnChainInfo) SectorOnChainInfo { + return SectorOnChainInfo{ SectorNumber: v3.SectorNumber, SealProof: v3.SealProof, @@ -425,9 +429,11 @@ func fromV3SectorOnChainInfo(v3 miner3.SectorOnChainInfo) SectorOnChainInfo { ExpectedDayReward: v3.ExpectedDayReward, ExpectedStoragePledge: v3.ExpectedStoragePledge, } + } func fromV3SectorPreCommitOnChainInfo(v3 miner3.SectorPreCommitOnChainInfo) SectorPreCommitOnChainInfo { + return SectorPreCommitOnChainInfo{ Info: (SectorPreCommitInfo)(v3.Info), PreCommitDeposit: v3.PreCommitDeposit, @@ -435,4 +441,5 @@ func fromV3SectorPreCommitOnChainInfo(v3 miner3.SectorPreCommitOnChainInfo) Sect DealWeight: v3.DealWeight, VerifiedDealWeight: v3.VerifiedDealWeight, } + } diff --git a/chain/actors/builtin/miner/v4.go b/chain/actors/builtin/miner/v4.go index e277c0298..698bdf2f5 100644 --- a/chain/actors/builtin/miner/v4.go +++ b/chain/actors/builtin/miner/v4.go @@ -16,6 +16,7 @@ import ( "github.com/filecoin-project/lotus/chain/actors/adt" 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" ) @@ -391,12 +392,14 @@ func (d *deadline4) PartitionsPoSted() (bitfield.BitField, error) { } 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) { @@ -412,6 +415,7 @@ func (p *partition4) RecoveringSectors() (bitfield.BitField, error) { } func fromV4SectorOnChainInfo(v4 miner4.SectorOnChainInfo) SectorOnChainInfo { + return SectorOnChainInfo{ SectorNumber: v4.SectorNumber, SealProof: v4.SealProof, @@ -425,9 +429,11 @@ func fromV4SectorOnChainInfo(v4 miner4.SectorOnChainInfo) SectorOnChainInfo { ExpectedDayReward: v4.ExpectedDayReward, ExpectedStoragePledge: v4.ExpectedStoragePledge, } + } func fromV4SectorPreCommitOnChainInfo(v4 miner4.SectorPreCommitOnChainInfo) SectorPreCommitOnChainInfo { + return SectorPreCommitOnChainInfo{ Info: (SectorPreCommitInfo)(v4.Info), PreCommitDeposit: v4.PreCommitDeposit, @@ -435,4 +441,5 @@ func fromV4SectorPreCommitOnChainInfo(v4 miner4.SectorPreCommitOnChainInfo) Sect DealWeight: v4.DealWeight, VerifiedDealWeight: v4.VerifiedDealWeight, } + } diff --git a/chain/actors/builtin/multisig/actor.go.template b/chain/actors/builtin/multisig/actor.go.template index 76aff2581..304c0610c 100644 --- a/chain/actors/builtin/multisig/actor.go.template +++ b/chain/actors/builtin/multisig/actor.go.template @@ -15,7 +15,8 @@ import ( msig0 "github.com/filecoin-project/specs-actors/actors/builtin/multisig" msig4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/multisig" {{range .versions}} - builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin"{{end}} + builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin" +{{end}} "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/adt" @@ -24,16 +25,19 @@ import ( ) func init() { -{{range .versions}} builtin.RegisterActorState(builtin{{.}}.MultisigActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { +{{range .versions}} + builtin.RegisterActorState(builtin{{.}}.MultisigActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load{{.}}(store, root) }) {{end}}} func Load(store adt.Store, act *types.Actor) (State, error) { switch act.Code { -{{range .versions}} case builtin{{.}}.MultisigActorCodeID: +{{range .versions}} + case builtin{{.}}.MultisigActorCodeID: return load{{.}}(store, act.Head) -{{end}} } +{{end}} + } return nil, xerrors.Errorf("unknown actor code %s", act.Code) } @@ -60,7 +64,8 @@ var Methods = builtin{{.latestVersion}}.MethodsMultisig func Message(version actors.Version, from address.Address) MessageBuilder { switch version { -{{range .versions}} case actors.Version{{.}}: +{{range .versions}} + case actors.Version{{.}}: return message{{.}}{{"{"}}{{if (ge . 2)}}message0{from}{{else}}from{{end}}} {{end}} default: panic(fmt.Sprintf("unsupported actors version: %d", version)) diff --git a/chain/actors/builtin/multisig/message.go.template b/chain/actors/builtin/multisig/message.go.template index dfd5c6fc0..6bff8983a 100644 --- a/chain/actors/builtin/multisig/message.go.template +++ b/chain/actors/builtin/multisig/message.go.template @@ -72,7 +72,9 @@ func (m message{{.v}}) Create( Params: enc, Value: initialAmount, }, nil -}{{if (le .v 1)}} +} + +{{if (le .v 1)}} func (m message0) Propose(msig, to address.Address, amt abi.TokenAmount, method abi.MethodNum, params []byte) (*types.Message, error) { @@ -140,4 +142,5 @@ func (m message0) Cancel(msig address.Address, txID uint64, hashData *ProposalHa Method: builtin0.MethodsMultisig.Cancel, Params: enc, }, nil -}{{end}} +} +{{end}} diff --git a/chain/actors/builtin/multisig/multisig.go b/chain/actors/builtin/multisig/multisig.go index 0cee644c5..79b1a57d7 100644 --- a/chain/actors/builtin/multisig/multisig.go +++ b/chain/actors/builtin/multisig/multisig.go @@ -16,8 +16,11 @@ import ( msig4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/multisig" 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" "github.com/filecoin-project/lotus/chain/actors" @@ -27,15 +30,19 @@ import ( ) func init() { + builtin.RegisterActorState(builtin0.MultisigActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load0(store, root) }) + builtin.RegisterActorState(builtin2.MultisigActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load2(store, root) }) + builtin.RegisterActorState(builtin3.MultisigActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load3(store, root) }) + builtin.RegisterActorState(builtin4.MultisigActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load4(store, root) }) @@ -43,14 +50,19 @@ func init() { func Load(store adt.Store, act *types.Actor) (State, error) { 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) + } return nil, xerrors.Errorf("unknown actor code %s", act.Code) } @@ -78,12 +90,16 @@ var Methods = builtin4.MethodsMultisig func Message(version actors.Version, from address.Address) MessageBuilder { switch version { + case actors.Version0: return message0{from} + case actors.Version2: return message2{message0{from}} + case actors.Version3: return message3{message0{from}} + case actors.Version4: return message4{message0{from}} default: diff --git a/chain/actors/builtin/multisig/state.go.template b/chain/actors/builtin/multisig/state.go.template index c62655646..2316aadba 100644 --- a/chain/actors/builtin/multisig/state.go.template +++ b/chain/actors/builtin/multisig/state.go.template @@ -14,8 +14,10 @@ import ( "github.com/filecoin-project/lotus/chain/actors/adt" -{{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" +{{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" ) var _ State = (*state{{.v}})(nil) diff --git a/chain/actors/builtin/multisig/v3.go b/chain/actors/builtin/multisig/v3.go index 1a7611440..8834e4553 100644 --- a/chain/actors/builtin/multisig/v3.go +++ b/chain/actors/builtin/multisig/v3.go @@ -15,6 +15,7 @@ import ( "github.com/filecoin-project/lotus/chain/actors/adt" builtin3 "github.com/filecoin-project/specs-actors/v3/actors/builtin" + msig3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/multisig" ) diff --git a/chain/actors/builtin/multisig/v4.go b/chain/actors/builtin/multisig/v4.go index 50c1e9620..9f9dc7573 100644 --- a/chain/actors/builtin/multisig/v4.go +++ b/chain/actors/builtin/multisig/v4.go @@ -15,6 +15,7 @@ import ( "github.com/filecoin-project/lotus/chain/actors/adt" builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" + msig4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/multisig" ) diff --git a/chain/actors/builtin/paych/actor.go.template b/chain/actors/builtin/paych/actor.go.template index bd0d6c03c..3f68a5cfa 100644 --- a/chain/actors/builtin/paych/actor.go.template +++ b/chain/actors/builtin/paych/actor.go.template @@ -15,7 +15,8 @@ import ( paych0 "github.com/filecoin-project/specs-actors/actors/builtin/paych" {{range .versions}} - builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin"{{end}} + builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin" +{{end}} "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/adt" @@ -24,7 +25,8 @@ import ( ) func init() { -{{range .versions}} builtin.RegisterActorState(builtin{{.}}.PaymentChannelActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { +{{range .versions}} + builtin.RegisterActorState(builtin{{.}}.PaymentChannelActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load{{.}}(store, root) }) {{end}}} @@ -32,9 +34,11 @@ func init() { // Load returns an abstract copy of payment channel state, irregardless of actor version func Load(store adt.Store, act *types.Actor) (State, error) { switch act.Code { -{{range .versions}} case builtin{{.}}.PaymentChannelActorCodeID: +{{range .versions}} + case builtin{{.}}.PaymentChannelActorCodeID: return load{{.}}(store, act.Head) -{{end}} } +{{end}} + } return nil, xerrors.Errorf("unknown actor code %s", act.Code) } @@ -88,9 +92,11 @@ var Methods = builtin{{.latestVersion}}.MethodsPaych func Message(version actors.Version, from address.Address) MessageBuilder { switch version { -{{range .versions}} case actors.Version{{.}}: +{{range .versions}} + case actors.Version{{.}}: return message{{.}}{from} -{{end}} default: +{{end}} + default: panic(fmt.Sprintf("unsupported actors version: %d", version)) } } diff --git a/chain/actors/builtin/paych/paych.go b/chain/actors/builtin/paych/paych.go index a62b2691f..30e4408d8 100644 --- a/chain/actors/builtin/paych/paych.go +++ b/chain/actors/builtin/paych/paych.go @@ -16,8 +16,11 @@ import ( paych0 "github.com/filecoin-project/specs-actors/actors/builtin/paych" 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" "github.com/filecoin-project/lotus/chain/actors" @@ -27,15 +30,19 @@ import ( ) func init() { + builtin.RegisterActorState(builtin0.PaymentChannelActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load0(store, root) }) + builtin.RegisterActorState(builtin2.PaymentChannelActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load2(store, root) }) + builtin.RegisterActorState(builtin3.PaymentChannelActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load3(store, root) }) + builtin.RegisterActorState(builtin4.PaymentChannelActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load4(store, root) }) @@ -44,14 +51,19 @@ func init() { // Load returns an abstract copy of payment channel state, irregardless of actor version func Load(store adt.Store, act *types.Actor) (State, error) { 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) + } return nil, xerrors.Errorf("unknown actor code %s", act.Code) } @@ -106,14 +118,19 @@ var Methods = builtin4.MethodsPaych func Message(version actors.Version, from address.Address) MessageBuilder { switch version { + case actors.Version0: return message0{from} + case actors.Version2: return message2{from} + case actors.Version3: return message3{from} + case actors.Version4: return message4{from} + default: panic(fmt.Sprintf("unsupported actors version: %d", version)) } diff --git a/chain/actors/builtin/power/actor.go.template b/chain/actors/builtin/power/actor.go.template index 56b591f7c..82f791e58 100644 --- a/chain/actors/builtin/power/actor.go.template +++ b/chain/actors/builtin/power/actor.go.template @@ -14,11 +14,13 @@ import ( "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/types" {{range .versions}} - builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin"{{end}} + builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin" +{{end}} ) func init() { -{{range .versions}} builtin.RegisterActorState(builtin{{.}}.StoragePowerActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { +{{range .versions}} + builtin.RegisterActorState(builtin{{.}}.StoragePowerActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load{{.}}(store, root) }) {{end}}} @@ -30,9 +32,11 @@ var ( func Load(store adt.Store, act *types.Actor) (State, error) { switch act.Code { -{{range .versions}} case builtin{{.}}.StoragePowerActorCodeID: +{{range .versions}} + case builtin{{.}}.StoragePowerActorCodeID: return load{{.}}(store, act.Head) -{{end}} } +{{end}} + } return nil, xerrors.Errorf("unknown actor code %s", act.Code) } diff --git a/chain/actors/builtin/power/power.go b/chain/actors/builtin/power/power.go index 56b6f7c95..bf530a21a 100644 --- a/chain/actors/builtin/power/power.go +++ b/chain/actors/builtin/power/power.go @@ -15,21 +15,28 @@ import ( "github.com/filecoin-project/lotus/chain/types" 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" ) func init() { + builtin.RegisterActorState(builtin0.StoragePowerActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load0(store, root) }) + builtin.RegisterActorState(builtin2.StoragePowerActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load2(store, root) }) + builtin.RegisterActorState(builtin3.StoragePowerActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load3(store, root) }) + builtin.RegisterActorState(builtin4.StoragePowerActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load4(store, root) }) @@ -42,14 +49,19 @@ var ( func Load(store adt.Store, act *types.Actor) (State, error) { 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) + } return nil, xerrors.Errorf("unknown actor code %s", act.Code) } diff --git a/chain/actors/builtin/power/state.go.template b/chain/actors/builtin/power/state.go.template index b71ac1c07..4cb904a1d 100644 --- a/chain/actors/builtin/power/state.go.template +++ b/chain/actors/builtin/power/state.go.template @@ -11,8 +11,10 @@ import ( "github.com/filecoin-project/lotus/chain/actors/adt" "github.com/filecoin-project/lotus/chain/actors/builtin" -{{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" +{{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" ) diff --git a/chain/actors/builtin/power/v3.go b/chain/actors/builtin/power/v3.go index 174a091cd..2ef1e2808 100644 --- a/chain/actors/builtin/power/v3.go +++ b/chain/actors/builtin/power/v3.go @@ -12,6 +12,7 @@ import ( "github.com/filecoin-project/lotus/chain/actors/builtin" 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" ) diff --git a/chain/actors/builtin/power/v4.go b/chain/actors/builtin/power/v4.go index 95cb47a65..686550456 100644 --- a/chain/actors/builtin/power/v4.go +++ b/chain/actors/builtin/power/v4.go @@ -12,6 +12,7 @@ import ( "github.com/filecoin-project/lotus/chain/actors/builtin" 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" ) diff --git a/chain/actors/builtin/reward/actor.go.template b/chain/actors/builtin/reward/actor.go.template index 476ec6190..81437d26f 100644 --- a/chain/actors/builtin/reward/actor.go.template +++ b/chain/actors/builtin/reward/actor.go.template @@ -8,7 +8,8 @@ import ( "github.com/filecoin-project/go-state-types/cbor" {{range .versions}} - builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin"{{end}} + builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin" +{{end}} "github.com/filecoin-project/lotus/chain/actors/adt" "github.com/filecoin-project/lotus/chain/actors/builtin" @@ -16,7 +17,8 @@ import ( ) func init() { -{{range .versions}} builtin.RegisterActorState(builtin{{.}}.RewardActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { +{{range .versions}} + builtin.RegisterActorState(builtin{{.}}.RewardActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load{{.}}(store, root) }) {{end}}} @@ -28,9 +30,11 @@ var ( func Load(store adt.Store, act *types.Actor) (State, error) { switch act.Code { -{{range .versions}} case builtin{{.}}.RewardActorCodeID: +{{range .versions}} + case builtin{{.}}.RewardActorCodeID: return load{{.}}(store, act.Head) -{{end}} } +{{end}} + } return nil, xerrors.Errorf("unknown actor code %s", act.Code) } diff --git a/chain/actors/builtin/reward/reward.go b/chain/actors/builtin/reward/reward.go index ac836f854..1037cf741 100644 --- a/chain/actors/builtin/reward/reward.go +++ b/chain/actors/builtin/reward/reward.go @@ -9,8 +9,11 @@ import ( "github.com/filecoin-project/go-state-types/cbor" 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" "github.com/filecoin-project/lotus/chain/actors/adt" @@ -19,15 +22,19 @@ import ( ) func init() { + builtin.RegisterActorState(builtin0.RewardActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load0(store, root) }) + builtin.RegisterActorState(builtin2.RewardActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load2(store, root) }) + builtin.RegisterActorState(builtin3.RewardActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load3(store, root) }) + builtin.RegisterActorState(builtin4.RewardActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load4(store, root) }) @@ -40,14 +47,19 @@ var ( func Load(store adt.Store, act *types.Actor) (State, error) { 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) + } return nil, xerrors.Errorf("unknown actor code %s", act.Code) } diff --git a/chain/actors/builtin/reward/state.go.template b/chain/actors/builtin/reward/state.go.template index 906b97837..1758d1413 100644 --- a/chain/actors/builtin/reward/state.go.template +++ b/chain/actors/builtin/reward/state.go.template @@ -33,10 +33,14 @@ func (s *state{{.v}}) ThisEpochReward() (abi.TokenAmount, error) { } func (s *state{{.v}}) ThisEpochRewardSmoothed() (builtin.FilterEstimate, error) { - {{if (ge .v 2)}}return builtin.FilterEstimate{ +{{if (ge .v 2)}} + return builtin.FilterEstimate{ PositionEstimate: s.State.ThisEpochRewardSmoothed.PositionEstimate, VelocityEstimate: s.State.ThisEpochRewardSmoothed.VelocityEstimate, - }, nil{{else}}return builtin.FromV0FilterEstimate(*s.State.ThisEpochRewardSmoothed), nil{{end}} + }, nil +{{else}} + return builtin.FromV0FilterEstimate(*s.State.ThisEpochRewardSmoothed), nil +{{end}} } func (s *state{{.v}}) ThisEpochBaselinePower() (abi.StoragePower, error) { diff --git a/chain/actors/builtin/reward/v0.go b/chain/actors/builtin/reward/v0.go index eba6f65d7..fe053cc16 100644 --- a/chain/actors/builtin/reward/v0.go +++ b/chain/actors/builtin/reward/v0.go @@ -33,7 +33,9 @@ func (s *state0) ThisEpochReward() (abi.TokenAmount, error) { } func (s *state0) ThisEpochRewardSmoothed() (builtin.FilterEstimate, error) { + return builtin.FromV0FilterEstimate(*s.State.ThisEpochRewardSmoothed), nil + } func (s *state0) ThisEpochBaselinePower() (abi.StoragePower, error) { diff --git a/chain/actors/builtin/reward/v2.go b/chain/actors/builtin/reward/v2.go index c9a591532..90621e467 100644 --- a/chain/actors/builtin/reward/v2.go +++ b/chain/actors/builtin/reward/v2.go @@ -33,10 +33,12 @@ func (s *state2) ThisEpochReward() (abi.TokenAmount, error) { } 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) { diff --git a/chain/actors/builtin/reward/v3.go b/chain/actors/builtin/reward/v3.go index 18bd58f8e..926cc085b 100644 --- a/chain/actors/builtin/reward/v3.go +++ b/chain/actors/builtin/reward/v3.go @@ -33,10 +33,12 @@ func (s *state3) ThisEpochReward() (abi.TokenAmount, error) { } 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) { diff --git a/chain/actors/builtin/reward/v4.go b/chain/actors/builtin/reward/v4.go index 7320d1701..f034b0018 100644 --- a/chain/actors/builtin/reward/v4.go +++ b/chain/actors/builtin/reward/v4.go @@ -33,10 +33,12 @@ func (s *state4) ThisEpochReward() (abi.TokenAmount, error) { } 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) { diff --git a/chain/actors/builtin/verifreg/actor.go.template b/chain/actors/builtin/verifreg/actor.go.template index d194a2c89..22e809ccf 100644 --- a/chain/actors/builtin/verifreg/actor.go.template +++ b/chain/actors/builtin/verifreg/actor.go.template @@ -9,7 +9,8 @@ import ( "github.com/filecoin-project/go-state-types/cbor" {{range .versions}} - builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin"{{end}} + builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin" +{{end}} "github.com/filecoin-project/lotus/chain/actors/adt" "github.com/filecoin-project/lotus/chain/actors/builtin" @@ -17,10 +18,12 @@ import ( ) func init() { -{{range .versions}} builtin.RegisterActorState(builtin{{.}}.VerifiedRegistryActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { +{{range .versions}} + builtin.RegisterActorState(builtin{{.}}.VerifiedRegistryActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load{{.}}(store, root) }) -{{end}}} +{{end}} +} var ( Address = builtin{{.latestVersion}}.VerifiedRegistryActorAddr @@ -29,9 +32,11 @@ var ( func Load(store adt.Store, act *types.Actor) (State, error) { switch act.Code { -{{range .versions}} case builtin{{.}}.VerifiedRegistryActorCodeID: +{{range .versions}} + case builtin{{.}}.VerifiedRegistryActorCodeID: return load{{.}}(store, act.Head) -{{end}} } +{{end}} + } return nil, xerrors.Errorf("unknown actor code %s", act.Code) } diff --git a/chain/actors/builtin/verifreg/verifreg.go b/chain/actors/builtin/verifreg/verifreg.go index 88e8adfc1..32f50a4ae 100644 --- a/chain/actors/builtin/verifreg/verifreg.go +++ b/chain/actors/builtin/verifreg/verifreg.go @@ -10,8 +10,11 @@ import ( "github.com/filecoin-project/go-state-types/cbor" 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" "github.com/filecoin-project/lotus/chain/actors/adt" @@ -20,18 +23,23 @@ import ( ) func init() { + builtin.RegisterActorState(builtin0.VerifiedRegistryActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load0(store, root) }) + builtin.RegisterActorState(builtin2.VerifiedRegistryActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load2(store, root) }) + builtin.RegisterActorState(builtin3.VerifiedRegistryActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load3(store, root) }) + builtin.RegisterActorState(builtin4.VerifiedRegistryActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load4(store, root) }) + } var ( @@ -41,14 +49,19 @@ var ( func Load(store adt.Store, act *types.Actor) (State, error) { 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) + } return nil, xerrors.Errorf("unknown actor code %s", act.Code) } From 94d3c8bed7885a51f1a1280de56e912fe3df9063 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 30 Apr 2021 18:02:57 +0200 Subject: [PATCH 36/41] fix lint --- chain/actors/agen/main.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/chain/actors/agen/main.go b/chain/actors/agen/main.go index 637361f17..82a92aa00 100644 --- a/chain/actors/agen/main.go +++ b/chain/actors/agen/main.go @@ -3,11 +3,12 @@ package main import ( "bytes" "fmt" - "golang.org/x/xerrors" "io/ioutil" "os" "path/filepath" "text/template" + + "golang.org/x/xerrors" ) var latestVersion = 4 From a80259d986facfa66b08ef2264572ecfd0267a76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 30 Apr 2021 18:43:06 +0200 Subject: [PATCH 37/41] Actor upgrade checklist --- api/test/ccupgrade.go | 2 +- api/test/deadlines.go | 2 +- api/test/mining.go | 2 +- api/test/test.go | 2 +- api/test/window_post.go | 8 ++++---- chain/actors/agen/main.go | 4 ++-- cmd/lotus-storage-miner/actor_test.go | 2 +- documentation/misc/actors_version_checklist.md | 18 ++++++++++++++++++ 8 files changed, 29 insertions(+), 11 deletions(-) create mode 100644 documentation/misc/actors_version_checklist.md diff --git a/api/test/ccupgrade.go b/api/test/ccupgrade.go index 8c2bdc9b4..283c8f610 100644 --- a/api/test/ccupgrade.go +++ b/api/test/ccupgrade.go @@ -31,7 +31,7 @@ func TestCCUpgrade(t *testing.T, b APIBuilder, blocktime time.Duration) { func testCCUpgrade(t *testing.T, b APIBuilder, blocktime time.Duration, upgradeHeight abi.ChainEpoch) { ctx := context.Background() - n, sn := b(t, []FullNodeOpts{FullNodeWithActorsV4At(upgradeHeight)}, OneMiner) + n, sn := b(t, []FullNodeOpts{FullNodeWithLatestActorsAt(upgradeHeight)}, OneMiner) client := n[0].FullNode.(*impl.FullNodeAPI) miner := sn[0] diff --git a/api/test/deadlines.go b/api/test/deadlines.go index 182b6d302..43fa731be 100644 --- a/api/test/deadlines.go +++ b/api/test/deadlines.go @@ -63,7 +63,7 @@ func TestDeadlineToggling(t *testing.T, b APIBuilder, blocktime time.Duration) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - n, sn := b(t, []FullNodeOpts{FullNodeWithActorsV4At(upgradeH)}, OneMiner) + n, sn := b(t, []FullNodeOpts{FullNodeWithLatestActorsAt(upgradeH)}, OneMiner) client := n[0].FullNode.(*impl.FullNodeAPI) minerA := sn[0] diff --git a/api/test/mining.go b/api/test/mining.go index 8e300a9c9..4a4f1e1a4 100644 --- a/api/test/mining.go +++ b/api/test/mining.go @@ -206,7 +206,7 @@ func TestDealMining(t *testing.T, b APIBuilder, blocktime time.Duration, carExpo func (ts *testSuite) testNonGenesisMiner(t *testing.T) { ctx := context.Background() n, sn := ts.makeNodes(t, []FullNodeOpts{ - FullNodeWithActorsV4At(-1), + FullNodeWithLatestActorsAt(-1), }, []StorageMiner{ {Full: 0, Preseal: PresealGenesis}, }) diff --git a/api/test/test.go b/api/test/test.go index 21ebc94b5..d09827f5e 100644 --- a/api/test/test.go +++ b/api/test/test.go @@ -121,7 +121,7 @@ var OneMiner = []StorageMiner{{Full: 0, Preseal: PresealGenesis}} var OneFull = DefaultFullOpts(1) var TwoFull = DefaultFullOpts(2) -var FullNodeWithActorsV4At = func(upgradeHeight abi.ChainEpoch) FullNodeOpts { +var FullNodeWithLatestActorsAt = func(upgradeHeight abi.ChainEpoch) FullNodeOpts { if upgradeHeight == -1 { upgradeHeight = 3 } diff --git a/api/test/window_post.go b/api/test/window_post.go index 1ff9f79b3..bb5010b25 100644 --- a/api/test/window_post.go +++ b/api/test/window_post.go @@ -223,7 +223,7 @@ func testWindowPostUpgrade(t *testing.T, b APIBuilder, blocktime time.Duration, ctx, cancel := context.WithCancel(context.Background()) defer cancel() - n, sn := b(t, []FullNodeOpts{FullNodeWithActorsV4At(upgradeHeight)}, OneMiner) + n, sn := b(t, []FullNodeOpts{FullNodeWithLatestActorsAt(upgradeHeight)}, OneMiner) client := n[0].FullNode.(*impl.FullNodeAPI) miner := sn[0] @@ -442,7 +442,7 @@ func TestTerminate(t *testing.T, b APIBuilder, blocktime time.Duration) { nSectors := uint64(2) - n, sn := b(t, []FullNodeOpts{FullNodeWithActorsV4At(-1)}, []StorageMiner{{Full: 0, Preseal: int(nSectors)}}) + n, sn := b(t, []FullNodeOpts{FullNodeWithLatestActorsAt(-1)}, []StorageMiner{{Full: 0, Preseal: int(nSectors)}}) client := n[0].FullNode.(*impl.FullNodeAPI) miner := sn[0] @@ -617,7 +617,7 @@ func TestWindowPostDispute(t *testing.T, b APIBuilder, blocktime time.Duration) /// // Then we're going to manually submit bad proofs. n, sn := b(t, []FullNodeOpts{ - FullNodeWithActorsV4At(-1), + FullNodeWithLatestActorsAt(-1), }, []StorageMiner{ {Full: 0, Preseal: PresealGenesis}, {Full: 0}, @@ -900,7 +900,7 @@ func TestWindowPostDisputeFails(t *testing.T, b APIBuilder, blocktime time.Durat ctx, cancel := context.WithCancel(context.Background()) defer cancel() - n, sn := b(t, []FullNodeOpts{FullNodeWithActorsV4At(-1)}, OneMiner) + n, sn := b(t, []FullNodeOpts{FullNodeWithLatestActorsAt(-1)}, OneMiner) client := n[0].FullNode.(*impl.FullNodeAPI) miner := sn[0] diff --git a/chain/actors/agen/main.go b/chain/actors/agen/main.go index 82a92aa00..7554c3938 100644 --- a/chain/actors/agen/main.go +++ b/chain/actors/agen/main.go @@ -36,13 +36,13 @@ var actors = map[string][]int{ } func main() { - if err := run(); err != nil { + if err := generateAdapters(); err != nil { fmt.Println(err) return } } -func run() error { +func generateAdapters() error { for act, versions := range actors { actDir := filepath.Join("chain/actors/builtin", act) diff --git a/cmd/lotus-storage-miner/actor_test.go b/cmd/lotus-storage-miner/actor_test.go index bbe9362d0..5bc82d842 100644 --- a/cmd/lotus-storage-miner/actor_test.go +++ b/cmd/lotus-storage-miner/actor_test.go @@ -51,7 +51,7 @@ func TestWorkerKeyChange(t *testing.T) { blocktime := 1 * time.Millisecond - n, sn := builder.MockSbBuilder(t, []test.FullNodeOpts{test.FullNodeWithActorsV4At(-1), test.FullNodeWithActorsV4At(-1)}, test.OneMiner) + n, sn := builder.MockSbBuilder(t, []test.FullNodeOpts{test.FullNodeWithLatestActorsAt(-1), test.FullNodeWithLatestActorsAt(-1)}, test.OneMiner) client1 := n[0] client2 := n[1] diff --git a/documentation/misc/actors_version_checklist.md b/documentation/misc/actors_version_checklist.md new file mode 100644 index 000000000..764b52e55 --- /dev/null +++ b/documentation/misc/actors_version_checklist.md @@ -0,0 +1,18 @@ +### Actor version integration checklist + +- [ ] Import new actors +- [ ] Generate adapters + - [ ] Add the new version in `chain/actors/agen/main.go` + - [ ] Update adapter code in `chain/actors/builtin` if needed +- [ ] Update `chain/actors/policy/policy.go` +- [ ] Update `chain/actors/version.go` +- [ ] Register in `chain/vm/invoker.go` +- [ ] Register in `chain/vm/mkactor.go` +- [ ] Update `chain/types/state.go` +- [ ] Update `chain/state/statetree.go` +- [ ] Update `chain/stmgr/forks.go` + - [ ] Schedule + - [ ] Migration +- [ ] Define upgrade heights in `build/params_` +- [ ] Update upgrade schedule in `api/test/test.go` +- [ ] Register in init in `chain/stmgr/utils.go` From cd2b959a88fc947b0a0e640949a547f9b546c8b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Fri, 30 Apr 2021 20:35:01 +0200 Subject: [PATCH 38/41] wip partial codegen --- chain/actors/policy/policy.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/chain/actors/policy/policy.go b/chain/actors/policy/policy.go index 07f489b11..be756ea6d 100644 --- a/chain/actors/policy/policy.go +++ b/chain/actors/policy/policy.go @@ -7,6 +7,14 @@ import ( "github.com/filecoin-project/go-state-types/network" "github.com/filecoin-project/lotus/chain/actors" + /* TEMPLATE START + {{range .versions}} + market{{.v}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin/market" + miner{{.v}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin/miner" + power{{.v}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin/power" + verifreg{{.v}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin/verifreg" + {{end}} + * GENERATED WITH make gen */ 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" @@ -27,7 +35,7 @@ import ( miner4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/miner" paych4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/paych" verifreg4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/verifreg" -) + /* GENERATED END */) const ( ChainFinality = miner4.ChainFinality From 60446b46c8e0aaabfc09e018e8594ba59ad0cc6a Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Tue, 4 May 2021 13:53:08 -0400 Subject: [PATCH 39/41] Generate policy.go --- chain/actors/agen/main.go | 36 ++++ chain/actors/policy/policy.go | 78 +++++++-- chain/actors/policy/policy.go.template | 232 +++++++++++++++++++++++++ 3 files changed, 331 insertions(+), 15 deletions(-) create mode 100644 chain/actors/policy/policy.go.template diff --git a/chain/actors/agen/main.go b/chain/actors/agen/main.go index 7554c3938..2182b02ac 100644 --- a/chain/actors/agen/main.go +++ b/chain/actors/agen/main.go @@ -40,6 +40,11 @@ func main() { fmt.Println(err) return } + + if err := generatePolicy("chain/actors/policy/policy.go"); err != nil { + fmt.Println(err) + return + } } func generateAdapters() error { @@ -144,3 +149,34 @@ func generateMessages(actDir string) error { return nil } + +func generatePolicy(policyPath string) error { + + pf, err := ioutil.ReadFile(policyPath + ".template") + if err != nil { + if os.IsNotExist(err) { + return nil // skip + } + + return xerrors.Errorf("loading policy file: %w", err) + } + + tpl := template.Must(template.New("").Funcs(template.FuncMap{ + "import": func(v int) string { return versionImports[v] }, + }).Parse(string(pf))) + var b bytes.Buffer + + err = tpl.Execute(&b, map[string]interface{}{ + "versions": versions, + "latestVersion": latestVersion, + }) + if err != nil { + return err + } + + if err := ioutil.WriteFile(policyPath, b.Bytes(), 0666); err != nil { + return err + } + + return nil +} diff --git a/chain/actors/policy/policy.go b/chain/actors/policy/policy.go index be756ea6d..164f19a76 100644 --- a/chain/actors/policy/policy.go +++ b/chain/actors/policy/policy.go @@ -7,14 +7,6 @@ import ( "github.com/filecoin-project/go-state-types/network" "github.com/filecoin-project/lotus/chain/actors" - /* TEMPLATE START - {{range .versions}} - market{{.v}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin/market" - miner{{.v}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin/miner" - power{{.v}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin/power" - verifreg{{.v}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin/verifreg" - {{end}} - * GENERATED WITH make gen */ 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" @@ -33,9 +25,10 @@ import ( 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" - paych4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/paych" verifreg4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/verifreg" - /* GENERATED END */) + + paych4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/paych" +) const ( ChainFinality = miner4.ChainFinality @@ -47,7 +40,9 @@ const ( // 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)) @@ -71,6 +66,7 @@ func AddSupportedProofTypes(types ...abi.RegisteredSealProof) { panic("must specify v1 proof types only") } // Set for all miner versions. + miner0.SupportedProofTypes[t] = struct{}{} miner2.PreCommitSealProofTypesV0[t] = struct{}{} @@ -87,6 +83,7 @@ func AddSupportedProofTypes(types ...abi.RegisteredSealProof) { miner4.PreCommitSealProofTypesV7[t] = struct{}{} miner4.PreCommitSealProofTypesV7[t+abi.RegisteredSealProof_StackedDrg2KiBV1_1] = struct{}{} miner4.PreCommitSealProofTypesV8[t+abi.RegisteredSealProof_StackedDrg2KiBV1_1] = struct{}{} + } } @@ -94,22 +91,29 @@ func AddSupportedProofTypes(types ...abi.RegisteredSealProof) { // 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 + } // TODO: this function shouldn't really exist. Instead, the API should expose the precommit delay. func GetPreCommitChallengeDelay() abi.ChainEpoch { - return miner0.PreCommitChallengeDelay + return miner4.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 } @@ -121,27 +125,42 @@ func SetConsensusMinerMinPower(p abi.StoragePower) { for _, policy := range builtin4.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 + } func GetMaxProveCommitDuration(ver actors.Version, t abi.RegisteredSealProof) abi.ChainEpoch { switch ver { + case actors.Version0: + return miner0.MaxSealDuration[t] + case actors.Version2: + return miner2.MaxProveCommitDuration[t] + case actors.Version3: + return miner3.MaxProveCommitDuration[t] + case actors.Version4: + return miner4.MaxProveCommitDuration[t] + default: panic("unsupported actors version") } @@ -153,26 +172,36 @@ func DealProviderCollateralBounds( circulatingFil abi.TokenAmount, nwVer network.Version, ) (min, max abi.TokenAmount) { switch actors.VersionForNetwork(nwVer) { + case actors.Version0: + return market0.DealProviderCollateralBounds(size, verified, rawBytePower, qaPower, baselinePower, circulatingFil, nwVer) + case actors.Version2: + return market2.DealProviderCollateralBounds(size, verified, rawBytePower, qaPower, baselinePower, circulatingFil) + case actors.Version3: + return market3.DealProviderCollateralBounds(size, verified, rawBytePower, qaPower, baselinePower, circulatingFil) + case actors.Version4: + return market4.DealProviderCollateralBounds(size, verified, rawBytePower, qaPower, baselinePower, circulatingFil) + default: panic("unsupported actors version") } } func DealDurationBounds(pieceSize abi.PaddedPieceSize) (min, max abi.ChainEpoch) { - return market2.DealDurationBounds(pieceSize) + return market4.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) @@ -181,13 +210,18 @@ func SetWPoStChallengeWindow(period abi.ChainEpoch) { 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) - miner4.WPoStDisputeWindow = period * 30 // see the miner3 comment + + // by default, this is 2x finality which is 30 periods. + // scale it if we're scaling the challenge period. + miner4.WPoStDisputeWindow = period * 30 + } func GetWinningPoStSectorSetLookback(nwVer network.Version) abi.ChainEpoch { @@ -200,12 +234,12 @@ func GetWinningPoStSectorSetLookback(nwVer network.Version) abi.ChainEpoch { } func GetMaxSectorExpirationExtension() abi.ChainEpoch { - return miner0.MaxSectorExpirationExtension + return miner4.MaxSectorExpirationExtension } // TODO: we'll probably need to abstract over this better in the future. func GetMaxPoStPartitions(p abi.RegisteredPoStProof) (int, error) { - sectorsPerPart, err := builtin3.PoStProofWindowPoStPartitionSectors(p) + sectorsPerPart, err := builtin4.PoStProofWindowPoStPartitionSectors(p) if err != nil { return 0, err } @@ -241,14 +275,19 @@ func GetSectorMaxLifetime(proof abi.RegisteredSealProof, nwVer network.Version) func GetAddressedSectorsMax(nwVer network.Version) int { switch actors.VersionForNetwork(nwVer) { + case actors.Version0: return miner0.AddressedSectorsMax + case actors.Version2: return miner2.AddressedSectorsMax + case actors.Version3: return miner3.AddressedSectorsMax + case actors.Version4: return miner4.AddressedSectorsMax + default: panic("unsupported network version") } @@ -256,15 +295,24 @@ func GetAddressedSectorsMax(nwVer network.Version) int { func GetDeclarationsMax(nwVer network.Version) int { switch actors.VersionForNetwork(nwVer) { + case actors.Version0: + // TODO: Should we instead panic here since the concept doesn't exist yet? return miner0.AddressedPartitionsMax + case actors.Version2: + return miner2.DeclarationsMax + case actors.Version3: + return miner3.DeclarationsMax + case actors.Version4: + return miner4.DeclarationsMax + default: panic("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..b9e26171e --- /dev/null +++ b/chain/actors/policy/policy.go.template @@ -0,0 +1,232 @@ +package policy + +import ( + "sort" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/network" + "github.com/filecoin-project/lotus/chain/actors" + + {{range .versions}} + {{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}} + + paych{{.latestVersion}} "github.com/filecoin-project/specs-actors{{import .latestVersion}}actors/builtin/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}} + 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)) + {{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}} + miner{{.}}.PreCommitSealProofTypesV0[t] = struct{}{} + miner{{.}}.PreCommitSealProofTypesV7[t] = struct{}{} + miner{{.}}.PreCommitSealProofTypesV7[t+abi.RegisteredSealProof_StackedDrg2KiBV1_1] = struct{}{} + miner{{.}}.PreCommitSealProofTypesV8[t+abi.RegisteredSealProof_StackedDrg2KiBV1_1] = 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 actors.Version, t abi.RegisteredSealProof) abi.ChainEpoch { + switch ver { + {{range .versions}} + case actors.Version{{.}}: + {{if (eq . 0)}} + return miner{{.}}.MaxSealDuration[t] + {{else}} + return miner{{.}}.MaxProveCommitDuration[t] + {{end}} + {{end}} + default: + panic("unsupported actors version") + } +} + +func DealProviderCollateralBounds( + size abi.PaddedPieceSize, verified bool, + rawBytePower, qaPower, baselinePower abi.StoragePower, + circulatingFil abi.TokenAmount, nwVer network.Version, +) (min, max abi.TokenAmount) { + switch actors.VersionForNetwork(nwVer) { + {{range .versions}} + case actors.Version{{.}}: + {{if (eq . 0)}} + return market{{.}}.DealProviderCollateralBounds(size, verified, rawBytePower, qaPower, baselinePower, circulatingFil, nwVer) + {{else}} + return market{{.}}.DealProviderCollateralBounds(size, verified, rawBytePower, qaPower, baselinePower, circulatingFil) + {{end}} + {{end}} + default: + panic("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 + } + + return ChainFinality +} + +func GetMaxSectorExpirationExtension() abi.ChainEpoch { + return miner{{.latestVersion}}.MaxSectorExpirationExtension +} + +// TODO: we'll probably need to abstract over this better in the future. +func GetMaxPoStPartitions(p abi.RegisteredPoStProof) (int, error) { + sectorsPerPart, err := builtin{{.latestVersion}}.PoStProofWindowPoStPartitionSectors(p) + if err != nil { + return 0, err + } + return int(miner{{.latestVersion}}.AddressedSectorsMax / sectorsPerPart), nil +} + +func GetDefaultSectorSize() abi.SectorSize { + // supported sector sizes are the same across versions. + szs := make([]abi.SectorSize, 0, len(miner{{.latestVersion}}.PreCommitSealProofTypesV8)) + for spt := range miner{{.latestVersion}}.PreCommitSealProofTypesV8 { + 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] +} + +func GetSectorMaxLifetime(proof abi.RegisteredSealProof, nwVer network.Version) abi.ChainEpoch { + if nwVer <= network.Version10 { + return builtin{{.latestVersion}}.SealProofPoliciesV0[proof].SectorMaxLifetime + } + + return builtin{{.latestVersion}}.SealProofPoliciesV11[proof].SectorMaxLifetime +} + +func GetAddressedSectorsMax(nwVer network.Version) int { + switch actors.VersionForNetwork(nwVer) { + {{range .versions}} + case actors.Version{{.}}: + return miner{{.}}.AddressedSectorsMax + {{end}} + default: + panic("unsupported network version") + } +} + +func GetDeclarationsMax(nwVer network.Version) int { + switch actors.VersionForNetwork(nwVer) { + {{range .versions}} + case actors.Version{{.}}: + {{if (eq . 0)}} + // TODO: Should we instead panic here since the concept doesn't exist yet? + return miner{{.}}.AddressedPartitionsMax + {{else}} + return miner{{.}}.DeclarationsMax + {{end}} + {{end}} + default: + panic("unsupported network version") + } +} From c95206cde1068f9638f0e532686d6a135fbaec1b Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Wed, 5 May 2021 22:38:24 -0400 Subject: [PATCH 40/41] Add reminder for NewestNetworkVersion to actors version checklist --- documentation/misc/actors_version_checklist.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/documentation/misc/actors_version_checklist.md b/documentation/misc/actors_version_checklist.md index 764b52e55..308358948 100644 --- a/documentation/misc/actors_version_checklist.md +++ b/documentation/misc/actors_version_checklist.md @@ -1,6 +1,7 @@ ### Actor version integration checklist - [ ] Import new actors +- [ ] Define upgrade heights in `build/params_` - [ ] Generate adapters - [ ] Add the new version in `chain/actors/agen/main.go` - [ ] Update adapter code in `chain/actors/builtin` if needed @@ -13,6 +14,6 @@ - [ ] Update `chain/stmgr/forks.go` - [ ] Schedule - [ ] Migration -- [ ] Define upgrade heights in `build/params_` - [ ] Update upgrade schedule in `api/test/test.go` +- [ ] Update `NewestNetworkVersion` in `build/params_shared_vals.go` - [ ] Register in init in `chain/stmgr/utils.go` From 5f821cc733f32bd9f52872113a0ac3e412732667 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Thu, 6 May 2021 00:17:35 -0400 Subject: [PATCH 41/41] Generate builtin.go --- chain/actors/agen/main.go | 38 +++- chain/actors/builtin/builtin.go | 168 +++++++++++++----- chain/actors/builtin/builtin.go.template | 144 +++++++++++++++ .../actors/builtin/multisig/actor.go.template | 7 +- chain/actors/builtin/multisig/multisig.go | 3 +- chain/actors/policy/policy.go.template | 1 + 6 files changed, 312 insertions(+), 49 deletions(-) create mode 100644 chain/actors/builtin/builtin.go.template diff --git a/chain/actors/agen/main.go b/chain/actors/agen/main.go index 2182b02ac..7269d9ae5 100644 --- a/chain/actors/agen/main.go +++ b/chain/actors/agen/main.go @@ -45,6 +45,11 @@ func main() { fmt.Println(err) return } + + if err := generateBuiltin("chain/actors/builtin/builtin.go"); err != nil { + fmt.Println(err) + return + } } func generateAdapters() error { @@ -158,7 +163,7 @@ func generatePolicy(policyPath string) error { return nil // skip } - return xerrors.Errorf("loading policy file: %w", err) + return xerrors.Errorf("loading policy template file: %w", err) } tpl := template.Must(template.New("").Funcs(template.FuncMap{ @@ -180,3 +185,34 @@ func generatePolicy(policyPath string) error { return nil } + +func generateBuiltin(builtinPath string) error { + + bf, err := ioutil.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 versionImports[v] }, + }).Parse(string(bf))) + var b bytes.Buffer + + err = tpl.Execute(&b, map[string]interface{}{ + "versions": versions, + "latestVersion": latestVersion, + }) + if err != nil { + return err + } + + if err := ioutil.WriteFile(builtinPath, b.Bytes(), 0666); err != nil { + return err + } + + return nil +} diff --git a/chain/actors/builtin/builtin.go b/chain/actors/builtin/builtin.go index 4ff524797..5e34c015a 100644 --- a/chain/actors/builtin/builtin.go +++ b/chain/actors/builtin/builtin.go @@ -6,9 +6,16 @@ import ( "golang.org/x/xerrors" builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" + smoothing0 "github.com/filecoin-project/specs-actors/actors/util/smoothing" + builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" + smoothing2 "github.com/filecoin-project/specs-actors/v2/actors/util/smoothing" + builtin3 "github.com/filecoin-project/specs-actors/v3/actors/builtin" + smoothing3 "github.com/filecoin-project/specs-actors/v3/actors/util/smoothing" + builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" + smoothing4 "github.com/filecoin-project/specs-actors/v4/actors/util/smoothing" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/cbor" @@ -16,30 +23,25 @@ import ( "github.com/filecoin-project/lotus/chain/actors/adt" "github.com/filecoin-project/lotus/chain/types" - smoothing0 "github.com/filecoin-project/specs-actors/actors/util/smoothing" - smoothing2 "github.com/filecoin-project/specs-actors/v2/actors/util/smoothing" - smoothing3 "github.com/filecoin-project/specs-actors/v3/actors/util/smoothing" - smoothing4 "github.com/filecoin-project/specs-actors/v4/actors/util/smoothing" - - miner0 "github.com/filecoin-project/specs-actors/actors/builtin/miner" - proof0 "github.com/filecoin-project/specs-actors/actors/runtime/proof" + miner4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/miner" + proof4 "github.com/filecoin-project/specs-actors/v4/actors/runtime/proof" ) -var SystemActorAddr = builtin0.SystemActorAddr -var BurntFundsActorAddr = builtin0.BurntFundsActorAddr -var CronActorAddr = builtin0.CronActorAddr +var SystemActorAddr = builtin4.SystemActorAddr +var BurntFundsActorAddr = builtin4.BurntFundsActorAddr +var CronActorAddr = builtin4.CronActorAddr var SaftAddress = makeAddress("t0122") var ReserveAddress = makeAddress("t090") var RootVerifierAddress = makeAddress("t080") var ( - ExpectedLeadersPerEpoch = builtin0.ExpectedLeadersPerEpoch + ExpectedLeadersPerEpoch = builtin4.ExpectedLeadersPerEpoch ) const ( - EpochDurationSeconds = builtin0.EpochDurationSeconds - EpochsInDay = builtin0.EpochsInDay - SecondsInDay = builtin0.SecondsInDay + EpochDurationSeconds = builtin4.EpochDurationSeconds + EpochsInDay = builtin4.EpochsInDay + SecondsInDay = builtin4.SecondsInDay ) const ( @@ -47,31 +49,38 @@ const ( MethodConstructor = builtin4.MethodConstructor ) -// These are all just type aliases across actor versions 0, 2, & 3. In the future, that might change +// 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 = proof0.SectorInfo -type PoStProof = proof0.PoStProof +type SectorInfo = proof4.SectorInfo +type PoStProof = proof4.PoStProof type FilterEstimate = smoothing0.FilterEstimate -func FromV0FilterEstimate(v0 smoothing0.FilterEstimate) FilterEstimate { - return (FilterEstimate)(v0) //nolint:unconvert +func QAPowerForWeight(size abi.SectorSize, duration abi.ChainEpoch, dealWeight, verifiedWeight abi.DealWeight) abi.StoragePower { + return miner4.QAPowerForWeight(size, duration, dealWeight, verifiedWeight) } -// Doesn't change between actors v0, v2, and v3. -func QAPowerForWeight(size abi.SectorSize, duration abi.ChainEpoch, dealWeight, verifiedWeight abi.DealWeight) abi.StoragePower { - return miner0.QAPowerForWeight(size, duration, dealWeight, verifiedWeight) +func FromV0FilterEstimate(v0 smoothing0.FilterEstimate) FilterEstimate { + + return (FilterEstimate)(v0) //nolint:unconvert + } func FromV2FilterEstimate(v2 smoothing2.FilterEstimate) FilterEstimate { + return (FilterEstimate)(v2) + } func FromV3FilterEstimate(v3 smoothing3.FilterEstimate) FilterEstimate { + return (FilterEstimate)(v3) + } func FromV4FilterEstimate(v4 smoothing4.FilterEstimate) FilterEstimate { + return (FilterEstimate)(v4) + } type ActorStateLoader func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) @@ -92,52 +101,127 @@ func Load(store adt.Store, act *types.Actor) (cbor.Marshaler, error) { func ActorNameByCode(c cid.Cid) string { 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) + default: return "" } } func IsBuiltinActor(c cid.Cid) bool { - return builtin0.IsBuiltinActor(c) || - builtin2.IsBuiltinActor(c) || - builtin3.IsBuiltinActor(c) || - builtin4.IsBuiltinActor(c) + + if builtin0.IsBuiltinActor(c) { + return true + } + + if builtin2.IsBuiltinActor(c) { + return true + } + + if builtin3.IsBuiltinActor(c) { + return true + } + + if builtin4.IsBuiltinActor(c) { + return true + } + + return false } func IsAccountActor(c cid.Cid) bool { - return c == builtin0.AccountActorCodeID || - c == builtin2.AccountActorCodeID || - c == builtin3.AccountActorCodeID || - c == builtin4.AccountActorCodeID + + if c == builtin0.AccountActorCodeID { + return true + } + + if c == builtin2.AccountActorCodeID { + return true + } + + if c == builtin3.AccountActorCodeID { + return true + } + + if c == builtin4.AccountActorCodeID { + return true + } + + return false } func IsStorageMinerActor(c cid.Cid) bool { - return c == builtin0.StorageMinerActorCodeID || - c == builtin2.StorageMinerActorCodeID || - c == builtin3.StorageMinerActorCodeID || - c == builtin4.StorageMinerActorCodeID + + if c == builtin0.StorageMinerActorCodeID { + return true + } + + if c == builtin2.StorageMinerActorCodeID { + return true + } + + if c == builtin3.StorageMinerActorCodeID { + return true + } + + if c == builtin4.StorageMinerActorCodeID { + return true + } + + return false } func IsMultisigActor(c cid.Cid) bool { - return c == builtin0.MultisigActorCodeID || - c == builtin2.MultisigActorCodeID || - c == builtin3.MultisigActorCodeID || - c == builtin4.MultisigActorCodeID + + if c == builtin0.MultisigActorCodeID { + return true + } + + if c == builtin2.MultisigActorCodeID { + return true + } + + if c == builtin3.MultisigActorCodeID { + return true + } + + if c == builtin4.MultisigActorCodeID { + return true + } + + return false } func IsPaymentChannelActor(c cid.Cid) bool { - return c == builtin0.PaymentChannelActorCodeID || - c == builtin2.PaymentChannelActorCodeID || - c == builtin3.PaymentChannelActorCodeID || - c == builtin4.PaymentChannelActorCodeID + + if c == builtin0.PaymentChannelActorCodeID { + return true + } + + if c == builtin2.PaymentChannelActorCodeID { + return true + } + + if c == builtin3.PaymentChannelActorCodeID { + return true + } + + if c == builtin4.PaymentChannelActorCodeID { + return true + } + + return false } func makeAddress(addr string) address.Address { diff --git a/chain/actors/builtin/builtin.go.template b/chain/actors/builtin/builtin.go.template new file mode 100644 index 000000000..9b89b13f5 --- /dev/null +++ b/chain/actors/builtin/builtin.go.template @@ -0,0 +1,144 @@ +package builtin + +import ( + "github.com/filecoin-project/go-address" + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + {{range .versions}} + builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin" + smoothing{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/util/smoothing" + {{end}} + + "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/types" + + miner{{.latestVersion}} "github.com/filecoin-project/specs-actors{{import .latestVersion}}actors/builtin/miner" + proof{{.latestVersion}} "github.com/filecoin-project/specs-actors{{import .latestVersion}}actors/runtime/proof" +) + +var SystemActorAddr = builtin{{.latestVersion}}.SystemActorAddr +var BurntFundsActorAddr = builtin{{.latestVersion}}.BurntFundsActorAddr +var CronActorAddr = builtin{{.latestVersion}}.CronActorAddr +var SaftAddress = makeAddress("t0122") +var ReserveAddress = makeAddress("t090") +var RootVerifierAddress = makeAddress("t080") + +var ( + ExpectedLeadersPerEpoch = builtin{{.latestVersion}}.ExpectedLeadersPerEpoch +) + +const ( + EpochDurationSeconds = builtin{{.latestVersion}}.EpochDurationSeconds + EpochsInDay = builtin{{.latestVersion}}.EpochsInDay + SecondsInDay = builtin{{.latestVersion}}.SecondsInDay +) + +const ( + MethodSend = builtin{{.latestVersion}}.MethodSend + MethodConstructor = builtin{{.latestVersion}}.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{{.latestVersion}}.SectorInfo +type PoStProof = proof{{.latestVersion}}.PoStProof +type FilterEstimate = smoothing0.FilterEstimate + +func QAPowerForWeight(size abi.SectorSize, duration abi.ChainEpoch, dealWeight, verifiedWeight abi.DealWeight) abi.StoragePower { + return miner{{.latestVersion}}.QAPowerForWeight(size, duration, dealWeight, verifiedWeight) +} + +{{range .versions}} + func FromV{{.}}FilterEstimate(v{{.}} smoothing{{.}}.FilterEstimate) FilterEstimate { + {{if (eq . 0)}} + return (FilterEstimate)(v{{.}}) //nolint:unconvert + {{else}} + return (FilterEstimate)(v{{.}}) + {{end}} + } +{{end}} + +type ActorStateLoader func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) + +var ActorStateLoaders = make(map[cid.Cid]ActorStateLoader) + +func RegisterActorState(code cid.Cid, loader ActorStateLoader) { + ActorStateLoaders[code] = loader +} + +func Load(store adt.Store, act *types.Actor) (cbor.Marshaler, error) { + loader, found := ActorStateLoaders[act.Code] + if !found { + return nil, xerrors.Errorf("unknown actor code %s", act.Code) + } + return loader(store, act.Head) +} + +func ActorNameByCode(c cid.Cid) string { + switch { + {{range .versions}} + case builtin{{.}}.IsBuiltinActor(c): + return builtin{{.}}.ActorNameByCode(c) + {{end}} + default: + return "" + } +} + +func IsBuiltinActor(c cid.Cid) bool { + {{range .versions}} + if builtin{{.}}.IsBuiltinActor(c) { + return true + } + {{end}} + return false +} + +func IsAccountActor(c cid.Cid) bool { + {{range .versions}} + if c == builtin{{.}}.AccountActorCodeID { + return true + } + {{end}} + return false +} + +func IsStorageMinerActor(c cid.Cid) bool { + {{range .versions}} + if c == builtin{{.}}.StorageMinerActorCodeID { + return true + } + {{end}} + return false +} + +func IsMultisigActor(c cid.Cid) bool { + {{range .versions}} + if c == builtin{{.}}.MultisigActorCodeID { + return true + } + {{end}} + return false +} + +func IsPaymentChannelActor(c cid.Cid) bool { + {{range .versions}} + if c == builtin{{.}}.PaymentChannelActorCodeID { + return true + } + {{end}} + 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/multisig/actor.go.template b/chain/actors/builtin/multisig/actor.go.template index 304c0610c..19d99dcb7 100644 --- a/chain/actors/builtin/multisig/actor.go.template +++ b/chain/actors/builtin/multisig/actor.go.template @@ -12,8 +12,7 @@ import ( "github.com/filecoin-project/go-state-types/cbor" "github.com/ipfs/go-cid" - msig0 "github.com/filecoin-project/specs-actors/actors/builtin/multisig" - msig4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/multisig" + msig{{.latestVersion}} "github.com/filecoin-project/specs-actors/v4/actors/builtin/multisig" {{range .versions}} builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin" {{end}} @@ -58,7 +57,7 @@ type State interface { decodeTransaction(val *cbg.Deferred) (Transaction, error) } -type Transaction = msig0.Transaction +type Transaction = msig{{.latestVersion}}.Transaction var Methods = builtin{{.latestVersion}}.MethodsMultisig @@ -95,7 +94,7 @@ type ProposeReturn = msig{{.latestVersion}}.ProposeReturn type ProposeParams = msig{{.latestVersion}}.ProposeParams func txnParams(id uint64, data *ProposalHashData) ([]byte, error) { - params := msig{{.latestVersion}}.TxnIDParams{ID: msig4.TxnID(id)} + 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) diff --git a/chain/actors/builtin/multisig/multisig.go b/chain/actors/builtin/multisig/multisig.go index 79b1a57d7..d8f6fabae 100644 --- a/chain/actors/builtin/multisig/multisig.go +++ b/chain/actors/builtin/multisig/multisig.go @@ -12,7 +12,6 @@ import ( "github.com/filecoin-project/go-state-types/cbor" "github.com/ipfs/go-cid" - msig0 "github.com/filecoin-project/specs-actors/actors/builtin/multisig" msig4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/multisig" builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" @@ -84,7 +83,7 @@ type State interface { decodeTransaction(val *cbg.Deferred) (Transaction, error) } -type Transaction = msig0.Transaction +type Transaction = msig4.Transaction var Methods = builtin4.MethodsMultisig diff --git a/chain/actors/policy/policy.go.template b/chain/actors/policy/policy.go.template index b9e26171e..d395d7132 100644 --- a/chain/actors/policy/policy.go.template +++ b/chain/actors/policy/policy.go.template @@ -161,6 +161,7 @@ func GetWinningPoStSectorSetLookback(nwVer network.Version) abi.ChainEpoch { return 10 } + // NOTE: if this ever changes, adjust it in a (*Miner).mineOne() logline as well return ChainFinality }