From 63189cd81deea4f7ea93aa9a47377fae45703cdf 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 01/88] Generate wrappers for new actor versions --- Makefile | 10 +- 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 | 218 +++++++++ chain/actors/builtin/account/account.go | 12 + .../actors/builtin/account/actor.go.template | 41 ++ .../actors/builtin/account/state.go.template | 30 ++ chain/actors/builtin/builtin.go | 168 +++++-- chain/actors/builtin/builtin.go.template | 144 ++++++ chain/actors/builtin/cron/actor.go.template | 10 + chain/actors/builtin/init/actor.go.template | 60 +++ chain/actors/builtin/init/init.go | 12 + chain/actors/builtin/init/state.go.template | 89 ++++ chain/actors/builtin/init/v3.go | 3 +- chain/actors/builtin/init/v4.go | 3 +- chain/actors/builtin/market/actor.go.template | 142 ++++++ chain/actors/builtin/market/market.go | 17 +- 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 | 270 ++++++++++ chain/actors/builtin/miner/miner.go | 22 +- chain/actors/builtin/miner/state.go.template | 460 ++++++++++++++++++ chain/actors/builtin/miner/v0.go | 7 + chain/actors/builtin/miner/v2.go | 6 + chain/actors/builtin/miner/v3.go | 29 +- chain/actors/builtin/miner/v4.go | 29 +- .../actors/builtin/multisig/actor.go.template | 118 +++++ chain/actors/builtin/multisig/message.go | 79 --- .../builtin/multisig/message.go.template | 146 ++++++ .../multisig/{state.go => multisig.go} | 86 +++- .../actors/builtin/multisig/state.go.template | 97 ++++ .../builtin/multisig/{state0.go => v0.go} | 6 +- .../builtin/multisig/{state2.go => v2.go} | 3 +- .../builtin/multisig/{state3.go => v3.go} | 5 +- .../builtin/multisig/{state4.go => v4.go} | 7 +- chain/actors/builtin/paych/actor.go.template | 109 +++++ chain/actors/builtin/paych/message.go | 36 -- .../actors/builtin/paych/message.go.template | 74 +++ .../builtin/paych/{state.go => paych.go} | 46 +- 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 | 78 +++ chain/actors/builtin/power/power.go | 14 +- chain/actors/builtin/power/state.go.template | 151 ++++++ chain/actors/builtin/power/v0.go | 11 +- chain/actors/builtin/power/v2.go | 6 +- chain/actors/builtin/power/v3.go | 5 +- chain/actors/builtin/power/v4.go | 5 +- chain/actors/builtin/reward/actor.go.template | 60 +++ chain/actors/builtin/reward/reward.go | 15 +- chain/actors/builtin/reward/state.go.template | 103 ++++ chain/actors/builtin/reward/v0.go | 8 +- 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 | 51 ++ .../actors/builtin/verifreg/state.go.template | 58 +++ chain/actors/builtin/verifreg/verifreg.go | 14 + chain/actors/policy/policy.go | 68 ++- chain/actors/policy/policy.go.template | 233 +++++++++ cmd/lotus-storage-miner/actor_test.go | 2 +- .../misc/actors_version_checklist.md | 17 + 71 files changed, 3666 insertions(+), 301 deletions(-) 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 create mode 100644 chain/actors/builtin/builtin.go.template 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 create mode 100644 chain/actors/policy/policy.go.template create mode 100644 documentation/misc/actors_version_checklist.md diff --git a/Makefile b/Makefile index 16269e133..93b647942 100644 --- a/Makefile +++ b/Makefile @@ -325,6 +325,10 @@ type-gen: method-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 > api/apistruct/struct.go goimports -w api/apistruct @@ -333,9 +337,9 @@ api-gen: docsgen: docsgen-md docsgen-openrpc -docsgen-md-bin: +docsgen-md-bin: actors-gen go build $(GOFLAGS) -o docgen-md ./api/docgen/cmd -docsgen-openrpc-bin: +docsgen-openrpc-bin: actors-gen go build $(GOFLAGS) -o docgen-openrpc ./api/docgen-openrpc/cmd docsgen-md: docsgen-md-full docsgen-md-storage docsgen-md-worker @@ -358,7 +362,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 .PHONY: gen print-%: 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 c896e7e00..8060c18f3 100644 --- a/api/test/deadlines.go +++ b/api/test/deadlines.go @@ -61,7 +61,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 613a4f1dc..eeddc23a0 100644 --- a/api/test/test.go +++ b/api/test/test.go @@ -120,7 +120,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 4992741f4..c987fa1f9 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 new file mode 100644 index 000000000..7269d9ae5 --- /dev/null +++ b/chain/actors/agen/main.go @@ -0,0 +1,218 @@ +package main + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "text/template" + + "golang.org/x/xerrors" +) + +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 := generateAdapters(); err != nil { + fmt.Println(err) + return + } + + if err := generatePolicy("chain/actors/policy/policy.go"); err != nil { + fmt.Println(err) + return + } + + if err := generateBuiltin("chain/actors/builtin/builtin.go"); err != nil { + fmt.Println(err) + return + } +} + +func generateAdapters() error { + for act, versions := range actors { + actDir := filepath.Join("chain/actors/builtin", act) + + if err := generateState(actDir); err != nil { + return err + } + + if err := generateMessages(actDir); 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, + "latestVersion": latestVersion, + }) + 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 +} + +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 message 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 +} + +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 template 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 +} + +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/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 new file mode 100644 index 000000000..f75af3dfb --- /dev/null +++ b/chain/actors/builtin/account/actor.go.template @@ -0,0 +1,41 @@ +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 +} 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/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..5b700cec8 --- /dev/null +++ b/chain/actors/builtin/init/actor.go.template @@ -0,0 +1,60 @@ +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/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 new file mode 100644 index 000000000..95f052bda --- /dev/null +++ b/chain/actors/builtin/init/state.go.template @@ -0,0 +1,89 @@ +package init + +import ( + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/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" +) + +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/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 new file mode 100644 index 000000000..eaa06b867 --- /dev/null +++ b/chain/actors/builtin/market/actor.go.template @@ -0,0 +1,142 @@ +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, + } +} \ No newline at end of file diff --git a/chain/actors/builtin/market/market.go b/chain/actors/builtin/market/market.go index 738151503..63e8c42d3 100644 --- a/chain/actors/builtin/market/market.go +++ b/chain/actors/builtin/market/market.go @@ -9,10 +9,14 @@ 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" "github.com/filecoin-project/lotus/chain/actors/adt" @@ -21,15 +25,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) }) @@ -40,16 +48,21 @@ 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) + 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/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..4b3d8db5e --- /dev/null +++ b/chain/actors/builtin/miner/actor.go.template @@ -0,0 +1,270 @@ +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..a426e063b 100644 --- a/chain/actors/builtin/miner/miner.go +++ b/chain/actors/builtin/miner/miner.go @@ -18,28 +18,37 @@ 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" ) 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 @@ -58,16 +67,21 @@ 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) + 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 new file mode 100644 index 000000000..0769eea10 --- /dev/null +++ b/chain/actors/builtin/miner/state.go.template @@ -0,0 +1,460 @@ +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..2dc8ae23e 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 } @@ -396,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) { @@ -413,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 5e058ed1f..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" ) @@ -208,9 +209,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 +221,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 +269,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 +378,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) { @@ -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 b354dbc33..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" ) @@ -208,9 +209,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 +221,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 +269,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 +378,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) { @@ -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 new file mode 100644 index 000000000..304c0610c --- /dev/null +++ b/chain/actors/builtin/multisig/actor.go.template @@ -0,0 +1,118 @@ +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..6bff8983a --- /dev/null +++ b/chain/actors/builtin/multisig/message.go.template @@ -0,0 +1,146 @@ +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..79b1a57d7 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,27 +12,37 @@ 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" ) 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) }) @@ -37,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) } @@ -67,3 +85,69 @@ 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..2316aadba --- /dev/null +++ b/chain/actors/builtin/multisig/state.go.template @@ -0,0 +1,97 @@ +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..8834e4553 100644 --- a/chain/actors/builtin/multisig/state3.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" ) @@ -74,12 +75,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..9f9dc7573 100644 --- a/chain/actors/builtin/multisig/state4.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" ) @@ -69,17 +70,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..3f68a5cfa --- /dev/null +++ b/chain/actors/builtin/paych/actor.go.template @@ -0,0 +1,109 @@ +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..30e4408d8 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,27 +13,36 @@ 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" ) 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) }) @@ -41,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) } @@ -98,3 +113,32 @@ 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..82f791e58 --- /dev/null +++ b/chain/actors/builtin/power/actor.go.template @@ -0,0 +1,78 @@ +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..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) }) @@ -40,16 +47,21 @@ 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) + 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 new file mode 100644 index 000000000..4cb904a1d --- /dev/null +++ b/chain/actors/builtin/power/state.go.template @@ -0,0 +1,151 @@ +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..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" ) @@ -121,12 +122,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..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" ) @@ -121,12 +122,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..81437d26f --- /dev/null +++ b/chain/actors/builtin/reward/actor.go.template @@ -0,0 +1,60 @@ +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..1037cf741 100644 --- a/chain/actors/builtin/reward/reward.go +++ b/chain/actors/builtin/reward/reward.go @@ -7,9 +7,13 @@ 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" + builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" "github.com/filecoin-project/lotus/chain/actors/adt" @@ -18,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) }) @@ -37,16 +45,21 @@ 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) + 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 new file mode 100644 index 000000000..1758d1413 --- /dev/null +++ b/chain/actors/builtin/reward/state.go.template @@ -0,0 +1,103 @@ +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..fe053cc16 100644 --- a/chain/actors/builtin/reward/v0.go +++ b/chain/actors/builtin/reward/v0.go @@ -28,12 +28,14 @@ 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 } func (s *state0) ThisEpochRewardSmoothed() (builtin.FilterEstimate, error) { + return builtin.FromV0FilterEstimate(*s.State.ThisEpochRewardSmoothed), nil + } func (s *state0) ThisEpochBaselinePower() (abi.StoragePower, error) { @@ -52,11 +54,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/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 new file mode 100644 index 000000000..22e809ccf --- /dev/null +++ b/chain/actors/builtin/verifreg/actor.go.template @@ -0,0 +1,51 @@ +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..32f50a4ae 100644 --- a/chain/actors/builtin/verifreg/verifreg.go +++ b/chain/actors/builtin/verifreg/verifreg.go @@ -8,9 +8,13 @@ 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" + builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" "github.com/filecoin-project/lotus/chain/actors/adt" @@ -19,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 ( @@ -40,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) } diff --git a/chain/actors/policy/policy.go b/chain/actors/policy/policy.go index 07f489b11..164f19a76 100644 --- a/chain/actors/policy/policy.go +++ b/chain/actors/policy/policy.go @@ -25,8 +25,9 @@ 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" + + paych4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/paych" ) const ( @@ -39,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)) @@ -63,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{}{} @@ -79,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{}{} + } } @@ -86,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 } @@ -113,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") } @@ -145,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) @@ -173,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 { @@ -192,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 } @@ -233,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") } @@ -248,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..d395d7132 --- /dev/null +++ b/chain/actors/policy/policy.go.template @@ -0,0 +1,233 @@ +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 + } + + // NOTE: if this ever changes, adjust it in a (*Miner).mineOne() logline as well + 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") + } +} 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..6c06441ab --- /dev/null +++ b/documentation/misc/actors_version_checklist.md @@ -0,0 +1,17 @@ +### 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 `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 +- [ ] 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 b5da2655dc3520a3808af4bb5ecfa5c5e8fd0844 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Thu, 6 May 2021 01:44:11 -0400 Subject: [PATCH 02/88] Introduce v5 actors --- api/test/test.go | 9 +- build/openrpc/full.json.gz | Bin 22804 -> 22804 bytes build/openrpc/miner.json.gz | Bin 7828 -> 7828 bytes build/openrpc/worker.json.gz | Bin 2573 -> 2573 bytes build/params_2k.go | 15 +- build/params_butterfly.go | 9 +- build/params_calibnet.go | 8 +- build/params_mainnet.go | 17 +- build/params_nerpanet.go | 7 +- build/params_shared_vals.go | 2 +- build/params_testground.go | 29 +- chain/actors/agen/main.go | 7 +- chain/actors/builtin/account/account.go | 9 + chain/actors/builtin/account/v5.go | 30 ++ chain/actors/builtin/builtin.go | 60 ++- chain/actors/builtin/cron/cron.go | 6 +- chain/actors/builtin/init/init.go | 13 +- chain/actors/builtin/init/v5.go | 87 ++++ chain/actors/builtin/market/market.go | 13 +- chain/actors/builtin/market/v5.go | 209 ++++++++ chain/actors/builtin/miner/miner.go | 11 +- chain/actors/builtin/miner/v5.go | 445 ++++++++++++++++++ .../actors/builtin/multisig/actor.go.template | 4 +- chain/actors/builtin/multisig/message5.go | 71 +++ chain/actors/builtin/multisig/multisig.go | 24 +- chain/actors/builtin/multisig/v5.go | 96 ++++ chain/actors/builtin/paych/message5.go | 74 +++ chain/actors/builtin/paych/paych.go | 14 +- chain/actors/builtin/paych/v5.go | 104 ++++ chain/actors/builtin/power/power.go | 13 +- chain/actors/builtin/power/v5.go | 150 ++++++ chain/actors/builtin/reward/reward.go | 13 +- chain/actors/builtin/reward/v5.go | 88 ++++ chain/actors/builtin/verifreg/v5.go | 58 +++ chain/actors/builtin/verifreg/verifreg.go | 13 +- chain/actors/policy/policy.go | 70 ++- chain/actors/version.go | 3 + chain/gen/genesis/util.go | 2 +- chain/messagepool/selection.go | 2 +- chain/stmgr/forks.go | 115 ++++- chain/stmgr/stmgr.go | 6 +- chain/stmgr/utils.go | 3 + chain/vm/invoker.go | 2 + chain/vm/mkactor.go | 3 + cmd/tvx/codenames.go | 2 +- cmd/tvx/codenames_test.go | 2 +- documentation/en/api-methods.md | 2 +- go.mod | 3 +- go.sum | 4 + lotuspond/front/src/chain/methods.json | 104 ++++ testplans/lotus-soup/init.go | 2 +- 51 files changed, 1922 insertions(+), 111 deletions(-) create mode 100644 chain/actors/builtin/account/v5.go create mode 100644 chain/actors/builtin/init/v5.go create mode 100644 chain/actors/builtin/market/v5.go create mode 100644 chain/actors/builtin/miner/v5.go create mode 100644 chain/actors/builtin/multisig/message5.go create mode 100644 chain/actors/builtin/multisig/v5.go create mode 100644 chain/actors/builtin/paych/message5.go create mode 100644 chain/actors/builtin/paych/v5.go create mode 100644 chain/actors/builtin/power/v5.go create mode 100644 chain/actors/builtin/reward/v5.go create mode 100644 chain/actors/builtin/verifreg/v5.go diff --git a/api/test/test.go b/api/test/test.go index eeddc23a0..44530191b 100644 --- a/api/test/test.go +++ b/api/test/test.go @@ -122,7 +122,8 @@ var TwoFull = DefaultFullOpts(2) var FullNodeWithLatestActorsAt = func(upgradeHeight abi.ChainEpoch) FullNodeOpts { if upgradeHeight == -1 { - upgradeHeight = 3 + // Attention: Update this when introducing new actor versions or your tests will be sad + upgradeHeight = 4 } return FullNodeOpts{ @@ -138,8 +139,12 @@ var FullNodeWithLatestActorsAt = func(upgradeHeight abi.ChainEpoch) FullNodeOpts Migration: stmgr.UpgradeActorsV3, }, { Network: network.Version12, - Height: upgradeHeight, + Height: 3, Migration: stmgr.UpgradeActorsV4, + }, { + Network: network.Version13, + Height: upgradeHeight, + Migration: stmgr.UpgradeActorsV5, }}) }, } diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index 6cc950d730897d8c52ba5121c06489f43d23b890..65c5eddd3a304996f9bd099df40cf3a6af605706 100644 GIT binary patch delta 30 mcmbQTiE+v%#tB`FejB^fTp2HK{^Tkp%<(;nv0bT#g#iGWF2jlOJoy>9^P6rNUu5Mst003h#2y*}c delta 21 dcmbPYJH>WF2V?cdPG&idwZA;iu5Mst003Y;2!j9s diff --git a/build/openrpc/worker.json.gz b/build/openrpc/worker.json.gz index cd1e2683ac9a9c34cb00bda9242b14892b7336f3..798a6376cacd1d613567d6c963b3e734655e62e7 100644 GIT binary patch delta 21 ccmeAb=@psK#Mr*ExrvkGZ$)7NV>JT<09gtLb^rhX delta 21 ccmeAb=@psK#Mr&Dxrvh_^6BjtjMWSb09)}0B>(^b diff --git a/build/params_2k.go b/build/params_2k.go index 12d497c4b..d93b26468 100644 --- a/build/params_2k.go +++ b/build/params_2k.go @@ -24,7 +24,7 @@ var UpgradeIgnitionHeight = abi.ChainEpoch(-2) var UpgradeRefuelHeight = abi.ChainEpoch(-3) var UpgradeTapeHeight = abi.ChainEpoch(-4) -var UpgradeActorsV2Height = abi.ChainEpoch(10) +var UpgradeAssemblyHeight = abi.ChainEpoch(10) var UpgradeLiftoffHeight = abi.ChainEpoch(-5) var UpgradeKumquatHeight = abi.ChainEpoch(15) @@ -33,11 +33,13 @@ var UpgradePersianHeight = abi.ChainEpoch(25) var UpgradeOrangeHeight = abi.ChainEpoch(27) var UpgradeClausHeight = abi.ChainEpoch(30) -var UpgradeActorsV3Height = abi.ChainEpoch(35) +var UpgradeTrustHeight = abi.ChainEpoch(35) var UpgradeNorwegianHeight = abi.ChainEpoch(40) -var UpgradeActorsV4Height = abi.ChainEpoch(45) +var UpgradeTurboHeight = abi.ChainEpoch(45) + +var UpgradeHyperdriveHeight = abi.ChainEpoch(50) var DrandSchedule = map[abi.ChainEpoch]DrandEnum{ 0: DrandMainnet, @@ -68,16 +70,17 @@ func init() { UpgradeIgnitionHeight = getUpgradeHeight("LOTUS_IGNITION_HEIGHT", UpgradeIgnitionHeight) UpgradeRefuelHeight = getUpgradeHeight("LOTUS_REFUEL_HEIGHT", UpgradeRefuelHeight) UpgradeTapeHeight = getUpgradeHeight("LOTUS_TAPE_HEIGHT", UpgradeTapeHeight) - UpgradeActorsV2Height = getUpgradeHeight("LOTUS_ACTORSV2_HEIGHT", UpgradeActorsV2Height) + UpgradeAssemblyHeight = getUpgradeHeight("LOTUS_ACTORSV2_HEIGHT", UpgradeAssemblyHeight) UpgradeLiftoffHeight = getUpgradeHeight("LOTUS_LIFTOFF_HEIGHT", UpgradeLiftoffHeight) UpgradeKumquatHeight = getUpgradeHeight("LOTUS_KUMQUAT_HEIGHT", UpgradeKumquatHeight) UpgradeCalicoHeight = getUpgradeHeight("LOTUS_CALICO_HEIGHT", UpgradeCalicoHeight) UpgradePersianHeight = getUpgradeHeight("LOTUS_PERSIAN_HEIGHT", UpgradePersianHeight) UpgradeOrangeHeight = getUpgradeHeight("LOTUS_ORANGE_HEIGHT", UpgradeOrangeHeight) UpgradeClausHeight = getUpgradeHeight("LOTUS_CLAUS_HEIGHT", UpgradeClausHeight) - UpgradeActorsV3Height = getUpgradeHeight("LOTUS_ACTORSV3_HEIGHT", UpgradeActorsV3Height) + UpgradeTrustHeight = getUpgradeHeight("LOTUS_ACTORSV3_HEIGHT", UpgradeTrustHeight) UpgradeNorwegianHeight = getUpgradeHeight("LOTUS_NORWEGIAN_HEIGHT", UpgradeNorwegianHeight) - UpgradeActorsV4Height = getUpgradeHeight("LOTUS_ACTORSV4_HEIGHT", UpgradeActorsV4Height) + UpgradeTurboHeight = getUpgradeHeight("LOTUS_ACTORSV4_HEIGHT", UpgradeTurboHeight) + UpgradeHyperdriveHeight = getUpgradeHeight("LOTUS_HYPERDRIVE_HEIGHT", UpgradeHyperdriveHeight) BuildType |= Build2k } diff --git a/build/params_butterfly.go b/build/params_butterfly.go index 6daeca502..258f6ab0f 100644 --- a/build/params_butterfly.go +++ b/build/params_butterfly.go @@ -23,7 +23,7 @@ const UpgradeSmokeHeight = -2 const UpgradeIgnitionHeight = -3 const UpgradeRefuelHeight = -4 -var UpgradeActorsV2Height = abi.ChainEpoch(30) +var UpgradeAssemblyHeight = abi.ChainEpoch(30) const UpgradeTapeHeight = 60 const UpgradeLiftoffHeight = -5 @@ -32,9 +32,10 @@ const UpgradeCalicoHeight = 120 const UpgradePersianHeight = 150 const UpgradeClausHeight = 180 const UpgradeOrangeHeight = 210 -const UpgradeActorsV3Height = 240 -const UpgradeNorwegianHeight = UpgradeActorsV3Height + (builtin2.EpochsInHour * 12) -const UpgradeActorsV4Height = 8922 +const UpgradeTrustHeight = 240 +const UpgradeNorwegianHeight = UpgradeTrustHeight + (builtin2.EpochsInHour * 12) +const UpgradeTurboHeight = 8922 +const UpgradeHyperdriveHeight = 9999999 func init() { policy.SetConsensusMinerMinPower(abi.NewStoragePower(2 << 30)) diff --git a/build/params_calibnet.go b/build/params_calibnet.go index 997bb395b..4685ec30c 100644 --- a/build/params_calibnet.go +++ b/build/params_calibnet.go @@ -25,7 +25,7 @@ const UpgradeSmokeHeight = -2 const UpgradeIgnitionHeight = -3 const UpgradeRefuelHeight = -4 -var UpgradeActorsV2Height = abi.ChainEpoch(30) +var UpgradeAssemblyHeight = abi.ChainEpoch(30) const UpgradeTapeHeight = 60 @@ -40,10 +40,12 @@ const UpgradeClausHeight = 250 const UpgradeOrangeHeight = 300 -const UpgradeActorsV3Height = 600 +const UpgradeTrustHeight = 600 const UpgradeNorwegianHeight = 114000 -const UpgradeActorsV4Height = 193789 +const UpgradeTurboHeight = 193789 + +const UpgradeHyperdriveHeight = 9999999 func init() { policy.SetConsensusMinerMinPower(abi.NewStoragePower(32 << 30)) diff --git a/build/params_mainnet.go b/build/params_mainnet.go index ea4ae7d75..5c3171a27 100644 --- a/build/params_mainnet.go +++ b/build/params_mainnet.go @@ -34,7 +34,7 @@ const UpgradeSmokeHeight = 51000 const UpgradeIgnitionHeight = 94000 const UpgradeRefuelHeight = 130800 -const UpgradeActorsV2Height = 138720 +const UpgradeAssemblyHeight = 138720 const UpgradeTapeHeight = 140760 @@ -54,13 +54,16 @@ const UpgradeOrangeHeight = 336458 const UpgradeClausHeight = 343200 // 2021-03-04T00:00:30Z -var UpgradeActorsV3Height = abi.ChainEpoch(550321) +const UpgradeTrustHeight = 550321 // 2021-04-12T22:00:00Z const UpgradeNorwegianHeight = 665280 // 2021-04-29T06:00:00Z -var UpgradeActorsV4Height = abi.ChainEpoch(712320) +const UpgradeTurboHeight = 712320 + +// ??? +var UpgradeHyperdriveHeight = abi.ChainEpoch(9999999) func init() { policy.SetConsensusMinerMinPower(abi.NewStoragePower(10 << 40)) @@ -69,12 +72,8 @@ func init() { SetAddressNetwork(address.Mainnet) } - if os.Getenv("LOTUS_DISABLE_V3_ACTOR_MIGRATION") == "1" { - UpgradeActorsV3Height = math.MaxInt64 - } - - if os.Getenv("LOTUS_DISABLE_V4_ACTOR_MIGRATION") == "1" { - UpgradeActorsV4Height = math.MaxInt64 + if os.Getenv("LOTUS_DISABLE_HYPERDRIVE") == "1" { + UpgradeHyperdriveHeight = math.MaxInt64 } Devnet = false diff --git a/build/params_nerpanet.go b/build/params_nerpanet.go index fb6cfc47a..069016080 100644 --- a/build/params_nerpanet.go +++ b/build/params_nerpanet.go @@ -27,7 +27,7 @@ const UpgradeRefuelHeight = -3 const UpgradeLiftoffHeight = -5 -const UpgradeActorsV2Height = 30 // critical: the network can bootstrap from v1 only +const UpgradeAssemblyHeight = 30 // critical: the network can bootstrap from v1 only const UpgradeTapeHeight = 60 const UpgradeKumquatHeight = 90 @@ -39,9 +39,10 @@ const UpgradeClausHeight = 250 const UpgradeOrangeHeight = 300 -const UpgradeActorsV3Height = 600 +const UpgradeTrustHeight = 600 const UpgradeNorwegianHeight = 999999 -const UpgradeActorsV4Height = 99999999 +const UpgradeTurboHeight = 99999999 +const UpgradeHyperdriveHeight = 999999999 func init() { // Minimum block production power is set to 4 TiB diff --git a/build/params_shared_vals.go b/build/params_shared_vals.go index 92bbc5db9..e4240ccce 100644 --- a/build/params_shared_vals.go +++ b/build/params_shared_vals.go @@ -25,7 +25,7 @@ const UnixfsLinksPerLevel = 1024 // Consensus / Network const AllowableClockDriftSecs = uint64(1) -const NewestNetworkVersion = network.Version11 +const NewestNetworkVersion = network.Version13 const ActorUpgradeNetworkVersion = network.Version4 // Epochs diff --git a/build/params_testground.go b/build/params_testground.go index 7da3c2272..252d23e75 100644 --- a/build/params_testground.go +++ b/build/params_testground.go @@ -82,20 +82,21 @@ var ( UpgradeBreezeHeight abi.ChainEpoch = -1 BreezeGasTampingDuration abi.ChainEpoch = 0 - UpgradeSmokeHeight abi.ChainEpoch = -1 - UpgradeIgnitionHeight abi.ChainEpoch = -2 - UpgradeRefuelHeight abi.ChainEpoch = -3 - UpgradeTapeHeight abi.ChainEpoch = -4 - UpgradeActorsV2Height abi.ChainEpoch = 10 - UpgradeLiftoffHeight abi.ChainEpoch = -5 - UpgradeKumquatHeight abi.ChainEpoch = -6 - UpgradeCalicoHeight abi.ChainEpoch = -7 - UpgradePersianHeight abi.ChainEpoch = -8 - UpgradeOrangeHeight abi.ChainEpoch = -9 - UpgradeClausHeight abi.ChainEpoch = -10 - UpgradeActorsV3Height abi.ChainEpoch = -11 - UpgradeNorwegianHeight abi.ChainEpoch = -12 - UpgradeActorsV4Height abi.ChainEpoch = -13 + UpgradeSmokeHeight abi.ChainEpoch = -1 + UpgradeIgnitionHeight abi.ChainEpoch = -2 + UpgradeRefuelHeight abi.ChainEpoch = -3 + UpgradeTapeHeight abi.ChainEpoch = -4 + UpgradeAssemblyHeight abi.ChainEpoch = 10 + UpgradeLiftoffHeight abi.ChainEpoch = -5 + UpgradeKumquatHeight abi.ChainEpoch = -6 + UpgradeCalicoHeight abi.ChainEpoch = -7 + UpgradePersianHeight abi.ChainEpoch = -8 + UpgradeOrangeHeight abi.ChainEpoch = -9 + UpgradeClausHeight abi.ChainEpoch = -10 + UpgradeTrustHeight abi.ChainEpoch = -11 + UpgradeNorwegianHeight abi.ChainEpoch = -12 + UpgradeTurboHeight abi.ChainEpoch = -13 + UpgradeHyperdriveHeight abi.ChainEpoch = -13 DrandSchedule = map[abi.ChainEpoch]DrandEnum{ 0: DrandMainnet, diff --git a/chain/actors/agen/main.go b/chain/actors/agen/main.go index 7269d9ae5..d34b43ca8 100644 --- a/chain/actors/agen/main.go +++ b/chain/actors/agen/main.go @@ -11,15 +11,16 @@ import ( "golang.org/x/xerrors" ) -var latestVersion = 4 +var latestVersion = 5 -var versions = []int{0, 2, 3, latestVersion} +var versions = []int{0, 2, 3, 4, latestVersion} var versionImports = map[int]string{ 0: "/", 2: "/v2/", 3: "/v3/", - latestVersion: "/v4/", + 4: "/v4/", + latestVersion: "/v5/", } var actors = map[string][]int{ diff --git a/chain/actors/builtin/account/account.go b/chain/actors/builtin/account/account.go index 8242e300d..7ac8f62d0 100644 --- a/chain/actors/builtin/account/account.go +++ b/chain/actors/builtin/account/account.go @@ -18,6 +18,8 @@ import ( builtin3 "github.com/filecoin-project/specs-actors/v3/actors/builtin" builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" + + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" ) func init() { @@ -37,6 +39,10 @@ func init() { builtin.RegisterActorState(builtin4.AccountActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load4(store, root) }) + + builtin.RegisterActorState(builtin5.AccountActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { + return load5(store, root) + }) } var Methods = builtin4.MethodsAccount @@ -56,6 +62,9 @@ func Load(store adt.Store, act *types.Actor) (State, error) { case builtin4.AccountActorCodeID: return load4(store, act.Head) + case builtin5.AccountActorCodeID: + return load5(store, act.Head) + } return nil, xerrors.Errorf("unknown actor code %s", act.Code) } diff --git a/chain/actors/builtin/account/v5.go b/chain/actors/builtin/account/v5.go new file mode 100644 index 000000000..e2df5904d --- /dev/null +++ b/chain/actors/builtin/account/v5.go @@ -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" + + account5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/account" +) + +var _ State = (*state5)(nil) + +func load5(store adt.Store, root cid.Cid) (State, error) { + out := state5{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +type state5 struct { + account5.State + store adt.Store +} + +func (s *state5) PubkeyAddress() (address.Address, error) { + return s.Address, nil +} diff --git a/chain/actors/builtin/builtin.go b/chain/actors/builtin/builtin.go index 5e34c015a..74d622819 100644 --- a/chain/actors/builtin/builtin.go +++ b/chain/actors/builtin/builtin.go @@ -17,46 +17,49 @@ import ( builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" smoothing4 "github.com/filecoin-project/specs-actors/v4/actors/util/smoothing" + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" + smoothing5 "github.com/filecoin-project/specs-actors/v5/actors/util/smoothing" + "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" - miner4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/miner" - proof4 "github.com/filecoin-project/specs-actors/v4/actors/runtime/proof" + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" + proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" ) -var SystemActorAddr = builtin4.SystemActorAddr -var BurntFundsActorAddr = builtin4.BurntFundsActorAddr -var CronActorAddr = builtin4.CronActorAddr +var SystemActorAddr = builtin5.SystemActorAddr +var BurntFundsActorAddr = builtin5.BurntFundsActorAddr +var CronActorAddr = builtin5.CronActorAddr var SaftAddress = makeAddress("t0122") var ReserveAddress = makeAddress("t090") var RootVerifierAddress = makeAddress("t080") var ( - ExpectedLeadersPerEpoch = builtin4.ExpectedLeadersPerEpoch + ExpectedLeadersPerEpoch = builtin5.ExpectedLeadersPerEpoch ) const ( - EpochDurationSeconds = builtin4.EpochDurationSeconds - EpochsInDay = builtin4.EpochsInDay - SecondsInDay = builtin4.SecondsInDay + EpochDurationSeconds = builtin5.EpochDurationSeconds + EpochsInDay = builtin5.EpochsInDay + SecondsInDay = builtin5.SecondsInDay ) const ( - MethodSend = builtin4.MethodSend - MethodConstructor = builtin4.MethodConstructor + MethodSend = builtin5.MethodSend + MethodConstructor = builtin5.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 = proof4.SectorInfo -type PoStProof = proof4.PoStProof +type SectorInfo = proof5.SectorInfo +type PoStProof = proof5.PoStProof type FilterEstimate = smoothing0.FilterEstimate func QAPowerForWeight(size abi.SectorSize, duration abi.ChainEpoch, dealWeight, verifiedWeight abi.DealWeight) abi.StoragePower { - return miner4.QAPowerForWeight(size, duration, dealWeight, verifiedWeight) + return miner5.QAPowerForWeight(size, duration, dealWeight, verifiedWeight) } func FromV0FilterEstimate(v0 smoothing0.FilterEstimate) FilterEstimate { @@ -83,6 +86,12 @@ func FromV4FilterEstimate(v4 smoothing4.FilterEstimate) FilterEstimate { } +func FromV5FilterEstimate(v5 smoothing5.FilterEstimate) FilterEstimate { + + return (FilterEstimate)(v5) + +} + type ActorStateLoader func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) var ActorStateLoaders = make(map[cid.Cid]ActorStateLoader) @@ -114,6 +123,9 @@ func ActorNameByCode(c cid.Cid) string { case builtin4.IsBuiltinActor(c): return builtin4.ActorNameByCode(c) + case builtin5.IsBuiltinActor(c): + return builtin5.ActorNameByCode(c) + default: return "" } @@ -137,6 +149,10 @@ func IsBuiltinActor(c cid.Cid) bool { return true } + if builtin5.IsBuiltinActor(c) { + return true + } + return false } @@ -158,6 +174,10 @@ func IsAccountActor(c cid.Cid) bool { return true } + if c == builtin5.AccountActorCodeID { + return true + } + return false } @@ -179,6 +199,10 @@ func IsStorageMinerActor(c cid.Cid) bool { return true } + if c == builtin5.StorageMinerActorCodeID { + return true + } + return false } @@ -200,6 +224,10 @@ func IsMultisigActor(c cid.Cid) bool { return true } + if c == builtin5.MultisigActorCodeID { + return true + } + return false } @@ -221,6 +249,10 @@ func IsPaymentChannelActor(c cid.Cid) bool { return true } + if c == builtin5.PaymentChannelActorCodeID { + return true + } + return false } diff --git a/chain/actors/builtin/cron/cron.go b/chain/actors/builtin/cron/cron.go index 52a9fab07..a601f2b1e 100644 --- a/chain/actors/builtin/cron/cron.go +++ b/chain/actors/builtin/cron/cron.go @@ -1,10 +1,10 @@ package cron import ( - builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" ) var ( - Address = builtin4.CronActorAddr - Methods = builtin4.MethodsCron + Address = builtin5.CronActorAddr + Methods = builtin5.MethodsCron ) diff --git a/chain/actors/builtin/init/init.go b/chain/actors/builtin/init/init.go index 730d21fd8..605f2d103 100644 --- a/chain/actors/builtin/init/init.go +++ b/chain/actors/builtin/init/init.go @@ -20,6 +20,8 @@ import ( builtin3 "github.com/filecoin-project/specs-actors/v3/actors/builtin" builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" + + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" ) func init() { @@ -39,11 +41,15 @@ func init() { builtin.RegisterActorState(builtin4.InitActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load4(store, root) }) + + builtin.RegisterActorState(builtin5.InitActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { + return load5(store, root) + }) } var ( - Address = builtin4.InitActorAddr - Methods = builtin4.MethodsInit + Address = builtin5.InitActorAddr + Methods = builtin5.MethodsInit ) func Load(store adt.Store, act *types.Actor) (State, error) { @@ -61,6 +67,9 @@ func Load(store adt.Store, act *types.Actor) (State, error) { case builtin4.InitActorCodeID: return load4(store, act.Head) + case builtin5.InitActorCodeID: + return load5(store, act.Head) + } return nil, xerrors.Errorf("unknown actor code %s", act.Code) } diff --git a/chain/actors/builtin/init/v5.go b/chain/actors/builtin/init/v5.go new file mode 100644 index 000000000..01c20fc23 --- /dev/null +++ b/chain/actors/builtin/init/v5.go @@ -0,0 +1,87 @@ +package init + +import ( + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/ipfs/go-cid" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/node/modules/dtypes" + + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" + + init5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/init" + adt5 "github.com/filecoin-project/specs-actors/v5/actors/util/adt" +) + +var _ State = (*state5)(nil) + +func load5(store adt.Store, root cid.Cid) (State, error) { + out := state5{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +type state5 struct { + init5.State + store adt.Store +} + +func (s *state5) ResolveAddress(address address.Address) (address.Address, bool, error) { + return s.State.ResolveAddress(s.store, address) +} + +func (s *state5) MapAddressToNewID(address address.Address) (address.Address, error) { + return s.State.MapAddressToNewID(s.store, address) +} + +func (s *state5) ForEachActor(cb func(id abi.ActorID, address address.Address) error) error { + addrs, err := adt5.AsMap(s.store, s.State.AddressMap, builtin5.DefaultHamtBitwidth) + if err != nil { + return err + } + var actorID cbg.CborInt + return addrs.ForEach(&actorID, func(key string) error { + addr, err := address.NewFromBytes([]byte(key)) + if err != nil { + return err + } + return cb(abi.ActorID(actorID), addr) + }) +} + +func (s *state5) NetworkName() (dtypes.NetworkName, error) { + return dtypes.NetworkName(s.State.NetworkName), nil +} + +func (s *state5) SetNetworkName(name string) error { + s.State.NetworkName = name + return nil +} + +func (s *state5) Remove(addrs ...address.Address) (err error) { + m, err := adt5.AsMap(s.store, s.State.AddressMap, builtin5.DefaultHamtBitwidth) + if err != nil { + return err + } + for _, addr := range addrs { + if err = m.Delete(abi.AddrKey(addr)); err != nil { + return xerrors.Errorf("failed to delete entry for address: %s; err: %w", addr, err) + } + } + amr, err := m.Root() + if err != nil { + return xerrors.Errorf("failed to get address map root: %w", err) + } + s.State.AddressMap = amr + return nil +} + +func (s *state5) addressMap() (adt.Map, error) { + return adt5.AsMap(s.store, s.AddressMap, builtin5.DefaultHamtBitwidth) +} diff --git a/chain/actors/builtin/market/market.go b/chain/actors/builtin/market/market.go index 63e8c42d3..8f5964ecc 100644 --- a/chain/actors/builtin/market/market.go +++ b/chain/actors/builtin/market/market.go @@ -19,6 +19,8 @@ import ( builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" + "github.com/filecoin-project/lotus/chain/actors/adt" "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/types" @@ -41,11 +43,15 @@ func init() { builtin.RegisterActorState(builtin4.StorageMarketActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load4(store, root) }) + + builtin.RegisterActorState(builtin5.StorageMarketActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { + return load5(store, root) + }) } var ( - Address = builtin4.StorageMarketActorAddr - Methods = builtin4.MethodsMarket + Address = builtin5.StorageMarketActorAddr + Methods = builtin5.MethodsMarket ) func Load(store adt.Store, act *types.Actor) (State, error) { @@ -63,6 +69,9 @@ func Load(store adt.Store, act *types.Actor) (State, error) { case builtin4.StorageMarketActorCodeID: return load4(store, act.Head) + case builtin5.StorageMarketActorCodeID: + return load5(store, act.Head) + } return nil, xerrors.Errorf("unknown actor code %s", act.Code) } diff --git a/chain/actors/builtin/market/v5.go b/chain/actors/builtin/market/v5.go new file mode 100644 index 000000000..9acd3d57e --- /dev/null +++ b/chain/actors/builtin/market/v5.go @@ -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" + + market5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/market" + adt5 "github.com/filecoin-project/specs-actors/v5/actors/util/adt" +) + +var _ State = (*state5)(nil) + +func load5(store adt.Store, root cid.Cid) (State, error) { + out := state5{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +type state5 struct { + market5.State + store adt.Store +} + +func (s *state5) TotalLocked() (abi.TokenAmount, error) { + fml := types.BigAdd(s.TotalClientLockedCollateral, s.TotalProviderLockedCollateral) + fml = types.BigAdd(fml, s.TotalClientStorageFee) + return fml, nil +} + +func (s *state5) BalancesChanged(otherState State) (bool, error) { + otherState5, ok := otherState.(*state5) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.EscrowTable.Equals(otherState5.State.EscrowTable) || !s.State.LockedTable.Equals(otherState5.State.LockedTable), nil +} + +func (s *state5) StatesChanged(otherState State) (bool, error) { + otherState5, ok := otherState.(*state5) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.States.Equals(otherState5.State.States), nil +} + +func (s *state5) States() (DealStates, error) { + stateArray, err := adt5.AsArray(s.store, s.State.States, market5.StatesAmtBitwidth) + if err != nil { + return nil, err + } + return &dealStates5{stateArray}, nil +} + +func (s *state5) ProposalsChanged(otherState State) (bool, error) { + otherState5, ok := otherState.(*state5) + if !ok { + // there's no way to compare different versions of the state, so let's + // just say that means the state of balances has changed + return true, nil + } + return !s.State.Proposals.Equals(otherState5.State.Proposals), nil +} + +func (s *state5) Proposals() (DealProposals, error) { + proposalArray, err := adt5.AsArray(s.store, s.State.Proposals, market5.ProposalsAmtBitwidth) + if err != nil { + return nil, err + } + return &dealProposals5{proposalArray}, nil +} + +func (s *state5) EscrowTable() (BalanceTable, error) { + bt, err := adt5.AsBalanceTable(s.store, s.State.EscrowTable) + if err != nil { + return nil, err + } + return &balanceTable5{bt}, nil +} + +func (s *state5) LockedTable() (BalanceTable, error) { + bt, err := adt5.AsBalanceTable(s.store, s.State.LockedTable) + if err != nil { + return nil, err + } + return &balanceTable5{bt}, nil +} + +func (s *state5) VerifyDealsForActivation( + minerAddr address.Address, deals []abi.DealID, currEpoch, sectorExpiry abi.ChainEpoch, +) (weight, verifiedWeight abi.DealWeight, err error) { + w, vw, _, err := market5.ValidateDealsForActivation(&s.State, s.store, deals, minerAddr, sectorExpiry, currEpoch) + return w, vw, err +} + +func (s *state5) NextID() (abi.DealID, error) { + return s.State.NextID, nil +} + +type balanceTable5 struct { + *adt5.BalanceTable +} + +func (bt *balanceTable5) ForEach(cb func(address.Address, abi.TokenAmount) error) error { + asMap := (*adt5.Map)(bt.BalanceTable) + var ta abi.TokenAmount + return asMap.ForEach(&ta, func(key string) error { + a, err := address.NewFromBytes([]byte(key)) + if err != nil { + return err + } + return cb(a, ta) + }) +} + +type dealStates5 struct { + adt.Array +} + +func (s *dealStates5) Get(dealID abi.DealID) (*DealState, bool, error) { + var deal5 market5.DealState + found, err := s.Array.Get(uint64(dealID), &deal5) + if err != nil { + return nil, false, err + } + if !found { + return nil, false, nil + } + deal := fromV5DealState(deal5) + return &deal, true, nil +} + +func (s *dealStates5) ForEach(cb func(dealID abi.DealID, ds DealState) error) error { + var ds5 market5.DealState + return s.Array.ForEach(&ds5, func(idx int64) error { + return cb(abi.DealID(idx), fromV5DealState(ds5)) + }) +} + +func (s *dealStates5) decode(val *cbg.Deferred) (*DealState, error) { + var ds5 market5.DealState + if err := ds5.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return nil, err + } + ds := fromV5DealState(ds5) + return &ds, nil +} + +func (s *dealStates5) array() adt.Array { + return s.Array +} + +func fromV5DealState(v5 market5.DealState) DealState { + return (DealState)(v5) +} + +type dealProposals5 struct { + adt.Array +} + +func (s *dealProposals5) Get(dealID abi.DealID) (*DealProposal, bool, error) { + var proposal5 market5.DealProposal + found, err := s.Array.Get(uint64(dealID), &proposal5) + if err != nil { + return nil, false, err + } + if !found { + return nil, false, nil + } + proposal := fromV5DealProposal(proposal5) + return &proposal, true, nil +} + +func (s *dealProposals5) ForEach(cb func(dealID abi.DealID, dp DealProposal) error) error { + var dp5 market5.DealProposal + return s.Array.ForEach(&dp5, func(idx int64) error { + return cb(abi.DealID(idx), fromV5DealProposal(dp5)) + }) +} + +func (s *dealProposals5) decode(val *cbg.Deferred) (*DealProposal, error) { + var dp5 market5.DealProposal + if err := dp5.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return nil, err + } + dp := fromV5DealProposal(dp5) + return &dp, nil +} + +func (s *dealProposals5) array() adt.Array { + return s.Array +} + +func fromV5DealProposal(v5 market5.DealProposal) DealProposal { + return (DealProposal)(v5) +} diff --git a/chain/actors/builtin/miner/miner.go b/chain/actors/builtin/miner/miner.go index a426e063b..195e8f177 100644 --- a/chain/actors/builtin/miner/miner.go +++ b/chain/actors/builtin/miner/miner.go @@ -29,6 +29,8 @@ import ( builtin3 "github.com/filecoin-project/specs-actors/v3/actors/builtin" builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" + + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" ) func init() { @@ -49,9 +51,13 @@ func init() { return load4(store, root) }) + builtin.RegisterActorState(builtin5.StorageMinerActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { + return load5(store, root) + }) + } -var Methods = builtin4.MethodsMiner +var Methods = builtin5.MethodsMiner // Unchanged between v0, v2, v3, and v4 actors var WPoStProvingPeriod = miner0.WPoStProvingPeriod @@ -82,6 +88,9 @@ func Load(store adt.Store, act *types.Actor) (State, error) { case builtin4.StorageMinerActorCodeID: return load4(store, act.Head) + case builtin5.StorageMinerActorCodeID: + return load5(store, act.Head) + } return nil, xerrors.Errorf("unknown actor code %s", act.Code) } diff --git a/chain/actors/builtin/miner/v5.go b/chain/actors/builtin/miner/v5.go new file mode 100644 index 000000000..eea00b6e9 --- /dev/null +++ b/chain/actors/builtin/miner/v5.go @@ -0,0 +1,445 @@ +package miner + +import ( + "bytes" + "errors" + + "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" + + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" + + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" + adt5 "github.com/filecoin-project/specs-actors/v5/actors/util/adt" +) + +var _ State = (*state5)(nil) + +func load5(store adt.Store, root cid.Cid) (State, error) { + out := state5{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +type state5 struct { + miner5.State + store adt.Store +} + +type deadline5 struct { + miner5.Deadline + store adt.Store +} + +type partition5 struct { + miner5.Partition + store adt.Store +} + +func (s *state5) AvailableBalance(bal abi.TokenAmount) (available abi.TokenAmount, err error) { + defer func() { + if r := recover(); r != nil { + err = xerrors.Errorf("failed to get available balance: %w", r) + available = abi.NewTokenAmount(0) + } + }() + // this panics if the miner doesnt have enough funds to cover their locked pledge + available, err = s.GetAvailableBalance(bal) + return available, err +} + +func (s *state5) VestedFunds(epoch abi.ChainEpoch) (abi.TokenAmount, error) { + return s.CheckVestedFunds(s.store, epoch) +} + +func (s *state5) LockedFunds() (LockedFunds, error) { + return LockedFunds{ + VestingFunds: s.State.LockedFunds, + InitialPledgeRequirement: s.State.InitialPledge, + PreCommitDeposits: s.State.PreCommitDeposits, + }, nil +} + +func (s *state5) FeeDebt() (abi.TokenAmount, error) { + return s.State.FeeDebt, nil +} + +func (s *state5) InitialPledge() (abi.TokenAmount, error) { + return s.State.InitialPledge, nil +} + +func (s *state5) PreCommitDeposits() (abi.TokenAmount, error) { + return s.State.PreCommitDeposits, nil +} + +func (s *state5) GetSector(num abi.SectorNumber) (*SectorOnChainInfo, error) { + info, ok, err := s.State.GetSector(s.store, num) + if !ok || err != nil { + return nil, err + } + + ret := fromV5SectorOnChainInfo(*info) + return &ret, nil +} + +func (s *state5) FindSector(num abi.SectorNumber) (*SectorLocation, error) { + dlIdx, partIdx, err := s.State.FindSector(s.store, num) + if err != nil { + return nil, err + } + return &SectorLocation{ + Deadline: dlIdx, + Partition: partIdx, + }, nil +} + +func (s *state5) NumLiveSectors() (uint64, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return 0, err + } + var total uint64 + if err := dls.ForEach(s.store, func(dlIdx uint64, dl *miner5.Deadline) error { + total += dl.LiveSectors + return nil + }); err != nil { + return 0, err + } + return total, nil +} + +// GetSectorExpiration returns the effective expiration of the given sector. +// +// If the sector does not expire early, the Early expiration field is 0. +func (s *state5) GetSectorExpiration(num abi.SectorNumber) (*SectorExpiration, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return nil, err + } + // NOTE: this can be optimized significantly. + // 1. If the sector is non-faulty, it will either expire on-time (can be + // learned from the sector info), or in the next quantized expiration + // epoch (i.e., the first element in the partition's expiration queue. + // 2. If it's faulty, it will expire early within the first 14 entries + // of the expiration queue. + stopErr := errors.New("stop") + out := SectorExpiration{} + err = dls.ForEach(s.store, func(dlIdx uint64, dl *miner5.Deadline) error { + partitions, err := dl.PartitionsArray(s.store) + if err != nil { + return err + } + quant := s.State.QuantSpecForDeadline(dlIdx) + var part miner5.Partition + return partitions.ForEach(&part, func(partIdx int64) error { + if found, err := part.Sectors.IsSet(uint64(num)); err != nil { + return err + } else if !found { + return nil + } + if found, err := part.Terminated.IsSet(uint64(num)); err != nil { + return err + } else if found { + // already terminated + return stopErr + } + + q, err := miner5.LoadExpirationQueue(s.store, part.ExpirationsEpochs, quant, miner5.PartitionExpirationAmtBitwidth) + if err != nil { + return err + } + var exp miner5.ExpirationSet + return q.ForEach(&exp, func(epoch int64) error { + if early, err := exp.EarlySectors.IsSet(uint64(num)); err != nil { + return err + } else if early { + out.Early = abi.ChainEpoch(epoch) + return nil + } + if onTime, err := exp.OnTimeSectors.IsSet(uint64(num)); err != nil { + return err + } else if onTime { + out.OnTime = abi.ChainEpoch(epoch) + return stopErr + } + return nil + }) + }) + }) + if err == stopErr { + err = nil + } + if err != nil { + return nil, err + } + if out.Early == 0 && out.OnTime == 0 { + return nil, xerrors.Errorf("failed to find sector %d", num) + } + return &out, nil +} + +func (s *state5) GetPrecommittedSector(num abi.SectorNumber) (*SectorPreCommitOnChainInfo, error) { + info, ok, err := s.State.GetPrecommittedSector(s.store, num) + if !ok || err != nil { + return nil, err + } + + ret := fromV5SectorPreCommitOnChainInfo(*info) + + return &ret, nil +} + +func (s *state5) LoadSectors(snos *bitfield.BitField) ([]*SectorOnChainInfo, error) { + sectors, err := miner5.LoadSectors(s.store, s.State.Sectors) + if err != nil { + return nil, err + } + + // If no sector numbers are specified, load all. + if snos == nil { + infos := make([]*SectorOnChainInfo, 0, sectors.Length()) + var info5 miner5.SectorOnChainInfo + if err := sectors.ForEach(&info5, func(_ int64) error { + info := fromV5SectorOnChainInfo(info5) + infos = append(infos, &info) + return nil + }); err != nil { + return nil, err + } + return infos, nil + } + + // Otherwise, load selected. + infos5, err := sectors.Load(*snos) + if err != nil { + return nil, err + } + infos := make([]*SectorOnChainInfo, len(infos5)) + for i, info5 := range infos5 { + info := fromV5SectorOnChainInfo(*info5) + infos[i] = &info + } + return infos, nil +} + +func (s *state5) 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 *state5) LoadDeadline(idx uint64) (Deadline, error) { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return nil, err + } + dl, err := dls.LoadDeadline(s.store, idx) + if err != nil { + return nil, err + } + return &deadline5{*dl, s.store}, nil +} + +func (s *state5) ForEachDeadline(cb func(uint64, Deadline) error) error { + dls, err := s.State.LoadDeadlines(s.store) + if err != nil { + return err + } + return dls.ForEach(s.store, func(i uint64, dl *miner5.Deadline) error { + return cb(i, &deadline5{*dl, s.store}) + }) +} + +func (s *state5) NumDeadlines() (uint64, error) { + return miner5.WPoStPeriodDeadlines, nil +} + +func (s *state5) DeadlinesChanged(other State) (bool, error) { + other5, ok := other.(*state5) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + + return !s.State.Deadlines.Equals(other5.Deadlines), nil +} + +func (s *state5) MinerInfoChanged(other State) (bool, error) { + other0, ok := other.(*state5) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.Info.Equals(other0.State.Info), nil +} + +func (s *state5) Info() (MinerInfo, error) { + info, err := s.State.GetInfo(s.store) + if err != nil { + return MinerInfo{}, err + } + + var pid *peer.ID + if peerID, err := peer.IDFromBytes(info.PeerId); err == nil { + pid = &peerID + } + + mi := MinerInfo{ + Owner: info.Owner, + Worker: info.Worker, + ControlAddresses: info.ControlAddresses, + + NewWorker: address.Undef, + WorkerChangeEpoch: -1, + + PeerId: pid, + Multiaddrs: info.Multiaddrs, + WindowPoStProofType: info.WindowPoStProofType, + SectorSize: info.SectorSize, + WindowPoStPartitionSectors: info.WindowPoStPartitionSectors, + ConsensusFaultElapsed: info.ConsensusFaultElapsed, + } + + if info.PendingWorkerKey != nil { + mi.NewWorker = info.PendingWorkerKey.NewWorker + mi.WorkerChangeEpoch = info.PendingWorkerKey.EffectiveAt + } + + return mi, nil +} + +func (s *state5) DeadlineInfo(epoch abi.ChainEpoch) (*dline.Info, error) { + return s.State.RecordedDeadlineInfo(epoch), nil +} + +func (s *state5) DeadlineCronActive() (bool, error) { + return s.State.DeadlineCronActive, nil +} + +func (s *state5) sectors() (adt.Array, error) { + return adt5.AsArray(s.store, s.Sectors, miner5.SectorsAmtBitwidth) +} + +func (s *state5) decodeSectorOnChainInfo(val *cbg.Deferred) (SectorOnChainInfo, error) { + var si miner5.SectorOnChainInfo + err := si.UnmarshalCBOR(bytes.NewReader(val.Raw)) + if err != nil { + return SectorOnChainInfo{}, err + } + + return fromV5SectorOnChainInfo(si), nil +} + +func (s *state5) precommits() (adt.Map, error) { + return adt5.AsMap(s.store, s.PreCommittedSectors, builtin5.DefaultHamtBitwidth) +} + +func (s *state5) decodeSectorPreCommitOnChainInfo(val *cbg.Deferred) (SectorPreCommitOnChainInfo, error) { + var sp miner5.SectorPreCommitOnChainInfo + err := sp.UnmarshalCBOR(bytes.NewReader(val.Raw)) + if err != nil { + return SectorPreCommitOnChainInfo{}, err + } + + return fromV5SectorPreCommitOnChainInfo(sp), nil +} + +func (d *deadline5) LoadPartition(idx uint64) (Partition, error) { + p, err := d.Deadline.LoadPartition(d.store, idx) + if err != nil { + return nil, err + } + return &partition5{*p, d.store}, nil +} + +func (d *deadline5) ForEachPartition(cb func(uint64, Partition) error) error { + ps, err := d.Deadline.PartitionsArray(d.store) + if err != nil { + return err + } + var part miner5.Partition + return ps.ForEach(&part, func(i int64) error { + return cb(uint64(i), &partition5{part, d.store}) + }) +} + +func (d *deadline5) PartitionsChanged(other Deadline) (bool, error) { + other5, ok := other.(*deadline5) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + + return !d.Deadline.Partitions.Equals(other5.Deadline.Partitions), nil +} + +func (d *deadline5) PartitionsPoSted() (bitfield.BitField, error) { + return d.Deadline.PartitionsPoSted, nil +} + +func (d *deadline5) DisputableProofCount() (uint64, error) { + + ops, err := d.OptimisticProofsSnapshotArray(d.store) + if err != nil { + return 0, err + } + + return ops.Length(), nil + +} + +func (p *partition5) AllSectors() (bitfield.BitField, error) { + return p.Partition.Sectors, nil +} + +func (p *partition5) FaultySectors() (bitfield.BitField, error) { + return p.Partition.Faults, nil +} + +func (p *partition5) RecoveringSectors() (bitfield.BitField, error) { + return p.Partition.Recoveries, nil +} + +func fromV5SectorOnChainInfo(v5 miner5.SectorOnChainInfo) SectorOnChainInfo { + + return SectorOnChainInfo{ + SectorNumber: v5.SectorNumber, + SealProof: v5.SealProof, + SealedCID: v5.SealedCID, + DealIDs: v5.DealIDs, + Activation: v5.Activation, + Expiration: v5.Expiration, + DealWeight: v5.DealWeight, + VerifiedDealWeight: v5.VerifiedDealWeight, + InitialPledge: v5.InitialPledge, + ExpectedDayReward: v5.ExpectedDayReward, + ExpectedStoragePledge: v5.ExpectedStoragePledge, + } + +} + +func fromV5SectorPreCommitOnChainInfo(v5 miner5.SectorPreCommitOnChainInfo) SectorPreCommitOnChainInfo { + + return SectorPreCommitOnChainInfo{ + Info: (SectorPreCommitInfo)(v5.Info), + PreCommitDeposit: v5.PreCommitDeposit, + PreCommitEpoch: v5.PreCommitEpoch, + DealWeight: v5.DealWeight, + VerifiedDealWeight: v5.VerifiedDealWeight, + } + +} diff --git a/chain/actors/builtin/multisig/actor.go.template b/chain/actors/builtin/multisig/actor.go.template index 304c0610c..f741eba93 100644 --- a/chain/actors/builtin/multisig/actor.go.template +++ b/chain/actors/builtin/multisig/actor.go.template @@ -13,7 +13,7 @@ import ( "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{{import .latestVersion}}actors/builtin/multisig" {{range .versions}} builtin{{.}} "github.com/filecoin-project/specs-actors{{import .}}actors/builtin" {{end}} @@ -95,7 +95,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/message5.go b/chain/actors/builtin/multisig/message5.go new file mode 100644 index 000000000..9a8110f2c --- /dev/null +++ b/chain/actors/builtin/multisig/message5.go @@ -0,0 +1,71 @@ +package multisig + +import ( + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" + init5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/init" + multisig5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/multisig" + + "github.com/filecoin-project/lotus/chain/actors" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/types" +) + +type message5 struct{ message0 } + +func (m message5) Create( + signers []address.Address, threshold uint64, + unlockStart, unlockDuration abi.ChainEpoch, + initialAmount abi.TokenAmount, +) (*types.Message, error) { + + lenAddrs := uint64(len(signers)) + + if lenAddrs < threshold { + return nil, xerrors.Errorf("cannot require signing of more addresses than provided for multisig") + } + + if threshold == 0 { + threshold = lenAddrs + } + + if m.from == address.Undef { + return nil, xerrors.Errorf("must provide source address") + } + + // Set up constructor parameters for multisig + msigParams := &multisig5.ConstructorParams{ + Signers: signers, + NumApprovalsThreshold: threshold, + UnlockDuration: unlockDuration, + StartEpoch: unlockStart, + } + + enc, actErr := actors.SerializeParams(msigParams) + if actErr != nil { + return nil, actErr + } + + // new actors are created by invoking 'exec' on the init actor with the constructor params + execParams := &init5.ExecParams{ + CodeCID: builtin5.MultisigActorCodeID, + ConstructorParams: enc, + } + + enc, actErr = actors.SerializeParams(execParams) + if actErr != nil { + return nil, actErr + } + + return &types.Message{ + To: init_.Address, + From: m.from, + Method: builtin5.MethodsInit.Exec, + Params: enc, + Value: initialAmount, + }, nil +} diff --git a/chain/actors/builtin/multisig/multisig.go b/chain/actors/builtin/multisig/multisig.go index 79b1a57d7..eafd418e0 100644 --- a/chain/actors/builtin/multisig/multisig.go +++ b/chain/actors/builtin/multisig/multisig.go @@ -13,7 +13,7 @@ import ( "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" + msig5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/multisig" builtin0 "github.com/filecoin-project/specs-actors/actors/builtin" @@ -23,6 +23,8 @@ import ( builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" + builtin5 "github.com/filecoin-project/specs-actors/v5/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" @@ -46,6 +48,10 @@ func init() { builtin.RegisterActorState(builtin4.MultisigActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load4(store, root) }) + + builtin.RegisterActorState(builtin5.MultisigActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { + return load5(store, root) + }) } func Load(store adt.Store, act *types.Actor) (State, error) { @@ -63,6 +69,9 @@ func Load(store adt.Store, act *types.Actor) (State, error) { case builtin4.MultisigActorCodeID: return load4(store, act.Head) + case builtin5.MultisigActorCodeID: + return load5(store, act.Head) + } return nil, xerrors.Errorf("unknown actor code %s", act.Code) } @@ -86,7 +95,7 @@ type State interface { type Transaction = msig0.Transaction -var Methods = builtin4.MethodsMultisig +var Methods = builtin5.MethodsMultisig func Message(version actors.Version, from address.Address) MessageBuilder { switch version { @@ -102,6 +111,9 @@ func Message(version actors.Version, from address.Address) MessageBuilder { case actors.Version4: return message4{message0{from}} + + case actors.Version5: + return message5{message0{from}} default: panic(fmt.Sprintf("unsupported actors version: %d", version)) } @@ -125,12 +137,12 @@ type MessageBuilder interface { } // this type is the same between v0 and v2 -type ProposalHashData = msig4.ProposalHashData -type ProposeReturn = msig4.ProposeReturn -type ProposeParams = msig4.ProposeParams +type ProposalHashData = msig5.ProposalHashData +type ProposeReturn = msig5.ProposeReturn +type ProposeParams = msig5.ProposeParams func txnParams(id uint64, data *ProposalHashData) ([]byte, error) { - params := msig4.TxnIDParams{ID: msig4.TxnID(id)} + params := msig5.TxnIDParams{ID: msig5.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/v5.go b/chain/actors/builtin/multisig/v5.go new file mode 100644 index 000000000..e879783ba --- /dev/null +++ b/chain/actors/builtin/multisig/v5.go @@ -0,0 +1,96 @@ +package multisig + +import ( + "bytes" + "encoding/binary" + + adt5 "github.com/filecoin-project/specs-actors/v5/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" + + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" + + msig5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/multisig" +) + +var _ State = (*state5)(nil) + +func load5(store adt.Store, root cid.Cid) (State, error) { + out := state5{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +type state5 struct { + msig5.State + store adt.Store +} + +func (s *state5) LockedBalance(currEpoch abi.ChainEpoch) (abi.TokenAmount, error) { + return s.State.AmountLocked(currEpoch - s.State.StartEpoch), nil +} + +func (s *state5) StartEpoch() (abi.ChainEpoch, error) { + return s.State.StartEpoch, nil +} + +func (s *state5) UnlockDuration() (abi.ChainEpoch, error) { + return s.State.UnlockDuration, nil +} + +func (s *state5) InitialBalance() (abi.TokenAmount, error) { + return s.State.InitialBalance, nil +} + +func (s *state5) Threshold() (uint64, error) { + return s.State.NumApprovalsThreshold, nil +} + +func (s *state5) Signers() ([]address.Address, error) { + return s.State.Signers, nil +} + +func (s *state5) ForEachPendingTxn(cb func(id int64, txn Transaction) error) error { + arr, err := adt5.AsMap(s.store, s.State.PendingTxns, builtin5.DefaultHamtBitwidth) + if err != nil { + return err + } + var out msig5.Transaction + return arr.ForEach(&out, func(key string) error { + txid, n := binary.Varint([]byte(key)) + if n <= 0 { + return xerrors.Errorf("invalid pending transaction key: %v", key) + } + return cb(txid, (Transaction)(out)) //nolint:unconvert + }) +} + +func (s *state5) PendingTxnChanged(other State) (bool, error) { + other5, ok := other.(*state5) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.PendingTxns.Equals(other5.PendingTxns), nil +} + +func (s *state5) transactions() (adt.Map, error) { + return adt5.AsMap(s.store, s.PendingTxns, builtin5.DefaultHamtBitwidth) +} + +func (s *state5) decodeTransaction(val *cbg.Deferred) (Transaction, error) { + var tx msig5.Transaction + if err := tx.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return Transaction{}, err + } + return tx, nil +} diff --git a/chain/actors/builtin/paych/message5.go b/chain/actors/builtin/paych/message5.go new file mode 100644 index 000000000..37a2b6f04 --- /dev/null +++ b/chain/actors/builtin/paych/message5.go @@ -0,0 +1,74 @@ +package paych + +import ( + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" + init5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/init" + paych5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/paych" + + "github.com/filecoin-project/lotus/chain/actors" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/types" +) + +type message5 struct{ from address.Address } + +func (m message5) Create(to address.Address, initialAmount abi.TokenAmount) (*types.Message, error) { + params, aerr := actors.SerializeParams(&paych5.ConstructorParams{From: m.from, To: to}) + if aerr != nil { + return nil, aerr + } + enc, aerr := actors.SerializeParams(&init5.ExecParams{ + CodeCID: builtin5.PaymentChannelActorCodeID, + ConstructorParams: params, + }) + if aerr != nil { + return nil, aerr + } + + return &types.Message{ + To: init_.Address, + From: m.from, + Value: initialAmount, + Method: builtin5.MethodsInit.Exec, + Params: enc, + }, nil +} + +func (m message5) Update(paych address.Address, sv *SignedVoucher, secret []byte) (*types.Message, error) { + params, aerr := actors.SerializeParams(&paych5.UpdateChannelStateParams{ + Sv: *sv, + Secret: secret, + }) + if aerr != nil { + return nil, aerr + } + + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin5.MethodsPaych.UpdateChannelState, + Params: params, + }, nil +} + +func (m message5) Settle(paych address.Address) (*types.Message, error) { + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin5.MethodsPaych.Settle, + }, nil +} + +func (m message5) Collect(paych address.Address) (*types.Message, error) { + return &types.Message{ + To: paych, + From: m.from, + Value: abi.NewTokenAmount(0), + Method: builtin5.MethodsPaych.Collect, + }, nil +} diff --git a/chain/actors/builtin/paych/paych.go b/chain/actors/builtin/paych/paych.go index 30e4408d8..bafd6e94d 100644 --- a/chain/actors/builtin/paych/paych.go +++ b/chain/actors/builtin/paych/paych.go @@ -23,6 +23,8 @@ import ( builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" + builtin5 "github.com/filecoin-project/specs-actors/v5/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" @@ -46,6 +48,10 @@ func init() { builtin.RegisterActorState(builtin4.PaymentChannelActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load4(store, root) }) + + builtin.RegisterActorState(builtin5.PaymentChannelActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { + return load5(store, root) + }) } // Load returns an abstract copy of payment channel state, irregardless of actor version @@ -64,6 +70,9 @@ func Load(store adt.Store, act *types.Actor) (State, error) { case builtin4.PaymentChannelActorCodeID: return load4(store, act.Head) + case builtin5.PaymentChannelActorCodeID: + return load5(store, act.Head) + } return nil, xerrors.Errorf("unknown actor code %s", act.Code) } @@ -114,7 +123,7 @@ func DecodeSignedVoucher(s string) (*SignedVoucher, error) { return &sv, nil } -var Methods = builtin4.MethodsPaych +var Methods = builtin5.MethodsPaych func Message(version actors.Version, from address.Address) MessageBuilder { switch version { @@ -131,6 +140,9 @@ func Message(version actors.Version, from address.Address) MessageBuilder { case actors.Version4: return message4{from} + case actors.Version5: + return message5{from} + default: panic(fmt.Sprintf("unsupported actors version: %d", version)) } diff --git a/chain/actors/builtin/paych/v5.go b/chain/actors/builtin/paych/v5.go new file mode 100644 index 000000000..f878d858c --- /dev/null +++ b/chain/actors/builtin/paych/v5.go @@ -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" + + paych5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/paych" + adt5 "github.com/filecoin-project/specs-actors/v5/actors/util/adt" +) + +var _ State = (*state5)(nil) + +func load5(store adt.Store, root cid.Cid) (State, error) { + out := state5{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +type state5 struct { + paych5.State + store adt.Store + lsAmt *adt5.Array +} + +// Channel owner, who has funded the actor +func (s *state5) From() (address.Address, error) { + return s.State.From, nil +} + +// Recipient of payouts from channel +func (s *state5) To() (address.Address, error) { + return s.State.To, nil +} + +// Height at which the channel can be `Collected` +func (s *state5) SettlingAt() (abi.ChainEpoch, error) { + return s.State.SettlingAt, nil +} + +// Amount successfully redeemed through the payment channel, paid out on `Collect()` +func (s *state5) ToSend() (abi.TokenAmount, error) { + return s.State.ToSend, nil +} + +func (s *state5) getOrLoadLsAmt() (*adt5.Array, error) { + if s.lsAmt != nil { + return s.lsAmt, nil + } + + // Get the lane state from the chain + lsamt, err := adt5.AsArray(s.store, s.State.LaneStates, paych5.LaneStatesAmtBitwidth) + if err != nil { + return nil, err + } + + s.lsAmt = lsamt + return lsamt, nil +} + +// Get total number of lanes +func (s *state5) LaneCount() (uint64, error) { + lsamt, err := s.getOrLoadLsAmt() + if err != nil { + return 0, err + } + return lsamt.Length(), nil +} + +// Iterate lane states +func (s *state5) ForEachLaneState(cb func(idx uint64, dl LaneState) error) error { + // Get the lane state from the chain + lsamt, err := s.getOrLoadLsAmt() + if err != nil { + return err + } + + // Note: we use a map instead of an array to store laneStates because the + // client sets the lane ID (the index) and potentially they could use a + // very large index. + var ls paych5.LaneState + return lsamt.ForEach(&ls, func(i int64) error { + return cb(uint64(i), &laneState5{ls}) + }) +} + +type laneState5 struct { + paych5.LaneState +} + +func (ls *laneState5) Redeemed() (big.Int, error) { + return ls.LaneState.Redeemed, nil +} + +func (ls *laneState5) Nonce() (uint64, error) { + return ls.LaneState.Nonce, nil +} diff --git a/chain/actors/builtin/power/power.go b/chain/actors/builtin/power/power.go index bf530a21a..2cf59ef02 100644 --- a/chain/actors/builtin/power/power.go +++ b/chain/actors/builtin/power/power.go @@ -21,6 +21,8 @@ import ( builtin3 "github.com/filecoin-project/specs-actors/v3/actors/builtin" builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" + + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" ) func init() { @@ -40,11 +42,15 @@ func init() { builtin.RegisterActorState(builtin4.StoragePowerActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load4(store, root) }) + + builtin.RegisterActorState(builtin5.StoragePowerActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { + return load5(store, root) + }) } var ( - Address = builtin4.StoragePowerActorAddr - Methods = builtin4.MethodsPower + Address = builtin5.StoragePowerActorAddr + Methods = builtin5.MethodsPower ) func Load(store adt.Store, act *types.Actor) (State, error) { @@ -62,6 +68,9 @@ func Load(store adt.Store, act *types.Actor) (State, error) { case builtin4.StoragePowerActorCodeID: return load4(store, act.Head) + case builtin5.StoragePowerActorCodeID: + return load5(store, act.Head) + } return nil, xerrors.Errorf("unknown actor code %s", act.Code) } diff --git a/chain/actors/builtin/power/v5.go b/chain/actors/builtin/power/v5.go new file mode 100644 index 000000000..33a5d3b62 --- /dev/null +++ b/chain/actors/builtin/power/v5.go @@ -0,0 +1,150 @@ +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" + + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" + + power5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/power" + adt5 "github.com/filecoin-project/specs-actors/v5/actors/util/adt" +) + +var _ State = (*state5)(nil) + +func load5(store adt.Store, root cid.Cid) (State, error) { + out := state5{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +type state5 struct { + power5.State + store adt.Store +} + +func (s *state5) TotalLocked() (abi.TokenAmount, error) { + return s.TotalPledgeCollateral, nil +} + +func (s *state5) TotalPower() (Claim, error) { + return Claim{ + RawBytePower: s.TotalRawBytePower, + QualityAdjPower: s.TotalQualityAdjPower, + }, nil +} + +// Committed power to the network. Includes miners below the minimum threshold. +func (s *state5) TotalCommitted() (Claim, error) { + return Claim{ + RawBytePower: s.TotalBytesCommitted, + QualityAdjPower: s.TotalQABytesCommitted, + }, nil +} + +func (s *state5) MinerPower(addr address.Address) (Claim, bool, error) { + claims, err := s.claims() + if err != nil { + return Claim{}, false, err + } + var claim power5.Claim + ok, err := claims.Get(abi.AddrKey(addr), &claim) + if err != nil { + return Claim{}, false, err + } + return Claim{ + RawBytePower: claim.RawBytePower, + QualityAdjPower: claim.QualityAdjPower, + }, ok, nil +} + +func (s *state5) MinerNominalPowerMeetsConsensusMinimum(a address.Address) (bool, error) { + return s.State.MinerNominalPowerMeetsConsensusMinimum(s.store, a) +} + +func (s *state5) TotalPowerSmoothed() (builtin.FilterEstimate, error) { + return builtin.FromV5FilterEstimate(s.State.ThisEpochQAPowerSmoothed), nil +} + +func (s *state5) MinerCounts() (uint64, uint64, error) { + return uint64(s.State.MinerAboveMinPowerCount), uint64(s.State.MinerCount), nil +} + +func (s *state5) ListAllMiners() ([]address.Address, error) { + claims, err := s.claims() + if err != nil { + return nil, err + } + + var miners []address.Address + err = claims.ForEach(nil, func(k string) error { + a, err := address.NewFromBytes([]byte(k)) + if err != nil { + return err + } + miners = append(miners, a) + return nil + }) + if err != nil { + return nil, err + } + + return miners, nil +} + +func (s *state5) ForEachClaim(cb func(miner address.Address, claim Claim) error) error { + claims, err := s.claims() + if err != nil { + return err + } + + var claim power5.Claim + return claims.ForEach(&claim, func(k string) error { + a, err := address.NewFromBytes([]byte(k)) + if err != nil { + return err + } + return cb(a, Claim{ + RawBytePower: claim.RawBytePower, + QualityAdjPower: claim.QualityAdjPower, + }) + }) +} + +func (s *state5) ClaimsChanged(other State) (bool, error) { + other5, ok := other.(*state5) + if !ok { + // treat an upgrade as a change, always + return true, nil + } + return !s.State.Claims.Equals(other5.State.Claims), nil +} + +func (s *state5) claims() (adt.Map, error) { + return adt5.AsMap(s.store, s.Claims, builtin5.DefaultHamtBitwidth) +} + +func (s *state5) decodeClaim(val *cbg.Deferred) (Claim, error) { + var ci power5.Claim + if err := ci.UnmarshalCBOR(bytes.NewReader(val.Raw)); err != nil { + return Claim{}, err + } + return fromV5Claim(ci), nil +} + +func fromV5Claim(v5 power5.Claim) Claim { + return Claim{ + RawBytePower: v5.RawBytePower, + QualityAdjPower: v5.QualityAdjPower, + } +} diff --git a/chain/actors/builtin/reward/reward.go b/chain/actors/builtin/reward/reward.go index 1037cf741..5f6131334 100644 --- a/chain/actors/builtin/reward/reward.go +++ b/chain/actors/builtin/reward/reward.go @@ -16,6 +16,8 @@ import ( builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" + "github.com/filecoin-project/lotus/chain/actors/adt" "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/types" @@ -38,11 +40,15 @@ func init() { builtin.RegisterActorState(builtin4.RewardActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { return load4(store, root) }) + + builtin.RegisterActorState(builtin5.RewardActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { + return load5(store, root) + }) } var ( - Address = builtin4.RewardActorAddr - Methods = builtin4.MethodsReward + Address = builtin5.RewardActorAddr + Methods = builtin5.MethodsReward ) func Load(store adt.Store, act *types.Actor) (State, error) { @@ -60,6 +66,9 @@ func Load(store adt.Store, act *types.Actor) (State, error) { case builtin4.RewardActorCodeID: return load4(store, act.Head) + case builtin5.RewardActorCodeID: + return load5(store, act.Head) + } return nil, xerrors.Errorf("unknown actor code %s", act.Code) } diff --git a/chain/actors/builtin/reward/v5.go b/chain/actors/builtin/reward/v5.go new file mode 100644 index 000000000..0dd75de73 --- /dev/null +++ b/chain/actors/builtin/reward/v5.go @@ -0,0 +1,88 @@ +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" + + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" + reward5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/reward" + smoothing5 "github.com/filecoin-project/specs-actors/v5/actors/util/smoothing" +) + +var _ State = (*state5)(nil) + +func load5(store adt.Store, root cid.Cid) (State, error) { + out := state5{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +type state5 struct { + reward5.State + store adt.Store +} + +func (s *state5) ThisEpochReward() (abi.TokenAmount, error) { + return s.State.ThisEpochReward, nil +} + +func (s *state5) ThisEpochRewardSmoothed() (builtin.FilterEstimate, error) { + + return builtin.FilterEstimate{ + PositionEstimate: s.State.ThisEpochRewardSmoothed.PositionEstimate, + VelocityEstimate: s.State.ThisEpochRewardSmoothed.VelocityEstimate, + }, nil + +} + +func (s *state5) ThisEpochBaselinePower() (abi.StoragePower, error) { + return s.State.ThisEpochBaselinePower, nil +} + +func (s *state5) TotalStoragePowerReward() (abi.TokenAmount, error) { + return s.State.TotalStoragePowerReward, nil +} + +func (s *state5) EffectiveBaselinePower() (abi.StoragePower, error) { + return s.State.EffectiveBaselinePower, nil +} + +func (s *state5) EffectiveNetworkTime() (abi.ChainEpoch, error) { + return s.State.EffectiveNetworkTime, nil +} + +func (s *state5) CumsumBaseline() (reward5.Spacetime, error) { + return s.State.CumsumBaseline, nil +} + +func (s *state5) CumsumRealized() (reward5.Spacetime, error) { + return s.State.CumsumRealized, nil +} + +func (s *state5) InitialPledgeForPower(qaPower abi.StoragePower, networkTotalPledge abi.TokenAmount, networkQAPower *builtin.FilterEstimate, circSupply abi.TokenAmount) (abi.TokenAmount, error) { + return miner5.InitialPledgeForPower( + qaPower, + s.State.ThisEpochBaselinePower, + s.State.ThisEpochRewardSmoothed, + smoothing5.FilterEstimate{ + PositionEstimate: networkQAPower.PositionEstimate, + VelocityEstimate: networkQAPower.VelocityEstimate, + }, + circSupply, + ), nil +} + +func (s *state5) PreCommitDepositForPower(networkQAPower builtin.FilterEstimate, sectorWeight abi.StoragePower) (abi.TokenAmount, error) { + return miner5.PreCommitDepositForPower(s.State.ThisEpochRewardSmoothed, + smoothing5.FilterEstimate{ + PositionEstimate: networkQAPower.PositionEstimate, + VelocityEstimate: networkQAPower.VelocityEstimate, + }, + sectorWeight), nil +} diff --git a/chain/actors/builtin/verifreg/v5.go b/chain/actors/builtin/verifreg/v5.go new file mode 100644 index 000000000..367d38498 --- /dev/null +++ b/chain/actors/builtin/verifreg/v5.go @@ -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" + + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" + verifreg5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/verifreg" + adt5 "github.com/filecoin-project/specs-actors/v5/actors/util/adt" +) + +var _ State = (*state5)(nil) + +func load5(store adt.Store, root cid.Cid) (State, error) { + out := state5{store: store} + err := store.Get(store.Context(), root, &out) + if err != nil { + return nil, err + } + return &out, nil +} + +type state5 struct { + verifreg5.State + store adt.Store +} + +func (s *state5) RootKey() (address.Address, error) { + return s.State.RootKey, nil +} + +func (s *state5) VerifiedClientDataCap(addr address.Address) (bool, abi.StoragePower, error) { + return getDataCap(s.store, actors.Version5, s.verifiedClients, addr) +} + +func (s *state5) VerifierDataCap(addr address.Address) (bool, abi.StoragePower, error) { + return getDataCap(s.store, actors.Version5, s.verifiers, addr) +} + +func (s *state5) ForEachVerifier(cb func(addr address.Address, dcap abi.StoragePower) error) error { + return forEachCap(s.store, actors.Version5, s.verifiers, cb) +} + +func (s *state5) ForEachClient(cb func(addr address.Address, dcap abi.StoragePower) error) error { + return forEachCap(s.store, actors.Version5, s.verifiedClients, cb) +} + +func (s *state5) verifiedClients() (adt.Map, error) { + return adt5.AsMap(s.store, s.VerifiedClients, builtin5.DefaultHamtBitwidth) +} + +func (s *state5) verifiers() (adt.Map, error) { + return adt5.AsMap(s.store, s.Verifiers, builtin5.DefaultHamtBitwidth) +} diff --git a/chain/actors/builtin/verifreg/verifreg.go b/chain/actors/builtin/verifreg/verifreg.go index 32f50a4ae..baca66177 100644 --- a/chain/actors/builtin/verifreg/verifreg.go +++ b/chain/actors/builtin/verifreg/verifreg.go @@ -17,6 +17,8 @@ import ( builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" + "github.com/filecoin-project/lotus/chain/actors/adt" "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/types" @@ -40,11 +42,15 @@ func init() { return load4(store, root) }) + builtin.RegisterActorState(builtin5.VerifiedRegistryActorCodeID, func(store adt.Store, root cid.Cid) (cbor.Marshaler, error) { + return load5(store, root) + }) + } var ( - Address = builtin4.VerifiedRegistryActorAddr - Methods = builtin4.MethodsVerifiedRegistry + Address = builtin5.VerifiedRegistryActorAddr + Methods = builtin5.MethodsVerifiedRegistry ) func Load(store adt.Store, act *types.Actor) (State, error) { @@ -62,6 +68,9 @@ func Load(store adt.Store, act *types.Actor) (State, error) { case builtin4.VerifiedRegistryActorCodeID: return load4(store, act.Head) + case builtin5.VerifiedRegistryActorCodeID: + return load5(store, act.Head) + } return nil, xerrors.Errorf("unknown actor code %s", act.Code) } diff --git a/chain/actors/policy/policy.go b/chain/actors/policy/policy.go index 164f19a76..191ffb5f5 100644 --- a/chain/actors/policy/policy.go +++ b/chain/actors/policy/policy.go @@ -27,14 +27,19 @@ import ( miner4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/miner" verifreg4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/verifreg" - paych4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/paych" + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" + market5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/market" + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" + verifreg5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/verifreg" + + paych5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/paych" ) const ( - ChainFinality = miner4.ChainFinality + ChainFinality = miner5.ChainFinality SealRandomnessLookback = ChainFinality - PaychSettleDelay = paych4.SettleDelay - MaxPreCommitRandomnessLookback = builtin4.EpochsInDay + SealRandomnessLookback + PaychSettleDelay = paych5.SettleDelay + MaxPreCommitRandomnessLookback = builtin5.EpochsInDay + SealRandomnessLookback ) // SetSupportedProofTypes sets supported proof types, across all actor versions. @@ -55,6 +60,10 @@ func SetSupportedProofTypes(types ...abi.RegisteredSealProof) { miner4.PreCommitSealProofTypesV7 = make(map[abi.RegisteredSealProof]struct{}, len(types)*2) miner4.PreCommitSealProofTypesV8 = make(map[abi.RegisteredSealProof]struct{}, len(types)) + miner5.PreCommitSealProofTypesV0 = make(map[abi.RegisteredSealProof]struct{}, len(types)) + miner5.PreCommitSealProofTypesV7 = make(map[abi.RegisteredSealProof]struct{}, len(types)*2) + miner5.PreCommitSealProofTypesV8 = make(map[abi.RegisteredSealProof]struct{}, len(types)) + AddSupportedProofTypes(types...) } @@ -84,6 +93,11 @@ func AddSupportedProofTypes(types ...abi.RegisteredSealProof) { miner4.PreCommitSealProofTypesV7[t+abi.RegisteredSealProof_StackedDrg2KiBV1_1] = struct{}{} miner4.PreCommitSealProofTypesV8[t+abi.RegisteredSealProof_StackedDrg2KiBV1_1] = struct{}{} + miner5.PreCommitSealProofTypesV0[t] = struct{}{} + miner5.PreCommitSealProofTypesV7[t] = struct{}{} + miner5.PreCommitSealProofTypesV7[t+abi.RegisteredSealProof_StackedDrg2KiBV1_1] = struct{}{} + miner5.PreCommitSealProofTypesV8[t+abi.RegisteredSealProof_StackedDrg2KiBV1_1] = struct{}{} + } } @@ -100,11 +114,13 @@ func SetPreCommitChallengeDelay(delay abi.ChainEpoch) { miner4.PreCommitChallengeDelay = delay + miner5.PreCommitChallengeDelay = delay + } // TODO: this function shouldn't really exist. Instead, the API should expose the precommit delay. func GetPreCommitChallengeDelay() abi.ChainEpoch { - return miner4.PreCommitChallengeDelay + return miner5.PreCommitChallengeDelay } // SetConsensusMinerMinPower sets the minimum power of an individual miner must @@ -126,6 +142,10 @@ func SetConsensusMinerMinPower(p abi.StoragePower) { policy.ConsensusMinerMinPower = p } + for _, policy := range builtin5.PoStProofPolicies { + policy.ConsensusMinerMinPower = p + } + } // SetMinVerifiedDealSize sets the minimum size of a verified deal. This should @@ -140,6 +160,8 @@ func SetMinVerifiedDealSize(size abi.StoragePower) { verifreg4.MinVerifiedDealSize = size + verifreg5.MinVerifiedDealSize = size + } func GetMaxProveCommitDuration(ver actors.Version, t abi.RegisteredSealProof) abi.ChainEpoch { @@ -161,6 +183,10 @@ func GetMaxProveCommitDuration(ver actors.Version, t abi.RegisteredSealProof) ab return miner4.MaxProveCommitDuration[t] + case actors.Version5: + + return miner5.MaxProveCommitDuration[t] + default: panic("unsupported actors version") } @@ -189,13 +215,17 @@ func DealProviderCollateralBounds( return market4.DealProviderCollateralBounds(size, verified, rawBytePower, qaPower, baselinePower, circulatingFil) + case actors.Version5: + + return market5.DealProviderCollateralBounds(size, verified, rawBytePower, qaPower, baselinePower, circulatingFil) + default: panic("unsupported actors version") } } func DealDurationBounds(pieceSize abi.PaddedPieceSize) (min, max abi.ChainEpoch) { - return market4.DealDurationBounds(pieceSize) + return market5.DealDurationBounds(pieceSize) } // Sets the challenge window and scales the proving period to match (such that @@ -222,6 +252,13 @@ func SetWPoStChallengeWindow(period abi.ChainEpoch) { // scale it if we're scaling the challenge period. miner4.WPoStDisputeWindow = period * 30 + miner5.WPoStChallengeWindow = period + miner5.WPoStProvingPeriod = period * abi.ChainEpoch(miner5.WPoStPeriodDeadlines) + + // by default, this is 2x finality which is 30 periods. + // scale it if we're scaling the challenge period. + miner5.WPoStDisputeWindow = period * 30 + } func GetWinningPoStSectorSetLookback(nwVer network.Version) abi.ChainEpoch { @@ -234,22 +271,22 @@ func GetWinningPoStSectorSetLookback(nwVer network.Version) abi.ChainEpoch { } func GetMaxSectorExpirationExtension() abi.ChainEpoch { - return miner4.MaxSectorExpirationExtension + return miner5.MaxSectorExpirationExtension } // TODO: we'll probably need to abstract over this better in the future. func GetMaxPoStPartitions(p abi.RegisteredPoStProof) (int, error) { - sectorsPerPart, err := builtin4.PoStProofWindowPoStPartitionSectors(p) + sectorsPerPart, err := builtin5.PoStProofWindowPoStPartitionSectors(p) if err != nil { return 0, err } - return int(miner4.AddressedSectorsMax / sectorsPerPart), nil + return int(miner5.AddressedSectorsMax / sectorsPerPart), nil } func GetDefaultSectorSize() abi.SectorSize { // supported sector sizes are the same across versions. - szs := make([]abi.SectorSize, 0, len(miner4.PreCommitSealProofTypesV8)) - for spt := range miner4.PreCommitSealProofTypesV8 { + szs := make([]abi.SectorSize, 0, len(miner5.PreCommitSealProofTypesV8)) + for spt := range miner5.PreCommitSealProofTypesV8 { ss, err := spt.SectorSize() if err != nil { panic(err) @@ -267,10 +304,10 @@ func GetDefaultSectorSize() abi.SectorSize { func GetSectorMaxLifetime(proof abi.RegisteredSealProof, nwVer network.Version) abi.ChainEpoch { if nwVer <= network.Version10 { - return builtin4.SealProofPoliciesV0[proof].SectorMaxLifetime + return builtin5.SealProofPoliciesV0[proof].SectorMaxLifetime } - return builtin4.SealProofPoliciesV11[proof].SectorMaxLifetime + return builtin5.SealProofPoliciesV11[proof].SectorMaxLifetime } func GetAddressedSectorsMax(nwVer network.Version) int { @@ -288,6 +325,9 @@ func GetAddressedSectorsMax(nwVer network.Version) int { case actors.Version4: return miner4.AddressedSectorsMax + case actors.Version5: + return miner5.AddressedSectorsMax + default: panic("unsupported network version") } @@ -313,6 +353,10 @@ func GetDeclarationsMax(nwVer network.Version) int { return miner4.DeclarationsMax + case actors.Version5: + + return miner5.DeclarationsMax + default: panic("unsupported network version") } diff --git a/chain/actors/version.go b/chain/actors/version.go index bd7b708bd..e6ca2e9bd 100644 --- a/chain/actors/version.go +++ b/chain/actors/version.go @@ -13,6 +13,7 @@ const ( Version2 Version = 2 Version3 Version = 3 Version4 Version = 4 + Version5 Version = 5 ) // Converts a network version into an actors adt version. @@ -26,6 +27,8 @@ func VersionForNetwork(version network.Version) Version { return Version3 case network.Version12: return Version4 + case network.Version13: + return Version5 default: panic(fmt.Sprintf("unsupported network version %d", version)) } diff --git a/chain/gen/genesis/util.go b/chain/gen/genesis/util.go index 54cc30cc1..1ebc58121 100644 --- a/chain/gen/genesis/util.go +++ b/chain/gen/genesis/util.go @@ -63,7 +63,7 @@ var GenesisNetworkVersion = func() network.Version { if build.UpgradeIgnitionHeight >= 0 { return network.Version2 } - if build.UpgradeActorsV2Height >= 0 { + if build.UpgradeAssemblyHeight >= 0 { return network.Version3 } if build.UpgradeLiftoffHeight >= 0 { diff --git a/chain/messagepool/selection.go b/chain/messagepool/selection.go index 0a836804f..6c9d506ef 100644 --- a/chain/messagepool/selection.go +++ b/chain/messagepool/selection.go @@ -701,7 +701,7 @@ func (*MessagePool) getGasPerf(gasReward *big.Int, gasLimit int64) float64 { } func isMessageMute(m *types.Message, ts *types.TipSet) bool { - if api.RunningNodeType != api.NodeFull || ts.Height() > build.UpgradeActorsV4Height { + if api.RunningNodeType != api.NodeFull || ts.Height() > build.UpgradeTurboHeight { return false } diff --git a/chain/stmgr/forks.go b/chain/stmgr/forks.go index a7b56f679..de5e91388 100644 --- a/chain/stmgr/forks.go +++ b/chain/stmgr/forks.go @@ -9,6 +9,8 @@ import ( "sync" "time" + "github.com/filecoin-project/specs-actors/v5/actors/migration/nv13" + "github.com/filecoin-project/go-state-types/rt" "github.com/filecoin-project/go-address" @@ -143,7 +145,7 @@ func DefaultUpgradeSchedule() UpgradeSchedule { Network: network.Version3, Migration: UpgradeRefuel, }, { - Height: build.UpgradeActorsV2Height, + Height: build.UpgradeAssemblyHeight, Network: network.Version4, Expensive: true, Migration: UpgradeActorsV2, @@ -172,7 +174,7 @@ func DefaultUpgradeSchedule() UpgradeSchedule { Network: network.Version9, Migration: nil, }, { - Height: build.UpgradeActorsV3Height, + Height: build.UpgradeTrustHeight, Network: network.Version10, Migration: UpgradeActorsV3, PreMigrations: []PreMigration{{ @@ -192,7 +194,7 @@ func DefaultUpgradeSchedule() UpgradeSchedule { Network: network.Version11, Migration: nil, }, { - Height: build.UpgradeActorsV4Height, + Height: build.UpgradeTurboHeight, Network: network.Version12, Migration: UpgradeActorsV4, PreMigrations: []PreMigration{{ @@ -207,7 +209,22 @@ func DefaultUpgradeSchedule() UpgradeSchedule { StopWithin: 5, }}, Expensive: true, - }} + }, { + Height: build.UpgradeHyperdriveHeight, + Network: network.Version13, + Migration: UpgradeActorsV5, + PreMigrations: []PreMigration{{ + PreMigration: PreUpgradeActorsV5, + StartWithin: 120, + DontStartWithin: 60, + StopWithin: 35, + }, { + PreMigration: PreUpgradeActorsV5, + StartWithin: 30, + DontStartWithin: 15, + StopWithin: 5, + }}, + Expensive: true}} for _, u := range updates { if u.Height < 0 { @@ -1053,7 +1070,7 @@ func upgradeActorsV3Common( // Perform the migration newHamtRoot, err := nv10.MigrateStateTree(ctx, store, stateRoot.Actors, epoch, config, migrationLogger{}, cache) if err != nil { - return cid.Undef, xerrors.Errorf("upgrading to actors v2: %w", err) + return cid.Undef, xerrors.Errorf("upgrading to actors v3: %w", err) } // Persist the result. @@ -1139,7 +1156,93 @@ func upgradeActorsV4Common( // Perform the migration newHamtRoot, err := nv12.MigrateStateTree(ctx, store, stateRoot.Actors, epoch, config, migrationLogger{}, cache) if err != nil { - return cid.Undef, xerrors.Errorf("upgrading to actors v2: %w", err) + return cid.Undef, xerrors.Errorf("upgrading to actors v4: %w", err) + } + + // Persist the result. + newRoot, err := store.Put(ctx, &types.StateRoot{ + Version: types.StateTreeVersion3, + Actors: newHamtRoot, + Info: stateRoot.Info, + }) + if err != nil { + return cid.Undef, xerrors.Errorf("failed to persist new state root: %w", err) + } + + // Persist the new tree. + + { + from := buf + to := buf.Read() + + if err := vm.Copy(ctx, from, to, newRoot); err != nil { + return cid.Undef, xerrors.Errorf("copying migrated tree: %w", err) + } + } + + return newRoot, nil +} + +func UpgradeActorsV5(ctx context.Context, sm *StateManager, cache MigrationCache, cb ExecCallback, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) (cid.Cid, error) { + // Use all the CPUs except 3. + workerCount := runtime.NumCPU() - 3 + if workerCount <= 0 { + workerCount = 1 + } + + config := nv13.Config{ + MaxWorkers: uint(workerCount), + JobQueueSize: 1000, + ResultQueueSize: 100, + ProgressLogPeriod: 10 * time.Second, + } + + newRoot, err := upgradeActorsV5Common(ctx, sm, cache, root, epoch, ts, config) + if err != nil { + return cid.Undef, xerrors.Errorf("migrating actors v5 state: %w", err) + } + + return newRoot, nil +} + +func PreUpgradeActorsV5(ctx context.Context, sm *StateManager, cache MigrationCache, root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet) error { + // Use half the CPUs for pre-migration, but leave at least 3. + workerCount := runtime.NumCPU() + if workerCount <= 4 { + workerCount = 1 + } else { + workerCount /= 2 + } + config := nv13.Config{MaxWorkers: uint(workerCount)} + _, err := upgradeActorsV5Common(ctx, sm, cache, root, epoch, ts, config) + return err +} + +func upgradeActorsV5Common( + ctx context.Context, sm *StateManager, cache MigrationCache, + root cid.Cid, epoch abi.ChainEpoch, ts *types.TipSet, + config nv13.Config, +) (cid.Cid, error) { + buf := blockstore.NewTieredBstore(sm.cs.StateBlockstore(), blockstore.NewMemorySync()) + store := store.ActorStore(ctx, buf) + + // Load the state root. + var stateRoot types.StateRoot + if err := store.Get(ctx, root, &stateRoot); err != nil { + return cid.Undef, xerrors.Errorf("failed to decode state root: %w", err) + } + + if stateRoot.Version != types.StateTreeVersion3 { + return cid.Undef, xerrors.Errorf( + "expected state root version 3 for actors v5 upgrade, got %d", + stateRoot.Version, + ) + } + + // Perform the migration + newHamtRoot, err := nv13.MigrateStateTree(ctx, store, stateRoot.Actors, epoch, config, migrationLogger{}, cache) + if err != nil { + return cid.Undef, xerrors.Errorf("upgrading to actors v5: %w", err) } // Persist the result. diff --git a/chain/stmgr/stmgr.go b/chain/stmgr/stmgr.go index ffbe08474..93832f185 100644 --- a/chain/stmgr/stmgr.go +++ b/chain/stmgr/stmgr.go @@ -1139,8 +1139,8 @@ func (sm *StateManager) GetFilVested(ctx context.Context, height abi.ChainEpoch, } } - // After UpgradeActorsV2Height these funds are accounted for in GetFilReserveDisbursed - if height <= build.UpgradeActorsV2Height { + // After UpgradeAssemblyHeight these funds are accounted for in GetFilReserveDisbursed + if height <= build.UpgradeAssemblyHeight { // continue to use preIgnitionGenInfos, nothing changed at the Ignition epoch vf = big.Add(vf, sm.genesisPledge) // continue to use preIgnitionGenInfos, nothing changed at the Ignition epoch @@ -1263,7 +1263,7 @@ func (sm *StateManager) GetVMCirculatingSupplyDetailed(ctx context.Context, heig } filReserveDisbursed := big.Zero() - if height > build.UpgradeActorsV2Height { + if height > build.UpgradeAssemblyHeight { filReserveDisbursed, err = GetFilReserveDisbursed(ctx, st) if err != nil { return api.CirculatingSupply{}, xerrors.Errorf("failed to calculate filReserveDisbursed: %w", err) diff --git a/chain/stmgr/utils.go b/chain/stmgr/utils.go index be4b9cd4f..ce6d0bf2b 100644 --- a/chain/stmgr/utils.go +++ b/chain/stmgr/utils.go @@ -9,6 +9,8 @@ import ( "runtime" "strings" + exported5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/exported" + "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/network" @@ -550,6 +552,7 @@ func init() { actors = append(actors, exported2.BuiltinActors()...) actors = append(actors, exported3.BuiltinActors()...) actors = append(actors, exported4.BuiltinActors()...) + actors = append(actors, exported5.BuiltinActors()...) for _, actor := range actors { exports := actor.Exports() diff --git a/chain/vm/invoker.go b/chain/vm/invoker.go index 8e0e6edd6..126b57090 100644 --- a/chain/vm/invoker.go +++ b/chain/vm/invoker.go @@ -19,6 +19,7 @@ import ( vmr "github.com/filecoin-project/specs-actors/v2/actors/runtime" exported3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/exported" exported4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/exported" + exported5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/exported" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/exitcode" @@ -66,6 +67,7 @@ func NewActorRegistry() *ActorRegistry { inv.Register(ActorsVersionPredicate(actors.Version2), exported2.BuiltinActors()...) inv.Register(ActorsVersionPredicate(actors.Version3), exported3.BuiltinActors()...) inv.Register(ActorsVersionPredicate(actors.Version4), exported4.BuiltinActors()...) + inv.Register(ActorsVersionPredicate(actors.Version5), exported5.BuiltinActors()...) return inv } diff --git a/chain/vm/mkactor.go b/chain/vm/mkactor.go index 11de7362b..669c1450f 100644 --- a/chain/vm/mkactor.go +++ b/chain/vm/mkactor.go @@ -18,6 +18,7 @@ import ( builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" builtin3 "github.com/filecoin-project/specs-actors/v3/actors/builtin" builtin4 "github.com/filecoin-project/specs-actors/v4/actors/builtin" + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" "github.com/filecoin-project/go-address" "github.com/filecoin-project/lotus/chain/actors/aerrors" @@ -105,6 +106,8 @@ func newAccountActor(ver actors.Version) *types.Actor { code = builtin3.AccountActorCodeID case actors.Version4: code = builtin4.AccountActorCodeID + case actors.Version5: + code = builtin5.AccountActorCodeID default: panic("unsupported actors version") } diff --git a/cmd/tvx/codenames.go b/cmd/tvx/codenames.go index b9f590914..f8da07e8d 100644 --- a/cmd/tvx/codenames.go +++ b/cmd/tvx/codenames.go @@ -20,7 +20,7 @@ var ProtocolCodenames = []struct { {build.UpgradeSmokeHeight + 1, "smoke"}, {build.UpgradeIgnitionHeight + 1, "ignition"}, {build.UpgradeRefuelHeight + 1, "refuel"}, - {build.UpgradeActorsV2Height + 1, "actorsv2"}, + {build.UpgradeAssemblyHeight + 1, "actorsv2"}, {build.UpgradeTapeHeight + 1, "tape"}, {build.UpgradeLiftoffHeight + 1, "liftoff"}, {build.UpgradeKumquatHeight + 1, "postliftoff"}, diff --git a/cmd/tvx/codenames_test.go b/cmd/tvx/codenames_test.go index bef2e982f..e7136d6cc 100644 --- a/cmd/tvx/codenames_test.go +++ b/cmd/tvx/codenames_test.go @@ -18,7 +18,7 @@ func TestProtocolCodenames(t *testing.T) { t.Fatal("expected breeze codename") } - if height := build.UpgradeActorsV2Height + 1; GetProtocolCodename(abi.ChainEpoch(height)) != "actorsv2" { + if height := build.UpgradeAssemblyHeight + 1; GetProtocolCodename(abi.ChainEpoch(height)) != "actorsv2" { t.Fatal("expected actorsv2 codename") } diff --git a/documentation/en/api-methods.md b/documentation/en/api-methods.md index 493fd7d2b..743432cb0 100644 --- a/documentation/en/api-methods.md +++ b/documentation/en/api-methods.md @@ -4517,7 +4517,7 @@ Inputs: ] ``` -Response: `11` +Response: `13` ### StateReadState StateReadState returns the indicated actor's state. diff --git a/go.mod b/go.mod index 86e6da40d..af2ee9c3e 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,7 @@ require ( github.com/filecoin-project/go-multistore v0.0.3 github.com/filecoin-project/go-padreader v0.0.0-20200903213702-ed5fae088b20 github.com/filecoin-project/go-paramfetch v0.0.2-0.20200701152213-3e0f0afdc261 - github.com/filecoin-project/go-state-types v0.1.0 + github.com/filecoin-project/go-state-types v0.1.1-0.20210506134452-99b279731c48 github.com/filecoin-project/go-statemachine v0.0.0-20200925024713-05bd7c71fbfe github.com/filecoin-project/go-statestore v0.1.1 github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b @@ -47,6 +47,7 @@ require ( github.com/filecoin-project/specs-actors/v2 v2.3.5-0.20210114162132-5b58b773f4fb github.com/filecoin-project/specs-actors/v3 v3.1.0 github.com/filecoin-project/specs-actors/v4 v4.0.0 + github.com/filecoin-project/specs-actors/v5 v5.0.0-20210510162709-3255bdd9f2bb 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 diff --git a/go.sum b/go.sum index 8bcfafdfc..ecdab7a65 100644 --- a/go.sum +++ b/go.sum @@ -295,6 +295,8 @@ github.com/filecoin-project/go-state-types v0.0.0-20200928172055-2df22083d8ab/go github.com/filecoin-project/go-state-types v0.0.0-20201102161440-c8033295a1fc/go.mod h1:ezYnPf0bNkTsDibL/psSz5dy4B5awOJ/E7P2Saeep8g= github.com/filecoin-project/go-state-types v0.1.0 h1:9r2HCSMMCmyMfGyMKxQtv0GKp6VT/m5GgVk8EhYbLJU= github.com/filecoin-project/go-state-types v0.1.0/go.mod h1:ezYnPf0bNkTsDibL/psSz5dy4B5awOJ/E7P2Saeep8g= +github.com/filecoin-project/go-state-types v0.1.1-0.20210506134452-99b279731c48 h1:Jc4OprDp3bRDxbsrXNHPwJabZJM3iDy+ri8/1e0ZnX4= +github.com/filecoin-project/go-state-types v0.1.1-0.20210506134452-99b279731c48/go.mod h1:ezYnPf0bNkTsDibL/psSz5dy4B5awOJ/E7P2Saeep8g= github.com/filecoin-project/go-statemachine v0.0.0-20200925024713-05bd7c71fbfe h1:dF8u+LEWeIcTcfUcCf3WFVlc81Fr2JKg8zPzIbBDKDw= github.com/filecoin-project/go-statemachine v0.0.0-20200925024713-05bd7c71fbfe/go.mod h1:FGwQgZAt2Gh5mjlwJUlVB62JeYdo+if0xWxSEfBD9ig= github.com/filecoin-project/go-statestore v0.1.0/go.mod h1:LFc9hD+fRxPqiHiaqUEZOinUJB4WARkRfNl10O7kTnI= @@ -314,6 +316,8 @@ github.com/filecoin-project/specs-actors/v3 v3.1.0 h1:s4qiPw8pgypqBGAy853u/zdZJ7 github.com/filecoin-project/specs-actors/v3 v3.1.0/go.mod h1:mpynccOLlIRy0QnR008BwYBwT9fen+sPR13MA1VmMww= github.com/filecoin-project/specs-actors/v4 v4.0.0 h1:vMALksY5G3J5rj3q9rbcyB+f4Tk1xrLqSgdB3jOok4s= github.com/filecoin-project/specs-actors/v4 v4.0.0/go.mod h1:TkHXf/l7Wyw4ZejyXIPS2rK8bBO0rdwhTZyQQgaglng= +github.com/filecoin-project/specs-actors/v5 v5.0.0-20210510162709-3255bdd9f2bb h1:i2ZBHLiNYyyhNlfjfB/TGtGLlb8dgiGiVCDZlGpUtUc= +github.com/filecoin-project/specs-actors/v5 v5.0.0-20210510162709-3255bdd9f2bb/go.mod h1:XAgQWq5pu0MBwx3MI5uJ6fK/Q8jCkZnKNNLxvDcbXew= github.com/filecoin-project/specs-storage v0.1.1-0.20201105051918-5188d9774506 h1:Ur/l2+6qN+lQiqjozWWc5p9UDaAMDZKTlDS98oRnlIw= github.com/filecoin-project/specs-storage v0.1.1-0.20201105051918-5188d9774506/go.mod h1:nJRRM7Aa9XVvygr3W9k6xGF46RWzr2zxF/iGoAIfA/g= github.com/filecoin-project/test-vectors/schema v0.0.5 h1:w3zHQhzM4pYxJDl21avXjOKBLF8egrvwUwjpT8TquDg= diff --git a/lotuspond/front/src/chain/methods.json b/lotuspond/front/src/chain/methods.json index a09d3ec91..12e0e8abf 100644 --- a/lotuspond/front/src/chain/methods.json +++ b/lotuspond/front/src/chain/methods.json @@ -410,5 +410,109 @@ "AddVerifiedClient", "UseBytes", "RestoreBytes" + ], + "fil/5/account": [ + "Send", + "Constructor", + "PubkeyAddress" + ], + "fil/5/cron": [ + "Send", + "Constructor", + "EpochTick" + ], + "fil/5/init": [ + "Send", + "Constructor", + "Exec" + ], + "fil/5/multisig": [ + "Send", + "Constructor", + "Propose", + "Approve", + "Cancel", + "AddSigner", + "RemoveSigner", + "SwapSigner", + "ChangeNumApprovalsThreshold", + "LockBalance" + ], + "fil/5/paymentchannel": [ + "Send", + "Constructor", + "UpdateChannelState", + "Settle", + "Collect" + ], + "fil/5/reward": [ + "Send", + "Constructor", + "AwardBlockReward", + "ThisEpochReward", + "UpdateNetworkKPI" + ], + "fil/5/storagemarket": [ + "Send", + "Constructor", + "AddBalance", + "WithdrawBalance", + "PublishStorageDeals", + "VerifyDealsForActivation", + "ActivateDeals", + "OnMinerSectorsTerminate", + "ComputeDataCommitment", + "CronTick" + ], + "fil/5/storageminer": [ + "Send", + "Constructor", + "ControlAddresses", + "ChangeWorkerAddress", + "ChangePeerID", + "SubmitWindowedPoSt", + "PreCommitSector", + "ProveCommitSector", + "ExtendSectorExpiration", + "TerminateSectors", + "DeclareFaults", + "DeclareFaultsRecovered", + "OnDeferredCronEvent", + "CheckSectorProven", + "ApplyRewards", + "ReportConsensusFault", + "WithdrawBalance", + "ConfirmSectorProofsValid", + "ChangeMultiaddrs", + "CompactPartitions", + "CompactSectorNumbers", + "ConfirmUpdateWorkerKey", + "RepayDebt", + "ChangeOwnerAddress", + "DisputeWindowedPoSt" + ], + "fil/5/storagepower": [ + "Send", + "Constructor", + "CreateMiner", + "UpdateClaimedPower", + "EnrollCronEvent", + "OnEpochTickEnd", + "UpdatePledgeTotal", + "SubmitPoRepForBulkVerify", + "CurrentTotalPower" + ], + "fil/5/system": [ + "Send", + "Constructor" + ], + "fil/5/verifiedregistry": [ + "Send", + "Constructor", + "AddVerifier", + "RemoveVerifier", + "AddVerifiedClient", + "UseBytes", + "RestoreBytes" ] } \ No newline at end of file diff --git a/testplans/lotus-soup/init.go b/testplans/lotus-soup/init.go index 5690e803a..a8d8e7478 100644 --- a/testplans/lotus-soup/init.go +++ b/testplans/lotus-soup/init.go @@ -53,5 +53,5 @@ func init() { build.UpgradeLiftoffHeight = -3 // We need to _run_ this upgrade because genesis doesn't support v2, so // we run it at height 0. - build.UpgradeActorsV2Height = 0 + build.UpgradeAssemblyHeight = 0 } From 506f39b29457b02ffc170663ac065f8f8168d952 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 10 Mar 2021 16:16:44 +0100 Subject: [PATCH 03/88] WIP: Integrate FIP0013 --- api/api_storage.go | 2 + api/apistruct/struct.go | 12 + build/parameters.go | 4 + build/proof-params/srs-inner-product.json | 7 + chain/gen/gen.go | 24 +- chain/gen/genesis/miners.go | 6 +- chain/vm/gas.go | 30 +- chain/vm/gas_v0.go | 7 + chain/vm/invoker.go | 2 +- chain/vm/runtime.go | 8 +- chain/vm/syscalls.go | 44 +-- cli/params.go | 2 +- cmd/lotus-bench/main.go | 4 +- cmd/lotus-seal-worker/main.go | 2 +- cmd/lotus-shed/params.go | 2 +- cmd/lotus-storage-miner/info.go | 2 + cmd/lotus-storage-miner/init.go | 2 +- cmd/lotus-storage-miner/init_restore.go | 2 +- cmd/lotus-storage-miner/sectors.go | 48 ++++ cmd/lotus/daemon.go | 2 +- documentation/en/api-methods-miner.md | 20 ++ extern/filecoin-ffi | 2 +- .../sector-storage/ffiwrapper/sealer_test.go | 103 ++++++- extern/sector-storage/ffiwrapper/types.go | 12 +- .../sector-storage/ffiwrapper/verifier_cgo.go | 22 +- extern/sector-storage/mock/mock.go | 45 ++- extern/storage-sealing/commit_batch.go | 271 ++++++++++++++++++ extern/storage-sealing/fsm.go | 14 + extern/storage-sealing/fsm_events.go | 12 + extern/storage-sealing/sealiface/config.go | 4 + extern/storage-sealing/sealing.go | 10 + extern/storage-sealing/sector_state.go | 108 +++---- extern/storage-sealing/states_sealing.go | 32 +++ go.mod | 4 +- go.sum | 10 +- lotuspond/front/src/chain/methods.json | 3 +- node/config/def.go | 8 + node/impl/storminer.go | 8 + node/modules/storageminer.go | 8 +- storage/sealing.go | 8 + 40 files changed, 783 insertions(+), 133 deletions(-) create mode 100644 build/proof-params/srs-inner-product.json create mode 100644 extern/storage-sealing/commit_batch.go diff --git a/api/api_storage.go b/api/api_storage.go index 9662e8cd8..a9dec3d0e 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -80,6 +80,8 @@ type StorageMiner interface { // SectorTerminatePending returns a list of pending sector terminations to be sent in the next batch message SectorTerminatePending(ctx context.Context) ([]abi.SectorID, error) //perm:admin SectorMarkForUpgrade(ctx context.Context, id abi.SectorNumber) error //perm:admin + SectorCommitFlush(ctx context.Context) (*cid.Cid, error) //perm:admin + SectorCommitPending(ctx context.Context) ([]abi.SectorID, error) //perm:admin // WorkerConnect tells the node to connect to workers RPC WorkerConnect(context.Context, string) error //perm:admin retry:true diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index 21ed6d56a..6917a2967 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -639,6 +639,10 @@ type StorageMinerStruct struct { SealingSchedDiag func(p0 context.Context, p1 bool) (interface{}, error) `perm:"admin"` + SectorCommitFlush func(p0 context.Context) (*cid.Cid, error) `perm:"admin"` + + SectorCommitPending func(p0 context.Context) ([]abi.SectorID, error) `perm:"admin"` + SectorGetExpectedSealDuration func(p0 context.Context) (time.Duration, error) `perm:"read"` SectorGetSealDelay func(p0 context.Context) (time.Duration, error) `perm:"read"` @@ -1923,6 +1927,14 @@ func (s *StorageMinerStruct) SealingSchedDiag(p0 context.Context, p1 bool) (inte return s.Internal.SealingSchedDiag(p0, p1) } +func (s *StorageMinerStruct) SectorCommitFlush(p0 context.Context) (*cid.Cid, error) { + return s.Internal.SectorCommitFlush(p0) +} + +func (s *StorageMinerStruct) SectorCommitPending(p0 context.Context) ([]abi.SectorID, error) { + return s.Internal.SectorCommitPending(p0) +} + func (s *StorageMinerStruct) SectorGetExpectedSealDuration(p0 context.Context) (time.Duration, error) { return s.Internal.SectorGetExpectedSealDuration(p0) } diff --git a/build/parameters.go b/build/parameters.go index 7d34a7831..b70dad1c1 100644 --- a/build/parameters.go +++ b/build/parameters.go @@ -5,3 +5,7 @@ import rice "github.com/GeertJohan/go.rice" func ParametersJSON() []byte { return rice.MustFindBox("proof-params").MustBytes("parameters.json") } + +func SrsJSON() []byte { + return rice.MustFindBox("proof-params").MustBytes("srs-inner-product.json") +} diff --git a/build/proof-params/srs-inner-product.json b/build/proof-params/srs-inner-product.json new file mode 100644 index 000000000..8566bf5fd --- /dev/null +++ b/build/proof-params/srs-inner-product.json @@ -0,0 +1,7 @@ +{ + "v28-fil-inner-product-v1.srs": { + "cid": "Qmdq44DjcQnFfU3PJcdX7J49GCqcUYszr1TxMbHtAkvQ3g", + "digest": "ae20310138f5ba81451d723f858e3797", + "sector_size": 0 + } +} diff --git a/chain/gen/gen.go b/chain/gen/gen.go index b4e04424c..af2611676 100644 --- a/chain/gen/gen.go +++ b/chain/gen/gen.go @@ -25,7 +25,7 @@ import ( "go.opencensus.io/trace" "golang.org/x/xerrors" - proof2 "github.com/filecoin-project/specs-actors/v2/actors/runtime/proof" + proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/blockstore" @@ -51,7 +51,7 @@ const msgsPerBlock = 20 //nolint:deadcode,varcheck var log = logging.Logger("gen") -var ValidWpostForTesting = []proof2.PoStProof{{ +var ValidWpostForTesting = []proof5.PoStProof{{ ProofBytes: []byte("valid proof"), }} @@ -460,7 +460,7 @@ func (cg *ChainGen) NextTipSetFromMinersWithMessages(base *types.TipSet, miners func (cg *ChainGen) makeBlock(parents *types.TipSet, m address.Address, vrfticket *types.Ticket, eticket *types.ElectionProof, bvals []types.BeaconEntry, height abi.ChainEpoch, - wpost []proof2.PoStProof, msgs []*types.SignedMessage) (*types.FullBlock, error) { + wpost []proof5.PoStProof, msgs []*types.SignedMessage) (*types.FullBlock, error) { var ts uint64 if cg.Timestamper != nil { @@ -598,7 +598,7 @@ func (mca mca) WalletSign(ctx context.Context, a address.Address, v []byte) (*cr type WinningPoStProver interface { GenerateCandidates(context.Context, abi.PoStRandomness, uint64) ([]uint64, error) - ComputeProof(context.Context, []proof2.SectorInfo, abi.PoStRandomness) ([]proof2.PoStProof, error) + ComputeProof(context.Context, []proof5.SectorInfo, abi.PoStRandomness) ([]proof5.PoStProof, error) } type wppProvider struct{} @@ -607,7 +607,7 @@ func (wpp *wppProvider) GenerateCandidates(ctx context.Context, _ abi.PoStRandom return []uint64{0}, nil } -func (wpp *wppProvider) ComputeProof(context.Context, []proof2.SectorInfo, abi.PoStRandomness) ([]proof2.PoStProof, error) { +func (wpp *wppProvider) ComputeProof(context.Context, []proof5.SectorInfo, abi.PoStRandomness) ([]proof5.PoStProof, error) { return ValidWpostForTesting, nil } @@ -685,15 +685,23 @@ type genFakeVerifier struct{} var _ ffiwrapper.Verifier = (*genFakeVerifier)(nil) -func (m genFakeVerifier) VerifySeal(svi proof2.SealVerifyInfo) (bool, error) { +func (m genFakeVerifier) VerifySeal(svi proof5.SealVerifyInfo) (bool, error) { return true, nil } -func (m genFakeVerifier) VerifyWinningPoSt(ctx context.Context, info proof2.WinningPoStVerifyInfo) (bool, error) { +func (m genFakeVerifier) VerifyAggregateSeals(aggregate proof5.AggregateSealVerifyProofAndInfos) (bool, error) { panic("not supported") } -func (m genFakeVerifier) VerifyWindowPoSt(ctx context.Context, info proof2.WindowPoStVerifyInfo) (bool, error) { +func (m genFakeVerifier) AggregateSealProofs(proofType abi.RegisteredSealProof, proofs [][]byte) ([]byte, error) { + panic("not supported") +} + +func (m genFakeVerifier) VerifyWinningPoSt(ctx context.Context, info proof5.WinningPoStVerifyInfo) (bool, error) { + panic("not supported") +} + +func (m genFakeVerifier) VerifyWindowPoSt(ctx context.Context, info proof5.WindowPoStVerifyInfo) (bool, error) { panic("not supported") } diff --git a/chain/gen/genesis/miners.go b/chain/gen/genesis/miners.go index 297543886..be33560e5 100644 --- a/chain/gen/genesis/miners.go +++ b/chain/gen/genesis/miners.go @@ -27,7 +27,7 @@ import ( miner0 "github.com/filecoin-project/specs-actors/actors/builtin/miner" power0 "github.com/filecoin-project/specs-actors/actors/builtin/power" reward0 "github.com/filecoin-project/specs-actors/actors/builtin/reward" - runtime2 "github.com/filecoin-project/specs-actors/v2/actors/runtime" + runtime5 "github.com/filecoin-project/specs-actors/v5/actors/runtime" "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/store" @@ -46,7 +46,7 @@ func MinerAddress(genesisIndex uint64) address.Address { } type fakedSigSyscalls struct { - runtime2.Syscalls + runtime5.Syscalls } func (fss *fakedSigSyscalls) VerifySignature(signature crypto.Signature, signer address.Address, plaintext []byte) error { @@ -54,7 +54,7 @@ func (fss *fakedSigSyscalls) VerifySignature(signature crypto.Signature, signer } func mkFakedSigSyscalls(base vm.SyscallBuilder) vm.SyscallBuilder { - return func(ctx context.Context, rt *vm.Runtime) runtime2.Syscalls { + return func(ctx context.Context, rt *vm.Runtime) runtime5.Syscalls { return &fakedSigSyscalls{ base(ctx, rt), } diff --git a/chain/vm/gas.go b/chain/vm/gas.go index eef431aef..5ecc42345 100644 --- a/chain/vm/gas.go +++ b/chain/vm/gas.go @@ -9,8 +9,8 @@ import ( addr "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/crypto" - vmr2 "github.com/filecoin-project/specs-actors/v2/actors/runtime" - proof2 "github.com/filecoin-project/specs-actors/v2/actors/runtime/proof" + vmr5 "github.com/filecoin-project/specs-actors/v5/actors/runtime" + proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" "github.com/ipfs/go-cid" ) @@ -74,8 +74,9 @@ type Pricelist interface { OnVerifySignature(sigType crypto.SigType, planTextSize int) (GasCharge, error) OnHashing(dataSize int) GasCharge OnComputeUnsealedSectorCid(proofType abi.RegisteredSealProof, pieces []abi.PieceInfo) GasCharge - OnVerifySeal(info proof2.SealVerifyInfo) GasCharge - OnVerifyPost(info proof2.WindowPoStVerifyInfo) GasCharge + OnVerifySeal(info proof5.SealVerifyInfo) GasCharge + OnVerifyAggregateSeals() GasCharge + OnVerifyPost(info proof5.WindowPoStVerifyInfo) GasCharge OnVerifyConsensusFault() GasCharge } @@ -111,6 +112,7 @@ var prices = map[abi.ChainEpoch]Pricelist{ hashingBase: 31355, computeUnsealedSectorCidBase: 98647, verifySealBase: 2000, // TODO gas , it VerifySeal syscall is not used + verifyAggregateSealBase: 0, verifyPostLookup: map[abi.RegisteredPoStProof]scalingCost{ abi.RegisteredPoStProof_StackedDrgWindow512MiBV1: { flat: 123861062, @@ -158,7 +160,8 @@ var prices = map[abi.ChainEpoch]Pricelist{ hashingBase: 31355, computeUnsealedSectorCidBase: 98647, - verifySealBase: 2000, // TODO gas , it VerifySeal syscall is not used + verifySealBase: 2000, // TODO gas , it VerifySeal syscall is not used + verifyAggregateSealBase: 400_000_000, // TODO (~40ms, I think) verifyPostLookup: map[abi.RegisteredPoStProof]scalingCost{ abi.RegisteredPoStProof_StackedDrgWindow512MiBV1: { flat: 117680921, @@ -198,7 +201,7 @@ func PricelistByEpoch(epoch abi.ChainEpoch) Pricelist { } type pricedSyscalls struct { - under vmr2.Syscalls + under vmr5.Syscalls pl Pricelist chargeGas func(GasCharge) } @@ -232,7 +235,7 @@ func (ps pricedSyscalls) ComputeUnsealedSectorCID(reg abi.RegisteredSealProof, p } // Verifies a sector seal proof. -func (ps pricedSyscalls) VerifySeal(vi proof2.SealVerifyInfo) error { +func (ps pricedSyscalls) VerifySeal(vi proof5.SealVerifyInfo) error { ps.chargeGas(ps.pl.OnVerifySeal(vi)) defer ps.chargeGas(gasOnActorExec) @@ -240,7 +243,7 @@ func (ps pricedSyscalls) VerifySeal(vi proof2.SealVerifyInfo) error { } // Verifies a proof of spacetime. -func (ps pricedSyscalls) VerifyPoSt(vi proof2.WindowPoStVerifyInfo) error { +func (ps pricedSyscalls) VerifyPoSt(vi proof5.WindowPoStVerifyInfo) error { ps.chargeGas(ps.pl.OnVerifyPost(vi)) defer ps.chargeGas(gasOnActorExec) @@ -257,14 +260,14 @@ func (ps pricedSyscalls) VerifyPoSt(vi proof2.WindowPoStVerifyInfo) error { // the "parent grinding fault", in which case it must be the sibling of h1 (same parent tipset) and one of the // blocks in the parent of h2 (i.e. h2's grandparent). // Returns nil and an error if the headers don't prove a fault. -func (ps pricedSyscalls) VerifyConsensusFault(h1 []byte, h2 []byte, extra []byte) (*vmr2.ConsensusFault, error) { +func (ps pricedSyscalls) VerifyConsensusFault(h1 []byte, h2 []byte, extra []byte) (*vmr5.ConsensusFault, error) { ps.chargeGas(ps.pl.OnVerifyConsensusFault()) defer ps.chargeGas(gasOnActorExec) return ps.under.VerifyConsensusFault(h1, h2, extra) } -func (ps pricedSyscalls) BatchVerifySeals(inp map[address.Address][]proof2.SealVerifyInfo) (map[address.Address][]bool, error) { +func (ps pricedSyscalls) BatchVerifySeals(inp map[address.Address][]proof5.SealVerifyInfo) (map[address.Address][]bool, error) { count := int64(0) for _, svis := range inp { count += int64(len(svis)) @@ -277,3 +280,10 @@ func (ps pricedSyscalls) BatchVerifySeals(inp map[address.Address][]proof2.SealV return ps.under.BatchVerifySeals(inp) } + +func (ps pricedSyscalls) VerifyAggregateSeals(aggregate proof5.AggregateSealVerifyProofAndInfos) error { + ps.chargeGas(ps.pl.OnVerifyAggregateSeals()) + defer ps.chargeGas(gasOnActorExec) + + return ps.under.VerifyAggregateSeals(aggregate) +} diff --git a/chain/vm/gas_v0.go b/chain/vm/gas_v0.go index 7c864b7f9..d54760b69 100644 --- a/chain/vm/gas_v0.go +++ b/chain/vm/gas_v0.go @@ -91,6 +91,7 @@ type pricelistV0 struct { computeUnsealedSectorCidBase int64 verifySealBase int64 + verifyAggregateSealBase int64 verifyPostLookup map[abi.RegisteredPoStProof]scalingCost verifyPostDiscount bool verifyConsensusFault int64 @@ -185,6 +186,12 @@ func (pl *pricelistV0) OnVerifySeal(info proof2.SealVerifyInfo) GasCharge { return newGasCharge("OnVerifySeal", pl.verifySealBase, 0) } +// OnVerifyAggregateSeals +func (pl *pricelistV0) OnVerifyAggregateSeals() GasCharge { + // TODO: this needs more cost tunning + return newGasCharge("OnVerifyAggregateSeals", pl.verifyAggregateSealBase, 0) +} + // OnVerifyPost func (pl *pricelistV0) OnVerifyPost(info proof2.WindowPoStVerifyInfo) GasCharge { sectorSize := "unknown" diff --git a/chain/vm/invoker.go b/chain/vm/invoker.go index 126b57090..4a8032770 100644 --- a/chain/vm/invoker.go +++ b/chain/vm/invoker.go @@ -16,10 +16,10 @@ import ( exported0 "github.com/filecoin-project/specs-actors/actors/builtin/exported" exported2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/exported" - vmr "github.com/filecoin-project/specs-actors/v2/actors/runtime" exported3 "github.com/filecoin-project/specs-actors/v3/actors/builtin/exported" exported4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/exported" exported5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/exported" + vmr "github.com/filecoin-project/specs-actors/v5/actors/runtime" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/exitcode" diff --git a/chain/vm/runtime.go b/chain/vm/runtime.go index cdb1720de..a3e2f293f 100644 --- a/chain/vm/runtime.go +++ b/chain/vm/runtime.go @@ -16,7 +16,7 @@ import ( "github.com/filecoin-project/go-state-types/network" rtt "github.com/filecoin-project/go-state-types/rt" rt0 "github.com/filecoin-project/specs-actors/actors/runtime" - rt2 "github.com/filecoin-project/specs-actors/v2/actors/runtime" + rt5 "github.com/filecoin-project/specs-actors/v5/actors/runtime" "github.com/ipfs/go-cid" ipldcbor "github.com/ipfs/go-ipld-cbor" "go.opencensus.io/trace" @@ -54,8 +54,8 @@ func (m *Message) ValueReceived() abi.TokenAmount { var EnableGasTracing = false type Runtime struct { - rt2.Message - rt2.Syscalls + rt5.Message + rt5.Syscalls ctx context.Context @@ -136,7 +136,7 @@ func (rt *Runtime) StorePut(x cbor.Marshaler) cid.Cid { } var _ rt0.Runtime = (*Runtime)(nil) -var _ rt2.Runtime = (*Runtime)(nil) +var _ rt5.Runtime = (*Runtime)(nil) func (rt *Runtime) shimCall(f func() interface{}) (rval []byte, aerr aerrors.ActorError) { defer func() { diff --git a/chain/vm/syscalls.go b/chain/vm/syscalls.go index 0bcfe10a7..568197bc8 100644 --- a/chain/vm/syscalls.go +++ b/chain/vm/syscalls.go @@ -26,8 +26,8 @@ import ( "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" "github.com/filecoin-project/lotus/lib/sigs" - runtime2 "github.com/filecoin-project/specs-actors/v2/actors/runtime" - proof2 "github.com/filecoin-project/specs-actors/v2/actors/runtime/proof" + runtime5 "github.com/filecoin-project/specs-actors/v5/actors/runtime" + proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" ) func init() { @@ -36,10 +36,10 @@ func init() { // Actual type is defined in chain/types/vmcontext.go because the VMContext interface is there -type SyscallBuilder func(ctx context.Context, rt *Runtime) runtime2.Syscalls +type SyscallBuilder func(ctx context.Context, rt *Runtime) runtime5.Syscalls func Syscalls(verifier ffiwrapper.Verifier) SyscallBuilder { - return func(ctx context.Context, rt *Runtime) runtime2.Syscalls { + return func(ctx context.Context, rt *Runtime) runtime5.Syscalls { return &syscallShim{ ctx: ctx, @@ -90,7 +90,7 @@ func (ss *syscallShim) HashBlake2b(data []byte) [32]byte { // Checks validity of the submitted consensus fault with the two block headers needed to prove the fault // and an optional extra one to check common ancestry (as needed). // Note that the blocks are ordered: the method requires a.Epoch() <= b.Epoch(). -func (ss *syscallShim) VerifyConsensusFault(a, b, extra []byte) (*runtime2.ConsensusFault, error) { +func (ss *syscallShim) VerifyConsensusFault(a, b, extra []byte) (*runtime5.ConsensusFault, error) { // Note that block syntax is not validated. Any validly signed block will be accepted pursuant to the below conditions. // Whether or not it could ever have been accepted in a chain is not checked/does not matter here. // for that reason when checking block parent relationships, rather than instantiating a Tipset to do so @@ -133,14 +133,14 @@ func (ss *syscallShim) VerifyConsensusFault(a, b, extra []byte) (*runtime2.Conse } // (2) check for the consensus faults themselves - var consensusFault *runtime2.ConsensusFault + var consensusFault *runtime5.ConsensusFault // (a) double-fork mining fault if blockA.Height == blockB.Height { - consensusFault = &runtime2.ConsensusFault{ + consensusFault = &runtime5.ConsensusFault{ Target: blockA.Miner, Epoch: blockB.Height, - Type: runtime2.ConsensusFaultDoubleForkMining, + Type: runtime5.ConsensusFaultDoubleForkMining, } } @@ -148,10 +148,10 @@ func (ss *syscallShim) VerifyConsensusFault(a, b, extra []byte) (*runtime2.Conse // strictly speaking no need to compare heights based on double fork mining check above, // but at same height this would be a different fault. if types.CidArrsEqual(blockA.Parents, blockB.Parents) && blockA.Height != blockB.Height { - consensusFault = &runtime2.ConsensusFault{ + consensusFault = &runtime5.ConsensusFault{ Target: blockA.Miner, Epoch: blockB.Height, - Type: runtime2.ConsensusFaultTimeOffsetMining, + Type: runtime5.ConsensusFaultTimeOffsetMining, } } @@ -171,10 +171,10 @@ func (ss *syscallShim) VerifyConsensusFault(a, b, extra []byte) (*runtime2.Conse if types.CidArrsEqual(blockA.Parents, blockC.Parents) && blockA.Height == blockC.Height && types.CidArrsContains(blockB.Parents, blockC.Cid()) && !types.CidArrsContains(blockB.Parents, blockA.Cid()) { - consensusFault = &runtime2.ConsensusFault{ + consensusFault = &runtime5.ConsensusFault{ Target: blockA.Miner, Epoch: blockB.Height, - Type: runtime2.ConsensusFaultParentGrinding, + Type: runtime5.ConsensusFaultParentGrinding, } } } @@ -243,7 +243,7 @@ func (ss *syscallShim) workerKeyAtLookback(height abi.ChainEpoch) (address.Addre return ResolveToKeyAddr(ss.cstate, ss.cst, info.Worker) } -func (ss *syscallShim) VerifyPoSt(proof proof2.WindowPoStVerifyInfo) error { +func (ss *syscallShim) VerifyPoSt(proof proof5.WindowPoStVerifyInfo) error { ok, err := ss.verifier.VerifyWindowPoSt(context.TODO(), proof) if err != nil { return err @@ -254,7 +254,7 @@ func (ss *syscallShim) VerifyPoSt(proof proof2.WindowPoStVerifyInfo) error { return nil } -func (ss *syscallShim) VerifySeal(info proof2.SealVerifyInfo) error { +func (ss *syscallShim) VerifySeal(info proof5.SealVerifyInfo) error { //_, span := trace.StartSpan(ctx, "ValidatePoRep") //defer span.End() @@ -281,6 +281,18 @@ func (ss *syscallShim) VerifySeal(info proof2.SealVerifyInfo) error { return nil } +func (ss *syscallShim) VerifyAggregateSeals(aggregate proof5.AggregateSealVerifyProofAndInfos) error { + ok, err := ss.verifier.VerifyAggregateSeals(aggregate) + if err != nil { + return xerrors.Errorf("failed to verify aggregated PoRep: %w", err) + } + if !ok { + return fmt.Errorf("invalid aggredate proof") + } + + return nil +} + func (ss *syscallShim) VerifySignature(sig crypto.Signature, addr address.Address, input []byte) error { // TODO: in genesis setup, we are currently faking signatures @@ -294,7 +306,7 @@ func (ss *syscallShim) VerifySignature(sig crypto.Signature, addr address.Addres var BatchSealVerifyParallelism = goruntime.NumCPU() -func (ss *syscallShim) BatchVerifySeals(inp map[address.Address][]proof2.SealVerifyInfo) (map[address.Address][]bool, error) { +func (ss *syscallShim) BatchVerifySeals(inp map[address.Address][]proof5.SealVerifyInfo) (map[address.Address][]bool, error) { out := make(map[address.Address][]bool) sema := make(chan struct{}, BatchSealVerifyParallelism) @@ -306,7 +318,7 @@ func (ss *syscallShim) BatchVerifySeals(inp map[address.Address][]proof2.SealVer for i, s := range seals { wg.Add(1) - go func(ma address.Address, ix int, svi proof2.SealVerifyInfo, res []bool) { + go func(ma address.Address, ix int, svi proof5.SealVerifyInfo, res []bool) { defer wg.Done() sema <- struct{}{} diff --git a/cli/params.go b/cli/params.go index 8419507b8..1aa6555c5 100644 --- a/cli/params.go +++ b/cli/params.go @@ -23,7 +23,7 @@ var FetchParamCmd = &cli.Command{ } sectorSize := uint64(sectorSizeInt) - err = paramfetch.GetParams(ReqContext(cctx), build.ParametersJSON(), sectorSize) + err = paramfetch.GetParams(ReqContext(cctx), build.ParametersJSON(), build.SrsJSON(), sectorSize) if err != nil { return xerrors.Errorf("fetching proof parameters: %w", err) } diff --git a/cmd/lotus-bench/main.go b/cmd/lotus-bench/main.go index 81aa09a75..0b8ec6fe3 100644 --- a/cmd/lotus-bench/main.go +++ b/cmd/lotus-bench/main.go @@ -243,7 +243,7 @@ var sealBenchCmd = &cli.Command{ // Only fetch parameters if actually needed skipc2 := c.Bool("skip-commit2") if !skipc2 { - if err := paramfetch.GetParams(lcli.ReqContext(c), build.ParametersJSON(), uint64(sectorSize)); err != nil { + if err := paramfetch.GetParams(lcli.ReqContext(c), build.ParametersJSON(), build.SrsJSON(), uint64(sectorSize)); err != nil { return xerrors.Errorf("getting params: %w", err) } } @@ -738,7 +738,7 @@ var proveCmd = &cli.Command{ return xerrors.Errorf("unmarshalling input file: %w", err) } - if err := paramfetch.GetParams(lcli.ReqContext(c), build.ParametersJSON(), c2in.SectorSize); err != nil { + if err := paramfetch.GetParams(lcli.ReqContext(c), build.ParametersJSON(), build.SrsJSON(), c2in.SectorSize); err != nil { return xerrors.Errorf("getting params: %w", err) } diff --git a/cmd/lotus-seal-worker/main.go b/cmd/lotus-seal-worker/main.go index 24918e52a..5a78c6dac 100644 --- a/cmd/lotus-seal-worker/main.go +++ b/cmd/lotus-seal-worker/main.go @@ -228,7 +228,7 @@ var runCmd = &cli.Command{ } if cctx.Bool("commit") { - if err := paramfetch.GetParams(ctx, build.ParametersJSON(), uint64(ssize)); err != nil { + if err := paramfetch.GetParams(ctx, build.ParametersJSON(), build.SrsJSON(), uint64(ssize)); err != nil { return xerrors.Errorf("get params: %w", err) } } diff --git a/cmd/lotus-shed/params.go b/cmd/lotus-shed/params.go index 3f7e7b6fb..e45d9489c 100644 --- a/cmd/lotus-shed/params.go +++ b/cmd/lotus-shed/params.go @@ -25,7 +25,7 @@ var fetchParamCmd = &cli.Command{ return err } sectorSize := uint64(sectorSizeInt) - err = paramfetch.GetParams(lcli.ReqContext(cctx), build.ParametersJSON(), sectorSize) + err = paramfetch.GetParams(lcli.ReqContext(cctx), build.ParametersJSON(), build.SrsJSON(), sectorSize) if err != nil { return xerrors.Errorf("fetching proof parameters: %w", err) } diff --git a/cmd/lotus-storage-miner/info.go b/cmd/lotus-storage-miner/info.go index 7650de035..7a8835fee 100644 --- a/cmd/lotus-storage-miner/info.go +++ b/cmd/lotus-storage-miner/info.go @@ -295,6 +295,8 @@ var stateList = []stateMeta{ {col: color.FgYellow, state: sealing.Committing}, {col: color.FgYellow, state: sealing.SubmitCommit}, {col: color.FgYellow, state: sealing.CommitWait}, + {col: color.FgYellow, state: sealing.SubmitCommitAggregate}, + {col: color.FgYellow, state: sealing.CommitAggregateWait}, {col: color.FgYellow, state: sealing.FinalizeSector}, {col: color.FgCyan, state: sealing.Terminating}, diff --git a/cmd/lotus-storage-miner/init.go b/cmd/lotus-storage-miner/init.go index 2e38dcc06..bac8444cc 100644 --- a/cmd/lotus-storage-miner/init.go +++ b/cmd/lotus-storage-miner/init.go @@ -143,7 +143,7 @@ var initCmd = &cli.Command{ log.Info("Checking proof parameters") - if err := paramfetch.GetParams(ctx, build.ParametersJSON(), uint64(ssize)); err != nil { + if err := paramfetch.GetParams(ctx, build.ParametersJSON(), build.SrsJSON(), uint64(ssize)); err != nil { return xerrors.Errorf("fetching proof parameters: %w", err) } diff --git a/cmd/lotus-storage-miner/init_restore.go b/cmd/lotus-storage-miner/init_restore.go index 12358e63a..af4c43c95 100644 --- a/cmd/lotus-storage-miner/init_restore.go +++ b/cmd/lotus-storage-miner/init_restore.go @@ -249,7 +249,7 @@ var initRestoreCmd = &cli.Command{ log.Info("Checking proof parameters") - if err := paramfetch.GetParams(ctx, build.ParametersJSON(), uint64(mi.SectorSize)); err != nil { + if err := paramfetch.GetParams(ctx, build.ParametersJSON(), build.SrsJSON(), uint64(mi.SectorSize)); err != nil { return xerrors.Errorf("fetching proof parameters: %w", err) } diff --git a/cmd/lotus-storage-miner/sectors.go b/cmd/lotus-storage-miner/sectors.go index 3791dbf07..8da491841 100644 --- a/cmd/lotus-storage-miner/sectors.go +++ b/cmd/lotus-storage-miner/sectors.go @@ -45,6 +45,7 @@ var sectorsCmd = &cli.Command{ sectorsStartSealCmd, sectorsSealDelayCmd, sectorsCapacityCollateralCmd, + sectorsPendingCommit, }, } @@ -969,6 +970,53 @@ var sectorsUpdateCmd = &cli.Command{ }, } +var sectorsPendingCommit = &cli.Command{ + Name: "pending-commit", + Usage: "list sectors waiting in batch queue", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "publish-now", + Usage: "send a batch now", + }, + }, + Action: func(cctx *cli.Context) error { + api, closer, err := lcli.GetStorageMinerAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := lcli.ReqContext(cctx) + + if cctx.Bool("publish-now") { + cid, err := api.SectorCommitFlush(ctx) + if err != nil { + return xerrors.Errorf("flush: %w", err) + } + if cid == nil { + return xerrors.Errorf("no sectors to publish") + } + + fmt.Println("sector batch published: ", cid) + return nil + } + + pending, err := api.SectorCommitPending(ctx) + if err != nil { + return xerrors.Errorf("getting pending deals: %w", err) + } + + if len(pending) > 0 { + for _, sector := range pending { + fmt.Println(sector.Number) + } + return nil + } + + fmt.Println("No sectors queued to be committed") + return nil + }, +} + func yesno(b bool) string { if b { return color.GreenString("YES") diff --git a/cmd/lotus/daemon.go b/cmd/lotus/daemon.go index 5a59ec816..644892ee2 100644 --- a/cmd/lotus/daemon.go +++ b/cmd/lotus/daemon.go @@ -231,7 +231,7 @@ var DaemonCmd = &cli.Command{ freshRepo := err != repo.ErrRepoExists if !isLite { - if err := paramfetch.GetParams(lcli.ReqContext(cctx), build.ParametersJSON(), 0); err != nil { + if err := paramfetch.GetParams(lcli.ReqContext(cctx), build.ParametersJSON(), build.SrsJSON(), 0); err != nil { return xerrors.Errorf("fetching proof parameters: %w", err) } } diff --git a/documentation/en/api-methods-miner.md b/documentation/en/api-methods-miner.md index d140d7d85..5ebc44be5 100644 --- a/documentation/en/api-methods-miner.md +++ b/documentation/en/api-methods-miner.md @@ -98,6 +98,8 @@ * [SealingAbort](#SealingAbort) * [SealingSchedDiag](#SealingSchedDiag) * [Sector](#Sector) + * [SectorCommitFlush](#SectorCommitFlush) + * [SectorCommitPending](#SectorCommitPending) * [SectorGetExpectedSealDuration](#SectorGetExpectedSealDuration) * [SectorGetSealDelay](#SectorGetSealDelay) * [SectorMarkForUpgrade](#SectorMarkForUpgrade) @@ -1556,6 +1558,24 @@ Response: `{}` ## Sector +### SectorCommitFlush + + +Perms: admin + +Inputs: `null` + +Response: `null` + +### SectorCommitPending + + +Perms: admin + +Inputs: `null` + +Response: `null` + ### SectorGetExpectedSealDuration SectorGetExpectedSealDuration gets the expected time for a sector to seal diff --git a/extern/filecoin-ffi b/extern/filecoin-ffi index dc4e4e8dc..5f9f082b0 160000 --- a/extern/filecoin-ffi +++ b/extern/filecoin-ffi @@ -1 +1 @@ -Subproject commit dc4e4e8dc9554dedb6f48304f7f0c6328331f9ec +Subproject commit 5f9f082b03a22bbf0f31adcb2bdf7f539cee6b6b diff --git a/extern/sector-storage/ffiwrapper/sealer_test.go b/extern/sector-storage/ffiwrapper/sealer_test.go index 2efcfc6a0..5af6a78e7 100644 --- a/extern/sector-storage/ffiwrapper/sealer_test.go +++ b/extern/sector-storage/ffiwrapper/sealer_test.go @@ -18,6 +18,7 @@ import ( commpffi "github.com/filecoin-project/go-commp-utils/ffiwrapper" proof2 "github.com/filecoin-project/specs-actors/v2/actors/runtime/proof" + proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" "github.com/ipfs/go-cid" @@ -83,9 +84,10 @@ func (s *seal) precommit(t *testing.T, sb *Sealer, id storage.SectorRef, done fu s.cids = cids } -func (s *seal) commit(t *testing.T, sb *Sealer, done func()) { +var seed = abi.InteractiveSealRandomness{0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 45, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 9} + +func (s *seal) commit(t *testing.T, sb *Sealer, done func()) storage.Proof { defer done() - seed := abi.InteractiveSealRandomness{0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 45, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 9} pc1, err := sb.SealCommit1(context.TODO(), s.ref, s.ticket, seed, []abi.PieceInfo{s.pi}, s.cids) if err != nil { @@ -112,6 +114,8 @@ func (s *seal) commit(t *testing.T, sb *Sealer, done func()) { if !ok { t.Fatal("proof failed to validate") } + + return proof } func (s *seal) unseal(t *testing.T, sb *Sealer, sp *basicfs.Provider, si storage.SectorRef, done func()) { @@ -229,7 +233,12 @@ func getGrothParamFileAndVerifyingKeys(s abi.SectorSize) { panic(err) } - err = paramfetch.GetParams(context.TODO(), dat, uint64(s)) + datSrs, err := ioutil.ReadFile("../../../build/proof-params/srs-inner-product.json") + if err != nil { + panic(err) + } + + err = paramfetch.GetParams(context.TODO(), dat, datSrs, uint64(s)) if err != nil { panic(xerrors.Errorf("failed to acquire Groth parameters for 2KiB sectors: %w", err)) } @@ -462,6 +471,94 @@ func TestSealAndVerify3(t *testing.T) { post(t, sb, []abi.SectorID{si1.ID, si2.ID}, s1, s2, s3) } +func TestSealAndVerifyAggregate(t *testing.T) { + numAgg := 5 + + if testing.Short() { + t.Skip("skipping test in short mode") + } + + defer requireFDsClosed(t, openFDs(t)) + + if runtime.NumCPU() < 10 && os.Getenv("CI") == "" { // don't bother on slow hardware + t.Skip("this is slow") + } + _ = os.Setenv("RUST_LOG", "info") + + getGrothParamFileAndVerifyingKeys(sectorSize) + + cdir, err := ioutil.TempDir("", "sbtest-c-") + if err != nil { + t.Fatal(err) + } + miner := abi.ActorID(123) + + sp := &basicfs.Provider{ + Root: cdir, + } + sb, err := New(sp) + if err != nil { + t.Fatalf("%+v", err) + } + cleanup := func() { + if t.Failed() { + fmt.Printf("not removing %s\n", cdir) + return + } + if err := os.RemoveAll(cdir); err != nil { + t.Error(err) + } + } + defer cleanup() + + avi := proof5.AggregateSealVerifyProofAndInfos{ + Miner: miner, + Infos: make([]proof5.AggregateSealVerifyInfo, numAgg), + } + + toAggregate := make([][]byte, numAgg) + for i := 0; i < numAgg; i++ { + si := storage.SectorRef{ + ID: abi.SectorID{Miner: miner, Number: abi.SectorNumber(i + 1)}, + ProofType: sealProofType, + } + + s := seal{ref: si} + s.precommit(t, sb, si, func() {}) + toAggregate[i] = s.commit(t, sb, func() {}) + + avi.Infos[i] = proof5.AggregateSealVerifyInfo{ + Number: abi.SectorNumber(i + 1), + Randomness: s.ticket, + InteractiveRandomness: seed, + SealedCID: s.cids.Sealed, + UnsealedCID: s.cids.Unsealed, + } + } + + aggStart := time.Now() + + avi.Proof, err = ProofVerifier.AggregateSealProofs(sealProofType, toAggregate) + require.NoError(t, err) + + aggDone := time.Now() + + _, err = ProofVerifier.AggregateSealProofs(sealProofType, toAggregate) + require.NoError(t, err) + + aggHot := time.Now() + + ok, err := ProofVerifier.VerifyAggregateSeals(avi) + require.NoError(t, err) + require.True(t, ok) + + verifDone := time.Now() + + fmt.Printf("Aggregate: %s\n", aggDone.Sub(aggStart).String()) + fmt.Printf("Hot: %s\n", aggHot.Sub(aggDone).String()) + fmt.Printf("Verify: %s\n", verifDone.Sub(aggHot).String()) +} + func BenchmarkWriteWithAlignment(b *testing.B) { bt := abi.UnpaddedPieceSize(2 * 127 * 1024 * 1024) b.SetBytes(int64(bt)) diff --git a/extern/sector-storage/ffiwrapper/types.go b/extern/sector-storage/ffiwrapper/types.go index b7e96636a..9c84794bf 100644 --- a/extern/sector-storage/ffiwrapper/types.go +++ b/extern/sector-storage/ffiwrapper/types.go @@ -4,7 +4,7 @@ import ( "context" "io" - proof2 "github.com/filecoin-project/specs-actors/v2/actors/runtime/proof" + proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" "github.com/ipfs/go-cid" @@ -34,11 +34,15 @@ type Storage interface { } type Verifier interface { - VerifySeal(proof2.SealVerifyInfo) (bool, error) - VerifyWinningPoSt(ctx context.Context, info proof2.WinningPoStVerifyInfo) (bool, error) - VerifyWindowPoSt(ctx context.Context, info proof2.WindowPoStVerifyInfo) (bool, error) + VerifySeal(proof5.SealVerifyInfo) (bool, error) + VerifyAggregateSeals(aggregate proof5.AggregateSealVerifyProofAndInfos) (bool, error) + VerifyWinningPoSt(ctx context.Context, info proof5.WinningPoStVerifyInfo) (bool, error) + VerifyWindowPoSt(ctx context.Context, info proof5.WindowPoStVerifyInfo) (bool, error) GenerateWinningPoStSectorChallenge(context.Context, abi.RegisteredPoStProof, abi.ActorID, abi.PoStRandomness, uint64) ([]uint64, error) + + // cheap, makes no sense to put this on the storage interface + AggregateSealProofs(proofType abi.RegisteredSealProof, proofs [][]byte) ([]byte, error) } type SectorProvider interface { diff --git a/extern/sector-storage/ffiwrapper/verifier_cgo.go b/extern/sector-storage/ffiwrapper/verifier_cgo.go index 15e0e6ab3..b31903080 100644 --- a/extern/sector-storage/ffiwrapper/verifier_cgo.go +++ b/extern/sector-storage/ffiwrapper/verifier_cgo.go @@ -10,13 +10,13 @@ import ( ffi "github.com/filecoin-project/filecoin-ffi" "github.com/filecoin-project/go-state-types/abi" - proof2 "github.com/filecoin-project/specs-actors/v2/actors/runtime/proof" + proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" "github.com/filecoin-project/specs-storage/storage" "github.com/filecoin-project/lotus/extern/sector-storage/storiface" ) -func (sb *Sealer) GenerateWinningPoSt(ctx context.Context, minerID abi.ActorID, sectorInfo []proof2.SectorInfo, randomness abi.PoStRandomness) ([]proof2.PoStProof, error) { +func (sb *Sealer) GenerateWinningPoSt(ctx context.Context, minerID abi.ActorID, sectorInfo []proof5.SectorInfo, randomness abi.PoStRandomness) ([]proof5.PoStProof, error) { randomness[31] &= 0x3f privsectors, skipped, done, err := sb.pubSectorToPriv(ctx, minerID, sectorInfo, nil, abi.RegisteredSealProof.RegisteredWinningPoStProof) // TODO: FAULTS? if err != nil { @@ -30,7 +30,7 @@ func (sb *Sealer) GenerateWinningPoSt(ctx context.Context, minerID abi.ActorID, return ffi.GenerateWinningPoSt(minerID, privsectors, randomness) } -func (sb *Sealer) GenerateWindowPoSt(ctx context.Context, minerID abi.ActorID, sectorInfo []proof2.SectorInfo, randomness abi.PoStRandomness) ([]proof2.PoStProof, []abi.SectorID, error) { +func (sb *Sealer) GenerateWindowPoSt(ctx context.Context, minerID abi.ActorID, sectorInfo []proof5.SectorInfo, randomness abi.PoStRandomness) ([]proof5.PoStProof, []abi.SectorID, error) { randomness[31] &= 0x3f privsectors, skipped, done, err := sb.pubSectorToPriv(ctx, minerID, sectorInfo, nil, abi.RegisteredSealProof.RegisteredWindowPoStProof) if err != nil { @@ -55,7 +55,7 @@ func (sb *Sealer) GenerateWindowPoSt(ctx context.Context, minerID abi.ActorID, s return proof, faultyIDs, err } -func (sb *Sealer) pubSectorToPriv(ctx context.Context, mid abi.ActorID, sectorInfo []proof2.SectorInfo, faults []abi.SectorNumber, rpt func(abi.RegisteredSealProof) (abi.RegisteredPoStProof, error)) (ffi.SortedPrivateSectorInfo, []abi.SectorID, func(), error) { +func (sb *Sealer) pubSectorToPriv(ctx context.Context, mid abi.ActorID, sectorInfo []proof5.SectorInfo, faults []abi.SectorNumber, rpt func(abi.RegisteredSealProof) (abi.RegisteredPoStProof, error)) (ffi.SortedPrivateSectorInfo, []abi.SectorID, func(), error) { fmap := map[abi.SectorNumber]struct{}{} for _, fault := range faults { fmap[fault] = struct{}{} @@ -111,11 +111,15 @@ type proofVerifier struct{} var ProofVerifier = proofVerifier{} -func (proofVerifier) VerifySeal(info proof2.SealVerifyInfo) (bool, error) { +func (proofVerifier) VerifySeal(info proof5.SealVerifyInfo) (bool, error) { return ffi.VerifySeal(info) } -func (proofVerifier) VerifyWinningPoSt(ctx context.Context, info proof2.WinningPoStVerifyInfo) (bool, error) { +func (proofVerifier) VerifyAggregateSeals(aggregate proof5.AggregateSealVerifyProofAndInfos) (bool, error) { + return ffi.VerifyAggregateSeals(aggregate) +} + +func (proofVerifier) VerifyWinningPoSt(ctx context.Context, info proof5.WinningPoStVerifyInfo) (bool, error) { info.Randomness[31] &= 0x3f _, span := trace.StartSpan(ctx, "VerifyWinningPoSt") defer span.End() @@ -123,7 +127,7 @@ func (proofVerifier) VerifyWinningPoSt(ctx context.Context, info proof2.WinningP return ffi.VerifyWinningPoSt(info) } -func (proofVerifier) VerifyWindowPoSt(ctx context.Context, info proof2.WindowPoStVerifyInfo) (bool, error) { +func (proofVerifier) VerifyWindowPoSt(ctx context.Context, info proof5.WindowPoStVerifyInfo) (bool, error) { info.Randomness[31] &= 0x3f _, span := trace.StartSpan(ctx, "VerifyWindowPoSt") defer span.End() @@ -135,3 +139,7 @@ func (proofVerifier) GenerateWinningPoStSectorChallenge(ctx context.Context, pro randomness[31] &= 0x3f return ffi.GenerateWinningPoStSectorChallenge(proofType, minerID, randomness, eligibleSectorCount) } + +func (v proofVerifier) AggregateSealProofs(proofType abi.RegisteredSealProof, proofs [][]byte) ([]byte, error) { + return ffi.AggregateSealProofs(proofType, proofs) +} diff --git a/extern/sector-storage/mock/mock.go b/extern/sector-storage/mock/mock.go index ae7d54985..ec6020010 100644 --- a/extern/sector-storage/mock/mock.go +++ b/extern/sector-storage/mock/mock.go @@ -9,7 +9,7 @@ import ( "math/rand" "sync" - proof2 "github.com/filecoin-project/specs-actors/v2/actors/runtime/proof" + proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" ffiwrapper2 "github.com/filecoin-project/go-commp-utils/ffiwrapper" commcid "github.com/filecoin-project/go-fil-commcid" @@ -300,14 +300,14 @@ func AddOpFinish(ctx context.Context) (context.Context, func()) { } } -func (mgr *SectorMgr) GenerateWinningPoSt(ctx context.Context, minerID abi.ActorID, sectorInfo []proof2.SectorInfo, randomness abi.PoStRandomness) ([]proof2.PoStProof, error) { +func (mgr *SectorMgr) GenerateWinningPoSt(ctx context.Context, minerID abi.ActorID, sectorInfo []proof5.SectorInfo, randomness abi.PoStRandomness) ([]proof5.PoStProof, error) { mgr.lk.Lock() defer mgr.lk.Unlock() return generateFakePoSt(sectorInfo, abi.RegisteredSealProof.RegisteredWinningPoStProof, randomness), nil } -func (mgr *SectorMgr) GenerateWindowPoSt(ctx context.Context, minerID abi.ActorID, sectorInfo []proof2.SectorInfo, randomness abi.PoStRandomness) ([]proof2.PoStProof, []abi.SectorID, error) { +func (mgr *SectorMgr) GenerateWindowPoSt(ctx context.Context, minerID abi.ActorID, sectorInfo []proof5.SectorInfo, randomness abi.PoStRandomness) ([]proof5.PoStProof, []abi.SectorID, error) { mgr.lk.Lock() defer mgr.lk.Unlock() @@ -315,7 +315,8 @@ func (mgr *SectorMgr) GenerateWindowPoSt(ctx context.Context, minerID abi.ActorI return nil, nil, xerrors.Errorf("failed to post (mock)") } - si := make([]proof2.SectorInfo, 0, len(sectorInfo)) + si := make([]proof5.SectorInfo, 0, len(sectorInfo)) + var skipped []abi.SectorID var err error @@ -343,7 +344,7 @@ func (mgr *SectorMgr) GenerateWindowPoSt(ctx context.Context, minerID abi.ActorI return generateFakePoSt(si, abi.RegisteredSealProof.RegisteredWindowPoStProof, randomness), skipped, nil } -func generateFakePoStProof(sectorInfo []proof2.SectorInfo, randomness abi.PoStRandomness) []byte { +func generateFakePoStProof(sectorInfo []proof5.SectorInfo, randomness abi.PoStRandomness) []byte { randomness[31] &= 0x3f hasher := sha256.New() @@ -358,13 +359,13 @@ func generateFakePoStProof(sectorInfo []proof2.SectorInfo, randomness abi.PoStRa } -func generateFakePoSt(sectorInfo []proof2.SectorInfo, rpt func(abi.RegisteredSealProof) (abi.RegisteredPoStProof, error), randomness abi.PoStRandomness) []proof2.PoStProof { +func generateFakePoSt(sectorInfo []proof5.SectorInfo, rpt func(abi.RegisteredSealProof) (abi.RegisteredPoStProof, error), randomness abi.PoStRandomness) []proof5.PoStProof { wp, err := rpt(sectorInfo[0].SealProof) if err != nil { panic(err) } - return []proof2.PoStProof{ + return []proof5.PoStProof{ { PoStProof: wp, ProofBytes: generateFakePoStProof(sectorInfo, randomness), @@ -489,7 +490,7 @@ func (mgr *SectorMgr) ReturnFetch(ctx context.Context, callID storiface.CallID, panic("not supported") } -func (m mockVerif) VerifySeal(svi proof2.SealVerifyInfo) (bool, error) { +func (m mockVerif) VerifySeal(svi proof5.SealVerifyInfo) (bool, error) { plen, err := svi.SealProof.ProofSize() if err != nil { return false, err @@ -501,6 +502,7 @@ func (m mockVerif) VerifySeal(svi proof2.SealVerifyInfo) (bool, error) { // only the first 32 bytes, the rest are 0. for i, b := range svi.Proof[:32] { + // unsealed+sealed-seed*ticket if b != svi.UnsealedCID.Bytes()[i]+svi.SealedCID.Bytes()[31-i]-svi.InteractiveRandomness[i]*svi.Randomness[i] { return false, nil } @@ -509,12 +511,35 @@ func (m mockVerif) VerifySeal(svi proof2.SealVerifyInfo) (bool, error) { return true, nil } -func (m mockVerif) VerifyWinningPoSt(ctx context.Context, info proof2.WinningPoStVerifyInfo) (bool, error) { +func (m mockVerif) VerifyAggregateSeals(aggregate proof5.AggregateSealVerifyProofAndInfos) (bool, error) { + out := make([]byte, 200) + for pi, svi := range aggregate.Infos { + for i := 0; i < 32; i++ { + b := svi.UnsealedCID.Bytes()[i] + svi.SealedCID.Bytes()[31-i] - svi.InteractiveRandomness[i]*svi.Randomness[i] // raw proof byte + b *= uint8(pi) // with aggregate index + out[i] += b + } + } + + return bytes.Equal(aggregate.Proof, out), nil +} + +func (m mockVerif) AggregateSealProofs(proofType abi.RegisteredSealProof, proofs [][]byte) ([]byte, error) { + out := make([]byte, 200) // todo: figure out more real length + for pi, proof := range proofs { + for i := range proof[:32] { + out[i] += proof[i] * uint8(pi) + } + } + return out, nil +} + +func (m mockVerif) VerifyWinningPoSt(ctx context.Context, info proof5.WinningPoStVerifyInfo) (bool, error) { info.Randomness[31] &= 0x3f return true, nil } -func (m mockVerif) VerifyWindowPoSt(ctx context.Context, info proof2.WindowPoStVerifyInfo) (bool, error) { +func (m mockVerif) VerifyWindowPoSt(ctx context.Context, info proof5.WindowPoStVerifyInfo) (bool, error) { if len(info.Proofs) != 1 { return false, xerrors.Errorf("expected 1 proof entry") } diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go new file mode 100644 index 000000000..74f163010 --- /dev/null +++ b/extern/storage-sealing/commit_batch.go @@ -0,0 +1,271 @@ +package sealing + +import ( + "bytes" + "context" + "sort" + "sync" + "time" + + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-bitfield" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" + proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" +) + +var ( + // TODO: config! + + CommitBatchWait = 5 * time.Minute +) + +type CommitBatcherApi interface { + SendMsg(ctx context.Context, from, to address.Address, method abi.MethodNum, value, maxFee abi.TokenAmount, params []byte) (cid.Cid, error) + StateMinerInfo(context.Context, address.Address, TipSetToken) (miner.MinerInfo, error) +} + +type AggregateInput struct { + // TODO: Something changed in actors, I think this now needs to be AggregateSealVerifyProofAndInfos + info proof5.AggregateSealVerifyInfo + proof []byte +} + +type CommitBatcher struct { + api CommitBatcherApi + maddr address.Address + mctx context.Context + addrSel AddrSel + feeCfg FeeConfig + getConfig GetSealingConfigFunc + verif ffiwrapper.Verifier + + todo map[abi.SectorNumber]AggregateInput + waiting map[abi.SectorNumber][]chan cid.Cid + + notify, stop, stopped chan struct{} + force chan chan *cid.Cid + lk sync.Mutex +} + +func NewCommitBatcher(mctx context.Context, maddr address.Address, api CommitBatcherApi, addrSel AddrSel, feeCfg FeeConfig, getConfig GetSealingConfigFunc, verif ffiwrapper.Verifier) *CommitBatcher { + b := &CommitBatcher{ + api: api, + maddr: maddr, + mctx: mctx, + addrSel: addrSel, + feeCfg: feeCfg, + getConfig: getConfig, + verif: verif, + + todo: map[abi.SectorNumber]AggregateInput{}, + waiting: map[abi.SectorNumber][]chan cid.Cid{}, + + notify: make(chan struct{}, 1), + force: make(chan chan *cid.Cid), + stop: make(chan struct{}), + stopped: make(chan struct{}), + } + + go b.run() + + return b +} + +func (b *CommitBatcher) run() { + var forceRes chan *cid.Cid + var lastMsg *cid.Cid + + for { + if forceRes != nil { + forceRes <- lastMsg + forceRes = nil + } + lastMsg = nil + + var sendAboveMax, sendAboveMin bool + select { + case <-b.stop: + close(b.stopped) + return + case <-b.notify: + sendAboveMax = true + case <-time.After(TerminateBatchWait): + sendAboveMin = true + case fr := <-b.force: // user triggered + forceRes = fr + } + + var err error + lastMsg, err = b.processBatch(sendAboveMax, sendAboveMin) + if err != nil { + log.Warnw("TerminateBatcher processBatch error", "error", err) + } + } +} + +func (b *CommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { + b.lk.Lock() + defer b.lk.Unlock() + params := miner5.ProveCommitAggregateParams{ + SectorNumbers: bitfield.New(), + } + + total := len(b.todo) + if total == 0 { + return nil, nil // nothing to do + } + + cfg, err := b.getConfig() + if err != nil { + return nil, xerrors.Errorf("getting config: %w", err) + } + + if notif && total < cfg.MaxCommitBatch { + return nil, nil + } + + if after && total < cfg.MinCommitBatch { + return nil, nil + } + + spt := b.todo[0].info.SealProof + proofs := make([][]byte, total) + + for id, p := range b.todo { + if p.info.SealProof != spt { + // todo: handle when we'll have proof upgrade + return nil, xerrors.Errorf("different seal proof types in commit batch: %w", err) + } + + params.SectorNumbers.Set(uint64(id)) + proofs[id] = p.proof + } + + params.AggregateProof, err = b.verif.AggregateSealProofs(spt, proofs) + if err != nil { + return nil, xerrors.Errorf("aggregating proofs: %w", err) + } + + enc := new(bytes.Buffer) + if err := params.MarshalCBOR(enc); err != nil { + return nil, xerrors.Errorf("couldn't serialize TerminateSectors params: %w", err) + } + + mi, err := b.api.StateMinerInfo(b.mctx, b.maddr, nil) + if err != nil { + return nil, xerrors.Errorf("couldn't get miner info: %w", err) + } + + from, _, err := b.addrSel(b.mctx, mi, api.CommitAddr, b.feeCfg.MaxCommitGasFee, b.feeCfg.MaxCommitGasFee) + if err != nil { + return nil, xerrors.Errorf("no good address found: %w", err) + } + + mcid, err := b.api.SendMsg(b.mctx, from, b.maddr, miner.Methods.ProveCommitAggregate, big.Zero(), b.feeCfg.MaxCommitGasFee, enc.Bytes()) + if err != nil { + return nil, xerrors.Errorf("sending message failed: %w", err) + } + + log.Infow("Sent ProveCommitAggregate message", "cid", mcid, "from", from, "sectors", total) + + err = params.SectorNumbers.ForEach(func(us uint64) error { + sn := abi.SectorNumber(us) + + for _, ch := range b.waiting[sn] { + ch <- mcid // buffered + } + delete(b.waiting, sn) + delete(b.todo, sn) + return nil + }) + if err != nil { + return nil, xerrors.Errorf("done sectors foreach: %w", err) + } + + return &mcid, nil +} + +// register commit, wait for batch message, return message CID +func (b *CommitBatcher) AddCommit(ctx context.Context, s abi.SectorNumber, in AggregateInput) (mcid cid.Cid, err error) { + b.lk.Lock() + b.todo[s] = in + + sent := make(chan cid.Cid, 1) + b.waiting[s] = append(b.waiting[s], sent) + + select { + case b.notify <- struct{}{}: + default: // already have a pending notification, don't need more + } + b.lk.Unlock() + + select { + case c := <-sent: + return c, nil + case <-ctx.Done(): + return cid.Undef, ctx.Err() + } +} + +func (b *CommitBatcher) Flush(ctx context.Context) (*cid.Cid, error) { + resCh := make(chan *cid.Cid, 1) + select { + case b.force <- resCh: + select { + case res := <-resCh: + return res, nil + case <-ctx.Done(): + return nil, ctx.Err() + } + case <-ctx.Done(): + return nil, ctx.Err() + } +} + +func (b *CommitBatcher) Pending(ctx context.Context) ([]abi.SectorID, error) { + b.lk.Lock() + defer b.lk.Unlock() + + mid, err := address.IDFromAddress(b.maddr) + if err != nil { + return nil, err + } + + res := make([]abi.SectorID, 0) + for _, s := range b.todo { + res = append(res, abi.SectorID{ + Miner: abi.ActorID(mid), + Number: s.info.Number, + }) + } + + sort.Slice(res, func(i, j int) bool { + if res[i].Miner != res[j].Miner { + return res[i].Miner < res[j].Miner + } + + return res[i].Number < res[j].Number + }) + + return res, nil +} + +func (b *CommitBatcher) Stop(ctx context.Context) error { + close(b.stop) + + select { + case <-b.stopped: + return nil + case <-ctx.Done(): + return ctx.Err() + } +} diff --git a/extern/storage-sealing/fsm.go b/extern/storage-sealing/fsm.go index d14d363e5..367938099 100644 --- a/extern/storage-sealing/fsm.go +++ b/extern/storage-sealing/fsm.go @@ -91,12 +91,22 @@ var fsmPlanners = map[SectorState]func(events []statemachine.Event, state *Secto SubmitCommit: planOne( on(SectorCommitSubmitted{}, CommitWait), on(SectorCommitFailed{}, CommitFailed), + on(SectorSubmitCommitAggregate{}, SubmitCommitAggregate), + ), + SubmitCommitAggregate: planOne( + on(SectorCommitAggregateSent{}, CommitWait), + on(SectorCommitFailed{}, CommitFailed), ), CommitWait: planOne( on(SectorProving{}, FinalizeSector), on(SectorCommitFailed{}, CommitFailed), on(SectorRetrySubmitCommit{}, SubmitCommit), ), + CommitAggregateWait: planOne( + on(SectorProving{}, FinalizeSector), + on(SectorCommitFailed{}, CommitFailed), + on(SectorRetrySubmitCommit{}, SubmitCommit), + ), FinalizeSector: planOne( on(SectorFinalized{}, Proving), @@ -338,8 +348,12 @@ func (m *Sealing) plan(events []statemachine.Event, state *SectorInfo) (func(sta return m.handleCommitting, processed, nil case SubmitCommit: return m.handleSubmitCommit, processed, nil + case CommitAggregateWait: + fallthrough case CommitWait: return m.handleCommitWait, processed, nil + case SubmitCommitAggregate: + return m.handleSubmitCommitAggregate, processed, nil case FinalizeSector: return m.handleFinalizeSector, processed, nil diff --git a/extern/storage-sealing/fsm_events.go b/extern/storage-sealing/fsm_events.go index 8d11b248b..bced1921f 100644 --- a/extern/storage-sealing/fsm_events.go +++ b/extern/storage-sealing/fsm_events.go @@ -233,6 +233,10 @@ func (evt SectorCommitted) apply(state *SectorInfo) { state.Proof = evt.Proof } +type SectorSubmitCommitAggregate struct{} + +func (evt SectorSubmitCommitAggregate) apply(*SectorInfo) {} + type SectorCommitSubmitted struct { Message cid.Cid } @@ -241,6 +245,14 @@ func (evt SectorCommitSubmitted) apply(state *SectorInfo) { state.CommitMessage = &evt.Message } +type SectorCommitAggregateSent struct { + Message cid.Cid +} + +func (evt SectorCommitAggregateSent) apply(state *SectorInfo) { + state.CommitMessage = &evt.Message +} + type SectorProving struct{} func (evt SectorProving) apply(*SectorInfo) {} diff --git a/extern/storage-sealing/sealiface/config.go b/extern/storage-sealing/sealiface/config.go index 7ac5f6160..f62911b70 100644 --- a/extern/storage-sealing/sealiface/config.go +++ b/extern/storage-sealing/sealiface/config.go @@ -17,4 +17,8 @@ type Config struct { WaitDealsDelay time.Duration AlwaysKeepUnsealedCopy bool + + AggregateCommits bool + MinCommitBatch int + MaxCommitBatch int } diff --git a/extern/storage-sealing/sealing.go b/extern/storage-sealing/sealing.go index 8feca3b7b..d990cb02f 100644 --- a/extern/storage-sealing/sealing.go +++ b/extern/storage-sealing/sealing.go @@ -103,6 +103,7 @@ type Sealing struct { stats SectorStats terminator *TerminateBatcher + commiter *CommitBatcher getConfig GetSealingConfigFunc dealInfo *CurrentDealInfoManager @@ -152,6 +153,7 @@ func New(api SealingAPI, fc FeeConfig, events Events, maddr address.Address, ds addrSel: as, terminator: NewTerminationBatcher(context.TODO(), maddr, api, as, fc), + commiter: NewCommitBatcher(context.TODO(), maddr, api, as, fc, gc, verif), getConfig: gc, dealInfo: &CurrentDealInfoManager{api}, @@ -202,6 +204,14 @@ func (m *Sealing) TerminatePending(ctx context.Context) ([]abi.SectorID, error) return m.terminator.Pending(ctx) } +func (m *Sealing) CommitFlush(ctx context.Context) (*cid.Cid, error) { + return m.commiter.Flush(ctx) +} + +func (m *Sealing) CommitPending(ctx context.Context) ([]abi.SectorID, error) { + return m.commiter.Pending(ctx) +} + func (m *Sealing) currentSealProof(ctx context.Context) (abi.RegisteredSealProof, error) { mi, err := m.api.StateMinerInfo(ctx, m.maddr, nil) if err != nil { diff --git a/extern/storage-sealing/sector_state.go b/extern/storage-sealing/sector_state.go index b636614d1..b6b7cbf7b 100644 --- a/extern/storage-sealing/sector_state.go +++ b/extern/storage-sealing/sector_state.go @@ -3,61 +3,69 @@ package sealing type SectorState string var ExistSectorStateList = map[SectorState]struct{}{ - Empty: {}, - WaitDeals: {}, - Packing: {}, - AddPiece: {}, - AddPieceFailed: {}, - GetTicket: {}, - PreCommit1: {}, - PreCommit2: {}, - PreCommitting: {}, - PreCommitWait: {}, - WaitSeed: {}, - Committing: {}, - SubmitCommit: {}, - CommitWait: {}, - FinalizeSector: {}, - Proving: {}, - FailedUnrecoverable: {}, - SealPreCommit1Failed: {}, - SealPreCommit2Failed: {}, - PreCommitFailed: {}, - ComputeProofFailed: {}, - CommitFailed: {}, - PackingFailed: {}, - FinalizeFailed: {}, - DealsExpired: {}, - RecoverDealIDs: {}, - Faulty: {}, - FaultReported: {}, - FaultedFinal: {}, - Terminating: {}, - TerminateWait: {}, - TerminateFinality: {}, - TerminateFailed: {}, - Removing: {}, - RemoveFailed: {}, - Removed: {}, + Empty: {}, + WaitDeals: {}, + Packing: {}, + AddPiece: {}, + AddPieceFailed: {}, + GetTicket: {}, + PreCommit1: {}, + PreCommit2: {}, + PreCommitting: {}, + PreCommitWait: {}, + WaitSeed: {}, + Committing: {}, + SubmitCommit: {}, + CommitWait: {}, + SubmitCommitAggregate: {}, + CommitAggregateWait: {}, + FinalizeSector: {}, + Proving: {}, + FailedUnrecoverable: {}, + SealPreCommit1Failed: {}, + SealPreCommit2Failed: {}, + PreCommitFailed: {}, + ComputeProofFailed: {}, + CommitFailed: {}, + PackingFailed: {}, + FinalizeFailed: {}, + DealsExpired: {}, + RecoverDealIDs: {}, + Faulty: {}, + FaultReported: {}, + FaultedFinal: {}, + Terminating: {}, + TerminateWait: {}, + TerminateFinality: {}, + TerminateFailed: {}, + Removing: {}, + RemoveFailed: {}, + Removed: {}, } const ( UndefinedSectorState SectorState = "" // happy path - Empty SectorState = "Empty" // deprecated - WaitDeals SectorState = "WaitDeals" // waiting for more pieces (deals) to be added to the sector - AddPiece SectorState = "AddPiece" // put deal data (and padding if required) into the sector - Packing SectorState = "Packing" // sector not in sealStore, and not on chain - GetTicket SectorState = "GetTicket" // generate ticket - PreCommit1 SectorState = "PreCommit1" // do PreCommit1 - PreCommit2 SectorState = "PreCommit2" // do PreCommit2 - PreCommitting SectorState = "PreCommitting" // on chain pre-commit - PreCommitWait SectorState = "PreCommitWait" // waiting for precommit to land on chain - WaitSeed SectorState = "WaitSeed" // waiting for seed - Committing SectorState = "Committing" // compute PoRep - SubmitCommit SectorState = "SubmitCommit" // send commit message to the chain - CommitWait SectorState = "CommitWait" // wait for the commit message to land on chain + Empty SectorState = "Empty" // deprecated + WaitDeals SectorState = "WaitDeals" // waiting for more pieces (deals) to be added to the sector + AddPiece SectorState = "AddPiece" // put deal data (and padding if required) into the sector + Packing SectorState = "Packing" // sector not in sealStore, and not on chain + GetTicket SectorState = "GetTicket" // generate ticket + PreCommit1 SectorState = "PreCommit1" // do PreCommit1 + PreCommit2 SectorState = "PreCommit2" // do PreCommit2 + PreCommitting SectorState = "PreCommitting" // on chain pre-commit + PreCommitWait SectorState = "PreCommitWait" // waiting for precommit to land on chain + WaitSeed SectorState = "WaitSeed" // waiting for seed + Committing SectorState = "Committing" // compute PoRep + + // single commit + SubmitCommit SectorState = "SubmitCommit" // send commit message to the chain + CommitWait SectorState = "CommitWait" // wait for the commit message to land on chain + + SubmitCommitAggregate SectorState = "SubmitCommitAggregate" + CommitAggregateWait SectorState = "CommitAggregateWait" + FinalizeSector SectorState = "FinalizeSector" Proving SectorState = "Proving" // error modes @@ -91,7 +99,7 @@ func toStatState(st SectorState) statSectorState { switch st { case UndefinedSectorState, Empty, WaitDeals, AddPiece: return sstStaging - case Packing, GetTicket, PreCommit1, PreCommit2, PreCommitting, PreCommitWait, WaitSeed, Committing, SubmitCommit, CommitWait, FinalizeSector: + case Packing, GetTicket, PreCommit1, PreCommit2, PreCommitting, PreCommitWait, WaitSeed, Committing, SubmitCommit, CommitWait, SubmitCommitAggregate, CommitAggregateWait, FinalizeSector: return sstSealing case Proving, Removed, Removing, Terminating, TerminateWait, TerminateFinality, TerminateFailed: return sstProving diff --git a/extern/storage-sealing/states_sealing.go b/extern/storage-sealing/states_sealing.go index e371ab33f..296d92b9c 100644 --- a/extern/storage-sealing/states_sealing.go +++ b/extern/storage-sealing/states_sealing.go @@ -12,6 +12,7 @@ import ( "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/go-state-types/exitcode" "github.com/filecoin-project/go-statemachine" + "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" "github.com/filecoin-project/specs-storage/storage" "github.com/filecoin-project/lotus/api" @@ -452,6 +453,14 @@ func (m *Sealing) handleCommitting(ctx statemachine.Context, sector SectorInfo) } func (m *Sealing) handleSubmitCommit(ctx statemachine.Context, sector SectorInfo) error { + cfg, err := m.getConfig() + if err != nil { + return xerrors.Errorf("getting config: %w", err) + } + if cfg.AggregateCommits { + return ctx.Send(SectorSubmitCommitAggregate{}) + } + tok, _, err := m.api.ChainHead(ctx.Context()) if err != nil { log.Errorf("handleCommitting: api error, not proceeding: %+v", err) @@ -514,6 +523,29 @@ func (m *Sealing) handleSubmitCommit(ctx statemachine.Context, sector SectorInfo }) } +func (m *Sealing) handleSubmitCommitAggregate(ctx statemachine.Context, sector SectorInfo) error { + if sector.CommD == nil || sector.CommR == nil { + return ctx.Send(SectorCommitFailed{xerrors.Errorf("sector had nil commR or commD")}) + } + + mcid, err := m.commiter.AddCommit(ctx.Context(), sector.SectorNumber, AggregateInput{ + info: proof.AggregateSealVerifyInfo{ + Number: sector.SectorNumber, + DealIDs: sector.dealIDs(), + Randomness: sector.TicketValue, + InteractiveRandomness: sector.SeedValue, + SealedCID: *sector.CommR, + UnsealedCID: *sector.CommD, + }, + proof: sector.Proof, // todo: this correct?? + }) + if err != nil { + return ctx.Send(SectorCommitFailed{xerrors.Errorf("queuing commit for aggregation failed: %w", err)}) + } + + return ctx.Send(SectorCommitAggregateSent{mcid}) +} + func (m *Sealing) handleCommitWait(ctx statemachine.Context, sector SectorInfo) error { if sector.CommitMessage == nil { log.Errorf("sector %d entered commit wait state without a message cid", sector.SectorNumber) diff --git a/go.mod b/go.mod index af2ee9c3e..4eb8e0844 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/filecoin-project/go-jsonrpc v0.1.4-0.20210217175800-45ea43ac2bec github.com/filecoin-project/go-multistore v0.0.3 github.com/filecoin-project/go-padreader v0.0.0-20200903213702-ed5fae088b20 - github.com/filecoin-project/go-paramfetch v0.0.2-0.20200701152213-3e0f0afdc261 + github.com/filecoin-project/go-paramfetch v0.0.2-0.20210330140417-936748d3f5ec github.com/filecoin-project/go-state-types v0.1.1-0.20210506134452-99b279731c48 github.com/filecoin-project/go-statemachine v0.0.0-20200925024713-05bd7c71fbfe github.com/filecoin-project/go-statestore v0.1.1 @@ -47,7 +47,7 @@ require ( github.com/filecoin-project/specs-actors/v2 v2.3.5-0.20210114162132-5b58b773f4fb github.com/filecoin-project/specs-actors/v3 v3.1.0 github.com/filecoin-project/specs-actors/v4 v4.0.0 - github.com/filecoin-project/specs-actors/v5 v5.0.0-20210510162709-3255bdd9f2bb + github.com/filecoin-project/specs-actors/v5 v5.0.0-20210512015452-4fe3889fff57 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 diff --git a/go.sum b/go.sum index ecdab7a65..a84e2e471 100644 --- a/go.sum +++ b/go.sum @@ -287,8 +287,8 @@ github.com/filecoin-project/go-multistore v0.0.3 h1:vaRBY4YiA2UZFPK57RNuewypB8u0 github.com/filecoin-project/go-multistore v0.0.3/go.mod h1:kaNqCC4IhU4B1uyr7YWFHd23TL4KM32aChS0jNkyUvQ= github.com/filecoin-project/go-padreader v0.0.0-20200903213702-ed5fae088b20 h1:+/4aUeUoKr6AKfPE3mBhXA5spIV6UcKdTYDPNU2Tdmg= github.com/filecoin-project/go-padreader v0.0.0-20200903213702-ed5fae088b20/go.mod h1:mPn+LRRd5gEKNAtc+r3ScpW2JRU/pj4NBKdADYWHiak= -github.com/filecoin-project/go-paramfetch v0.0.2-0.20200701152213-3e0f0afdc261 h1:A256QonvzRaknIIAuWhe/M2dpV2otzs3NBhi5TWa/UA= -github.com/filecoin-project/go-paramfetch v0.0.2-0.20200701152213-3e0f0afdc261/go.mod h1:fZzmf4tftbwf9S37XRifoJlz7nCjRdIrMGLR07dKLCc= +github.com/filecoin-project/go-paramfetch v0.0.2-0.20210330140417-936748d3f5ec h1:gExwWUiT1TcARkxGneS4nvp9C+wBsKU0bFdg7qFpNco= +github.com/filecoin-project/go-paramfetch v0.0.2-0.20210330140417-936748d3f5ec/go.mod h1:fZzmf4tftbwf9S37XRifoJlz7nCjRdIrMGLR07dKLCc= github.com/filecoin-project/go-state-types v0.0.0-20200903145444-247639ffa6ad/go.mod h1:IQ0MBPnonv35CJHtWSN3YY1Hz2gkPru1Q9qoaYLxx9I= github.com/filecoin-project/go-state-types v0.0.0-20200904021452-1883f36ca2f4/go.mod h1:IQ0MBPnonv35CJHtWSN3YY1Hz2gkPru1Q9qoaYLxx9I= github.com/filecoin-project/go-state-types v0.0.0-20200928172055-2df22083d8ab/go.mod h1:ezYnPf0bNkTsDibL/psSz5dy4B5awOJ/E7P2Saeep8g= @@ -310,14 +310,16 @@ github.com/filecoin-project/specs-actors v0.9.13 h1:rUEOQouefi9fuVY/2HOroROJlZbO github.com/filecoin-project/specs-actors v0.9.13/go.mod h1:TS1AW/7LbG+615j4NsjMK1qlpAwaFsG9w0V2tg2gSao= github.com/filecoin-project/specs-actors/v2 v2.0.1/go.mod h1:v2NZVYinNIKA9acEMBm5wWXxqv5+frFEbekBFemYghY= github.com/filecoin-project/specs-actors/v2 v2.3.2/go.mod h1:UuJQLoTx/HPvvWeqlIFmC/ywlOLHNe8SNQ3OunFbu2Y= +github.com/filecoin-project/specs-actors/v2 v2.3.4/go.mod h1:UuJQLoTx/HPvvWeqlIFmC/ywlOLHNe8SNQ3OunFbu2Y= github.com/filecoin-project/specs-actors/v2 v2.3.5-0.20210114162132-5b58b773f4fb h1:orr/sMzrDZUPAveRE+paBdu1kScIUO5zm+HYeh+VlhA= github.com/filecoin-project/specs-actors/v2 v2.3.5-0.20210114162132-5b58b773f4fb/go.mod h1:LljnY2Mn2homxZsmokJZCpRuhOPxfXhvcek5gWkmqAc= +github.com/filecoin-project/specs-actors/v3 v3.0.4-0.20210227000520-b3317b86f4d1/go.mod h1:oMcmEed6B7H/wHabM3RQphTIhq0ibAKsbpYs+bQ/uxQ= github.com/filecoin-project/specs-actors/v3 v3.1.0 h1:s4qiPw8pgypqBGAy853u/zdZJ7K9cTZdM1rTiSonHrg= github.com/filecoin-project/specs-actors/v3 v3.1.0/go.mod h1:mpynccOLlIRy0QnR008BwYBwT9fen+sPR13MA1VmMww= github.com/filecoin-project/specs-actors/v4 v4.0.0 h1:vMALksY5G3J5rj3q9rbcyB+f4Tk1xrLqSgdB3jOok4s= github.com/filecoin-project/specs-actors/v4 v4.0.0/go.mod h1:TkHXf/l7Wyw4ZejyXIPS2rK8bBO0rdwhTZyQQgaglng= -github.com/filecoin-project/specs-actors/v5 v5.0.0-20210510162709-3255bdd9f2bb h1:i2ZBHLiNYyyhNlfjfB/TGtGLlb8dgiGiVCDZlGpUtUc= -github.com/filecoin-project/specs-actors/v5 v5.0.0-20210510162709-3255bdd9f2bb/go.mod h1:XAgQWq5pu0MBwx3MI5uJ6fK/Q8jCkZnKNNLxvDcbXew= +github.com/filecoin-project/specs-actors/v5 v5.0.0-20210512015452-4fe3889fff57 h1:N6IBsnGXfAMXd677G6EiOKewFwQ7Ulcuupi4U6wYmXE= +github.com/filecoin-project/specs-actors/v5 v5.0.0-20210512015452-4fe3889fff57/go.mod h1:283yBMMUSDB2abcjP/hhrwTkhb9h3sfM6KGrep/ZlBI= github.com/filecoin-project/specs-storage v0.1.1-0.20201105051918-5188d9774506 h1:Ur/l2+6qN+lQiqjozWWc5p9UDaAMDZKTlDS98oRnlIw= github.com/filecoin-project/specs-storage v0.1.1-0.20201105051918-5188d9774506/go.mod h1:nJRRM7Aa9XVvygr3W9k6xGF46RWzr2zxF/iGoAIfA/g= github.com/filecoin-project/test-vectors/schema v0.0.5 h1:w3zHQhzM4pYxJDl21avXjOKBLF8egrvwUwjpT8TquDg= diff --git a/lotuspond/front/src/chain/methods.json b/lotuspond/front/src/chain/methods.json index 12e0e8abf..f90e91914 100644 --- a/lotuspond/front/src/chain/methods.json +++ b/lotuspond/front/src/chain/methods.json @@ -281,7 +281,8 @@ "ConfirmUpdateWorkerKey", "RepayDebt", "ChangeOwnerAddress", - "DisputeWindowedPoSt" + "DisputeWindowedPoSt", + "ProveCommitAggregate" ], "fil/3/storagepower": [ "Send", diff --git a/node/config/def.go b/node/config/def.go index 63099516b..94e87be3b 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -82,6 +82,10 @@ type SealingConfig struct { AlwaysKeepUnsealedCopy bool + AggregateCommits bool + MinCommitBatch int + MaxCommitBatch int + // Keep this many sectors in sealing pipeline, start CC if needed // todo TargetSealingSectors uint64 @@ -237,6 +241,10 @@ func DefaultStorageMiner() *StorageMiner { MaxSealingSectorsForDeals: 0, WaitDealsDelay: Duration(time.Hour * 6), AlwaysKeepUnsealedCopy: true, + + AggregateCommits: true, + MinCommitBatch: 5, // todo: base this on some real numbers + MaxCommitBatch: 400, }, Storage: sectorstorage.SealerConfig{ diff --git a/node/impl/storminer.go b/node/impl/storminer.go index cad886e2d..8766ba154 100644 --- a/node/impl/storminer.go +++ b/node/impl/storminer.go @@ -378,6 +378,14 @@ func (sm *StorageMinerAPI) SectorMarkForUpgrade(ctx context.Context, id abi.Sect return sm.Miner.MarkForUpgrade(id) } +func (sm *StorageMinerAPI) SectorCommitFlush(ctx context.Context) (*cid.Cid, error) { + return sm.Miner.CommitFlush(ctx) +} + +func (sm *StorageMinerAPI) SectorCommitPending(ctx context.Context) ([]abi.SectorID, error) { + return sm.Miner.CommitPending(ctx) +} + func (sm *StorageMinerAPI) WorkerConnect(ctx context.Context, url string) error { w, err := connectRemoteWorker(ctx, sm, url) if err != nil { diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index 1781d0493..1d89a0c4b 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -99,7 +99,7 @@ func GetParams(spt abi.RegisteredSealProof) error { } // TODO: We should fetch the params for the actual proof type, not just based on the size. - if err := paramfetch.GetParams(context.TODO(), build.ParametersJSON(), uint64(ssize)); err != nil { + if err := paramfetch.GetParams(context.TODO(), build.ParametersJSON(), build.SrsJSON(), uint64(ssize)); err != nil { return xerrors.Errorf("fetching proof parameters: %w", err) } @@ -824,6 +824,9 @@ func NewSetSealConfigFunc(r repo.LockedRepo) (dtypes.SetSealingConfigFunc, error MaxSealingSectorsForDeals: cfg.MaxSealingSectorsForDeals, WaitDealsDelay: config.Duration(cfg.WaitDealsDelay), AlwaysKeepUnsealedCopy: cfg.AlwaysKeepUnsealedCopy, + AggregateCommits: cfg.AggregateCommits, + MinCommitBatch: cfg.MinCommitBatch, + MaxCommitBatch: cfg.MaxCommitBatch, } }) return @@ -839,6 +842,9 @@ func NewGetSealConfigFunc(r repo.LockedRepo) (dtypes.GetSealingConfigFunc, error MaxSealingSectorsForDeals: cfg.Sealing.MaxSealingSectorsForDeals, WaitDealsDelay: time.Duration(cfg.Sealing.WaitDealsDelay), AlwaysKeepUnsealedCopy: cfg.Sealing.AlwaysKeepUnsealedCopy, + AggregateCommits: cfg.Sealing.AggregateCommits, + MinCommitBatch: cfg.Sealing.MinCommitBatch, + MaxCommitBatch: cfg.Sealing.MaxCommitBatch, } }) return diff --git a/storage/sealing.go b/storage/sealing.go index 8981c3738..b3d38909b 100644 --- a/storage/sealing.go +++ b/storage/sealing.go @@ -59,6 +59,14 @@ func (m *Miner) TerminatePending(ctx context.Context) ([]abi.SectorID, error) { return m.sealing.TerminatePending(ctx) } +func (m *Miner) CommitFlush(ctx context.Context) (*cid.Cid, error) { + return m.sealing.CommitFlush(ctx) +} + +func (m *Miner) CommitPending(ctx context.Context) ([]abi.SectorID, error) { + return m.sealing.CommitPending(ctx) +} + func (m *Miner) MarkForUpgrade(id abi.SectorNumber) error { return m.sealing.MarkForUpgrade(id) } From 5f8c80533a18b9e16f6414ff1ca71be970f7b944 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Mon, 17 May 2021 13:15:07 -0400 Subject: [PATCH 04/88] Update to latest actors and FFI --- extern/filecoin-ffi | 2 +- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/extern/filecoin-ffi b/extern/filecoin-ffi index 5f9f082b0..941bf0917 160000 --- a/extern/filecoin-ffi +++ b/extern/filecoin-ffi @@ -1 +1 @@ -Subproject commit 5f9f082b03a22bbf0f31adcb2bdf7f539cee6b6b +Subproject commit 941bf0917e155dc9a5886e0508e4f900039df9dc diff --git a/go.mod b/go.mod index 4eb8e0844..98e9d3691 100644 --- a/go.mod +++ b/go.mod @@ -47,7 +47,7 @@ require ( github.com/filecoin-project/specs-actors/v2 v2.3.5-0.20210114162132-5b58b773f4fb github.com/filecoin-project/specs-actors/v3 v3.1.0 github.com/filecoin-project/specs-actors/v4 v4.0.0 - github.com/filecoin-project/specs-actors/v5 v5.0.0-20210512015452-4fe3889fff57 + github.com/filecoin-project/specs-actors/v5 v5.0.0-20210517165532-c7cff61d07fb 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 diff --git a/go.sum b/go.sum index a84e2e471..1c811efb6 100644 --- a/go.sum +++ b/go.sum @@ -318,8 +318,8 @@ github.com/filecoin-project/specs-actors/v3 v3.1.0 h1:s4qiPw8pgypqBGAy853u/zdZJ7 github.com/filecoin-project/specs-actors/v3 v3.1.0/go.mod h1:mpynccOLlIRy0QnR008BwYBwT9fen+sPR13MA1VmMww= github.com/filecoin-project/specs-actors/v4 v4.0.0 h1:vMALksY5G3J5rj3q9rbcyB+f4Tk1xrLqSgdB3jOok4s= github.com/filecoin-project/specs-actors/v4 v4.0.0/go.mod h1:TkHXf/l7Wyw4ZejyXIPS2rK8bBO0rdwhTZyQQgaglng= -github.com/filecoin-project/specs-actors/v5 v5.0.0-20210512015452-4fe3889fff57 h1:N6IBsnGXfAMXd677G6EiOKewFwQ7Ulcuupi4U6wYmXE= -github.com/filecoin-project/specs-actors/v5 v5.0.0-20210512015452-4fe3889fff57/go.mod h1:283yBMMUSDB2abcjP/hhrwTkhb9h3sfM6KGrep/ZlBI= +github.com/filecoin-project/specs-actors/v5 v5.0.0-20210517165532-c7cff61d07fb h1:818gGdeEC+7aHGl2X7ptdtYuqoEgRsY3jwz+DvUYUFk= +github.com/filecoin-project/specs-actors/v5 v5.0.0-20210517165532-c7cff61d07fb/go.mod h1:283yBMMUSDB2abcjP/hhrwTkhb9h3sfM6KGrep/ZlBI= github.com/filecoin-project/specs-storage v0.1.1-0.20201105051918-5188d9774506 h1:Ur/l2+6qN+lQiqjozWWc5p9UDaAMDZKTlDS98oRnlIw= github.com/filecoin-project/specs-storage v0.1.1-0.20201105051918-5188d9774506/go.mod h1:nJRRM7Aa9XVvygr3W9k6xGF46RWzr2zxF/iGoAIfA/g= github.com/filecoin-project/test-vectors/schema v0.0.5 h1:w3zHQhzM4pYxJDl21avXjOKBLF8egrvwUwjpT8TquDg= From 8f42f375cf1d9d90b0b0d6e48ab98904dbaf4410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Mon, 17 May 2021 19:55:44 +0200 Subject: [PATCH 05/88] Update ffi --- extern/filecoin-ffi | 2 +- go.sum | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/extern/filecoin-ffi b/extern/filecoin-ffi index 941bf0917..525851103 160000 --- a/extern/filecoin-ffi +++ b/extern/filecoin-ffi @@ -1 +1 @@ -Subproject commit 941bf0917e155dc9a5886e0508e4f900039df9dc +Subproject commit 525851103fcf548dff1d4db6b5a1a2a6d9e10833 diff --git a/go.sum b/go.sum index 1c811efb6..c281e0fdb 100644 --- a/go.sum +++ b/go.sum @@ -318,6 +318,7 @@ github.com/filecoin-project/specs-actors/v3 v3.1.0 h1:s4qiPw8pgypqBGAy853u/zdZJ7 github.com/filecoin-project/specs-actors/v3 v3.1.0/go.mod h1:mpynccOLlIRy0QnR008BwYBwT9fen+sPR13MA1VmMww= github.com/filecoin-project/specs-actors/v4 v4.0.0 h1:vMALksY5G3J5rj3q9rbcyB+f4Tk1xrLqSgdB3jOok4s= github.com/filecoin-project/specs-actors/v4 v4.0.0/go.mod h1:TkHXf/l7Wyw4ZejyXIPS2rK8bBO0rdwhTZyQQgaglng= +github.com/filecoin-project/specs-actors/v5 v5.0.0-20210512015452-4fe3889fff57/go.mod h1:283yBMMUSDB2abcjP/hhrwTkhb9h3sfM6KGrep/ZlBI= github.com/filecoin-project/specs-actors/v5 v5.0.0-20210517165532-c7cff61d07fb h1:818gGdeEC+7aHGl2X7ptdtYuqoEgRsY3jwz+DvUYUFk= github.com/filecoin-project/specs-actors/v5 v5.0.0-20210517165532-c7cff61d07fb/go.mod h1:283yBMMUSDB2abcjP/hhrwTkhb9h3sfM6KGrep/ZlBI= github.com/filecoin-project/specs-storage v0.1.1-0.20201105051918-5188d9774506 h1:Ur/l2+6qN+lQiqjozWWc5p9UDaAMDZKTlDS98oRnlIw= From 6278bdc69a2fa614a38ac1a24f07460cf3550212 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Mon, 17 May 2021 20:47:41 +0200 Subject: [PATCH 06/88] Make things build --- chain/actors/policy/policy.go | 4 ++++ chain/gen/gen.go | 2 +- cmd/lotus-bench/caching_verifier.go | 9 +++++++++ extern/sector-storage/ffiwrapper/sealer_test.go | 5 +++-- extern/sector-storage/ffiwrapper/types.go | 2 +- extern/sector-storage/ffiwrapper/verifier_cgo.go | 4 ++-- extern/sector-storage/mock/mock.go | 2 +- extern/storage-sealing/commit_batch.go | 14 ++++++-------- extern/storage-sealing/states_sealing.go | 1 - storage/wdpost_run_test.go | 9 +++++++++ 10 files changed, 36 insertions(+), 16 deletions(-) diff --git a/chain/actors/policy/policy.go b/chain/actors/policy/policy.go index 191ffb5f5..113544c05 100644 --- a/chain/actors/policy/policy.go +++ b/chain/actors/policy/policy.go @@ -302,6 +302,10 @@ func GetDefaultSectorSize() abi.SectorSize { return szs[0] } +func GetDefaultAggregationProof() abi.RegisteredAggregationProof { + return abi.RegisteredAggregationProof_SnarkPackV1 +} + func GetSectorMaxLifetime(proof abi.RegisteredSealProof, nwVer network.Version) abi.ChainEpoch { if nwVer <= network.Version10 { return builtin5.SealProofPoliciesV0[proof].SectorMaxLifetime diff --git a/chain/gen/gen.go b/chain/gen/gen.go index af2611676..32b238433 100644 --- a/chain/gen/gen.go +++ b/chain/gen/gen.go @@ -693,7 +693,7 @@ func (m genFakeVerifier) VerifyAggregateSeals(aggregate proof5.AggregateSealVeri panic("not supported") } -func (m genFakeVerifier) AggregateSealProofs(proofType abi.RegisteredSealProof, proofs [][]byte) ([]byte, error) { +func (m genFakeVerifier) AggregateSealProofs(proofType abi.RegisteredSealProof, rap abi.RegisteredAggregationProof, proofs [][]byte) ([]byte, error) { panic("not supported") } diff --git a/cmd/lotus-bench/caching_verifier.go b/cmd/lotus-bench/caching_verifier.go index 5b434c762..55786c585 100644 --- a/cmd/lotus-bench/caching_verifier.go +++ b/cmd/lotus-bench/caching_verifier.go @@ -8,6 +8,7 @@ import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" proof2 "github.com/filecoin-project/specs-actors/v2/actors/runtime/proof" + proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" "github.com/ipfs/go-datastore" "github.com/minio/blake2b-simd" cbg "github.com/whyrusleeping/cbor-gen" @@ -96,4 +97,12 @@ func (cv *cachingVerifier) GenerateWinningPoStSectorChallenge(ctx context.Contex return cv.backend.GenerateWinningPoStSectorChallenge(ctx, proofType, a, rnd, u) } +func (cv cachingVerifier) VerifyAggregateSeals(aggregate proof5.AggregateSealVerifyProofAndInfos) (bool, error) { + return cv.backend.VerifyAggregateSeals(aggregate) +} + +func (cv cachingVerifier) AggregateSealProofs(proofType abi.RegisteredSealProof, rap abi.RegisteredAggregationProof, proofs [][]byte) ([]byte, error) { + return cv.backend.AggregateSealProofs(proofType, rap, proofs) +} + var _ ffiwrapper.Verifier = (*cachingVerifier)(nil) diff --git a/extern/sector-storage/ffiwrapper/sealer_test.go b/extern/sector-storage/ffiwrapper/sealer_test.go index 5af6a78e7..a1b27cc87 100644 --- a/extern/sector-storage/ffiwrapper/sealer_test.go +++ b/extern/sector-storage/ffiwrapper/sealer_test.go @@ -32,6 +32,7 @@ import ( ffi "github.com/filecoin-project/filecoin-ffi" + "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper/basicfs" "github.com/filecoin-project/lotus/extern/sector-storage/storiface" "github.com/filecoin-project/lotus/extern/storage-sealing/lib/nullreader" @@ -538,12 +539,12 @@ func TestSealAndVerifyAggregate(t *testing.T) { aggStart := time.Now() - avi.Proof, err = ProofVerifier.AggregateSealProofs(sealProofType, toAggregate) + avi.Proof, err = ProofVerifier.AggregateSealProofs(sealProofType, policy.GetDefaultAggregationProof(), toAggregate) require.NoError(t, err) aggDone := time.Now() - _, err = ProofVerifier.AggregateSealProofs(sealProofType, toAggregate) + _, err = ProofVerifier.AggregateSealProofs(sealProofType, policy.GetDefaultAggregationProof(), toAggregate) require.NoError(t, err) aggHot := time.Now() diff --git a/extern/sector-storage/ffiwrapper/types.go b/extern/sector-storage/ffiwrapper/types.go index 9c84794bf..aa0658397 100644 --- a/extern/sector-storage/ffiwrapper/types.go +++ b/extern/sector-storage/ffiwrapper/types.go @@ -42,7 +42,7 @@ type Verifier interface { GenerateWinningPoStSectorChallenge(context.Context, abi.RegisteredPoStProof, abi.ActorID, abi.PoStRandomness, uint64) ([]uint64, error) // cheap, makes no sense to put this on the storage interface - AggregateSealProofs(proofType abi.RegisteredSealProof, proofs [][]byte) ([]byte, error) + AggregateSealProofs(proofType abi.RegisteredSealProof, rap abi.RegisteredAggregationProof, proofs [][]byte) ([]byte, error) } type SectorProvider interface { diff --git a/extern/sector-storage/ffiwrapper/verifier_cgo.go b/extern/sector-storage/ffiwrapper/verifier_cgo.go index b31903080..2650fba02 100644 --- a/extern/sector-storage/ffiwrapper/verifier_cgo.go +++ b/extern/sector-storage/ffiwrapper/verifier_cgo.go @@ -140,6 +140,6 @@ func (proofVerifier) GenerateWinningPoStSectorChallenge(ctx context.Context, pro return ffi.GenerateWinningPoStSectorChallenge(proofType, minerID, randomness, eligibleSectorCount) } -func (v proofVerifier) AggregateSealProofs(proofType abi.RegisteredSealProof, proofs [][]byte) ([]byte, error) { - return ffi.AggregateSealProofs(proofType, proofs) +func (v proofVerifier) AggregateSealProofs(proofType abi.RegisteredSealProof, rap abi.RegisteredAggregationProof, proofs [][]byte) ([]byte, error) { + return ffi.AggregateSealProofs(proofType, rap, proofs) } diff --git a/extern/sector-storage/mock/mock.go b/extern/sector-storage/mock/mock.go index ec6020010..8a70ed7bd 100644 --- a/extern/sector-storage/mock/mock.go +++ b/extern/sector-storage/mock/mock.go @@ -524,7 +524,7 @@ func (m mockVerif) VerifyAggregateSeals(aggregate proof5.AggregateSealVerifyProo return bytes.Equal(aggregate.Proof, out), nil } -func (m mockVerif) AggregateSealProofs(proofType abi.RegisteredSealProof, proofs [][]byte) ([]byte, error) { +func (m mockVerif) AggregateSealProofs(proofType abi.RegisteredSealProof, rap abi.RegisteredAggregationProof, proofs [][]byte) ([]byte, error) { out := make([]byte, 200) // todo: figure out more real length for pi, proof := range proofs { for i := range proof[:32] { diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go index 74f163010..8b5d9b543 100644 --- a/extern/storage-sealing/commit_batch.go +++ b/extern/storage-sealing/commit_batch.go @@ -28,13 +28,16 @@ var ( CommitBatchWait = 5 * time.Minute ) +const arp = abi.RegisteredAggregationProof_SnarkPackV1 + type CommitBatcherApi interface { SendMsg(ctx context.Context, from, to address.Address, method abi.MethodNum, value, maxFee abi.TokenAmount, params []byte) (cid.Cid, error) StateMinerInfo(context.Context, address.Address, TipSetToken) (miner.MinerInfo, error) } type AggregateInput struct { - // TODO: Something changed in actors, I think this now needs to be AggregateSealVerifyProofAndInfos + spt abi.RegisteredSealProof + // TODO: Something changed in actors, I think this now needs to be AggregateSealVerifyProofAndInfos todo ?? info proof5.AggregateSealVerifyInfo proof []byte } @@ -137,20 +140,15 @@ func (b *CommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { return nil, nil } - spt := b.todo[0].info.SealProof + spt := b.todo[0].spt proofs := make([][]byte, total) for id, p := range b.todo { - if p.info.SealProof != spt { - // todo: handle when we'll have proof upgrade - return nil, xerrors.Errorf("different seal proof types in commit batch: %w", err) - } - params.SectorNumbers.Set(uint64(id)) proofs[id] = p.proof } - params.AggregateProof, err = b.verif.AggregateSealProofs(spt, proofs) + params.AggregateProof, err = b.verif.AggregateSealProofs(spt, arp, proofs) if err != nil { return nil, xerrors.Errorf("aggregating proofs: %w", err) } diff --git a/extern/storage-sealing/states_sealing.go b/extern/storage-sealing/states_sealing.go index 296d92b9c..9975b1a37 100644 --- a/extern/storage-sealing/states_sealing.go +++ b/extern/storage-sealing/states_sealing.go @@ -531,7 +531,6 @@ func (m *Sealing) handleSubmitCommitAggregate(ctx statemachine.Context, sector S mcid, err := m.commiter.AddCommit(ctx.Context(), sector.SectorNumber, AggregateInput{ info: proof.AggregateSealVerifyInfo{ Number: sector.SectorNumber, - DealIDs: sector.dealIDs(), Randomness: sector.TicketValue, InteractiveRandomness: sector.SeedValue, SealedCID: *sector.CommR, diff --git a/storage/wdpost_run_test.go b/storage/wdpost_run_test.go index 4bf30e3e9..2e412880a 100644 --- a/storage/wdpost_run_test.go +++ b/storage/wdpost_run_test.go @@ -23,6 +23,7 @@ import ( miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/miner" proof2 "github.com/filecoin-project/specs-actors/v2/actors/runtime/proof" tutils "github.com/filecoin-project/specs-actors/v2/support/testing" + proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" @@ -144,6 +145,14 @@ func (m mockVerif) VerifyWindowPoSt(ctx context.Context, info proof2.WindowPoStV return true, nil } +func (m mockVerif) AggregateSealProofs(proofType abi.RegisteredSealProof, rap abi.RegisteredAggregationProof, proofs [][]byte) ([]byte, error) { + panic("implement me") +} + +func (m mockVerif) VerifyAggregateSeals(aggregate proof5.AggregateSealVerifyProofAndInfos) (bool, error) { + panic("implement me") +} + func (m mockVerif) VerifySeal(proof2.SealVerifyInfo) (bool, error) { panic("implement me") } From 578bef4f832a2846ab17107d28adb01983b1d9e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Mon, 17 May 2021 20:56:28 +0200 Subject: [PATCH 07/88] Update gen --- build/openrpc/full.json.gz | Bin 22804 -> 22810 bytes build/openrpc/miner.json.gz | Bin 7828 -> 7890 bytes build/openrpc/worker.json.gz | Bin 2573 -> 2577 bytes chain/actors/policy/policy.go.template | 4 ++++ lotuspond/front/src/chain/methods.json | 7 ++++--- 5 files changed, 8 insertions(+), 3 deletions(-) diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index 65c5eddd3a304996f9bd099df40cf3a6af605706..1aad25356f8daa0c721ab56cf6c7f47c4f4b8fea 100644 GIT binary patch literal 22810 zcmV*4Ky|+#iwFP!00000|LpyFbKAInZQ?w4L}`PBQmK znQucRB%w_KYyy<5PUXG-3Jdo|ASFApjIKKqi^N7O=X`M1?;YwR68KK%ptG~JyY;5i z?O-yborCWkj=6_A2c1jGLk4cHkHO*fMW@@DqJUvaWQ})Q+phv=uk)kZnIJx=T*ts?{-O3o2KB|MmSx*ZoWC%`^e?fm)YpCj5Q!;m=O1p~jHg`W3; zx~Rv4(BUsZw)zWy8#t73bkDbr)xTZf?*KT!TgPLF$*E7B@k{XHS3dU<)1AwzU4Vjs z1~0+)KI!uS@h~8OZCwzK0s_6QLPw(Emwoc1PyYP#&rY|)!pQ^%^UgshTNJ3a7~o?B z2J#g^^2Lw_^5F=-gYC06BDm7=TlVJvi`CpLDt%9|mxurZNn~vJB?0e9Ew#eEmAJ zZ2nGRg|c&-nF#U28KVhI?->$K1}G>r^Fclam;nglBIq7Yd=H5s>}>CB?|kca#KLtB zzDCV_`_b(L=i$t4E^{f3bD}RVj%icid&#CrqUu6pO+fy z_s$IsiQ75Y-|cozAcviU&VPuv{omfv*dhNjM*sfTzdB+}&~@chL+?7EJ_dHPA)%?;k&s z`K5!uki!kdRQx9D)Z@&hWy^_wRs&u;^d4t|?J#!>Ja^s)FqCU^61>YuF<{r_G5{%&7sWUyVsmwt5xmaj?5}jNO-tyi`$_LR;0;C0i7@@-IOgTm%9%L;)fWa=~Dp zqAkZ4Co4Pe`hyl9bU6!zg9{fCj)%D9&>v$L1Y+kfM7TKl40*#>s>}eBk(yD$0d(B} zF@{`#~Zu_NBT@}YBw z+_)F9_g?}?TmZq9C^d#`yp^Rx-#fp_!#14n4*r1-f_pROj^E7p;NBZP*kgBS;{QeO z|MtT6xx-SoZbkOuMZBNKPgq~MIPUXYYs>F2D^ETsD z|6X&d&k;WojIiCIj%qJF(=m<^rQajyqF@n@$ak>dphqRMC_+AAK61oCD4z8#d)PPi zuopL6C2GkOF+nX?n4q9@&>3#;?C$M%x}96>+#%Wf=bN+ZfYM-bqb(!y- zFPI$DknqmIyY8aO3A~b6M-Zs4XyBUmxUQ$3UBf`^ob1mp2#?0RbI{rDbUW|$4+$2d z5Sr_%#R>EzJ{3)c}1U{U5<^E{Jh(KV{k`|so+v~#i$j7J0tY*R;Es) zJ7cON!@FZtBfdjUq6duAKoAci?~kbqrKq;mCw=mPa&!PLhN3`#01S{K6uzggp-j|< zfMcH_-UXQd!hnbGkSFe>kOM|1s88Y^m5dqzH}pM8dR#%TeqjK5i~^rBh6f&!qVZz$ zfKxDrQ`9Fe9!g0UPrhXs(24pCI6;6R;-UaRo;)IyIA{xe86%>GHio{B25>Ybi|vyV66{Pgzr;=?((I_>oi&rf^c;sZDY zz3bECi?fSkaCG_c_z%(I$w%4|FQ_TsvCdfO+T7>YM; zhM%%7P)tF7m5&0!K!8Jo06EE&xF}jV`FU2%u%Tjx0*xw)8On;7h#8JNmH zL6A&CjH1tZ+bbSz{w1HG7q+-Xe2$dVtGN)XZTifyeC`0ZJ9N6fJ*xJouI8vp(TPGO zPeEAqTyJSR+x54!b3_os?eAy0e#y-&(cs-;zbEc5W*4!f#ro@)E|*c>jr22hrD>+B ztI-Il52#WG#cxdjS^~VZxHtY42^Y5)BC<`~rgdf{=P0^Iwce(>zOm9-mp9$BN%M=v z2D&T=5OgJ;h=1qpOA?vE@x@7e2k#R{w4|@_oCe6Mq&N4jnAwh16exLZLV{w(zrU*h zN1~KgcrW>_fsZJtQHE3nCn)V*2JJ;JV^Ag^L`Z8VK}SWUX18;EaZ=RD4hFu9M$=#q z?>m&C86VHt==J^Gz5n-+?9PVHKW`?}-SLFp@!7z=zk~Pttn;Jcoz|jUi*h&BK=a+s z0#h!*EnBly@vMrcs^yMk@)y46ew4Fn%2RdQ)eLn-JWp>+6WXcEf~nk#E0+l-b5t%6 z%)B=Qo2xT(`8fEibMU<>ZPi0)CIL}hRdl1Kwd#7eQD=8|xBl#AbquXfakiPo82Rfr zu^SsgLL0T7BFDTj*+d(ZyTBBx6NNe^=r)xGNvZ?M&iELCQ zCbB{FU`T_6;+BwF`hM$^<}C?Mas2M`TTU z;c;5QwBq{896=7^7`v9_Z!pO(Jt-x`zpC8BvVqfk%*A3D-|;^&WOz}(kq2X0bG1#U z@Ouk1AJ>qNm!Ru>Tt&^(T7f^%ybJu@{4+)A1uA)upnE)qqzRCgU5l5BSaesiN+{z5~~ z)t>?W{Hlikf*&2e>;m}_FZkh6>hHss-~L<~<%Bd9kM8gBzsat@i~aliaWHkpQ`GtK?O_y3$)xStNn4JSWhp@! z4=a>-Snd@o+Vi)E&(Lcsgnd37XMqy)1`?M}2x9CkpcCc0)S3g8HP(b9IlZ(FOiK`1)-y=jbJPA>Jk3oARxS0Y`jH^eVck=VxV&2s9ir z#kZK;{poNZf>8x=t<_x!EQM>q7OFShgT*%4Z5!7l^&xvtGB8W(1NKW;ia5+>T91OV zc9r+IEHNpa=*;UDe6P(S6lQ!gIo1N@V3yn}r?U3Ku+r9^5YI|RybROQtyFL=W!qz! zmStL&X<4RanN|tY+Fzg*udvoN0Q9L-yH-3gV z$J&fP79Cii!c}v8G}n`~96gAdF^hs)A@g5?RHXVhll*6^0guu<1_sc%li)J;`i`8q zV^X3br1nY1EuGNFW4ye4H`5>_0Kve+C{SS5P&*W)_Vlqmd77KcU7*Ux~;l1kD!~mH#uP-jn~HFg|ZS>r`>vr zKAoLeC$hdl&~43|bY&AG&B2_T+3O^2F5pkPp!N#IzURuQj& z4jAGr=6DLz3cj}mJ~3@>?LZkw&58+s#$EP#W)^t&w(e%x!cx{4aFy*Dp#2_sak>^otZs zd5k8!U#2Ef_tn@>LbM;pO@zS3!(pEc5T7AklFukm8`naC&42>+ifg+v>jlzM)TSuN z&b%|ZAolP-IyT28CgOIuzuV9`Th}x3IZ>TG*B}aS>%<`n?|fN8%am@xT=A@)9O2Og zF=Jl2ZWchwbZOwwi64qXIa9x*^m@r%v`xq@izQcsCDS{hVj|<01{cBY&NGxNy{(cf zc^DCX3_a(VPA1pQ4|uk|S+o>c^u|CCT4=oHP-n{m2YOEENlq(HvU;d=!^x=@Q@XMv zy*`mvqWLY4vyysimXLCl>5mkn-PzvVZ^+EsrQ3MkeEs(-Q!;&4h=aTn$l+}O2^*Hc zV$;)~Z>Wk4xR67x%WOr3FHYpG)ki4Ul7H`k0j1s|0aA6>Y4hcJVv(;59QjCqb1_s9 zjWzMVS6bEh`NfGu>C$Ude!m`cI>4dbFimr}?J_^Bm)?{mEd%IRJbcTs5^Vqbb zTTyirQ-xpiVR!!>qG0}UI7ERNAxm|(z+@p)JUQk}ifvew)^Ume1P%(g^i#(k>Sk;W zli@M&f)WILE%$k8Ee&Ua6UnVOA0br*fy~-x|^Cfclg{Z>0&6 zdK_|XkQ?XN$g=|}E96lk=jbn)hrKICHN~z#v`Gd&aTgan=-_)NvT?XgQ~kK*)mkAb zR4ZmjxtwN1P`mrx&JCPNr_{&@`N!%7``wgJ4Q2nJcfiIPkqV-!5l+Q#j>W?sGMQ`f2(s0##sy1Fg_^Jn)CbGx(hKz!um=o;5{YN9ESbVY21iUZa-by;Q)eeWplqb!fmD=wavTyjzewuz87 zK5q}IWXhjgqZ+O5dRbys4OLUO-~85YFny`L-fWneJzWO1lO106HkG!i&~$2Nm%L5( zXoJs8DrwnGvusgfKO;BV&dsDBCuyEr@;#Sy)Oui-bX3~rC*N&5;Kk|QyPXekQoeEz z+HYwteNDAZj$@kOHz^;OW|xU+xY?oAY}cgaEcn@seSdoIBZnh50VDQstoCqZW_IBo ztE0$ESaM|HXVXoTyKXe?JGvD#?be*f>#Iu>dXmA&O~hJ;#G;HZrv%UmqBbH^3DPgJ z@eNgB?^O9Lre!Uf^DHGjQf*Ypk3vlNoBfyIg<76Qa-+CqL&=dUpYgX3nU)bj;81sU zMmXxT9?aTzzjXxgG66~~!~Myk-E}`PwZ>}iA-kkbYs31e`if#7=G$C!LZYH6LE&tS zq*qR2&T)KkBDSO_3VL&$U)c&)H&(D}!yi_Wg39#3@-SUYd>XXy^-onc(5*%)7ggZ~ zEzVk!ZGRC-Pz3>&|1hOi4F#skDOP>R#WCPi7Flsf)aRsUt<-JAD_pMJNWXr3hh){e z6s<1~IGLN;xava*8aEWXSLUvj2lo;)o)(t2#qWi7%0sg%8?$slefKk?M`gX#ktI<+ zE}PK+DvJAx3qU}I?IEMm-%Zp7EIG1 z3dK>FhS1E_jD-*|6)7R_%;JVA`Zi7!g*Z0|^MGZv%K zKYQr!541&6tEd?xiE3t~Ml~|uYgnTV_il+5^sE_4JChYK(w$^i-}0Qxo$c4Pc6v8x zLZ@gcW8riYRlG-DWt3U2INr!G27j*UMb^UU;&O7QNZSl6QRK?_M)7kVE~fsr4XVF_ zLr`lvbR%d?z*mq(r=+DvOQTcXx2X`Yq&9$@qlwQM;1mSvy-liPlj_){I*&@aYL$jM z(@fj&^ilOXSD!eU&Bwq+fr|K?aRD--O>sXa*Tbkdz)B0P^MNE;bLbGjD6%t4-4C%! zB2C3eSL*X5d)Df*!DD~uxzI~$2xPH}fldK^wbuG8kOYq{?*sRG!b(HR9(J70| z0$)bF}Es#hl(l_e}I(MPp93-SF zS0Mzmyerv;YCu4$AaLXl1_*T#7^r8oBsP##0E!Ra0=Hw-ClkcSRBTSUraEbya;D}X z11>}pO2qC9LPEq&jZ_?ca{2N0Q}6#B9UlMj>H7a29rjND-}T|`dw|Fk2b4&pK7|2> z0}la)xIpHNPLK>w)YT3UkoSq`O@=H=MmZZJ0vzahqQ}g4VkSqRMVkZ2RNa+f9GgAm zXCBN7>Haxnr;OtX!NdIJD1Q{421RUtE4&-Mhz!2VRcBFvsEltDavw%+0zdumo zXj%Gad2kdcK8NfQPcWDHQ_msO>m0wl4$uULlL&H4swJ}~=xfM9=Rd^T{%`MS?2!K% zqksSFU!88JG@HU;;Jauv4fgQ9Lm8U!@tlob-{0N)e-Fv-Z0P*+W-{F!Pv{+=4cz-X zc)!mYC#_ar-4rmTzPm+LJ%1*kFg>A-rSy94P5RM`Z6@

-fDhK`xeCY=8+2=BLD= zuArx45gB^3pYqgAVSL&GpQ`=W0@gA^`28Rv=!Xqt`)&ma{-UNlM>&ni%8LAHx&;E@ z@3Aot$o}5l+tFS~0_6Vlc6d+rM}NKE+jIBm{vQ9E?E1UdzrP;`Q)fIyogd#GO?;Q; zDHRhLzcfH8+YjUjw%$(5Q?EUgldC|KHN3s;n)i;=P*xdB1 zw3P;TOlhg1AM_A$dp>e1B-r`d#tbPlutsw1q-O3mDm5um(FsDr5RY%0QqYZ&w?a!U zd(E`hOnc2-=WAve5i>leSMdG}sUz2LLq|0q-PGL5%$G9b$y0Mn>@Bgk#D2Yq{hA~C z3ahXy8HnZ3b&sG2#mYAY^Z5pvgv2ZpE~T$%FTo2#^YW5WEC%iQN@6@rxl{m7mDWfW z0np)ebzbf3EHHWM;+?g*;g|98Dr?+lZjAerBa5y%;jXY6RK)8X@f*ZYFh#{(Wk$JE zEf{;+HInj|byIEjTPO`ih^vqQa6&`E0UbspdJMUAk8{ExK!kg95JZ)t5XO4wn@hT_ z2=rmj-OhG}iH+KzH^@Vfp?0pX#U`47rYu0JHMTG9sNSfiSOo;?M}&;xGd7sVUm~BJ zZMH9}fziMkoGNQ@pE|-78uj9?ZGD83CVu9(28<>!F=onaMqG;N7a8O%}$NMlwLrHZgMpBERBk*?wWBcF zR&KS{qqPLw67WqY;8h9-Oi-Ymo(1h{PQr5y)CR1PC2rBg_aH}!J&=5S#YPKBb+NB% z(bX#B&}7DKY8H@><#G)Jah6uLQ&*)o?{;pnbEhI>e7-rm4k#U#X@h0d5%uzW=L;ss zG$cyfyr9+*f(|88SIDCHI9fX%|IUV|Nd=$Z-|dRd2qvRz+T*$(8IZD>LODB%b}4D2 z05`1^tT!|yMp|E!EVYe7#YBtN;7gMwZTYpy25I-f$p&e+RH<~h*6P4o9kjGM$mA3r z5vcgcWr#bU#T0FY!9gao;8tGi@it6ol zHSH{2mmEaYvHeD-)z4^fCn>ro50MJRE~f1WG8CKhigsBZs`ilM-(?>*g`(+3Q?%r1 zUcsBiN6hn}D~%9`9_e*8kqq+&w9IVra!{YPL)}! zpzr5MlIjaGr0vuLsRkP9cn5gvcnmR-!p>xZ%`Di1MRkz(XB#>2XL~^w4_<{=ZN7}L_UxL@ zq{wHx)9rjfd`5%2^rIefI1Lh^3=Qs3AeJp9BvNHm(Eq*tz1Q#F?)|>^=6AtM(KNY| z$&AqYezadNIVcqfoqBkL2Oc`3f%@E5`4cwmidZr!6}blDrEZF$v%8{s-hsj%coGwx z-qa?zF+M2mc2rL$MKeaJqBwF}q_m3$s_6f!c43Mv*sSE2J3G5oo)kW%-Vx-^_&Q|c z=0Gn~y$a@i{if9bJv<`F)t8Ew;KkQ(rsjJ2erlYTGDX2a{001wjpfCG$q|55$gv5U zh#IR4AXfghMjTLQ#L&m}t5tnl)wflBH&uP72zTnbpg2Q&;2V>wlJmaM1hejlZ=iW* z!IZM)I;=ZSt#WP3aGeljJCSDB z21shWRjZ7!#JZj%eoV;_7aePiwrgCyWXq-!GsxX0%Ne1Z`g;L^L;X3R z!X?CQ3ybj$@u<;Gu8Y&1d{kwu?o1IANy&A9X@L3s&~+0Flpg*EN!;`vkH(#Y zo!!0Evr9VDk8Y6fK?fbp&k(wS98>XpYp>h64nl&CDfL`BBb|dc+wp%1B>Ol;flg#0 zKS9)ND66Sz>U){RY34Ic?FSYr<#S(Z0_jR-tz^KtB`~sA$gI$F4N1MTUuPF^j<`bK zZHbSpZ647yG)hyNZ+rn8jY_H*rUcz{K;-WzJSp>$BVd>d7$PnTw)*6Qn0Oi5G-cfh zCU7pzKnDmwPAAye0-qRCADdQhV8S?p?&_|sYiAEyMt8&VJcR3;PHoK(W}R~JTA{&q z=TcX$I+jIa>m?M1NnZ<2sV*2pg#_;^a1i0%)g3Bp^=q{(R=j4#YgWAWbeQ7nIxS0k z1J*j*)e1dI6D^9F$*QzgeM~#m*0ef9ai2j7s~kZv2{iHZGH6+5Xqlm9hBoA?4Y~T0 zAWEI)X=!_415;pgL*Vwe`lc4ZY3Y~}ok7o=1IPr-k)jUqq#(HHNqVTie~DSZ{_s z4~#;@7=XT@bpeJPfgwVDB1oAFD2Y`X&KP1M)<@Axn^fB78MSz4eesS>D!r;mko6Km zd*`%wPD_I=4YqgAr*M_2vrJlnIlGZ1`-MR9!>!Dk+~GdKB_E`^%YH#oZdnV^!JM*a`v?>eaYUWpu5HBaw;!U%J@^vWU1SV2HEC-%mSB0Zt1}RV?&<4=beD)St>k z@o1ft(1gIft+IJ#cy#ExhrS=sX$y0bO!Z}xut%O-{(i~p*Co&1Xm4p_ zL2iDy>Dc|EU(t_fw}O~vsw?}Ask~wP0h(eOGH(vV(Wm0xe(1Uqt_F$m$pr@--XTST zht%`vOx%+c{tk+*e*N747nr>LvnD(|xBow#j@w)9ZIgpIPr)u~1FlgcX#;C4aIwI}0+$B_E*1({DDZTk!0%OhTE+OT6+dZ<1$6ag z!9elZemew^j3*BPYT3W87O{FJ1|;^nHd_lK&27ZVACU3uA*49JW$c!*uk~n)4BfIw z%Oal;i>%Ut3g)nkexSK>AifnBcw$3VSSskR;emCM{a4AQGh%4b?Q<{(Jb;8jN6d@3 zjIM$+#{K2mmW3kjsTgyn{xjudPvO${1UQ;yCzhS8f}Omp(>N>odV5da!#3fT3Qeqr zxm1c_mC?!(9$gSF)y~C2Z83?zqL+niOgGh(n|0Hn4MHZ4 z-hlFPjtl9dAx=enXA?XHadFF%krdQzu}@S_(AxsHsW_-~s0Rk6z**eLWGzksA}kCL znA1?jWzZkcC+HqJ+?&hZ(!&uY`52Qs`4iJK|jbBcNs3WbjT~uGq0t*W)9_XsP&Z8~LzSI1` zY^d8mL0P}fbNcp4p0eLdJQD<`H=Ejm=V)8xXCb zq5(H|^-)!UBmarFs{mp%=y`}6!nfE2C9z6Tcq#|Nlw?oZ+vp>fD-aToI(NVg1L;2) zAtCh=6U;I6zyNvzmKhn>s_!e{w-IE(53sXpto0PQKoN5lHu3|69ax!{buA3boV^EI zhMn!5?VSg)kG05b^~h{(=G5XIi+fgpdv>>L41LcLKb%7B!GVX46rXGbf=b$HBJJXZ z+;~|Gv>4Qs4`4yart*4m_$DcAp@9@1(P0Yh{K6top^{l@W$g^}u^Ye{0W<*2M}$99 zp^ObWWlqs^>}E}Q(Ylkk8bi%<#9OEJ%v4{Fpnd(O;@2}gx=qB=hNLZz?x{qMdp42Oc6VybKF<;V zjM_zdPuE_8HAx}lO7M3)6f@6)xJCwV!wC(E44Ha%ajDStRF%RcEpL>+929Vf3Gh*X zsk<5rN5)~m0rIIc&b~mzO+U8$%d z?tTK$+qhuC9E@{_$J*~^TjV~glmZa)`SzVO~8=Ie#h zX-1-2l@X>VAd6CA9Y|eMxARFVMo+X}R6lMQqcz`G(`!L`SSB}@{Fomw&ET3-?;1me zWx{f-C?-eFJfYk_EVg@i)5`X=I$XzMBZXqjj)yTbeGm)MVw4YSLTclEtR)2A9OtE{ zV1`pPRItXF)+pi0Vw~L?ql6nYp;M$HYPJG8=?2y#0$FZE<}t|96%#}wc(_=mDfHC+ zR0lng88g%I$dpVJIu7!qG#3%3;S@~Z9a46WV@O5_h~tog2MIXgv`++HQ7vfKZ_$}5 zpA-WFJuu{U_3_Z5oBBkbEOA()$YeACgmRT$1CNN*G>!wr#?*6DamhS7LxDQDl%PI= z9ubHI`O_BQFub zBdPwlsHZu?X_@OJsk$K9Ndj%wI@^iGFc!mD4D*yRj70?&6|4jm)EL7FX10t;MXLTo zC>oXq18vftr0OLa2R4JgFE;;foHt}0QlD5Zzd>Anok#lwxVz<-mS3)fUsjkOUcp(PE&g?doC6p0@_y+Y`P(M)oVrSJVcK z0!o~P#APi{Nu~|+b0S@1kH?S@wssHq1*_H&xnR#VlqR(if#*3C}s0xVVVz)mef)BamIZiZ8@0bV3vbf4z~Nc zLLV+k{4w=B@ujU;mac4mFENx`=Nh-S4=cp8LcCT-ze4FH57;?sdF;=T>5(_`9?s#^ z3o@ie1VRmCahRdoF$$0jS4SuZbHo88bKua)L~e2w0WZ$KxV5P?6H(CPu# zR2a7I=e|x4*yE6gEe(`a-G+wAshVa4AnL>tfb=D!a^*@27+o?b;Q`fXIJsz?-~g%I zN(W72hi6)B40@A(pKT2Aw{;pJeQP>&o2L!R)o&t?`_&h9w`A-GS2v-Ba)UWXh1BH@ zvj`POX6CZGsRW=_G*`A3%_Iu>!=8cRI*ULC%j;+PdrdL4aO#4rZ@oz`(Uo*3y4n`2 z$5+q&-T1;K{*12r(yp^`d()$fuA<5sG;?`Umzv$0jvLZAD|@iA2P=Dcma>P{>Medk z824Jw_O&*$;x<3n^62vSI_m-rCyvL+x$F6exNy)`I8@rysxfDrUuHn5*92u2hGhY# z1)LUeTEJ-mrv;oAa6TU3T$@ZrI?cn+$o;VHh_TK-FpZ}^L$5?d)fg?xW|l>YFBKRx z#)46&*2&%CKa2k?{5=%28&3$W|iL?u}f{M#8OVJl46PCYSv>h>D+N;QLDRl#*>*Zl+RR&;(FM z@bkc8%NlIi?exYp;6vznZ9*QUDz`N1ZweYMy0z%mqTA<)ZZ|y+*Sk6+Tum+8`*f!o zFgM5j`mJ2GY7Q`Uf7UV=NeOm^fGjWQZY@Xq8IG!!wOZC{S?iN#ts5V>;+=t^d@meK z@N(Q$HSu7kOHI7u@*}cVOIB-TwN@5z#gw1=~6&?UR z4mmn>9Ko~yK+WTg=IS@shu&hpheXxpkw}Yu*Bf%>$^+G+N?+=S0}ngk4$bq)NJn^z z2)HteKMtnOc#1kdzCD_B&<00cG4ZM}MpXye^R6&K!5Iua-aNQPNgI_!Y#^g~ zV{+4HqDV`eRv?$vEpR(VeG(PLOy)RIt+;@aSDgBxA~BgoJhLPu&`;>pJ;3Qifv6Dami z>V<@y*N8xm&(n`?aWwfiXc&^HeG2%ug6ZdMewg zCO=3*UDC8OG8-JaukKN?iUJ4ssMB`1;<8ja+_R7*AusbUFR_{a)kLYv4CjTLmNUeR zQ_}^RnvQU4Hbf&$v+C0#bc@hiBJ}k9#oVZgcq(RGhRdtCmr3D>4Yr#2vJXtyT|xv4 zhpMb%3VJn|nhcC;acrWQYI+J{Y}hROc^1kw?lwk!O>356%cgBaAhUVe_85=0l$vJG zk0r*I7_W>N>zj7NQ@q(kd@~)b_I4^9t(2Hu%H3UQ=Gjs&oM~b~CS9qeNV4iqp#N2^ z$2TQ`lZ}|^D001;C@a!cGWuXXMgXeEvoU2zX%m>%N`L|4GlZ0yfX}GbbI3B__~L~1 z$?eCJj|ZSPrlIGm^ywVlMM?^Ahvp1m!Z?EN|9@p#1#8LnGcDQbq{((=)+`ZoIh`rY zb!9OV@ipJx+1-27k$FDc6^c*aBe+7LcuJmm1U)E!bhec)&WBu3*y{}Y-ukui%|tib z*ta1E63j43G(F8~En$+ssVJesWlV8S95|r|IVPiC==)wv6WBsCRcN8a>UDs(Zs{E& z%5%mHB9wGj?U`L(C1d}J?xDkhlav8W6{15BO&EZ@U?#TJC$|C_>P95q2%msz-N*aq9pv0RC`VFG3A|{N56FKp#Gf}V#&<2*P*}Yh5%$I#{WA0Z_ zo!vT%`(rxs!xj<9(v=&Sseb)du&COrvZ&In%bq@l+z$;YQaq@|0m#x7ltt&zL-%;# zA>h!-M34t*vzoS%${1h?+EGr5QpQg(Kp+SSfT95!j>fXa=PNK1v`N!4KcG|WA{UIM z&zUsR`Z7iYIMDNu+s({4m|@R5=#y9Al0r9IUyY#)XdsB7eCLFLwBK^XVt_G%F2F-c zCZk%Ssi^J5@=IU8v@ut%2l?gHkKd+D#QFq)SKtMqvNidEm;uI-uk6?cXh;JDrW5t8 za^|)Dn6^Aak&^JGYD*~%^_>xf{wRPh5+4D{Tuq}m@owk|s;?##sZB=%LH$YSd7>o% z1|D_p7!ck`kiKp=4)i(>UF% zF3o)82vFt56=pDSb&CN~Kjv~@L_^R|}uoQ4x}2 z?XFz7xmX6#FF{CbkjyHvXifFjh$bN0V>}(%!s;Z&!;x$jaTEZCI1@)G-YD^ShzlG@ zzyK)>z$svOBCtftXfdgU32^anh~y^`#Ah_P15$m!N}U9C}VAT?@briYvETC9y4> z6cv(#>Lzo-l&{Sr`QZ;!rbn4&e6M*3fRX2;9rfS4+#8hyiaX<*O6dX*)D& zLD$SAE*eKOxmaxX*2a#@TH#qMJZpt#t?(=aSt|%)t?;ZBp0&cWR(O^euN^VAR(Lh~ z^-55*p&Izzx&SUGNG|deyC^uOo(DM!pm#(=;-U zlIc=)nYc`guJTB|<3(NKw2*GfjeKfy%GQz!5qIB`J$0pm1)qgc%p^oaurz>I}R znpkmor!{acf*xS_9~J&!iUK9@WgPO50mu@$E?cdbHn%JfD68FTogb%GRQpI7MXfY# zMMe-6=eoE#s|y-=h2z*A7~Z$Z*`ot`HpaTG@#1E2rM~p@Y8vFl0QLVVOk}FVqB(b2Ral4$+k_vepWUx_5zqN3j+) zKo0h?H0hP9@hH@yU<#zc-oFuBfTy))EoBAQ8x;1b?ib`j$9g*jSHj?^mj$Rtf=+vM4E@{&(;?jF}6O523P0~yaG65 zc}Wm*S-P@zfq@S9w5?}hL|ti9E+?ypVBn0e*yzQ}t$0zS_3aorcgoQ8ij6KQy$k(l z_4-77LNq_wk3POQ5mRz$f(E zSu#B>@J&t1W)MI`jb(xYG)2K&%MCF}d#hMK(Vr0Ke^HK;vJ$KIPgt29`kE7jI_WO5S*WO|cyoPOwe ziJR06^Ra+vF(jt)A`#F@M0C1;vT2^Hj zq2-AvrDQ^5$bjQPEVi9bXD9~I*Yd}q@?p17+Cu3~hSIO=l(b4_dU4W{R_NM~p8_@2 zvw^fdDlKS7fYDT2cP0r=v)KS`)>BK!Y}V7CLaXYugI6%PLp;**wjx-WCYGdH;trc` z&WLX12Vg+FGF9|VKlCNJI%Q5kXA%itQVEf@fk`<*i{MXB+rO)pEt#`q&cHgn{`8fV zFw*469jr~l)gEqR`VlqLRG<^}Xl=tUclO@YDPh#&o*?M8yjP|gFuz!)Lii>-OtjH} z*hXD0xM8LWCwWfEkOs=l6~F-vxxQmcx1fh5rj|UliU*zDC3PX83nk5>LhruWd$T8= ziw~j=F);S4`B_ z<1pYlW3&`nb~~rU%|6h3`#Qi5x<-M1DQR`l15dwDgUUVr(N%{`Oi&LwoCYP`UqZ$| z`L0;)+%P=I#_2~tRv6$qy^am4MRVWS*03-=OX2Y1;oJKC- z@uqD0sb!n2X&dQ{W-{9HJ>SBaJat^-*^j&%%cEUHC#1nPES?#oZ!8=^qgttBxKyb1 zjR#V7%4@}Qu3^BjgMCQ&Qg|xe&XTaI`877zxe>rh*U5+iA(clk+W3%}MIepOu* z7Zh#?!YZ$itHTFB>1w&@H1cho4pZ$4eY8t~d;=1hYNjp_hIoH*Qso%X`u3Ua-B{e0 zs#bFw&AOY3Tj@%JP^c++FjI-w`wtX*!V>R>VN`2w%*FR6B-&n%f9B=5ne5bFfb9j? zUVtC<0=!{RuFk+j8f_gq98Zy&^tRXCLOaW;Z@+%~Ra%N|sn$LcX&ZV$hSUt)l?}%P z-4*I(;GM>GOuteYfgCl5A8Leb{sx@?bQG z_pUA|*^urAcMyYEw3e-r4raT#X$Zdtk)J|1vZPr;Dp0r!oRpta)bN*gylqDJ36k-n!JhYr~ zR#{Waae4LR2#+odlZ;l4!I-t@s%AvoJ`JSsh7JK#?EDuNNx2oT*XhJ&n2+56&Iq6Z zU_K(&NXxSLMJztO*DEG6erYq9R?xcNYi*#!kv&1s^)PAcd75ow**lDm#wr^#r*G-* zO$l3EC*xcihrc8v;Kn6#QENnXOTR4rTI>{*+RbGiLD|<2dlfms%n!NXT1FrTlxI}haGzupAxU9C-n zIF+RWH*<0(sxL>(lw>b%%$$_zu1yJ6gp*VE`^$w%h11h0wRQ-4i$p~e2^w$Ul7jEaEv$K1$_m!MgTNDhp zhUo_Bl16%fA*DM95Trw-yIUy-ctJ{HU`Xj2x}-ruhHj7=%ApZ~A(Rl z*StY)L!%9N<28pP@gcvp@P2@FiOsz|AH9*gqyxwP@k)$Ac$?mz{MX3RV~q?&)-rx^ zbWRuS?ErAnp?zcr#)~qS;nh)8HLfeIU2Pro&BO;Yoa-h%DTL_@LOVHM!}A7>v-%Uy zY07&;Pc-F?ygB}d{H4d9@k4SGJAdw+?uhs7eWsYT%f9B{scHf2>I!eB3~0PC0$DK#R-Idc>U%Br_4nT66e|#O_=8N6_nblUF>ru zmy9e34aA8?Oj9CY*rC9-?0X}LNmI7&?keGa^xe*}%RBXkT$M_TkY>WGqWzXSJ_nhL z#qWZdbbp9_4=C}u=bPf3O5D~r-#Ra`*%b?XJ65^pXui_4YHn}Dnb7&;QI)5MvT81W zkBl=eF~^Q}!1L(lu1al4$h7I&T^%Hz%oRpmFEtCGyt9)Q1C-46V${zz>F$D7; z^?W=gpt#ov4Oi|^Di2@zE_u0DJNo+VtSdIV~aQ7mw zlHc;i2rR=B7E3>z4*a>}#q&dnN5z*qo;-F!nnK|{0}eWyjMpgl?>eH-?SDkH&oUf zKCT5ieToH+lOVAqnG72MBKhSp$)jBKbkbEfLcBR!eovS7&|0Do~ppu zRA!^#YPuB);~M{q&RJ^VI5-W z_DKR_uWT0;Cy@9*Sz6^(Jd4&-A#x1COh%hC`E$GGu1Y6cXkKM-7OJLw^s<;#@A2Vz zm4(mM#Gi$HPiXs;vlETG(~3I|?nO8}Z=yqh&r5?Au@0T)9YG0l47Wd!W)!kz89)e! z7gp-JrSnm7QY^t?10VugTf>rOJy&dou>fJsvK=+7P5i6LXpc_Cz7VU)s|V$5Ci2NH zWra8j$Tmp{+1xnx5dE0 z``tMgF3*MnD*UU4TJFf1Fe>{nXU(4f!tES1PjruBLKF^*A2bpN|Gi)tf19T`i2LtZ zc*nBQq6DbK0ilg+oedcI%Nb4Kx!5CW(P=6pSELKy6+*F9pszm|(BMZ7>j9ZVDC^m?!3EtsFt?cgZ$c*WPG zcOryB(lK2^xdOjbz`Up+=2i8gsF1Zx`4G>;<=OKMr=#ZHO0i(36SunnUcljdAup{q zRZC@UuDI5y^;)}QwZYpbmg&bn{SRx)A|rjO>uYMPbLNa4S>?ojvLlQUQ;B|iEFiAP zh;3t;z5%h!(HfOqD)(U9Q$>QW5SHE}PLQ(zOGN!SU#Df|Tx6A(`ejiFRdkH#3xPr` z3AU$feE9rmt0^ROi9UDn>CG8|HJdr)y)*4x zxjk||g;1ZcHvq0SZ2CdEh(VhrXsD_E9pI+qK?XGuk^d0`m`1lLi5=SjoVoFwk@atX zxnz0I$i~@oV!#~vq%-^ORJjyHRqOUxINTSRbJNi{qTMqp>H1T1!AS3RXuB-J&ap2V zAq95j7W8^@Q=b&I0mNr`6bEq3oW2fIax6?Vvx0^hA5yz-GZ%X(h3&YL$Q4>?J2TB1 z+Oso84AOQ{hIa>47i2!WUu6KBfxs61PyjFT2Pt-B3X|36*s_9z+*?U|UJw4Xt|DEC zEm1NaC2ct&4(rn7JEp?WM2wbKu_fS2Y|8asYf&PG%iILoVHMD1XP6SVkDiH1320Yh8|N@EodGV^NX_p!&SR3M6@AV62%ID_(vkme4|dN^zI;d!pDQnzo# ztKm@WMD{Z%kXrp^CDrId7hF(`RcKpV{cl?eJIrh$A*&`chhaRc$8KcEVnrI^n$cSe zhLKa1JHM6$Oi%*}&{;KCj~cfAg}lXDEejc&bN>E6pX_^K)`rSKaPgK?Hlu~&g5QV$ z3d`^CP3Zr&KIp!Ei|ZWuEiT-fqr+j}x&qO$s8V#njP!VH3la?+c}$$`eY_Hk2pL~X z!^PZGEpci1_=j<9CNX*6{qpwVWaBYJBhX2Czu;xs;mJFt(`)ywjm`eMFwWE;RL|EX zd{bYY3Yir8cOg1b0kxrM`zc|N{;JLU6!|2cG~zcrpzh9qSZT<-9~PaI@`1)DEtwk$ z)qb9B)3Q6=9=*5Du?j9BDKee#*`3Wo8;#-&htC98RJ=pOmG%Z%5Cj^pyI0#kHyILC z*L7I}4=)l$wY^%m9~W@wzgegM6MGtsiT?5OG=^CG%H>%utmjezgp8y44_nqz9&l1; z`;P0NBhB92&-1JTCv)C&xa1Y$N4&Jvq4%^Ba!@4a!+V#5Yb)wG&iV(3n2Q0Oak@?) z9UU5bSE+%@6;d5eRf=&=LY_D$12Ry2Xe+F0v|)6u^w4)cpN-seE_shmE$KrJ;}`8I zmUkxeg0t>5J}t3Mt%x`HFI^i7$cG%-y9*MVy;R&Az7J-GSr43$vzba5I#3zRRHya{ zW0#mSU-px+b}pEg zBS{8u^N6E1so0*i&W>Jql9e~)inELrp5$SLR1!`|@E^^B(Ca6zfL59TS4l3$I#ZPe z6)2GEYBKOQEF<}DWCS__p!05}SbJfxuZ(Cl$YPq=G$~bLMLfMt7#8X+!{)2ZUaPl@KsV_ z7jK%QSF^(8b0cK)f12KO00uJ0d^sid*gN29FUD?ph`QfPZ`4%NM_(=G$stHh>VI~7 zdqid5tmT`;M zIOA0G)TjG%1$vQVx9WFmtsUU+>YpJcY|uIuGR8hL75gk=vnua0FnSZH#ST;5VR4+k z5cyIw)zs-Ucyxr}#ZMLZGvs}Kmp^?s)Tw8Dw@pj^G+x3}XxCB-3AqgCCWODR+8NLt zb>is%B0TL!)+B|<6Soocz-t=gbjNGDB2jEbzeefgjen3EN0Q?Sd+rno?QjI-fEefO z+^|@;-k#*x~ty*DVrO1ja=5vM%QMcc;xIC!z0*|S0YGHpFmaSJa_MXw8$ZsMjT z@iFPH%EXclNCzO#KV&b}^8lSz!-eEHa3J=tUM?!le>c&ZE!>VXyI}$@oH^+zkdRbk zmVdWL^TF)0ue@ivtmiZkDJiQ68F4mK{M#tAH5mIkFT{NkcZHKL`*10USe?3X~+d0Nvm?F7_oSKbdQnq5MIw>P;cLl z`f^{0cm@m>`DgV}y6G$ck^>&CLh6XFIM{q?yWI&)%x0-VLccuaRL@=dpgv`yZN&J) z6j%+k!=zGp-c^w&!cVc;Xx1QpSGSC$>INf_pDz?D-0$SHdtSfFmB z@b3HqA{BJFRGEZeNhysg|02B^lyE!S_61hORjE4Z+{r&pDpN9*B8dDl%%t{Dg1XK4 zgq;IF|8sK7wY-+EaZ`HMLtFul}xJ>amKhsLfoOt*e-K*nPKAd=`n?BHJ(bhct#^nE-Loq6q+Y$?0 zD@=vS^cESOPIDaC*EQmRrF3PUlfrE)609%Ta$xEdiu*8`*x=jf2(UigJYoP*fbK=e zV7)Fm7)%kr`7@E?^5mH@xFoV5sW6gynWq6ZT3jwx9z!iwn?bwCv(~FHVS!6B57l84 z{U__}<=rnfkr(T^B zL2UL?TRi(_f5~*B#)Q}?a6+iuMiV-{I~eDAVLy)Z@+BH^{FHpw{lr7vr!ngi;bj<+ zIPW(p_LHCh95nhu0V;NWM2zG^hGR`NUWPF(xTwRm_3ck<7YivG8%A&3q4pkKfOlKp zIz|xT8o#%d{MK8y8_(_jmE4a#@0b4_2f6A{x|Poo!x;Ar{nRE&hP!Ycr3GHWZdH3k zjgYqnl_6RZx=i9l1>v$pM1=LA-r9>bZg_iA%G#$dpLuuRfmurI13(%HPKy9kgeQx9 z8_Euo--ffr#jU1g&%byK!Ykz=2QST{T+PxSIT~8jhZq7%DPQN}d{Lr?`bVgyN8;aZ z4GN|ruqm+OOrP?-lsl0SD+vVy%2+#p+%2N1eqbR5xI^sjkuC#`S*iYjs)V`R**$Sg z){8x9|K~5>yD~|iI_sI7grA*++1CS@*`kIY=mR<+CER8KllTl1=(!ZQ9Z${qGA*g5Lc?)LQ6&Z%J~$=+Zp`K9sx+eLunRqC0%fE@;+WT;BF2aqZx=s%!< z&jZ~pqUIRSP90`wklc2jJ613uFs|aNK3i33U@Mtqea#n``4}4T^tY$BfccDMY5V#pF`ZPk`A|fVf(|b77&f+x5(AqVMCKi z{kbJMWKy_{XBP#t-pn&CT`e@baWCVJKTHgwkB)ekkVD2c^~OxNC{2jn;wyHW+yf?m z#+WQ4XB|$xMG0m!fZb82c@~=CPa$545gT~t8WP`@;Z-PAm#Swq6H~Uad`SfNt3oc0 zeyJgPwl`eeCJ(d4mKmY-<||CTf4h#q#T<*xJ3E9zD=_AA5An}ycqF%UIDu_8IfE^! zA;lg?0XKz=x>ID3K$vUg?B-00s4|*Z3R~D#{=g+omO_*w7od2zk0NH?^Gwp!A=1zS zY`6I4XGON}wyB(%KB6udKgvz~njB}XJfc;x!?y2iLQ)}eo9R>|gRzOS7$_-fMzoyt z*@+$)JINc*G}%$rT|7M{pl%k&u=Rcs(ei6|nSj*bC5nD>gG)G~fBlKDd}l6w&P~N7 z&>ggI4*Tj~L5Lwwq^5C1boqTXVv07PA8RA7Hd*qF$L`(MsI#Sh;YelEkVD_I8Or&T z_ZKx~eREGw;7)j;+ZO}Zl?;Rx->hC)ZWYbxAcvwx)umzCojZ%&hz*Sz=OD-idkfFD zI`>Jo`Xv3wC~3zJ8M=u3!&uGSJ>a@oC+p)Xo!(LQK`p_rrL<^w)Iv=Dr&Q6(>FehQ zJAboyAZX5Lc)N?vJ6yD%V(;~`6z;uIHF5d+gKPvL-DJ4$>XkbFVh1xJ)n}pxUFxi- z7Q~67FUY%R=m}*ySwd$~lR>Y;d__1!cn>1N%LAsfuf<<~S=P{zRh#*=J&|NGHn}9= zuCqc0s@K8hkGEQF=QFCPRpP2@J;0|QnAtWW>zyF6Y)$pWOpZxC;H9sV_E;Gxi`a)G zWL6WXZ^VhL44elti$|`;zx0ZxWi|JC)&f)onTR`5w!D1u1{1C$dGX%dBopx0y@ZKg zcyzHA$n<=udzo`jpc%8uU-3tdIA_yg4m$ZGrFH8Xb5e&~efQS%%c)U6k;89HnP`Rx z=@*O%2I@BWV}Ey^DFsqhO}ImrIE}^Iq$1*n?aiM{{z8(NI7-h>EU>OGH<@n|SPnV* zwA>Tm$4GIU+E_6BX37~t6H7NK&9(Gz-&8hc%^TV9IlGWsH3sMf@V;G&#}?q!@>Vo@ zAF)!-S>bWY>%*Kt63>f=vBN?~iW8ZIP*ZYyLK8QfaHbaZ%9;uAL@BJ~Q_U@d*>77r zr^irNJ-*iV!5V&kQloKIN~_)$sq4=EPIkd~v}Yny_JGgn8W{X=;&E}?AAAHChQ_ui zN6MvIuk{guo$6E$LszZx_6*_X#+m2(R$)8NWLm6)#zd#H`bwbfX)0 zO+L;wx6u;JU~iM@(8R2!*joLi$>*WX2sS@d zewM6(s7+Qc)g*z`Z{qj~m@e190{)R)KjLV_)XB;^sh6Ywpl=w%sm+WtD~-u&Q1=Lb z!=4*V>xy|+sVel`{)b~5=&Q*xB|OAc6aS*xHSg5)Tg`g%(P@*}T05_%kFPs>{mE}p z&9<3;L|w>Y>#`42Rs2T2Cr?%y3S$`9QmJN4&ew3uRF|1MuvOdFXU(8{h*rrcd%3Kh zUIPl#E>EAOOK4F21z`H}cy0QP*b})N+W+4JR#_;BB;BgvO=QzDRO&;>)T+n-BQq`~ zpTqeGwOU^o=J}6jMMO-e>pY)d-0|{IU9dH^E$QuW>tKQSBYDpwy-9-j-M7XZdL73Y zPvg9e(4v@G$AW&JlR)wsy92Dp3z(*>3xK#u6vjaey5lbY^RSA%AWr^u*U78eJy4h0 zHQ^*tVLo$>ICv_o-S!Th?lH;!Bs*OY^TpAzGdbu<;nnkHxr8xGozU1;Tg zNg9vpQi`DV3J*?DEd&W_Y+EocSSwG2TgGQ;pNPcFkIbHTmnv&m3u!( zk5GzYsz2a=l5w;^Zw-z2y(n9y<}spYv)UJ<%i6OqxzNXc*YhZ-SUx>HpykoW(Hz6R z$cd=O$KEF>Ebz0-huNwq?%&w*BsnBlP3?)Ik# z--Q0ETJI3Z0~L8rB$Rs^9*Mn70O$FGuizfhP|=^u_nR?dGhULCD6bu8SfK)Ok-x#O zC$f#>=u9HLuRop8y;1>gOq@s$ef}s8;M~Lbk?2tq&eA;-a)0-%SP)RT@@lI6gK$bN zD4zvMAO{koaKQccV*$eeL{3@4i3lU4aUsatG2+1cR{{*y&g>im~6|vyzZx+h@(_fMo_HaQY9&|CeUtUo;2A$&gc{(;!r46aJ^!|BV;1+QD zSLpz5>ZQ($3R{UdkNm7kX?*UI7AwUXfRn8nEF3hU(6Iee`_IO?LKxr$#MBq7k@PJ= zF)$i=tR_)~r``xc#ybEP#_f3u!zFl?iFx?;>-YLh-$@|n=>3=H=f;M@i-pBPiN_@> ze4;RMWMCVWz{{C?>DbWcDCwh-?X9Ymfvi1HEyGV`r>2j>`7f%mbr=pA(d~d!dJ#jU zm6ROc^-<^%u^R`1cn}*gYOvJry+6dJ#LOM8++?Epe(@pm^ahqu6WVh$s9>*xV!7nR zJEg7PoTw;za8x*f#E1h{GeNIlA!29b$lbqlj2$7EZ<;pqniFg=02dSO4WcJoBtoYW zS05Q*XvD&Q4b!uQ1=q$Gi>`Z&mQ3(x+9CtRkO%Vku9QI29y_!6EStMr)_9*X*I)1@ zxOowQNEPJl_?H*)e-+t%(Iu=s-#oMkKc5S;%+~V~{i8w(LvpJlvovH*EKzF5szCPK zzzS&sY01?~<+0Y|{8K2Y@r7%Yd(@mr@)*Kto)hN@{upH?uPADskk8G@L)25-cl_gXD~=b5ow;WAuw8?SBEzVO=iCy zJBc6>twM%$pT+QrdLT?LSwHFxDx`900)Uce9n6R->ZC})7>8`|4N_F5P~rsDlHUfs zTiM^v<_+m59N>g-4+0?3VMs;1uQJ$_fO88g9#61E&Xk-ZJ+m`*zAxbPadiRTEz}$E zbOX@ud3!#62l%}^eG)r4AqpB05(lRwWbE8OzrJ1!(Ru~?xwi)T_(9JK2KhU=JNtQg zxOf1NW`&488TI&I0fK(r=?^Y5hJmRJ=61ySfxNte3p4r!2Kt0X$m7)U$#FUR72U|} zYRX2KWK_6-zcgm!auvf;HZ7>N+#Z8pDz^PAGpdylhoI;k|biZgxo@ zXSVIjhJV0r;oQTOEIy83VcepJA(^aJo$=(G%IyTezd(zr5xCB4u%c5=O7(qnTSlQnZ}mg^}vP*l*Qm+(5Osco;H{FvOhT5EZEv4vE_Vutb$ zr%v9M_)1o{a`N&>9p8c6n@EoZFwuz3QuOpTn>z322AhEsH@X^|qb!i@MaHx|Ow$x|fS1d&E2?=iaOroR@R14tzb)16As~ zB$r;^mA69qe8 zFyd8p;3#izw>MuS2_pl&blri>Bs*V*Jw6Y2HCkU*blHI;hh;V|2yqbGelXAbHAJ5X z&W&Hq$V*b)C~vS?eNRRKZ)P^BeT{CKVP-P-%|@z`5qy4RIBQ!SFYevws1fgdfrMKr zTQ>y_o*wUnon7RgWE;DjNY~fY7t1-D#UP<=qS3-s#r3wFmJx29d^Gi&M%C!ZL&NBz z9vT6^2oXKXi(k9ke6F#X8HvwsvM9&Yv5F5{WwQj8)lHlP6K21Z|HP5=hG*)N%D^82 z>UV!559W!gCkO&O2UZ-1vV@ri5MCHBLFtY_MD8QAX00^^HlZ3@@#5VmyWSDt)m#K* zQfv%mYdSRwH~dO4!;)hs>`(jVEslDbBQcMBbK~@B*bB`@4DZa#S|UobOV-2R{47dWn>+NGK3}Y<>i!Sn0 zEOm6{9Ak8b`*n6xWp3_rZA-ujo>TMQU`gzm4jz1eeTBqgbEkO6cYu5R9c~~6ZzJDW zSk3XXu_cuaiw_Ey88&pQ;X$5Q=UM07 zhjBzIj3yMTjT2Jh!eJ1X2;J2Ppr5^zT=*%o!h!C}XXckPS<*%=`>&&2zgtq@$ymu{ zvBX+vaIY=%NuS8V?VD%v6Q=X(N}HO~Y4Vff11cudNpE{HBWorT|Me|}`HEj`IqJ(9 zfY&_ll&T;5r98QCe1Ul_Gd%AT80_t$&N$C>XWZ>wSk;_2L8bh1Dtp5j=pb;OJxaP{ zLTWu0#q>%^En&0Hb*KY{l=U6>=&Q{c^)^ky**V?BHttBj%#d3gFKZ0w0YoPXufZGk z@-#n3NWZ<&t@Tj4#B4S6Pk_#&wTe(bgH=44W<#zck!DHR!I2?T>Dd?H|U2VB_ z@7RVKOP5=Xdv{1*IJl#q2-ZoOyiNw64M!1P|C5^c{f@dW^vwpq^D0AI4xy+9YEVVD z@#g5DCf!ilqY~ZOpSKo{klrL!Q{7Z4+jPGY#* zr|=`*Rld~ich}afOIwwd>#?}!qkbsO`+6zTyw~v*gl72z(`{W{Jq98*{DfipKz!7Won{F%`{iuvb zd{!1Vnfx&vJ{O<5ghw{axwOq^EN#QA6s*4OzU<$TrHw%ksU(oRqmP6+Y^HF%ZC7J^ z`+gx^50 z$A4DvV=nmV@N;Wm0{F-mAg|PLz4msuvHhhL+iB;VqaRKDZ(qK*ge!ZkQ?$$DsmzdO zj&(3`3N!YOUr(&omve8483`{>+#+croysR{EtDNGJTl8rtxF|B6`shHolcBJcy1m> zaXEmL4IwV0X>FS>XA*v_|W zK-1dTJb;CdH~sWS{FrFHm(OI!!untCntcWc6t5^acLhS&$^uso`N**W1X&b7-1>2u zE4Mbn>q_PH>q{0>T*q+FLF4FYR(14`T>8KLDh#+ahzO5FDF_igNN{+(mV8=3#zYC= zJM6{z%tEgiIheZXJJN!PhF~)ScMcgD26P|LdHIK`^8sBRY69u_M&s*a$jnf7_G^z) z7Xa?%D@u_;* z;Yv0PN7GY!lV5@PXJ>UY>j!a1el)-#T-^EDV}6?f*46<1O~vQ&b|IyNWZJy0)(Uny z2xGE=T=*XI&5C{cZo!aXbf#s|?#hSNs;$$JcFmUT`9v3-?;Ju4X+eX}`&6`?F&DyB z@DjOWJfCu9Cf8FcTc$>2>+IkKsUwug>u;c+1_brL)P-!kI~1kyfRk(UT#BxEm6uxq z*Jz+BzN#kVTcN3y^Cvc>S$KFulJZc$la>&vr1+t7Ih^xz(D_#0Wx#bJDPhUC8p4*2 zj*bV}k82-SSEY~ZK7llRMUzM`zEL(m?%=RO;lN4U0SGaX!)CYJzLO_LRAkzD`i&+y zp7gxH`vB?OB-R|!o)ur_Jv!hX_`#Gx99w=Cynu<-JN)VOLZ8s$cuwWq_lwze$Qu=T zQ_*B`uLlw@9s?8FPC&j=Z%sOvgom^*y#lSxL`DMxE5n zx$%T{SH80g1heYw4)3aM<3K-ttQt|xnKf0pf z_arxZ&8iCR+aLKsQH=H{GDzg@Dt0<(Kk0gNRfH>|)??($r2D2vR~ABgT01m6Em&&o zSaPjMFOwWf6az6#$UqmpP)zr?EerUR40^WlZZDheZ&CP!s8B=?8ONa|Lgxdy{+@iW zU=6B;6ug_nwd)2C=!+*ck4U$!XYP+R2{lZ#_a;p<-qaL~*(w+yg~ZO(lulQIe8LF^ z-0_fs=~69^7+|YLZRbg(dubsjUn8%))nZ~ZlGX=X8pDs8rU?nH-{N;E=?5DHQfO`> zK|mq>91Zg5D)3cNQWl!in)0qO(KnvR`4{sg=hK38)C@t0upl|j|=@mS7xd3)7| zB8)h@m@PZCmFiI6_>OTpxvsq@RX$*nPHTcrTVc}2#fgdrlY+TSy3*v)ML*2kB5IIG z;^rf|j1I5r>?1;?OWnr~-c#3EGV*!kF*`^|_wG2=XHI&*=W#ZywaQKnFW$Hjr6TD8 zpGTg4rT9n6-D)&lMq_d_agsS>FCAOBV9d~WdKMh7>a>yl(Br>lBGo3Jh?v1uz z_I!%h+&%mEvu&3@HvX&T#@c$yA>>PL6lKA6@jIdbX1dmBrf#Uds8seY`6y?UBd+re;_`u)d?NBfy!-N>?t%=6ys4pVC9+0uT(^1x@%nqbnhj-((q-j^ zt8%}|HZuve$E*X}5wM2qUH=!Iq+p}{{}79gshz7_RZbH@+>3Yk=c_Q^K}$?d(?aEu zq^$<|Q%^car_*)3_P}&@S{s`IIQ@o9zysl25`V?K(lwgGDTD5~4)=yQ;k8O}tjAQ^ z7`{%#4ime;oF2EHzQPp)tKg6>W(Rw(%4cpQLRH_X=^0Z1z>upG$v_rwXc#T|M7`zS zTF0&WtxW?$a|uQn!2S&yHUy%4yDOKMu_At|>aMc1X5QKwZN@E5kyy*I^DO0{`DNU1 z*lqZD6O)8~J@wv4L@ND!(h9<4P0H?3oYr89 ztC3p{g6Upn(E-tK5(@>EUJaY|2w0dv#JREdID;}|8x-MSZL)~W99VfASO+sfo?v|n z7Y(!m;K^TL?*-@3sUU2W3LPBVT9!!*P9fWwFSTTWkPJ5BGWHhc0u@0wm( zL5Bp$3;J)rs?2ilGWF8OHe>d~jLt8J&m5M)?;VO81*FH->%{;b-deZcID&%YMRux) z2~f_+nV06QC4P`tfTa@mg4a`93ztjFS8nFoDkv;)`)BEj6Fj3R&?a>yNQ8~&;pK3{ z9K7#8RMdQVHU>VwRs8(l-TnQG@v&hWT-<@BM4a?NZ=9I`2H-?N!Fuc#qwZDQ+W`hX z5;P7{A1`)G^+XpBf(;dZ%zRUv1_EMjT>%~SUF@;@%@yzCp*kg7LgDY32>js?c_eOt zje=QKgjN#6S-NdzuBX%hBj|YbWvP#=f7nL)J>K$PD*0bbKEBB(u3?txnD`wCG5&sF zjL3sS3dN7Zh2(lDb+Y~Ss@1SvGhNR7|AS#1^M2_D zy6g3)zFZ+`%)GA!4PJz`EidGBw4aWBZ_At2YuJARVZ3PDH<3~$&csJc`O7FBlDXVl za=EH;h2Ge}`c*#}!D>%2>^_B777}wNUT#q0JWr+HLL9b`(aUuzEi$17IYlZ#k$3TN z^~HOV4JLj)HI(=^^L^eu%*OYLd3p{Tne%Oq?tXlPZTY0=ExjFYlvG_e^OsqYrDR~y zSlk6OhSYJVR;sL6t=fpPVuPVO<1_4t61tA?Cb5t%dc zv18#wx={54_JSb!W#Lc%tpjce-O}H0CQBemkb=0unkT`ASzLWBi9%Z|z;`usD!w$$ zwx@n|WY&Kl1uD?b$goyBB+yETynA?h(}iwkHrqn3#eI9@YGni4aexRH1t%RMg76b9 z>4z07RT+q41%suqd=3eL>y|JM8AC_e!N=HvD-$dv4~oVXPbLGG?chgc|$uVhLBb&Rxk^xt3CZpKvr*Ek4n)F@FH4e%ZexoZuqisQSR6QT> zXaJM!QzNPy@0&+c8;98Bx7su!zB-*m$uvp{TwW1zZnWr@DvT{0Xu10X7t?Zdw!F-a zNmENq)L;A+*SjBZ3sUum5%G3{oi!bcSxg6`YNbct~S zx86W7NE)qPhD;{TKU5T!X)d(%#XV3=-?ygdC0U4Du<0jmoczlm0M&Jp)qtS_m_WyE1kN*VrI5<7LRz0JewBglnvQQW&t~AY+<=}T zP=o8ip|5PgI5rFk4y=Nm4C0T9d@;3h<+-!rFb)}6CODQ>j2KQ%Q3KPd2i>1YUQZ52 zPM=W2Vla{TP6K}iu6Y>tLvYoSU;Y+SGm0FCbU~-YEUdD#t4`^D(LOHCAWs#BTo}zA zb)Lw#1AwFbvi8zcSKA$`V$O4ti;@XJTe4D{u3zoGTK&cTS?492z6QDf#aBFMdT!})<&<`mfT#jM=SMiI1C=-awXb|H6Dy|;S#a5K)~FXtBiX`gAWkMy8q{3 z)czbxIrh*6Jd0mWZlh|dIp@iDWwmQQiAB&TU!Pf4N@Lj=(_Osp9rPA5K^hm;#yMBz z9#kk5-Hdt5Eg0VvPu+L7Yr{Hl?AiX-%oZ~79AXH!m;f)mHd~lWEYY6Y;vAHt2(v7Q zhe}`5ppd?Plq z@T0HE{sDc`0Yd_LiFBc8_3(c(4w|Ro8Y7b^kp3W?>m|~@$hvJFk-7G0)`w*AbYK7M-?LG)V>rE38-kgNMP~8$eIb-JveIw}4EddTu<* zgALgn&)Q<2a4&8MZdja_@4OzP=BKYV{E0KMre)ZB=Fx^<19cSX;kIlg7#jr`1>4U2 zsk*(#5O@iFHa9o!@dvq>!ZYSPc9xmJ(TTE{kaiSD@AuJ(}+^>TRr|IvzQm4ZeM$)kBYA_0X>vz!m{!7u>^St{}qSf;1fBP@-J9@R6JVa>0wJ? zRKXEOCY)A8=?sHVd+{@?*zPh$6^TrR5$qxF>EX(m2&AQW^ZJ=7V_qaEvizf}rpoTj zH*L)3ip~B5_6Y9~!N?Z!l+Lowv_G7(@m6cR?vLv?nV#0g18k?5Fv)F$=!9x};(7LF zSvB1sen|Lna%VW@P+JJXOV`}|w8%-+CYi|GWP+ynkhda=-SGC=H&;+<+-eSmVo-&J zY8UoFN(83~Gud3syAUFQ-lpf1FQJm@XY~iVtNk_Wy2!?-qdPh!rfAG@(RBPaQ$X06*AT3`tw>Z7(Uq9&5HIuS2 zlW20)(EwE^oK~!7X&%#Du}Pa~kN`M?sRHz27{(9@+1#f8gzLMLY6T*SdvBS0>#h>7 z4q~o?P=9x+Z!be~*AOmNv*AFofpvuO$hMNhIT{rafwVT5L_}dAzDXlNDx@73ZN8;K zv7t=3I&+CYQShH;q}pT-&w{av2cT6AIts%a{H#rF8%;_yw)U6ugwQM=WVJwfdeAF9Ql1*=aW@|OuW0q#ZLuhfu@f%3$uF`fR8+dhtd|w#P|c0e zV~i87!7ZP3reA5u3h}|yU^seUySB=9w*DDAYmWYf2?c&cSLOSX4Q>I3&J=Z^37tuk zAaf|rgIMXg-`zyCieIC$X#N-nG`F{Ryl`_*b7M3?(&uq{SQ-HObcXfV+@|#EUc4@8 z3_Vok)^YSmo4bB7ZCnr-h^u1!L+(D?bNcBJA?b`7H#CTlwMnKMZ|a;8Ql2)G2H8F* za%xur))D={mu?q+(9_2Uv-y2nD4$IIYq8AgevaO*!^rsx<{T(o@rN=B1Ydc`$ItM5+ODJg9rcNttYy zt6-6TXl$R>CU4*Qz2}VfET{d|L+4#lR)=yp@6X`ijG8j2RIwlZNB;Idp_7!BSS0evd<9p6X=>Af_ zLpOJGPR+5-MUSyMKx>uf(#CM|(mH!z`|?amK}1&~A~#)PC&YYk3gwubq*0V3X`3H#s;g>ZBE~v!# z3II6XT3;2`FCRu&ilVKU4NFH<>9?Wz;UonVFR53Q;{xNI%juLQF}#)(cR6W&RKC#>9N3r0ou z8Z<}w*(}FrLTZYS7-7BC{qqET&KKsi7o`(>wWsF~0^YD)VS02NO$0}(H4#8XdRQ+% z$uL&F41|yDwm1CeS9@-Bdqx}$f8{)TT@NbXtf^6XHTw=7A^dfcz1x#-RhbyS6M67% z)hSqW4rryNY2_qT|19hUo#Y!gg4eS-E1!-dBGQHbN*z~c5CJFSHw8>vm+rG>BIBUA zAq${Rt2?gkjwhRjOvJq+u(mYm!{{%tLgH`mh!G(XUhZ;ooI!}3rupVMt@8F}a8u^Px z1qZZmb$gZ}2lpOodYwG$m@hn0S0t~jB(EEtG-d<|*iP}**SIXO$=Exdt<=^FqYZgG zo+XRoh+XHTo}tI$0t*3N4D+F)^SsB1k>Z4C{&cSTlEpPqm-(F=Cu*zh7UE}(c|#sb z*c%+ry+o`~5?p5a^}3OlXozj!dIM_iopApPG7h*)LBm9w76`4w?l(ajMi28E!4;n} zZ8cD)p3H#6#xTcd1lhB+;v04FFq@hD+jbVIT4fS4@CSz=Vixy6W=YXkN-Aw*oFGy} zitoQ%>Vex@gN|n7BIAwiKWZN^ehcHerg7dvh7oG7Gn`A}r1SYAw;}4~{JW6%h|d$( zZj;s~r>lkpke47J&i9>4Fw_%D8OAFr%Bx%!7u#(zzZiRUCg!#n zNaehd^DRERLfTF7JgcS`N965QVXWcU%P+6Gj6p_EaO+q!7$@Sg)Ax}Jb}{$9&8dt0*PL_sYF2QR_bJU(iKCl ziuqdKxjGcl%f@K_i6(|}iZgLH1lbD5`lL=aCXgd-`)?Eu1r^eMl3QdwR(4GN*{NtQ zw9kCFF=6;6?iNw%(9D?DNSSbmn(pGr(1FpAX zjcAGRKo^1&%|1v$G!$5UyMSBKWOPdc!W^kF__vPk+p;H47NfZq1vX9KSuC>_TC+9! za(*+%gKt5q$^H1M#LRv=i|$YVR!oHEq2?t07_)$W_E37o9()2!EV!wr(o6#Ar?9g( zlj!ZC9L3&>hh5$yKH5|>5B}Jk(@?L(^b$XAY*p-QGfL9Gulj~Q@DAJ-Qqae-r@3hP zjH5PAvcJXEe15u#&nOC0Z}i)WlhDVG3&QBMnZ{UCAZJXuAwV(|WEWD?nOSlYWi3aG z&WV}i9+8c*hlJ2}i>zNeLrma+dlRt!C4r_6L5dot?}T=J(DNvtsu=12gNXRz@5(QK zQVz~SEl0G>&5COgq+GOUx5J{6J_;x1CkiQqp7FiVGwL){4h|0mM|ll^m7@P-DVbm6 z);n3^f2L)uzsWkLUx4Jg<=_>7zr|LN~{uMT8#c)o%N_D z?_B)bQ9^j$v`}TtTFaq@@wQz~=k}nukb86AX-?HiDYPQ(U>rgGuXK$Na#=d{!h1lG>ksD*m7-X{BL}uEV&bfdEPlFVSEEO30 z-fBF-W)ySk0UGO_Qz5-2zS{j3lWrKgPIq&#H9PU&67UyKq=W+z)hP&7+Z z&yYg8xAO8^$op|iU0p@Becgqhdu>j^I*)d>8zkQ^fc@LGn|GIO9CqJi4-9)i`{>W& z5M`q21Iw=Rslo?mJ%khV^ch%IG233l_ImzZ8@RRjP#&Zs?#zATjV_KyCyBj(JaU}r za=2OE!nb2s)zQFuQ-)YE`Z(3gApY|luOx%lxT12uNl2ni3Uv191YVJ!gxX4m;)8QH zglM3IB1WfA_@Bo%aXLtQK_$9=>I05l)^F zqQydaaBOh+XV4S}v~5FXoMZl&8}p8JlLw`6(9;CPC~Y2Y<~(8vo&$G{kL|0e)1u5evMDszU{P|Io35DBTqC{eQA*B4_W=r1R8vbSqf8*Aj zITOV{))hG6oJ)`#!(U#u$((%doxreucBB5$a4r~2%51Z-2A)UgB#~x9A8-|D-wbI8 zM^hY+G}-AOH+CSezz2cixd#x|9IrI!h=I@)g`o^l35`$_*$1$dy3${^#u4V*HF2CM zy!p4W^#6MufLmp=?O4EBsoWPQcy+A2yrtYk?6(-EGWpF4B26`;eVR<2C~GSX=uQb1 zSKU$FYF(j1L-a_1$fX12q7CAwpX50FGl&bmCf#Gh)Fj=B`LpftEc&FQD-WQ5JEy&* z6_7`HIupx?CDF=dt?0ENI^!WW#$c1XD8RiHx+uUew>9K)i6ul5>$^YmnC8)p#wfIk z_QxW{GdLJM68&IHKRoK$X;p(qeQl|k#VXmzQrhMZj756HAy@@uE|L>(|LOmG5G2~s z&ytsi?eqiWmxyu3^_wnXQ9C+q-1^2M=L1uT$;{>5J$6_;0~b zAkvqB%)3^&y`Z=V?nY)Lt`_QIUa6H`h<-MIf$7y83I@X2Z*}xb*Bxr_)ct9&rUvo) zExFe0x>UUzug$}V2>r*JLb`zD;A^`rHM%zY)*M-P5qx3Xp*ev_2K+{gK2MG!`B-BV zH#qOqs?GVTJ_c*IljS14NQVOQPJ<_IeaDt1!OCDkYsonX1YE->T4tU_{GYsyRVjpcs9YB~ zCX^Gp4X9+uc6)Hz<*GacBZWL=l6XLr2)7_RsLV?;pyF3sgE9#smvWZ=G&XFfw(7N- zOZCV2Z=f{k=Y=H>kbs3HYn=$Do`0LRV+n>(p8JXOd&o2d4nWg`D|!t>@G5O+?&QALAaA;ll-r*N|`k{(#4Zw>NZzXRAFOpXx z8p$L)qX=^*jpLl=a_)7yM=E!SrMjramMt$y?`+L`%EGs`u&J5CIB6g|!@jU+l=`gh2p z?3eBEB)Er{{QI>>?vtZe?fY`bC@b5%Hw*by-e{Vc|vqD{@Jyr)MVMTD58W3_Mr#GkPj5 zpeA~S^sqWOQ;xx&Fgbqdp5T9ma^64t$x4DCGLLEKe9?|TRwj&tZeb`G({z8gxc!Mf z@sg-v1|v0a$PdgO1^a7JLyC=VOXU>#cFK$P;ilneI3ck1v-7u}k|+`X+3g=xU#H*L zgLxor+3$@}hMN{QI9Ac{O6^x0CD;Z?Du0 zoh$)=q2bD|tVxy2Z)#K-F+(h)d^5hcnmCm-z1g3Q3|7&&j`r9G%N}9v9%3!c?B{}n zqMm>ra?!b2qrtfK$_lhmi;nJW-!+W*HRFothS&botupaOx5{cd%R;T6RTBf1NMls_ zRc#d{0}+?a1;<7D=MLygD{YpTZ9g_75m`4X?3MmnOV!zYzH^&iMBOYzH6^v^Y-! zlbt2{P6ZJDcyv0=CDcGnyl3(JF?|f%RXN>S*n`E3dfq zdzMo#=oNE{mc5A;yaR(#{y4F>)zb%rmo{SaJoenU+D#0zZ@fB)fm5uSBzyU+19kf; z0}WI}XVO?^cv$X=!QkabwN!9m2n~$oW|oS=;Tu*+LBGqnqE2$yJWz^?yIynFfEq5J z4JSlC5MDO8{qCf^5I14qG_nr=pqL8-OYn_U6h%^mxd)dBBIOlqEd1F#m@z4>pV7Sb zG&tpe{DLEjBPNdte|B}WQU1>mwxG7Z`ZL!fvR1wCzSypP@n`Jbo5Tw?IPOux>2;ff z3J>S)$x=YYJgGDrgNJ(-G!t`~q-2p9w&ydg^2X3D zYLF#VFqs&9LSCta|0V!Ky1IcH<5WQ^tMros9%2pczr#`l<99KB6NU?OKm)?j!Rj%Z zd9<2$^|itGN$TWiVaH;*Q9o)1(y0r2k97Aqwwde&e(`)LjR4hMB-ZVg@x)ISwKp=N z5btUjy)Vqoqx#8kT{;NZ*w>v#a1s7vU9X7reyZJQ)N z_zO|W={WW>H7#zu=`U##7;ZFuq9XGWihWYSza-#dr7S^(LHkw1Xt)(4Em&0jV%XnODEZJ1w_%_5N8(U%K+aDe{%dKRh{m)_kDM zrhwg_x4OLG*owGWv!z8pwN?qvM2k74wt_3fekigsOUXzi#n+>~_q05cFy+0=!^D3a ze%$?+07{1zu-E-J7d!uM(ACf@9;bnT#lz6++v9_-a!WbZ^JO_0qm(qmEyE)!U#SQf zv?()pA?giZ=Ost$syRnjP;bIJLM*>q_}8*CtXuM_tZNoVxrNpUW9@~7>!`JjxScJf z)pmht@qLw=7jkzgNl*~N)?U~ zTtLrkYu3wo=C`VvaSr)`3Nh~RauPxrLre5e8ku~ULgHc@mJqKj1d|C($vMF!OnoXA+Z?y~_-=8dyJj(bnwfGu$+NaSCf)3up=lZO(E0Xqut#OisO$6iFb0Ti9bb=S&j_7uO|~66*Kb|l5Ezth z@Z|XBjTIWIYjLNnJ2PEkhX1G6ZBVn)Ewy+2)}3OEl^A8WnBrQlg#iFy`0Lf`sssQ^E04G3#{mK=$ZeZ2j4t=kE?9(n*^F0 zkdAjx_zDKxxRC2$9QyN#fZ{yZ{pZGxQ$gY;-9C_HLZtZx?^4A7d~>usyuH8q1vmQy z1=k@9Wb-gBgOdxwZeimF6Is{bsdhXNE{t+J`1;SC7MDmf4#_h3G4s$RxEL%#Ejq*c z?II~PLH%u(m4&B!t2{@hNl{of{%;7?Mt$xw)5+&Hvs+6oKajwOm$WHqgxjNLVnHY= z7xtK*O82nCF9jLv84T6;3a8*_TU)g44aH{g%th z>E9>(?IsCwVxMD}j6<+QraC{(3J*)E5n5`eX2qt|bY#!Ns1hNWg0vmF5IqAy`8N^!s1+dLHJpNjwPGe~%WmGzKS=OFSB#;Z}Tq|2&u4l-CPa+$_2K zmNDEu0Vu(K0t^(E=x5pV8#Ydd{Z;VH{80kk6xWqYZej^v;jK6k#p*wU*-si5mc#;V zEsUfrkLP!ffz8{dR3I&qqr5B@b?jU7{zYCegXS0knGb7$vh=W3<5 zrKsUKw`4qXK>&k;ngom18sU# zRt!AE_c{tEQs2E1^7kJX*dv_Mt$E>R0vnsNYf21;sY^#VzZc-vIDRJ-snN|c`>kC0;0~+mm>s*4eXK7`)fg!zJuK=)w=o;i zSlxr8cd`j@Y;k^|{xZ{CJ8EE}yEf1Oj4*N!*~=Yd5YeuC{>|_AH&lG8wIWG@Q@)3O zdcGd&mOf&-Q^;QB7-1~d4X8CJ8Tr(KG*o*SZ%(3eiO^rAorJeP z?M=C?l_%tvTpE9sFFK1IQliim%JI&#;IRGRX~F5RfI(N+LNMOxcslhE*h}8uOi-0i z-6ikTV2T4m*K{rk{p>b=UNeSRS2%aj5E#Qzt_E<)RNqjDr+=SqilW5btkLo^yqX8o zQ#wl(^anL4JMU}QjG_JLfhup65DUFvfDeKOl3#q7$A$+>C}G6v$!A8KipcEXN8HpjwR5DyivtKQ-U* zk`cn%)o~`N{P{2xLnf6OnUt8TFS06@_PVCHA=U9aCPHmm!%``d{@nV~G0p)k**3SI z=H!AniL((WjnI^3kmfHJQX_X#;41ohsjnVg4SgvE)^OgKWiNL+P#P`eoW2}?o4n;x>;K;Q_RZi!MTe_9$mm#+&|4U^<_izSoIbbe}9y**sk zcf+!^S>Ny6vWGNjX^}bsE9rTYve)*x7=oarZnl2xNrzh{!NRUvG4C^+Z;$({ki129 z`X5gJ2=Sr)JvN|;t*?D$V_k6lC2xVLB5?cb<1O&Z^(W|~0id*f;re(Qh}6yWbd97~ z-?n{Y=JS7ToMl5)joL=(uAw_5bcO~gDFMj=q!9+ByGueE=@98IY3UfcySqE3o1x+G zJnuQ*-Y@$v?0v7j*5%r!{Em zYV>6Eu&(Xo5qh~>r-R)8L%VvOx?uB!TBQ@P0no(T=J(CUFfS0sSQH?fes24y;Z+0e5!!mA{pyp0c4`sun+i$ z-)xLG?P93`dXoltyCq$PhEZf)iOn%UPRdUDA$JN$OdhS4Pj#5zS58y-nv~{iU~s;u zli?MMFIXE@vPA^R}=bp)Z9^0WB;L6SAKR=i>o53RWi~-*7tKA_1P8MZ@-bS_D zb2(42-OWbE%37Dxp0q|(nT zJYaIGj%E2Z=qx(b3(slF+e}jKVs0h=pz$4IlP6A4Tb+CYmFE1zYSRx_RBpdvJD2pc zH2_vc((!{&{%7^1S6cw*dR6S1n&48#+Z)^>UgjOYm+_KC%$kU@oT9$XWH2Fk*8}u! z)T_#{JYN}UD3c7GJVyg^d5e?$_2p40%P?#<$Q_!E7*fb-L})!3vgl5iTK?aItJk7i z6NO{MMYnl;PRR1aRR>iAxz^`0E=BiW`v86G^$_yA+3Fw%NDD#Zgq{(vDL2Ym6MC{@=D5KKPlup zaie=G??o3|;7*W!xZtE5abnjOLlP&FZpsXMl5UOQ{*i0J$;IF&+N^tuM%6}fuuVS| zs~itWn@@gsX}vw}Zot)S1MQRf zqP)c&POv+_&#dw1WX1@WfkQFlzm1K1^f?kASmgB8nZf-GNl^1dxLQLr=ocHO8N@cYH|c257BN6-fWH?c3Heo3A;{56m@7j^U?=lgL!*=TWP`(MUQ!X7D`2Z? zJ_QPxETch))1e8ud9~e?$lvXl;l#01{c0<_5y2$ML4@F;X-$5PSvf?dTH#_sc%B_< zsrXmVM=P;q^GD9WymM$)`4p>54nX#rQRjAvuPEsURrt_1o9uJ>Fp|ydZobE?Bi+H# zs$nN0Un(jq_Zjw;>KV-^$5WfDB!^9`Sf=QE^U$03^4hh~Q21ERTE%m-pf!~Yi_z!& zxjlCsy(J4SgQ8TD>N01@Y;Cu5nEChlHU4gI`I51I)@!zzkaG3U}?m5tD(_({s4~uPAd=3NB^s6$ddept*QUww23k zE8QFqh{%^$jfY5eqsGD6A#m>Fh8!9Evm&#OgL>}82N#+FXy1@;G9)`9t8+mWxRc$3 z4f~>%QvE7HmvCnwOL!cL>!L)8TcxG>lugY`-6J8_2yfJ{$4SU_)T5w$Ar~pi$`mZ} z$+Yzs?pJtuV=xF8Te>%1-kbbF%yffq_41sKcTKSnPW4@LD2R=Q`Pp3Lv&f}B9xcj= zTCeNBY6H_6c4Ke`nrZ%@wm01~r4PfwS>LxM5$-$|&DNg^{#YG9WkU){Np|6$h&n!g zOJFFBe%Qu)c-5Q&!a!Pft*q|G3`HL)2M93Wr91ZG*|_6U zJGpI0<(biKCl^up2~wo0fUkoqD6}$2K4=X1XYH6?c!uMp&~N|SNRhQ9zIpTZFpsVe zWO|BpF(CET6f%0@s*X34(}~4e929tmpg~7VC<~z`_8jZ0s{B%@F?(T^5^3wzx%Zv~ zAr*x64%1s>p_1p5aNr~5=Sro-@XvIjVtaT&`$L#P`TQv-^)79S3D8SMQ&XI<)OqTbn4lsEN!yno&md9?ZjJ(XHrv&*kr ziUqa+TqRzfQC3eK1F_aF&+`h1_h0LNY&pJ%7I4pGF{(Yr-CLrP`(x3AzJK`gO$t7{ z-Tw=iXZN4E*{@jNf6BTLmZ8zza=T!kc=yI(2&5SJj6t=v^`#OhKfn_}zSP8O(8d_W z*vecGE*fTtIh8uHE!yJ86dgH*cdj|1)Jb>QWD;O=S|e>U{MD3}=)8MKa2FNpjbJ>K zZsU?(c{iXqzol|z{R+E)xYMGIM5+AHK5tl4U^zC#Z6M+mEJMCNmSb3`zJGBXatAsL0_O+GLk||#Y$Y;)e`CwK{mTg;KU^uVXS?sH`Z812QjBD)^epj!yX1Kd)-AKISZ{i1_iB7`M!@!3JsbLgB1(Odxe};ibmb(2^1UA>p+oP0WoF(u8jK4%*SJefrP*3QhUX8{ev``FZ6U zgjQ>5GCZ^xpll9u0>m>f<1HiKD#;N!S>T=TYrqH3>DQek&LsuUjVkIs|04UR{vlb4 zeT1@5YDZgK*ZX4)1_vx&qB5FN^j)Ou)`a|<9bi#yNMZRm-e2m~q5Rj+pyHcYCv}2V zfy+g?+BWO-s%-U@+SJB%z(q|`zKtUo^3z0X>2;u~*R=&4k?$J1_V8M;YE=!DbZh%} zK5AGL5JbApY?9#4!UyrLaLKHv#?UsQAOfLWsBuj2AE%Mu5!qXdrKP~EHW1ZH@{|xz zCH7W)-^JZBMzcHXNM;~bxLY5tmjqPfjDFyMINCT`_7wwW`_u~(;}^QLm~NcacWvSP z6clj=a`K(nZaW4-NG&5TrP~~)t@)Yk}znGfgSKo zZCNja$;Xu z4>0%3^FfQGg$8G2S%RTd2@(Zc1a$nAlN$XMyy+zM;g`NpA%oU z2VQJnum_9kk5kDBS!H8>I|MUu<^F5>U$#^fyM3)wjJxBii6+A+h@yL9WPPr!s9K<* z3OmL|!E|+)zIMakKs2qEk*|priEKz(HUw-)3zosO5Y)$c=bg4B69;&t#Fvi=P$%IfYO~^cYoRJ!IR3E;2hZwhHNS=oyS`5q#3?(H!4WBAMW8y zyf$UKj4IL1c!2r0fg1l#u98fy-h3ZOqo$KIjwqvb*P(_+8sfq%ckJ)f{`LAVzGJ!#mUSy8pHhR+}LwDLj$|GluGgP zk(IHAvpFRJ1q!gUTE};%7eUzB^<;|gpW+Jzp#^n=8a|Y55B~;+;*0nTC1k9?IP_zk zqGcQUH)n+KEPb5*)Cm3uSxuB~(|U&ey1|Q!d0QZx_`x(C^*rmd1#}z{WVOv(48MYAed`Ekx@8Kd@?R#s5!f@Ww z4&NS@$l=o9r{o)=UwtblHyQnU^!JQl%PdY-Psj5^XL^s@?D;`LFGGwh8a|hP#NIuj z#<64C@}z7NUoIJ}=kTCbc>g;z+ewGITllFP{K7NB%6~q*1Uinqp-7ZH-u(WCR22kHg#+37MEOBDJgo7g&$Gt%F9?-; zl>EbL(9m(bfAZsR;lt72jKsF&H>@dx?z1pKm+!NC2U>9}#x1n?Lqgj9@^#MRUHeH} zC4~sY;PGi9enrtmk4^3Hn)c-*YQOm$ud|$d~g2YMgN(*X^9OK z5I(q`!&P61#T;3mN(qPcQTLscb}RPx$I|65i2#<;_i$j>8PAr{j!9m5woayXoDzoz zU;gzI-zLNpoO;3aR*&|UiOZFq0H>JXSfRA-4F+6R_vTV3`c%NFQcfn~yx4ZXT1MuZ zPvsXnz&C5A0Ra;ESt$0D>&h{rx}Cj1;~0PhQ1;d#%z=Qefd(SrGUyOQTVU)iB@=9> z!E)|rk;Pv(V1&tRz*cKvR#Lm^oHH5=60xD{$xS0%Oi?L3m|ax~Qyb1BbVwzx5hqf2 z_UnuD2j8zVvj18`RYeNbv0#MAIf@N@M#zG6Z*w-npW%$QhfDz5I~$%}&_4U(Dl4@T z8Bv>>Tl)_N=qp5-rN2x@xSi4+4)-XmG{}3N_{mdPibUq?^eC&9G;Sc_nE8%{1V2W? z-*rc7ze@2zYM%u2VjMj^ZSHQlh40Gw4&_m~H-Xq51y1Fb8fo`m?lR*l{f-@st_!}# z_cxGu0H);Rj@`}G1}sgdIZ-`(kyD_8X(gtV=Hn`3NvfhRvn(cIOG^eWvIpZCp?VQz;{lJ{O{%2W)`Wey_D7~8X*cYJ z`j+*zQ?KkJl*&ilV@Z_~Ggzk+_Q9pcAF)$=BJ6@Mlrp1hk#2d{hrUB&#DwvAAcOALk;x)3Zf)pF7zg$e?dLePItxaCAD z|F|E*q|-}+19!r3#Pp=8lm;5Uf3Ng$ZgY)nq427I?Vys@(M`O{89@JZL@{ufdK?0| z001a$AZ-v8%_EMIpkq38rsf)xiR4Ny$>V+jIqoRnug~tL@3wQj1G?nSzdxM94ui0J zxIMI5n|Bquz}DyMkCr|{rsgbUtE)GwwQoe1o>l>b6{fmn=Y$UM1=&$6InWs*>Lm@) zY5+jGKr49{2B9$`Me4WwWC1(81R#Vo2}xJbK-#lWB&b?CRJI zDe=E%wnmk&jP=bf)tVv6^;>*Gy&2R!|0K5(X!`G=d2o4 z_R-?>c+PfSc$13L;x|j{9FK77Y(NBpGRgVS$@mUM(YG^mve2^pjr^$ub{#S|Hlk)v zUH;Z|w*u`i%cotEN|1})Qf2yXX6h`QGMRYtxC$JUf-iNamqrtBb8QF`ZdHZ-y3-8uk}y&BO@BSW18?6eIeNW z>VzO-fgG%8^Dt7FKAz?pLU~nfT?ezhy*HzQNuz3l^%+;JjdV35s!o3L9A^R}&4QH4 zka~{viJRjTNzNnFvR^L3 zSds9zS2^?8RSXNPv8VC8l3I$T?+n#@gl* zDG?am)AGk}ssy%E`(SDyDnHiBF!~{43k}MQ8Rb$?yn3Ui^`5T-`3#>#xdm+X{B@o1 z=oB~3O@`#L1i^8CskI>b59#DV%sFuTEgDrPhnIWDIH9!a<$4Q7&+^jLjsFb3T2I;g zyCuw3+pclxEeJHgRd{eBJ!$y%_WEhW>vk^0dM&cKZB5>;?yaf^{YlXC6L$03PK(bX z(~G*^3M@+XznT>pIal&HRJg~FaAnjE<1LM}0vCA8Jo>j7m4y;4%JAE1Ig5zi<$P=Tp-Q*a5xJh` zm*MQ1aXBi-xj)nDs9#TvGYm_W=o`MpHww0YK`slZOW_a}xhO1uQlzVw5IY-O*5%sz zN^b6Rbz7xq@Pvk1Hqo0Kvk(2uo>98|)vN&jHg1}|G%YNq>~F!*fCe1G>b8#Q1DvT- zRN=!<=dc>bv0HgH4@SoLm?f209Vgv^_y)KMY8XRW?ImH&4F5ZMP=rQtX~(aSbtwhm aoq1Bg5PEs^fro>8dAZSs4}Rl`1ot1Is*~CP diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index 13b9917c830cc37a79050bcbd7dc7f844fdcb58f..cb24beaf8ac4ba08dd0136ac203ee80b0e01e7ec 100644 GIT binary patch delta 7877 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 literal 7828 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 diff --git a/build/openrpc/worker.json.gz b/build/openrpc/worker.json.gz index 798a6376cacd1d613567d6c963b3e734655e62e7..2214ad85af5e8b7f0c47a81106e161959b55c203 100644 GIT binary patch delta 2463 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 Date: Mon, 17 May 2021 22:02:23 +0200 Subject: [PATCH 08/88] Get PreCommitting to work --- build/params_2k.go | 20 ++++++------ chain/state/statetree.go | 13 ++++++++ chain/stmgr/forks.go | 2 +- chain/types/state.go | 4 ++- storage/adapter_storage_miner.go | 54 +++++++++++++++++++++++++++----- 5 files changed, 74 insertions(+), 19 deletions(-) diff --git a/build/params_2k.go b/build/params_2k.go index d93b26468..8ead218f5 100644 --- a/build/params_2k.go +++ b/build/params_2k.go @@ -24,22 +24,22 @@ var UpgradeIgnitionHeight = abi.ChainEpoch(-2) var UpgradeRefuelHeight = abi.ChainEpoch(-3) var UpgradeTapeHeight = abi.ChainEpoch(-4) -var UpgradeAssemblyHeight = abi.ChainEpoch(10) +var UpgradeAssemblyHeight = abi.ChainEpoch(5) var UpgradeLiftoffHeight = abi.ChainEpoch(-5) -var UpgradeKumquatHeight = abi.ChainEpoch(15) -var UpgradeCalicoHeight = abi.ChainEpoch(20) -var UpgradePersianHeight = abi.ChainEpoch(25) -var UpgradeOrangeHeight = abi.ChainEpoch(27) -var UpgradeClausHeight = abi.ChainEpoch(30) +var UpgradeKumquatHeight = abi.ChainEpoch(6) +var UpgradeCalicoHeight = abi.ChainEpoch(7) +var UpgradePersianHeight = abi.ChainEpoch(8) +var UpgradeOrangeHeight = abi.ChainEpoch(9) +var UpgradeClausHeight = abi.ChainEpoch(10) -var UpgradeTrustHeight = abi.ChainEpoch(35) +var UpgradeTrustHeight = abi.ChainEpoch(11) -var UpgradeNorwegianHeight = abi.ChainEpoch(40) +var UpgradeNorwegianHeight = abi.ChainEpoch(12) -var UpgradeTurboHeight = abi.ChainEpoch(45) +var UpgradeTurboHeight = abi.ChainEpoch(13) -var UpgradeHyperdriveHeight = abi.ChainEpoch(50) +var UpgradeHyperdriveHeight = abi.ChainEpoch(14) var DrandSchedule = map[abi.ChainEpoch]DrandEnum{ 0: DrandMainnet, diff --git a/chain/state/statetree.go b/chain/state/statetree.go index 33a8116df..d783fff69 100644 --- a/chain/state/statetree.go +++ b/chain/state/statetree.go @@ -25,6 +25,7 @@ import ( states2 "github.com/filecoin-project/specs-actors/v2/actors/states" states3 "github.com/filecoin-project/specs-actors/v3/actors/states" states4 "github.com/filecoin-project/specs-actors/v4/actors/states" + states5 "github.com/filecoin-project/specs-actors/v5/actors/states" ) var log = logging.Logger("statetree") @@ -191,6 +192,12 @@ func NewStateTree(cst cbor.IpldStore, ver types.StateTreeVersion) (*StateTree, e return nil, xerrors.Errorf("failed to create state tree: %w", err) } hamt = tree.Map + case types.StateTreeVersion4: + tree, err := states5.NewTree(store) + if err != nil { + return nil, xerrors.Errorf("failed to create state tree: %w", err) + } + hamt = tree.Map default: return nil, xerrors.Errorf("unsupported state tree version: %d", ver) } @@ -246,6 +253,12 @@ func LoadStateTree(cst cbor.IpldStore, c cid.Cid) (*StateTree, error) { if tree != nil { hamt = tree.Map } + case types.StateTreeVersion4: + var tree *states5.Tree + tree, err = states5.LoadTree(store, root.Actors) + if tree != nil { + hamt = tree.Map + } default: return nil, xerrors.Errorf("unsupported state tree version: %d", root.Version) } diff --git a/chain/stmgr/forks.go b/chain/stmgr/forks.go index de5e91388..ee5a26dea 100644 --- a/chain/stmgr/forks.go +++ b/chain/stmgr/forks.go @@ -1247,7 +1247,7 @@ func upgradeActorsV5Common( // Persist the result. newRoot, err := store.Put(ctx, &types.StateRoot{ - Version: types.StateTreeVersion3, + Version: types.StateTreeVersion4, Actors: newHamtRoot, Info: stateRoot.Info, }) diff --git a/chain/types/state.go b/chain/types/state.go index b561aab71..c8f8f1cd9 100644 --- a/chain/types/state.go +++ b/chain/types/state.go @@ -13,8 +13,10 @@ const ( StateTreeVersion1 // StateTreeVersion2 corresponds to actors v3. StateTreeVersion2 - // StateTreeVersion3 corresponds to actors >= v4. + // StateTreeVersion3 corresponds to actors v4. StateTreeVersion3 + // StateTreeVersion4 corresponds to actors v5. + StateTreeVersion4 ) type StateRoot struct { diff --git a/storage/adapter_storage_miner.go b/storage/adapter_storage_miner.go index 41d7461a8..f4d1e0038 100644 --- a/storage/adapter_storage_miner.go +++ b/storage/adapter_storage_miner.go @@ -16,6 +16,7 @@ import ( "github.com/filecoin-project/go-state-types/network" market2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/market" + market5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/market" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/blockstore" @@ -146,10 +147,36 @@ func (s SealingAPIAdapter) StateComputeDataCommitment(ctx context.Context, maddr return cid.Undef, xerrors.Errorf("failed to unmarshal TipSetToken to TipSetKey: %w", err) } - ccparams, err := actors.SerializeParams(&market2.ComputeDataCommitmentParams{ - DealIDs: deals, - SectorType: sectorType, - }) + ts, err := s.delegate.ChainGetTipSet(ctx, tsk) + if err != nil { + return cid.Cid{}, err + } + + // using parent ts because the migration won't be run on the first nv13 + // tipset we apply StateCall to (because we don't run migrations in StateCall + // and just apply to parent state) + nv, err := s.delegate.StateNetworkVersion(ctx, ts.Parents()) + if err != nil { + return cid.Cid{}, err + } + + var ccparams []byte + if nv < network.Version13 { + ccparams, err = actors.SerializeParams(&market2.ComputeDataCommitmentParams{ + DealIDs: deals, + SectorType: sectorType, + }) + } else { + ccparams, err = actors.SerializeParams(&market5.ComputeDataCommitmentParams{ + Inputs: []*market5.SectorDataSpec{ + { + DealIDs: deals, + SectorType: sectorType, + }, + }, + }) + } + if err != nil { return cid.Undef, xerrors.Errorf("computing params for ComputeDataCommitment: %w", err) } @@ -169,12 +196,25 @@ func (s SealingAPIAdapter) StateComputeDataCommitment(ctx context.Context, maddr return cid.Undef, xerrors.Errorf("receipt for ComputeDataCommitment had exit code %d", r.MsgRct.ExitCode) } - var c cbg.CborCid - if err := c.UnmarshalCBOR(bytes.NewReader(r.MsgRct.Return)); err != nil { + if nv < network.Version13 { + var c cbg.CborCid + if err := c.UnmarshalCBOR(bytes.NewReader(r.MsgRct.Return)); err != nil { + return cid.Undef, xerrors.Errorf("failed to unmarshal CBOR to CborCid: %w", err) + } + + return cid.Cid(c), nil + } + + var cr market5.ComputeDataCommitmentReturn + if err := cr.UnmarshalCBOR(bytes.NewReader(r.MsgRct.Return)); err != nil { return cid.Undef, xerrors.Errorf("failed to unmarshal CBOR to CborCid: %w", err) } - return cid.Cid(c), nil + if len(cr.CommDs) != 1 { + return cid.Undef, xerrors.Errorf("CommD output must have 1 entry") + } + + return cid.Cid(cr.CommDs[0]), nil } func (s SealingAPIAdapter) StateSectorPreCommitInfo(ctx context.Context, maddr address.Address, sectorNumber abi.SectorNumber, tok sealing.TipSetToken) (*miner.SectorPreCommitOnChainInfo, error) { From ba2032c642b212747eef8afa63d1778033e3f899 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Mon, 17 May 2021 22:51:29 +0200 Subject: [PATCH 09/88] Fix some aggregation bugs --- api/test/window_post.go | 6 ++++++ extern/storage-sealing/commit_batch.go | 4 ++-- extern/storage-sealing/states_sealing.go | 11 ++++++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/api/test/window_post.go b/api/test/window_post.go index c987fa1f9..b6804c401 100644 --- a/api/test/window_post.go +++ b/api/test/window_post.go @@ -188,6 +188,12 @@ func pledgeSectors(t *testing.T, ctx context.Context, miner TestStorageNode, n, } for len(toCheck) > 0 { + cb, err := miner.SectorCommitFlush(ctx) + require.NoError(t, err) + if cb != nil { + fmt.Printf("BATCH: %s\n", *cb) + } + for n := range toCheck { st, err := miner.SectorsStatus(ctx, n, false) require.NoError(t, err) diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go index 8b5d9b543..ad0b00e88 100644 --- a/extern/storage-sealing/commit_batch.go +++ b/extern/storage-sealing/commit_batch.go @@ -141,11 +141,11 @@ func (b *CommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { } spt := b.todo[0].spt - proofs := make([][]byte, total) + proofs := make([][]byte, 0, total) for id, p := range b.todo { params.SectorNumbers.Set(uint64(id)) - proofs[id] = p.proof + proofs = append(proofs, p.proof) } params.AggregateProof, err = b.verif.AggregateSealProofs(spt, arp, proofs) diff --git a/extern/storage-sealing/states_sealing.go b/extern/storage-sealing/states_sealing.go index 9975b1a37..739e8c946 100644 --- a/extern/storage-sealing/states_sealing.go +++ b/extern/storage-sealing/states_sealing.go @@ -11,6 +11,7 @@ import ( "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/go-state-types/exitcode" + "github.com/filecoin-project/go-state-types/network" "github.com/filecoin-project/go-statemachine" "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" "github.com/filecoin-project/specs-storage/storage" @@ -457,8 +458,16 @@ func (m *Sealing) handleSubmitCommit(ctx statemachine.Context, sector SectorInfo if err != nil { return xerrors.Errorf("getting config: %w", err) } + if cfg.AggregateCommits { - return ctx.Send(SectorSubmitCommitAggregate{}) + nv, err := m.api.StateNetworkVersion(ctx.Context(), nil) + if err != nil { + return xerrors.Errorf("getting network version: %w", err) + } + + if nv >= network.Version13 { + return ctx.Send(SectorSubmitCommitAggregate{}) + } } tok, _, err := m.api.ChainHead(ctx.Context()) From febf7cf28f9b8c4c9649dfb7b66381ecd3a12c97 Mon Sep 17 00:00:00 2001 From: vyzo Date: Tue, 18 May 2021 12:02:53 +0300 Subject: [PATCH 10/88] sane config defaults --- node/config/def.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node/config/def.go b/node/config/def.go index 94e87be3b..ba4ec7ce1 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -243,8 +243,8 @@ func DefaultStorageMiner() *StorageMiner { AlwaysKeepUnsealedCopy: true, AggregateCommits: true, - MinCommitBatch: 5, // todo: base this on some real numbers - MaxCommitBatch: 400, + MinCommitBatch: 1, // we must have at least one proof to aggregate + MaxCommitBatch: 204, // this is the maximum aggregation per FIP13 }, Storage: sectorstorage.SealerConfig{ From 357c0868b7ec4ce4a14f1ad9919dc9fbf10fe51e Mon Sep 17 00:00:00 2001 From: vyzo Date: Tue, 18 May 2021 12:20:19 +0300 Subject: [PATCH 11/88] proper config for termination batching and commit wait --- extern/storage-sealing/commit_batch.go | 13 ++++--- extern/storage-sealing/sealiface/config.go | 5 +++ extern/storage-sealing/sealing.go | 2 +- extern/storage-sealing/terminate_batch.go | 41 +++++++++++----------- node/config/def.go | 10 ++++-- 5 files changed, 39 insertions(+), 32 deletions(-) diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go index ad0b00e88..6b3cef6d5 100644 --- a/extern/storage-sealing/commit_batch.go +++ b/extern/storage-sealing/commit_batch.go @@ -22,12 +22,6 @@ import ( "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" ) -var ( - // TODO: config! - - CommitBatchWait = 5 * time.Minute -) - const arp = abi.RegisteredAggregationProof_SnarkPackV1 type CommitBatcherApi interface { @@ -87,6 +81,11 @@ func (b *CommitBatcher) run() { var forceRes chan *cid.Cid var lastMsg *cid.Cid + cfg, err := b.getConfig() + if err != nil { + panic(err) + } + for { if forceRes != nil { forceRes <- lastMsg @@ -101,7 +100,7 @@ func (b *CommitBatcher) run() { return case <-b.notify: sendAboveMax = true - case <-time.After(TerminateBatchWait): + case <-time.After(cfg.CommitBatchWait): sendAboveMin = true case fr := <-b.force: // user triggered forceRes = fr diff --git a/extern/storage-sealing/sealiface/config.go b/extern/storage-sealing/sealiface/config.go index f62911b70..1c0945db2 100644 --- a/extern/storage-sealing/sealiface/config.go +++ b/extern/storage-sealing/sealiface/config.go @@ -21,4 +21,9 @@ type Config struct { AggregateCommits bool MinCommitBatch int MaxCommitBatch int + CommitBatchWait time.Duration + + TerminateBatchMax uint64 + TerminateBatchMin uint64 + TerminateBatchWait time.Duration } diff --git a/extern/storage-sealing/sealing.go b/extern/storage-sealing/sealing.go index d990cb02f..35ff15f51 100644 --- a/extern/storage-sealing/sealing.go +++ b/extern/storage-sealing/sealing.go @@ -152,7 +152,7 @@ func New(api SealingAPI, fc FeeConfig, events Events, maddr address.Address, ds notifee: notifee, addrSel: as, - terminator: NewTerminationBatcher(context.TODO(), maddr, api, as, fc), + terminator: NewTerminationBatcher(context.TODO(), maddr, api, as, fc, gc), commiter: NewCommitBatcher(context.TODO(), maddr, api, as, fc, gc, verif), getConfig: gc, diff --git a/extern/storage-sealing/terminate_batch.go b/extern/storage-sealing/terminate_batch.go index 0e96e8384..3833109a1 100644 --- a/extern/storage-sealing/terminate_batch.go +++ b/extern/storage-sealing/terminate_batch.go @@ -21,14 +21,6 @@ import ( "github.com/filecoin-project/lotus/chain/actors/builtin/miner" ) -var ( - // TODO: config - - TerminateBatchMax uint64 = 100 // adjust based on real-world gas numbers, actors limit at 10k - TerminateBatchMin uint64 = 1 - TerminateBatchWait = 5 * time.Minute -) - type TerminateBatcherApi interface { StateSectorPartition(ctx context.Context, maddr address.Address, sectorNumber abi.SectorNumber, tok TipSetToken) (*SectorLocation, error) SendMsg(ctx context.Context, from, to address.Address, method abi.MethodNum, value, maxFee abi.TokenAmount, params []byte) (cid.Cid, error) @@ -38,11 +30,12 @@ type TerminateBatcherApi interface { } type TerminateBatcher struct { - api TerminateBatcherApi - maddr address.Address - mctx context.Context - addrSel AddrSel - feeCfg FeeConfig + api TerminateBatcherApi + maddr address.Address + mctx context.Context + addrSel AddrSel + feeCfg FeeConfig + getConfig GetSealingConfigFunc todo map[SectorLocation]*bitfield.BitField // MinerSectorLocation -> BitField @@ -53,13 +46,14 @@ type TerminateBatcher struct { lk sync.Mutex } -func NewTerminationBatcher(mctx context.Context, maddr address.Address, api TerminateBatcherApi, addrSel AddrSel, feeCfg FeeConfig) *TerminateBatcher { +func NewTerminationBatcher(mctx context.Context, maddr address.Address, api TerminateBatcherApi, addrSel AddrSel, feeCfg FeeConfig, getConfig GetSealingConfigFunc) *TerminateBatcher { b := &TerminateBatcher{ - api: api, - maddr: maddr, - mctx: mctx, - addrSel: addrSel, - feeCfg: feeCfg, + api: api, + maddr: maddr, + mctx: mctx, + addrSel: addrSel, + feeCfg: feeCfg, + getConfig: getConfig, todo: map[SectorLocation]*bitfield.BitField{}, waiting: map[abi.SectorNumber][]chan cid.Cid{}, @@ -113,6 +107,11 @@ func (b *TerminateBatcher) processBatch(notif, after bool) (*cid.Cid, error) { return nil, xerrors.Errorf("getting proving deadline info failed: %w", err) } + cfg, err := b.getConfig() + if err != nil { + return nil, xerrors.Errorf("getting sealing config: %W", err) + } + b.lk.Lock() defer b.lk.Unlock() params := miner2.TerminateSectorsParams{} @@ -193,11 +192,11 @@ func (b *TerminateBatcher) processBatch(notif, after bool) (*cid.Cid, error) { return nil, nil // nothing to do } - if notif && total < TerminateBatchMax { + if notif && total < cfg.TerminateBatchMax { return nil, nil } - if after && total < TerminateBatchMin { + if after && total < cfg.TerminateBatchMin { return nil, nil } diff --git a/node/config/def.go b/node/config/def.go index ba4ec7ce1..6419127d0 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -242,9 +242,13 @@ func DefaultStorageMiner() *StorageMiner { WaitDealsDelay: Duration(time.Hour * 6), AlwaysKeepUnsealedCopy: true, - AggregateCommits: true, - MinCommitBatch: 1, // we must have at least one proof to aggregate - MaxCommitBatch: 204, // this is the maximum aggregation per FIP13 + AggregateCommits: true, + MinCommitBatch: 1, // we must have at least one proof to aggregate + MaxCommitBatch: 204, // this is the maximum aggregation per FIP13 + CommitBatchWait: time.Day, // this can be up to 6 days + TerminateBatchMin: 1, // same as above + TerminateBatchMax: 204, // same as above + TerminateBatchWait: time.Day, // this can be up to 6 days }, Storage: sectorstorage.SealerConfig{ From fe9311e43562b6dff5e485a72556608abe579e5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 18 May 2021 12:46:13 +0200 Subject: [PATCH 12/88] update ffi --- extern/filecoin-ffi | 2 +- extern/sector-storage/ffiwrapper/sealer_test.go | 11 +++++++---- extern/sector-storage/ffiwrapper/types.go | 2 +- extern/sector-storage/ffiwrapper/verifier_cgo.go | 4 ++-- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/extern/filecoin-ffi b/extern/filecoin-ffi index 525851103..178ac15a5 160000 --- a/extern/filecoin-ffi +++ b/extern/filecoin-ffi @@ -1 +1 @@ -Subproject commit 525851103fcf548dff1d4db6b5a1a2a6d9e10833 +Subproject commit 178ac15a537626cac07d8af68bffc603011c0310 diff --git a/extern/sector-storage/ffiwrapper/sealer_test.go b/extern/sector-storage/ffiwrapper/sealer_test.go index a1b27cc87..172641bf7 100644 --- a/extern/sector-storage/ffiwrapper/sealer_test.go +++ b/extern/sector-storage/ffiwrapper/sealer_test.go @@ -513,8 +513,11 @@ func TestSealAndVerifyAggregate(t *testing.T) { defer cleanup() avi := proof5.AggregateSealVerifyProofAndInfos{ - Miner: miner, - Infos: make([]proof5.AggregateSealVerifyInfo, numAgg), + Miner: miner, + SealProof: sealProofType, + AggregateProof: policy.GetDefaultAggregationProof(), + Proof: nil, + Infos: make([]proof5.AggregateSealVerifyInfo, numAgg), } toAggregate := make([][]byte, numAgg) @@ -539,12 +542,12 @@ func TestSealAndVerifyAggregate(t *testing.T) { aggStart := time.Now() - avi.Proof, err = ProofVerifier.AggregateSealProofs(sealProofType, policy.GetDefaultAggregationProof(), toAggregate) + avi.Proof, err = ProofVerifier.AggregateSealProofs(avi, toAggregate) require.NoError(t, err) aggDone := time.Now() - _, err = ProofVerifier.AggregateSealProofs(sealProofType, policy.GetDefaultAggregationProof(), toAggregate) + _, err = ProofVerifier.AggregateSealProofs(avi, toAggregate) require.NoError(t, err) aggHot := time.Now() diff --git a/extern/sector-storage/ffiwrapper/types.go b/extern/sector-storage/ffiwrapper/types.go index aa0658397..99efa7521 100644 --- a/extern/sector-storage/ffiwrapper/types.go +++ b/extern/sector-storage/ffiwrapper/types.go @@ -42,7 +42,7 @@ type Verifier interface { GenerateWinningPoStSectorChallenge(context.Context, abi.RegisteredPoStProof, abi.ActorID, abi.PoStRandomness, uint64) ([]uint64, error) // cheap, makes no sense to put this on the storage interface - AggregateSealProofs(proofType abi.RegisteredSealProof, rap abi.RegisteredAggregationProof, proofs [][]byte) ([]byte, error) + AggregateSealProofs(aggregateInfo proof5.AggregateSealVerifyProofAndInfos, proofs [][]byte) ([]byte, error) } type SectorProvider interface { diff --git a/extern/sector-storage/ffiwrapper/verifier_cgo.go b/extern/sector-storage/ffiwrapper/verifier_cgo.go index 2650fba02..650155305 100644 --- a/extern/sector-storage/ffiwrapper/verifier_cgo.go +++ b/extern/sector-storage/ffiwrapper/verifier_cgo.go @@ -140,6 +140,6 @@ func (proofVerifier) GenerateWinningPoStSectorChallenge(ctx context.Context, pro return ffi.GenerateWinningPoStSectorChallenge(proofType, minerID, randomness, eligibleSectorCount) } -func (v proofVerifier) AggregateSealProofs(proofType abi.RegisteredSealProof, rap abi.RegisteredAggregationProof, proofs [][]byte) ([]byte, error) { - return ffi.AggregateSealProofs(proofType, rap, proofs) +func (v proofVerifier) AggregateSealProofs(aggregateInfo proof5.AggregateSealVerifyProofAndInfos, proofs [][]byte) ([]byte, error) { + return ffi.AggregateSealProofs(aggregateInfo, proofs) } From 74bad49068956231baf4c48bc69447aa52a95d2c Mon Sep 17 00:00:00 2001 From: vyzo Date: Tue, 18 May 2021 14:30:47 +0300 Subject: [PATCH 13/88] correctly handle commit batch timer --- extern/storage-sealing/commit_batch.go | 88 +++++++++++++++++++++- extern/storage-sealing/sealiface/config.go | 1 + extern/storage-sealing/states_sealing.go | 2 +- node/config/def.go | 7 +- 4 files changed, 90 insertions(+), 8 deletions(-) diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go index 6b3cef6d5..b61d5cdde 100644 --- a/extern/storage-sealing/commit_batch.go +++ b/extern/storage-sealing/commit_batch.go @@ -18,6 +18,7 @@ import ( proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" ) @@ -27,6 +28,8 @@ const arp = abi.RegisteredAggregationProof_SnarkPackV1 type CommitBatcherApi interface { SendMsg(ctx context.Context, from, to address.Address, method abi.MethodNum, value, maxFee abi.TokenAmount, params []byte) (cid.Cid, error) StateMinerInfo(context.Context, address.Address, TipSetToken) (miner.MinerInfo, error) + StateMarketStorageDeal(context.Context, abi.DealID, TipSetToken) (*api.MarketDeal, error) + ChainHead(ctx context.Context) (TipSetToken, abi.ChainEpoch, error) } type AggregateInput struct { @@ -45,6 +48,7 @@ type CommitBatcher struct { getConfig GetSealingConfigFunc verif ffiwrapper.Verifier + sectors map[abi.SectorNumber]SectorInfo todo map[abi.SectorNumber]AggregateInput waiting map[abi.SectorNumber][]chan cid.Cid @@ -100,7 +104,7 @@ func (b *CommitBatcher) run() { return case <-b.notify: sendAboveMax = true - case <-time.After(cfg.CommitBatchWait): + case <-time.After(b.batchWait(cfg.CommitBatchWait, cfg.CommitBatchSlack)): sendAboveMin = true case fr := <-b.force: // user triggered forceRes = fr @@ -114,6 +118,79 @@ func (b *CommitBatcher) run() { } } +func (b *CommitBatcher) batchWait(maxWait, slack time.Duration) time.Duration { + now := time.Now() + + b.lk.Lock() + defer b.lk.Unlock() + + var deadline time.Time + for sn := range b.todo { + sectorDeadline := b.getSectorDeadline(sn) + if deadline.IsZero() || (!sectorDeadline.IsZero() && sectorDeadline.Before(deadline)) { + deadline = sectorDeadline + } + } + for sn := range b.waiting { + sectorDeadline := b.getSectorDeadline(sn) + if deadline.IsZero() || (!sectorDeadline.IsZero() && sectorDeadline.Before(deadline)) { + deadline = sectorDeadline + } + } + + if deadline.IsZero() { + return maxWait + } + + deadline = deadline.Add(-slack) + if deadline.Before(now) { + return time.Nanosecond // can't return 0 + } + + wait := deadline.Sub(now) + if wait > maxWait { + wait = maxWait + } + + return wait +} + +func (b *CommitBatcher) getSectorDeadline(sn abi.SectorNumber) time.Time { + si, ok := b.sectors[sn] + if !ok { + return time.Time{} + } + + tok, curEpoch, err := b.api.ChainHead(b.mctx) + if err != nil { + log.Errorf("getting chain head: %s", err) + return time.Time{} + } + + deadlineEpoch := si.TicketEpoch + for _, p := range si.Pieces { + if p.DealInfo == nil { + continue + } + + proposal, err := b.api.StateMarketStorageDealProposal(b.mctx, p.DealInfo.DealID, tok) + if err != nil { + log.Errorf("getting deal proposal for %d: %s", p.DealInfo.DealID, err) + continue + } + + if proposal.StartEpoch < deadlineEpoch { + deadlineEpoch = proposal.StartEpoch + } + } + + if deadlineEpoch <= curEpoch { + return time.Now() + } + + return time.Duration(deadlineEpoch-curEpoch) * time.Duration(build.BlockDelaySecs) * time.Second +} + func (b *CommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { b.lk.Lock() defer b.lk.Unlock() @@ -182,6 +259,7 @@ func (b *CommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { } delete(b.waiting, sn) delete(b.todo, sn) + delete(b.sectors, sn) return nil }) if err != nil { @@ -192,12 +270,14 @@ func (b *CommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { } // register commit, wait for batch message, return message CID -func (b *CommitBatcher) AddCommit(ctx context.Context, s abi.SectorNumber, in AggregateInput) (mcid cid.Cid, err error) { +func (b *CommitBatcher) AddCommit(ctx context.Context, s SectorInfo, in AggregateInput) (mcid cid.Cid, err error) { + sn := s.SectorNumber b.lk.Lock() - b.todo[s] = in + b.sectors[sn] = s + b.todo[sn] = in sent := make(chan cid.Cid, 1) - b.waiting[s] = append(b.waiting[s], sent) + b.waiting[sn] = append(b.waiting[sn], sent) select { case b.notify <- struct{}{}: diff --git a/extern/storage-sealing/sealiface/config.go b/extern/storage-sealing/sealiface/config.go index 1c0945db2..f9784b642 100644 --- a/extern/storage-sealing/sealiface/config.go +++ b/extern/storage-sealing/sealiface/config.go @@ -22,6 +22,7 @@ type Config struct { MinCommitBatch int MaxCommitBatch int CommitBatchWait time.Duration + CommitBatchSlack time.Duration TerminateBatchMax uint64 TerminateBatchMin uint64 diff --git a/extern/storage-sealing/states_sealing.go b/extern/storage-sealing/states_sealing.go index 739e8c946..6e95fbd69 100644 --- a/extern/storage-sealing/states_sealing.go +++ b/extern/storage-sealing/states_sealing.go @@ -537,7 +537,7 @@ func (m *Sealing) handleSubmitCommitAggregate(ctx statemachine.Context, sector S return ctx.Send(SectorCommitFailed{xerrors.Errorf("sector had nil commR or commD")}) } - mcid, err := m.commiter.AddCommit(ctx.Context(), sector.SectorNumber, AggregateInput{ + mcid, err := m.commiter.AddCommit(ctx.Context(), sector, AggregateInput{ info: proof.AggregateSealVerifyInfo{ Number: sector.SectorNumber, Randomness: sector.TicketValue, diff --git a/node/config/def.go b/node/config/def.go index 6419127d0..553aabb1e 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -246,9 +246,10 @@ func DefaultStorageMiner() *StorageMiner { MinCommitBatch: 1, // we must have at least one proof to aggregate MaxCommitBatch: 204, // this is the maximum aggregation per FIP13 CommitBatchWait: time.Day, // this can be up to 6 days - TerminateBatchMin: 1, // same as above - TerminateBatchMax: 204, // same as above - TerminateBatchWait: time.Day, // this can be up to 6 days + CommitBatchSlack: 8 * time.Hour, + TerminateBatchMin: 1, + TerminateBatchMax: 100, + TerminateBatchWait: 5 * time.Minute, }, Storage: sectorstorage.SealerConfig{ From 6b3e04b9b16aeb9679c21331ae6349d93f705cc8 Mon Sep 17 00:00:00 2001 From: vyzo Date: Tue, 18 May 2021 15:28:51 +0300 Subject: [PATCH 14/88] cache sector deadlines. --- extern/storage-sealing/commit_batch.go | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go index b61d5cdde..b23832fe1 100644 --- a/extern/storage-sealing/commit_batch.go +++ b/extern/storage-sealing/commit_batch.go @@ -48,9 +48,9 @@ type CommitBatcher struct { getConfig GetSealingConfigFunc verif ffiwrapper.Verifier - sectors map[abi.SectorNumber]SectorInfo - todo map[abi.SectorNumber]AggregateInput - waiting map[abi.SectorNumber][]chan cid.Cid + deadlines map[abi.SectorNumber]time.Time + todo map[abi.SectorNumber]AggregateInput + waiting map[abi.SectorNumber][]chan cid.Cid notify, stop, stopped chan struct{} force chan chan *cid.Cid @@ -67,8 +67,9 @@ func NewCommitBatcher(mctx context.Context, maddr address.Address, api CommitBat getConfig: getConfig, verif: verif, - todo: map[abi.SectorNumber]AggregateInput{}, - waiting: map[abi.SectorNumber][]chan cid.Cid{}, + deadlines: map[abi.SectorNumber]time.Time{}, + todo: map[abi.SectorNumber]AggregateInput{}, + waiting: map[abi.SectorNumber][]chan cid.Cid{}, notify: make(chan struct{}, 1), force: make(chan chan *cid.Cid), @@ -126,13 +127,13 @@ func (b *CommitBatcher) batchWait(maxWait, slack time.Duration) time.Duration { var deadline time.Time for sn := range b.todo { - sectorDeadline := b.getSectorDeadline(sn) + sectorDeadline := b.deadlines[sn] if deadline.IsZero() || (!sectorDeadline.IsZero() && sectorDeadline.Before(deadline)) { deadline = sectorDeadline } } for sn := range b.waiting { - sectorDeadline := b.getSectorDeadline(sn) + sectorDeadline := b.deadlines[sn] if deadline.IsZero() || (!sectorDeadline.IsZero() && sectorDeadline.Before(deadline)) { deadline = sectorDeadline } @@ -155,12 +156,7 @@ func (b *CommitBatcher) batchWait(maxWait, slack time.Duration) time.Duration { return wait } -func (b *CommitBatcher) getSectorDeadline(sn abi.SectorNumber) time.Time { - si, ok := b.sectors[sn] - if !ok { - return time.Time{} - } - +func (b *CommitBatcher) getSectorDeadline(si SectorInfo) time.Time { tok, curEpoch, err := b.api.ChainHead(b.mctx) if err != nil { log.Errorf("getting chain head: %s", err) @@ -259,7 +255,7 @@ func (b *CommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { } delete(b.waiting, sn) delete(b.todo, sn) - delete(b.sectors, sn) + delete(b.deadlines, sn) return nil }) if err != nil { @@ -273,7 +269,7 @@ func (b *CommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { func (b *CommitBatcher) AddCommit(ctx context.Context, s SectorInfo, in AggregateInput) (mcid cid.Cid, err error) { sn := s.SectorNumber b.lk.Lock() - b.sectors[sn] = s + b.deadlines[sn] = b.getSectorDeadline(s) b.todo[sn] = in sent := make(chan cid.Cid, 1) From 7512748b56e2e6f03f90297794fdde4e7825ea1c Mon Sep 17 00:00:00 2001 From: vyzo Date: Tue, 18 May 2021 15:31:52 +0300 Subject: [PATCH 15/88] wire in sealing config values --- node/modules/storageminer.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index 1d89a0c4b..66052600e 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -827,6 +827,11 @@ func NewSetSealConfigFunc(r repo.LockedRepo) (dtypes.SetSealingConfigFunc, error AggregateCommits: cfg.AggregateCommits, MinCommitBatch: cfg.MinCommitBatch, MaxCommitBatch: cfg.MaxCommitBatch, + CommitBatchWait: cfg.CommitBatchWait, + CommitBatchSlack: cfg.CommitBatchSlack, + TerminateBatchMax: cfg.TerminateBatchMax, + TerminateBatchMin: cfg.TerminateBatchMin, + TerminateBatchWait: cfg.TerminateBatchWait, } }) return @@ -845,6 +850,11 @@ func NewGetSealConfigFunc(r repo.LockedRepo) (dtypes.GetSealingConfigFunc, error AggregateCommits: cfg.Sealing.AggregateCommits, MinCommitBatch: cfg.Sealing.MinCommitBatch, MaxCommitBatch: cfg.Sealing.MaxCommitBatch, + CommitBatchWait: cfg.Sealing.CommitBatchWait, + CommitBatchSlack: cfg.Sealing.CommitBatchSlack, + TerminateBatchMax: cfg.Sealing.TerminateBatchMax, + TerminateBatchMin: cfg.Sealing.TerminateBatchMin, + TerminateBatchWait: cfg.Sealing.TerminateBatchWait, } }) return From c544f4ce441f02c89b162c2352d5552ee650a390 Mon Sep 17 00:00:00 2001 From: vyzo Date: Tue, 18 May 2021 16:59:11 +0300 Subject: [PATCH 16/88] avoid extraneous rpc call for storage start epoch --- extern/storage-sealing/commit_batch.go | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go index b23832fe1..ba9d988ca 100644 --- a/extern/storage-sealing/commit_batch.go +++ b/extern/storage-sealing/commit_batch.go @@ -28,7 +28,6 @@ const arp = abi.RegisteredAggregationProof_SnarkPackV1 type CommitBatcherApi interface { SendMsg(ctx context.Context, from, to address.Address, method abi.MethodNum, value, maxFee abi.TokenAmount, params []byte) (cid.Cid, error) StateMinerInfo(context.Context, address.Address, TipSetToken) (miner.MinerInfo, error) - StateMarketStorageDeal(context.Context, abi.DealID, TipSetToken) (*api.MarketDeal, error) ChainHead(ctx context.Context) (TipSetToken, abi.ChainEpoch, error) } @@ -169,14 +168,9 @@ func (b *CommitBatcher) getSectorDeadline(si SectorInfo) time.Time { continue } - proposal, err := b.api.StateMarketStorageDealProposal(b.mctx, p.DealInfo.DealID, tok) - if err != nil { - log.Errorf("getting deal proposal for %d: %s", p.DealInfo.DealID, err) - continue - } - - if proposal.StartEpoch < deadlineEpoch { - deadlineEpoch = proposal.StartEpoch + startEpoch := p.DealInfo.DealSchedule.StartEpoch + if startEpoch < deadlineEpoch { + deadlineEpoch = startEpoch } } From c7ba083fa4159deb060125235220996a67d4387e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 18 May 2021 16:51:06 +0200 Subject: [PATCH 17/88] Import precommit batcher --- api/api_storage.go | 9 ++- api/apistruct/struct.go | 12 ++++ chain/vm/invoker.go | 2 +- extern/storage-sealing/commit_batch.go | 66 ++++++++++++---------- extern/storage-sealing/sealiface/config.go | 6 ++ extern/storage-sealing/sealing.go | 18 ++++-- extern/storage-sealing/terminate_batch.go | 8 ++- node/config/def.go | 31 ++++++++-- node/impl/storminer.go | 8 +++ node/modules/storageminer.go | 24 +++++--- storage/sealing.go | 8 +++ 11 files changed, 140 insertions(+), 52 deletions(-) diff --git a/api/api_storage.go b/api/api_storage.go index a9dec3d0e..f9a5ccdd8 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -80,8 +80,13 @@ type StorageMiner interface { // SectorTerminatePending returns a list of pending sector terminations to be sent in the next batch message SectorTerminatePending(ctx context.Context) ([]abi.SectorID, error) //perm:admin SectorMarkForUpgrade(ctx context.Context, id abi.SectorNumber) error //perm:admin - SectorCommitFlush(ctx context.Context) (*cid.Cid, error) //perm:admin - SectorCommitPending(ctx context.Context) ([]abi.SectorID, error) //perm:admin + // SectorPreCommitFlush immediately sends a PreCommit message with sectors batched for PreCommit. + // Returns null if message wasn't sent + SectorPreCommitFlush(ctx context.Context) (*cid.Cid, error) //perm:admin + // SectorPreCommitPending returns a list of pending PreCommit sectors to be sent in the next batch message + SectorPreCommitPending(ctx context.Context) ([]abi.SectorID, error) //perm:admin + SectorCommitFlush(ctx context.Context) (*cid.Cid, error) //perm:admin + SectorCommitPending(ctx context.Context) ([]abi.SectorID, error) //perm:admin // WorkerConnect tells the node to connect to workers RPC WorkerConnect(context.Context, string) error //perm:admin retry:true diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index 6917a2967..13375cf72 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -649,6 +649,10 @@ type StorageMinerStruct struct { SectorMarkForUpgrade func(p0 context.Context, p1 abi.SectorNumber) error `perm:"admin"` + SectorPreCommitFlush func(p0 context.Context) (*cid.Cid, error) `perm:"admin"` + + SectorPreCommitPending func(p0 context.Context) ([]abi.SectorID, error) `perm:"admin"` + SectorRemove func(p0 context.Context, p1 abi.SectorNumber) error `perm:"admin"` SectorSetExpectedSealDuration func(p0 context.Context, p1 time.Duration) error `perm:"write"` @@ -1947,6 +1951,14 @@ func (s *StorageMinerStruct) SectorMarkForUpgrade(p0 context.Context, p1 abi.Sec return s.Internal.SectorMarkForUpgrade(p0, p1) } +func (s *StorageMinerStruct) SectorPreCommitFlush(p0 context.Context) (*cid.Cid, error) { + return s.Internal.SectorPreCommitFlush(p0) +} + +func (s *StorageMinerStruct) SectorPreCommitPending(p0 context.Context) ([]abi.SectorID, error) { + return s.Internal.SectorPreCommitPending(p0) +} + func (s *StorageMinerStruct) SectorRemove(p0 context.Context, p1 abi.SectorNumber) error { return s.Internal.SectorRemove(p0, p1) } diff --git a/chain/vm/invoker.go b/chain/vm/invoker.go index 4a8032770..e4b154031 100644 --- a/chain/vm/invoker.go +++ b/chain/vm/invoker.go @@ -155,7 +155,7 @@ func (*ActorRegistry) transform(instance invokee) (nativeCode, error) { "vmr.Runtime, ") } if !runtimeType.Implements(t.In(0)) { - return nil, newErr("first arguemnt should be vmr.Runtime") + return nil, newErr("first argument should be vmr.Runtime") } if t.In(1).Kind() != reflect.Ptr { return nil, newErr("second argument should be of kind reflect.Ptr") diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go index ba9d988ca..5628acdc0 100644 --- a/extern/storage-sealing/commit_batch.go +++ b/extern/storage-sealing/commit_batch.go @@ -155,32 +155,6 @@ func (b *CommitBatcher) batchWait(maxWait, slack time.Duration) time.Duration { return wait } -func (b *CommitBatcher) getSectorDeadline(si SectorInfo) time.Time { - tok, curEpoch, err := b.api.ChainHead(b.mctx) - if err != nil { - log.Errorf("getting chain head: %s", err) - return time.Time{} - } - - deadlineEpoch := si.TicketEpoch - for _, p := range si.Pieces { - if p.DealInfo == nil { - continue - } - - startEpoch := p.DealInfo.DealSchedule.StartEpoch - if startEpoch < deadlineEpoch { - deadlineEpoch = startEpoch - } - } - - if deadlineEpoch <= curEpoch { - return time.Now() - } - - return time.Duration(deadlineEpoch-curEpoch) * time.Duration(build.BlockDelaySecs) * time.Second -} - func (b *CommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { b.lk.Lock() defer b.lk.Unlock() @@ -208,20 +182,27 @@ func (b *CommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { spt := b.todo[0].spt proofs := make([][]byte, 0, total) + infos := make([]proof5.AggregateSealVerifyInfo, 0, total) for id, p := range b.todo { params.SectorNumbers.Set(uint64(id)) proofs = append(proofs, p.proof) + infos = append(infos, p.info) } - params.AggregateProof, err = b.verif.AggregateSealProofs(spt, arp, proofs) + params.AggregateProof, err = b.verif.AggregateSealProofs(proof5.AggregateSealVerifyProofAndInfos{ + Miner: 0, + SealProof: spt, + AggregateProof: arp, + Infos: infos, + }, proofs) if err != nil { return nil, xerrors.Errorf("aggregating proofs: %w", err) } enc := new(bytes.Buffer) if err := params.MarshalCBOR(enc); err != nil { - return nil, xerrors.Errorf("couldn't serialize TerminateSectors params: %w", err) + return nil, xerrors.Errorf("couldn't serialize ProveCommitAggregateParams: %w", err) } mi, err := b.api.StateMinerInfo(b.mctx, b.maddr, nil) @@ -261,9 +242,16 @@ func (b *CommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { // register commit, wait for batch message, return message CID func (b *CommitBatcher) AddCommit(ctx context.Context, s SectorInfo, in AggregateInput) (mcid cid.Cid, err error) { + _, curEpoch, err := b.api.ChainHead(b.mctx) + if err != nil { + log.Errorf("getting chain head: %s", err) + return cid.Undef, nil + } + sn := s.SectorNumber + b.lk.Lock() - b.deadlines[sn] = b.getSectorDeadline(s) + b.deadlines[sn] = getSectorDeadline(curEpoch, s) b.todo[sn] = in sent := make(chan cid.Cid, 1) @@ -336,3 +324,23 @@ func (b *CommitBatcher) Stop(ctx context.Context) error { return ctx.Err() } } + +func getSectorDeadline(curEpoch abi.ChainEpoch, si SectorInfo) time.Time { + deadlineEpoch := si.TicketEpoch + for _, p := range si.Pieces { + if p.DealInfo == nil { + continue + } + + startEpoch := p.DealInfo.DealSchedule.StartEpoch + if startEpoch < deadlineEpoch { + deadlineEpoch = startEpoch + } + } + + if deadlineEpoch <= curEpoch { + return time.Now() + } + + return time.Now().Add(time.Duration(deadlineEpoch-curEpoch) * time.Duration(build.BlockDelaySecs) * time.Second) +} diff --git a/extern/storage-sealing/sealiface/config.go b/extern/storage-sealing/sealiface/config.go index f9784b642..54ba2ef58 100644 --- a/extern/storage-sealing/sealiface/config.go +++ b/extern/storage-sealing/sealiface/config.go @@ -18,6 +18,12 @@ type Config struct { AlwaysKeepUnsealedCopy bool + BatchPreCommits bool + MaxPreCommitBatch int + MinPreCommitBatch int + PreCommitBatchWait time.Duration + PreCommitBatchSlack time.Duration + AggregateCommits bool MinCommitBatch int MaxCommitBatch int diff --git a/extern/storage-sealing/sealing.go b/extern/storage-sealing/sealing.go index 35ff15f51..ede281e39 100644 --- a/extern/storage-sealing/sealing.go +++ b/extern/storage-sealing/sealing.go @@ -102,8 +102,9 @@ type Sealing struct { stats SectorStats - terminator *TerminateBatcher - commiter *CommitBatcher + terminator *TerminateBatcher + precommiter *PreCommitBatcher + commiter *CommitBatcher getConfig GetSealingConfigFunc dealInfo *CurrentDealInfoManager @@ -152,8 +153,9 @@ func New(api SealingAPI, fc FeeConfig, events Events, maddr address.Address, ds notifee: notifee, addrSel: as, - terminator: NewTerminationBatcher(context.TODO(), maddr, api, as, fc, gc), - commiter: NewCommitBatcher(context.TODO(), maddr, api, as, fc, gc, verif), + terminator: NewTerminationBatcher(context.TODO(), maddr, api, as, fc, gc), + precommiter: NewPreCommitBatcher(context.TODO(), maddr, api, as, fc, gc), + commiter: NewCommitBatcher(context.TODO(), maddr, api, as, fc, gc, verif), getConfig: gc, dealInfo: &CurrentDealInfoManager{api}, @@ -204,6 +206,14 @@ func (m *Sealing) TerminatePending(ctx context.Context) ([]abi.SectorID, error) return m.terminator.Pending(ctx) } +func (m *Sealing) SectorPreCommitFlush(ctx context.Context) (*cid.Cid, error) { + return m.precommiter.Flush(ctx) +} + +func (m *Sealing) SectorPreCommitPending(ctx context.Context) ([]abi.SectorID, error) { + return m.precommiter.Pending(ctx) +} + func (m *Sealing) CommitFlush(ctx context.Context) (*cid.Cid, error) { return m.commiter.Flush(ctx) } diff --git a/extern/storage-sealing/terminate_batch.go b/extern/storage-sealing/terminate_batch.go index 3833109a1..2bb2dc76a 100644 --- a/extern/storage-sealing/terminate_batch.go +++ b/extern/storage-sealing/terminate_batch.go @@ -80,6 +80,11 @@ func (b *TerminateBatcher) run() { } lastMsg = nil + cfg, err := b.getConfig() + if err != nil { + log.Warnw("TerminateBatcher getconfig error", "error", err) + } + var sendAboveMax, sendAboveMin bool select { case <-b.stop: @@ -87,13 +92,12 @@ func (b *TerminateBatcher) run() { return case <-b.notify: sendAboveMax = true - case <-time.After(TerminateBatchWait): + case <-time.After(cfg.TerminateBatchWait): sendAboveMin = true case fr := <-b.force: // user triggered forceRes = fr } - var err error lastMsg, err = b.processBatch(sendAboveMax, sendAboveMin) if err != nil { log.Warnw("TerminateBatcher processBatch error", "error", err) diff --git a/node/config/def.go b/node/config/def.go index 553aabb1e..207419c6c 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -82,9 +82,21 @@ type SealingConfig struct { AlwaysKeepUnsealedCopy bool + BatchPreCommits bool + MaxPreCommitBatch int + MinPreCommitBatch int + PreCommitBatchWait Duration + PreCommitBatchSlack Duration + AggregateCommits bool MinCommitBatch int MaxCommitBatch int + CommitBatchWait Duration + CommitBatchSlack Duration + + TerminateBatchMax uint64 + TerminateBatchMin uint64 + TerminateBatchWait Duration // Keep this many sectors in sealing pipeline, start CC if needed // todo TargetSealingSectors uint64 @@ -242,14 +254,21 @@ func DefaultStorageMiner() *StorageMiner { WaitDealsDelay: Duration(time.Hour * 6), AlwaysKeepUnsealedCopy: true, - AggregateCommits: true, - MinCommitBatch: 1, // we must have at least one proof to aggregate - MaxCommitBatch: 204, // this is the maximum aggregation per FIP13 - CommitBatchWait: time.Day, // this can be up to 6 days - CommitBatchSlack: 8 * time.Hour, + BatchPreCommits: true, + MinPreCommitBatch: 1, // we must have at least one proof to aggregate + MaxPreCommitBatch: 204, // todo max? + PreCommitBatchWait: Duration(24 * time.Hour), // this can be up to 6 days + PreCommitBatchSlack: Duration(8 * time.Hour), + + AggregateCommits: true, + MinCommitBatch: 1, // we must have at least one proof to aggregate + MaxCommitBatch: 204, // this is the maximum aggregation per FIP13 + CommitBatchWait: Duration(24 * time.Hour), // this can be up to 6 days + CommitBatchSlack: Duration(8 * time.Hour), + TerminateBatchMin: 1, TerminateBatchMax: 100, - TerminateBatchWait: 5 * time.Minute, + TerminateBatchWait: Duration(5 * time.Minute), }, Storage: sectorstorage.SealerConfig{ diff --git a/node/impl/storminer.go b/node/impl/storminer.go index 8766ba154..8660f1efb 100644 --- a/node/impl/storminer.go +++ b/node/impl/storminer.go @@ -374,6 +374,14 @@ func (sm *StorageMinerAPI) SectorTerminatePending(ctx context.Context) ([]abi.Se return sm.Miner.TerminatePending(ctx) } +func (sm *StorageMinerAPI) SectorPreCommitFlush(ctx context.Context) (*cid.Cid, error) { + return sm.Miner.SectorPreCommitFlush(ctx) +} + +func (sm *StorageMinerAPI) SectorPreCommitPending(ctx context.Context) ([]abi.SectorID, error) { + return sm.Miner.SectorPreCommitPending(ctx) +} + func (sm *StorageMinerAPI) SectorMarkForUpgrade(ctx context.Context, id abi.SectorNumber) error { return sm.Miner.MarkForUpgrade(id) } diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index 66052600e..6458bfe69 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -847,14 +847,22 @@ func NewGetSealConfigFunc(r repo.LockedRepo) (dtypes.GetSealingConfigFunc, error MaxSealingSectorsForDeals: cfg.Sealing.MaxSealingSectorsForDeals, WaitDealsDelay: time.Duration(cfg.Sealing.WaitDealsDelay), AlwaysKeepUnsealedCopy: cfg.Sealing.AlwaysKeepUnsealedCopy, - AggregateCommits: cfg.Sealing.AggregateCommits, - MinCommitBatch: cfg.Sealing.MinCommitBatch, - MaxCommitBatch: cfg.Sealing.MaxCommitBatch, - CommitBatchWait: cfg.Sealing.CommitBatchWait, - CommitBatchSlack: cfg.Sealing.CommitBatchSlack, - TerminateBatchMax: cfg.Sealing.TerminateBatchMax, - TerminateBatchMin: cfg.Sealing.TerminateBatchMin, - TerminateBatchWait: cfg.Sealing.TerminateBatchWait, + + BatchPreCommits: cfg.Sealing.BatchPreCommits, + MinPreCommitBatch: cfg.Sealing.MinPreCommitBatch, + MaxPreCommitBatch: cfg.Sealing.MaxPreCommitBatch, + PreCommitBatchWait: time.Duration(cfg.Sealing.PreCommitBatchWait), + PreCommitBatchSlack: time.Duration(cfg.Sealing.PreCommitBatchSlack), + + AggregateCommits: cfg.Sealing.AggregateCommits, + MinCommitBatch: cfg.Sealing.MinCommitBatch, + MaxCommitBatch: cfg.Sealing.MaxCommitBatch, + CommitBatchWait: time.Duration(cfg.Sealing.CommitBatchWait), + CommitBatchSlack: time.Duration(cfg.Sealing.CommitBatchSlack), + + TerminateBatchMax: cfg.Sealing.TerminateBatchMax, + TerminateBatchMin: cfg.Sealing.TerminateBatchMin, + TerminateBatchWait: time.Duration(cfg.Sealing.TerminateBatchWait), } }) return diff --git a/storage/sealing.go b/storage/sealing.go index b3d38909b..cd215f238 100644 --- a/storage/sealing.go +++ b/storage/sealing.go @@ -59,6 +59,14 @@ func (m *Miner) TerminatePending(ctx context.Context) ([]abi.SectorID, error) { return m.sealing.TerminatePending(ctx) } +func (m *Miner) SectorPreCommitFlush(ctx context.Context) (*cid.Cid, error) { + return m.sealing.SectorPreCommitFlush(ctx) +} + +func (m *Miner) SectorPreCommitPending(ctx context.Context) ([]abi.SectorID, error) { + return m.sealing.SectorPreCommitPending(ctx) +} + func (m *Miner) CommitFlush(ctx context.Context) (*cid.Cid, error) { return m.sealing.CommitFlush(ctx) } From 81b5d8c671d48ce7babe4f6982d684211bab6e66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 18 May 2021 16:53:49 +0200 Subject: [PATCH 18/88] Make things build with both batchers --- chain/gen/gen.go | 2 +- node/modules/storageminer.go | 24 ++++++++++++++++-------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/chain/gen/gen.go b/chain/gen/gen.go index 32b238433..b9173d781 100644 --- a/chain/gen/gen.go +++ b/chain/gen/gen.go @@ -693,7 +693,7 @@ func (m genFakeVerifier) VerifyAggregateSeals(aggregate proof5.AggregateSealVeri panic("not supported") } -func (m genFakeVerifier) AggregateSealProofs(proofType abi.RegisteredSealProof, rap abi.RegisteredAggregationProof, proofs [][]byte) ([]byte, error) { +func (m genFakeVerifier) AggregateSealProofs(ai proof5.AggregateSealVerifyProofAndInfos, proofs [][]byte) ([]byte, error) { panic("not supported") } diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index 6458bfe69..8a9a99175 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -824,14 +824,22 @@ func NewSetSealConfigFunc(r repo.LockedRepo) (dtypes.SetSealingConfigFunc, error MaxSealingSectorsForDeals: cfg.MaxSealingSectorsForDeals, WaitDealsDelay: config.Duration(cfg.WaitDealsDelay), AlwaysKeepUnsealedCopy: cfg.AlwaysKeepUnsealedCopy, - AggregateCommits: cfg.AggregateCommits, - MinCommitBatch: cfg.MinCommitBatch, - MaxCommitBatch: cfg.MaxCommitBatch, - CommitBatchWait: cfg.CommitBatchWait, - CommitBatchSlack: cfg.CommitBatchSlack, - TerminateBatchMax: cfg.TerminateBatchMax, - TerminateBatchMin: cfg.TerminateBatchMin, - TerminateBatchWait: cfg.TerminateBatchWait, + + BatchPreCommits: cfg.BatchPreCommits, + MinPreCommitBatch: cfg.MinPreCommitBatch, + MaxPreCommitBatch: cfg.MaxPreCommitBatch, + PreCommitBatchWait: config.Duration(cfg.PreCommitBatchWait), + PreCommitBatchSlack: config.Duration(cfg.PreCommitBatchSlack), + + AggregateCommits: cfg.AggregateCommits, + MinCommitBatch: cfg.MinCommitBatch, + MaxCommitBatch: cfg.MaxCommitBatch, + CommitBatchWait: config.Duration(cfg.CommitBatchWait), + CommitBatchSlack: config.Duration(cfg.CommitBatchSlack), + + TerminateBatchMax: cfg.TerminateBatchMax, + TerminateBatchMin: cfg.TerminateBatchMin, + TerminateBatchWait: config.Duration(cfg.TerminateBatchWait), } }) return From d92c5e100173a1aa74485357aea892b5dfbe6058 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 18 May 2021 16:54:55 +0200 Subject: [PATCH 19/88] Missing precommit batcher --- extern/storage-sealing/precommit_batch.go | 298 ++++++++++++++++++++++ 1 file changed, 298 insertions(+) create mode 100644 extern/storage-sealing/precommit_batch.go diff --git a/extern/storage-sealing/precommit_batch.go b/extern/storage-sealing/precommit_batch.go new file mode 100644 index 000000000..93846fbef --- /dev/null +++ b/extern/storage-sealing/precommit_batch.go @@ -0,0 +1,298 @@ +package sealing + +import ( + "bytes" + "context" + "sort" + "sync" + "time" + + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/big" + miner0 "github.com/filecoin-project/specs-actors/actors/builtin/miner" + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" +) + +var ( + // TODO: config + + PreCommitBatchMax uint64 = 100 // adjust based on real-world gas numbers, actors limit at 10k + PreCommitBatchMin uint64 = 1 + PreCommitBatchWait = 5 * time.Minute +) + +type PreCommitBatcherApi interface { + SendMsg(ctx context.Context, from, to address.Address, method abi.MethodNum, value, maxFee abi.TokenAmount, params []byte) (cid.Cid, error) + StateMinerInfo(context.Context, address.Address, TipSetToken) (miner.MinerInfo, error) + ChainHead(ctx context.Context) (TipSetToken, abi.ChainEpoch, error) +} + +type PreCommitBatcher struct { + api PreCommitBatcherApi + maddr address.Address + mctx context.Context + addrSel AddrSel + feeCfg FeeConfig + getConfig GetSealingConfigFunc + + deadlines map[abi.SectorNumber]time.Time + todo map[abi.SectorNumber]*miner0.SectorPreCommitInfo + waiting map[abi.SectorNumber][]chan cid.Cid + + notify, stop, stopped chan struct{} + force chan chan *cid.Cid + lk sync.Mutex +} + +func NewPreCommitBatcher(mctx context.Context, maddr address.Address, api PreCommitBatcherApi, addrSel AddrSel, feeCfg FeeConfig, getConfig GetSealingConfigFunc) *PreCommitBatcher { + b := &PreCommitBatcher{ + api: api, + maddr: maddr, + mctx: mctx, + addrSel: addrSel, + feeCfg: feeCfg, + getConfig: getConfig, + + deadlines: map[abi.SectorNumber]time.Time{}, + todo: map[abi.SectorNumber]*miner0.SectorPreCommitInfo{}, + waiting: map[abi.SectorNumber][]chan cid.Cid{}, + + notify: make(chan struct{}, 1), + force: make(chan chan *cid.Cid), + stop: make(chan struct{}), + stopped: make(chan struct{}), + } + + go b.run() + + return b +} + +func (b *PreCommitBatcher) run() { + var forceRes chan *cid.Cid + var lastMsg *cid.Cid + + cfg, err := b.getConfig() + if err != nil { + panic(err) + } + + for { + if forceRes != nil { + forceRes <- lastMsg + forceRes = nil + } + lastMsg = nil + + var sendAboveMax, sendAboveMin bool + select { + case <-b.stop: + close(b.stopped) + return + case <-b.notify: + sendAboveMax = true + case <-time.After(b.batchWait(cfg.PreCommitBatchWait, cfg.PreCommitBatchSlack)): + sendAboveMin = true + case fr := <-b.force: // user triggered + forceRes = fr + } + + var err error + lastMsg, err = b.processBatch(sendAboveMax, sendAboveMin) + if err != nil { + log.Warnw("TerminateBatcher processBatch error", "error", err) + } + } +} + +func (b *PreCommitBatcher) batchWait(maxWait, slack time.Duration) time.Duration { + now := time.Now() + + b.lk.Lock() + defer b.lk.Unlock() + + var deadline time.Time + for sn := range b.todo { + sectorDeadline := b.deadlines[sn] + if deadline.IsZero() || (!sectorDeadline.IsZero() && sectorDeadline.Before(deadline)) { + deadline = sectorDeadline + } + } + for sn := range b.waiting { + sectorDeadline := b.deadlines[sn] + if deadline.IsZero() || (!sectorDeadline.IsZero() && sectorDeadline.Before(deadline)) { + deadline = sectorDeadline + } + } + + if deadline.IsZero() { + return maxWait + } + + deadline = deadline.Add(-slack) + if deadline.Before(now) { + return time.Nanosecond // can't return 0 + } + + wait := deadline.Sub(now) + if wait > maxWait { + wait = maxWait + } + + return wait +} + +func (b *PreCommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { + b.lk.Lock() + defer b.lk.Unlock() + params := miner5.PreCommitSectorBatchParams{} + + total := len(b.todo) + if total == 0 { + return nil, nil // nothing to do + } + + cfg, err := b.getConfig() + if err != nil { + return nil, xerrors.Errorf("getting config: %w", err) + } + + if notif && total < cfg.MaxPreCommitBatch { + return nil, nil + } + + if after && total < cfg.MinPreCommitBatch { + return nil, nil + } + + for _, p := range b.todo { + params.Sectors = append(params.Sectors, p) + } + + enc := new(bytes.Buffer) + if err := params.MarshalCBOR(enc); err != nil { + return nil, xerrors.Errorf("couldn't serialize PreCommitSectorBatchParams: %w", err) + } + + mi, err := b.api.StateMinerInfo(b.mctx, b.maddr, nil) + if err != nil { + return nil, xerrors.Errorf("couldn't get miner info: %w", err) + } + + from, _, err := b.addrSel(b.mctx, mi, api.PreCommitAddr, b.feeCfg.MaxPreCommitGasFee, b.feeCfg.MaxPreCommitGasFee) + if err != nil { + return nil, xerrors.Errorf("no good address found: %w", err) + } + + mcid, err := b.api.SendMsg(b.mctx, from, b.maddr, miner.Methods.PreCommitSectorBatch, big.Zero(), b.feeCfg.MaxPreCommitGasFee, enc.Bytes()) + if err != nil { + return nil, xerrors.Errorf("sending message failed: %w", err) + } + + log.Infow("Sent ProveCommitAggregate message", "cid", mcid, "from", from, "sectors", total) + + for _, sector := range params.Sectors { + sn := sector.SectorNumber + + for _, ch := range b.waiting[sn] { + ch <- mcid // buffered + } + delete(b.waiting, sn) + delete(b.todo, sn) + delete(b.deadlines, sn) + } + + return &mcid, nil +} + +// register PreCommit, wait for batch message, return message CID +func (b *PreCommitBatcher) AddPreCommit(ctx context.Context, s SectorInfo, in *miner0.SectorPreCommitInfo) (mcid cid.Cid, err error) { + _, curEpoch, err := b.api.ChainHead(b.mctx) + if err != nil { + log.Errorf("getting chain head: %s", err) + return cid.Undef, nil + } + + sn := s.SectorNumber + + b.lk.Lock() + b.deadlines[sn] = getSectorDeadline(curEpoch, s) + b.todo[sn] = in + + sent := make(chan cid.Cid, 1) + b.waiting[sn] = append(b.waiting[sn], sent) + + select { + case b.notify <- struct{}{}: + default: // already have a pending notification, don't need more + } + b.lk.Unlock() + + select { + case c := <-sent: + return c, nil + case <-ctx.Done(): + return cid.Undef, ctx.Err() + } +} + +func (b *PreCommitBatcher) Flush(ctx context.Context) (*cid.Cid, error) { + resCh := make(chan *cid.Cid, 1) + select { + case b.force <- resCh: + select { + case res := <-resCh: + return res, nil + case <-ctx.Done(): + return nil, ctx.Err() + } + case <-ctx.Done(): + return nil, ctx.Err() + } +} + +func (b *PreCommitBatcher) Pending(ctx context.Context) ([]abi.SectorID, error) { + b.lk.Lock() + defer b.lk.Unlock() + + mid, err := address.IDFromAddress(b.maddr) + if err != nil { + return nil, err + } + + res := make([]abi.SectorID, 0) + for _, s := range b.todo { + res = append(res, abi.SectorID{ + Miner: abi.ActorID(mid), + Number: s.SectorNumber, + }) + } + + sort.Slice(res, func(i, j int) bool { + if res[i].Miner != res[j].Miner { + return res[i].Miner < res[j].Miner + } + + return res[i].Number < res[j].Number + }) + + return res, nil +} + +func (b *PreCommitBatcher) Stop(ctx context.Context) error { + close(b.stop) + + select { + case <-b.stopped: + return nil + case <-ctx.Done(): + return ctx.Err() + } +} From f66b9c56634930c2e4018dd1c3ebf040403f4b26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 18 May 2021 17:21:10 +0200 Subject: [PATCH 20/88] Maybe working precommit batching --- cmd/lotus-storage-miner/info.go | 2 + extern/storage-sealing/fsm.go | 7 ++- extern/storage-sealing/fsm_events.go | 4 ++ extern/storage-sealing/precommit_batch.go | 37 +++++++----- extern/storage-sealing/sector_state.go | 27 +++++---- extern/storage-sealing/states_sealing.go | 72 ++++++++++++++++------- 6 files changed, 99 insertions(+), 50 deletions(-) diff --git a/cmd/lotus-storage-miner/info.go b/cmd/lotus-storage-miner/info.go index 7a8835fee..0fe14f1ff 100644 --- a/cmd/lotus-storage-miner/info.go +++ b/cmd/lotus-storage-miner/info.go @@ -291,6 +291,8 @@ var stateList = []stateMeta{ {col: color.FgYellow, state: sealing.PreCommit2}, {col: color.FgYellow, state: sealing.PreCommitting}, {col: color.FgYellow, state: sealing.PreCommitWait}, + {col: color.FgYellow, state: sealing.SubmitPreCommitBatch}, + {col: color.FgYellow, state: sealing.PreCommitBatchWait}, {col: color.FgYellow, state: sealing.WaitSeed}, {col: color.FgYellow, state: sealing.Committing}, {col: color.FgYellow, state: sealing.SubmitCommit}, diff --git a/extern/storage-sealing/fsm.go b/extern/storage-sealing/fsm.go index 367938099..ab1cefc1e 100644 --- a/extern/storage-sealing/fsm.go +++ b/extern/storage-sealing/fsm.go @@ -71,6 +71,7 @@ var fsmPlanners = map[SectorState]func(events []statemachine.Event, state *Secto on(SectorSealPreCommit1Failed{}, SealPreCommit1Failed), ), PreCommitting: planOne( + on(SectorPreCommitBatch{}, SubmitPreCommitBatch), on(SectorSealPreCommit1Failed{}, SealPreCommit1Failed), on(SectorPreCommitted{}, PreCommitWait), on(SectorChainPreCommitFailed{}, PreCommitFailed), @@ -340,6 +341,8 @@ func (m *Sealing) plan(events []statemachine.Event, state *SectorInfo) (func(sta return m.handlePreCommit2, processed, nil case PreCommitting: return m.handlePreCommitting, processed, nil + case SubmitPreCommitBatch: + return m.handleSubmitPreCommitBatch, processed, nil case PreCommitWait: return m.handlePreCommitWait, processed, nil case WaitSeed: @@ -348,12 +351,12 @@ func (m *Sealing) plan(events []statemachine.Event, state *SectorInfo) (func(sta return m.handleCommitting, processed, nil case SubmitCommit: return m.handleSubmitCommit, processed, nil + case SubmitCommitAggregate: + return m.handleSubmitCommitAggregate, processed, nil case CommitAggregateWait: fallthrough case CommitWait: return m.handleCommitWait, processed, nil - case SubmitCommitAggregate: - return m.handleSubmitCommitAggregate, processed, nil case FinalizeSector: return m.handleFinalizeSector, processed, nil diff --git a/extern/storage-sealing/fsm_events.go b/extern/storage-sealing/fsm_events.go index bced1921f..2ae08130d 100644 --- a/extern/storage-sealing/fsm_events.go +++ b/extern/storage-sealing/fsm_events.go @@ -150,6 +150,10 @@ func (evt SectorPreCommit2) apply(state *SectorInfo) { state.CommR = &commr } +type SectorPreCommitBatch struct{} + +func (evt SectorPreCommitBatch) apply(*SectorInfo) {} + type SectorPreCommitLanded struct { TipSet TipSetToken } diff --git a/extern/storage-sealing/precommit_batch.go b/extern/storage-sealing/precommit_batch.go index 93846fbef..2016c6d8f 100644 --- a/extern/storage-sealing/precommit_batch.go +++ b/extern/storage-sealing/precommit_batch.go @@ -20,20 +20,17 @@ import ( "github.com/filecoin-project/lotus/chain/actors/builtin/miner" ) -var ( - // TODO: config - - PreCommitBatchMax uint64 = 100 // adjust based on real-world gas numbers, actors limit at 10k - PreCommitBatchMin uint64 = 1 - PreCommitBatchWait = 5 * time.Minute -) - type PreCommitBatcherApi interface { SendMsg(ctx context.Context, from, to address.Address, method abi.MethodNum, value, maxFee abi.TokenAmount, params []byte) (cid.Cid, error) StateMinerInfo(context.Context, address.Address, TipSetToken) (miner.MinerInfo, error) ChainHead(ctx context.Context) (TipSetToken, abi.ChainEpoch, error) } +type preCommitEntry struct { + deposit abi.TokenAmount + pci *miner0.SectorPreCommitInfo +} + type PreCommitBatcher struct { api PreCommitBatcherApi maddr address.Address @@ -43,7 +40,7 @@ type PreCommitBatcher struct { getConfig GetSealingConfigFunc deadlines map[abi.SectorNumber]time.Time - todo map[abi.SectorNumber]*miner0.SectorPreCommitInfo + todo map[abi.SectorNumber]*preCommitEntry waiting map[abi.SectorNumber][]chan cid.Cid notify, stop, stopped chan struct{} @@ -61,7 +58,7 @@ func NewPreCommitBatcher(mctx context.Context, maddr address.Address, api PreCom getConfig: getConfig, deadlines: map[abi.SectorNumber]time.Time{}, - todo: map[abi.SectorNumber]*miner0.SectorPreCommitInfo{}, + todo: map[abi.SectorNumber]*preCommitEntry{}, waiting: map[abi.SectorNumber][]chan cid.Cid{}, notify: make(chan struct{}, 1), @@ -172,8 +169,11 @@ func (b *PreCommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { return nil, nil } + deposit := big.Zero() + for _, p := range b.todo { - params.Sectors = append(params.Sectors, p) + params.Sectors = append(params.Sectors, p.pci) + deposit = big.Add(deposit, p.deposit) } enc := new(bytes.Buffer) @@ -186,12 +186,14 @@ func (b *PreCommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { return nil, xerrors.Errorf("couldn't get miner info: %w", err) } - from, _, err := b.addrSel(b.mctx, mi, api.PreCommitAddr, b.feeCfg.MaxPreCommitGasFee, b.feeCfg.MaxPreCommitGasFee) + goodFunds := big.Add(deposit, b.feeCfg.MaxPreCommitGasFee) + + from, _, err := b.addrSel(b.mctx, mi, api.PreCommitAddr, goodFunds, deposit) if err != nil { return nil, xerrors.Errorf("no good address found: %w", err) } - mcid, err := b.api.SendMsg(b.mctx, from, b.maddr, miner.Methods.PreCommitSectorBatch, big.Zero(), b.feeCfg.MaxPreCommitGasFee, enc.Bytes()) + mcid, err := b.api.SendMsg(b.mctx, from, b.maddr, miner.Methods.PreCommitSectorBatch, deposit, b.feeCfg.MaxPreCommitGasFee, enc.Bytes()) if err != nil { return nil, xerrors.Errorf("sending message failed: %w", err) } @@ -213,7 +215,7 @@ func (b *PreCommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { } // register PreCommit, wait for batch message, return message CID -func (b *PreCommitBatcher) AddPreCommit(ctx context.Context, s SectorInfo, in *miner0.SectorPreCommitInfo) (mcid cid.Cid, err error) { +func (b *PreCommitBatcher) AddPreCommit(ctx context.Context, s SectorInfo, deposit abi.TokenAmount, in *miner0.SectorPreCommitInfo) (mcid cid.Cid, err error) { _, curEpoch, err := b.api.ChainHead(b.mctx) if err != nil { log.Errorf("getting chain head: %s", err) @@ -224,7 +226,10 @@ func (b *PreCommitBatcher) AddPreCommit(ctx context.Context, s SectorInfo, in *m b.lk.Lock() b.deadlines[sn] = getSectorDeadline(curEpoch, s) - b.todo[sn] = in + b.todo[sn] = &preCommitEntry{ + deposit: deposit, + pci: in, + } sent := make(chan cid.Cid, 1) b.waiting[sn] = append(b.waiting[sn], sent) @@ -271,7 +276,7 @@ func (b *PreCommitBatcher) Pending(ctx context.Context) ([]abi.SectorID, error) for _, s := range b.todo { res = append(res, abi.SectorID{ Miner: abi.ActorID(mid), - Number: s.SectorNumber, + Number: s.pci.SectorNumber, }) } diff --git a/extern/storage-sealing/sector_state.go b/extern/storage-sealing/sector_state.go index b6b7cbf7b..23c7695e7 100644 --- a/extern/storage-sealing/sector_state.go +++ b/extern/storage-sealing/sector_state.go @@ -13,6 +13,8 @@ var ExistSectorStateList = map[SectorState]struct{}{ PreCommit2: {}, PreCommitting: {}, PreCommitWait: {}, + SubmitPreCommitBatch: {}, + PreCommitBatchWait: {}, WaitSeed: {}, Committing: {}, SubmitCommit: {}, @@ -47,17 +49,22 @@ const ( UndefinedSectorState SectorState = "" // happy path - Empty SectorState = "Empty" // deprecated - WaitDeals SectorState = "WaitDeals" // waiting for more pieces (deals) to be added to the sector - AddPiece SectorState = "AddPiece" // put deal data (and padding if required) into the sector - Packing SectorState = "Packing" // sector not in sealStore, and not on chain - GetTicket SectorState = "GetTicket" // generate ticket - PreCommit1 SectorState = "PreCommit1" // do PreCommit1 - PreCommit2 SectorState = "PreCommit2" // do PreCommit2 + Empty SectorState = "Empty" // deprecated + WaitDeals SectorState = "WaitDeals" // waiting for more pieces (deals) to be added to the sector + AddPiece SectorState = "AddPiece" // put deal data (and padding if required) into the sector + Packing SectorState = "Packing" // sector not in sealStore, and not on chain + GetTicket SectorState = "GetTicket" // generate ticket + PreCommit1 SectorState = "PreCommit1" // do PreCommit1 + PreCommit2 SectorState = "PreCommit2" // do PreCommit2 + PreCommitting SectorState = "PreCommitting" // on chain pre-commit PreCommitWait SectorState = "PreCommitWait" // waiting for precommit to land on chain - WaitSeed SectorState = "WaitSeed" // waiting for seed - Committing SectorState = "Committing" // compute PoRep + + SubmitPreCommitBatch SectorState = "SubmitPreCommitBatch" + PreCommitBatchWait SectorState = "PreCommitBatchWait" + + WaitSeed SectorState = "WaitSeed" // waiting for seed + Committing SectorState = "Committing" // compute PoRep // single commit SubmitCommit SectorState = "SubmitCommit" // send commit message to the chain @@ -99,7 +106,7 @@ func toStatState(st SectorState) statSectorState { switch st { case UndefinedSectorState, Empty, WaitDeals, AddPiece: return sstStaging - case Packing, GetTicket, PreCommit1, PreCommit2, PreCommitting, PreCommitWait, WaitSeed, Committing, SubmitCommit, CommitWait, SubmitCommitAggregate, CommitAggregateWait, FinalizeSector: + case Packing, GetTicket, PreCommit1, PreCommit2, PreCommitting, PreCommitWait, SubmitPreCommitBatch, PreCommitBatchWait, WaitSeed, Committing, SubmitCommit, CommitWait, SubmitCommitAggregate, CommitAggregateWait, FinalizeSector: return sstSealing case Proving, Removed, Removing, Terminating, TerminateWait, TerminateFinality, TerminateFailed: return sstProving diff --git a/extern/storage-sealing/states_sealing.go b/extern/storage-sealing/states_sealing.go index 6e95fbd69..607f10aba 100644 --- a/extern/storage-sealing/states_sealing.go +++ b/extern/storage-sealing/states_sealing.go @@ -226,56 +226,50 @@ func (m *Sealing) remarkForUpgrade(sid abi.SectorNumber) { } } -func (m *Sealing) handlePreCommitting(ctx statemachine.Context, sector SectorInfo) error { +func (m *Sealing) preCommitParams(ctx statemachine.Context, sector SectorInfo) (*miner.SectorPreCommitInfo, big.Int, TipSetToken, error) { tok, height, err := m.api.ChainHead(ctx.Context()) if err != nil { log.Errorf("handlePreCommitting: api error, not proceeding: %+v", err) - return nil - } - - mi, err := m.api.StateMinerInfo(ctx.Context(), m.maddr, tok) - if err != nil { - log.Errorf("handlePreCommitting: api error, not proceeding: %+v", err) - return nil + return nil, big.Zero(), nil, nil } if err := checkPrecommit(ctx.Context(), m.Address(), sector, tok, height, m.api); err != nil { switch err := err.(type) { case *ErrApi: log.Errorf("handlePreCommitting: api error, not proceeding: %+v", err) - return nil + return nil, big.Zero(), nil, nil case *ErrBadCommD: // TODO: Should this just back to packing? (not really needed since handlePreCommit1 will do that too) - return ctx.Send(SectorSealPreCommit1Failed{xerrors.Errorf("bad CommD error: %w", err)}) + return nil, big.Zero(), nil, ctx.Send(SectorSealPreCommit1Failed{xerrors.Errorf("bad CommD error: %w", err)}) case *ErrExpiredTicket: - return ctx.Send(SectorSealPreCommit1Failed{xerrors.Errorf("ticket expired: %w", err)}) + return nil, big.Zero(), nil, ctx.Send(SectorSealPreCommit1Failed{xerrors.Errorf("ticket expired: %w", err)}) case *ErrBadTicket: - return ctx.Send(SectorSealPreCommit1Failed{xerrors.Errorf("bad ticket: %w", err)}) + return nil, big.Zero(), nil, ctx.Send(SectorSealPreCommit1Failed{xerrors.Errorf("bad ticket: %w", err)}) case *ErrInvalidDeals: log.Warnf("invalid deals in sector %d: %v", sector.SectorNumber, err) - return ctx.Send(SectorInvalidDealIDs{Return: RetPreCommitting}) + return nil, big.Zero(), nil, ctx.Send(SectorInvalidDealIDs{Return: RetPreCommitting}) case *ErrExpiredDeals: - return ctx.Send(SectorDealsExpired{xerrors.Errorf("sector deals expired: %w", err)}) + return nil, big.Zero(), nil, ctx.Send(SectorDealsExpired{xerrors.Errorf("sector deals expired: %w", err)}) case *ErrPrecommitOnChain: - return ctx.Send(SectorPreCommitLanded{TipSet: tok}) // we re-did precommit + return nil, big.Zero(), nil, ctx.Send(SectorPreCommitLanded{TipSet: tok}) // we re-did precommit case *ErrSectorNumberAllocated: log.Errorf("handlePreCommitFailed: sector number already allocated, not proceeding: %+v", err) // TODO: check if the sector is committed (not sure how we'd end up here) - return nil + return nil, big.Zero(), nil, nil default: - return xerrors.Errorf("checkPrecommit sanity check error: %w", err) + return nil, big.Zero(), nil, xerrors.Errorf("checkPrecommit sanity check error: %w", err) } } expiration, err := m.pcp.Expiration(ctx.Context(), sector.Pieces...) if err != nil { - return ctx.Send(SectorSealPreCommit1Failed{xerrors.Errorf("handlePreCommitting: failed to compute pre-commit expiry: %w", err)}) + return nil, big.Zero(), nil, ctx.Send(SectorSealPreCommit1Failed{xerrors.Errorf("handlePreCommitting: failed to compute pre-commit expiry: %w", err)}) } // Sectors must last _at least_ MinSectorExpiration + MaxSealDuration. // TODO: The "+10" allows the pre-commit to take 10 blocks to be accepted. nv, err := m.api.StateNetworkVersion(ctx.Context(), tok) if err != nil { - return ctx.Send(SectorSealPreCommit1Failed{xerrors.Errorf("failed to get network version: %w", err)}) + return nil, big.Zero(), nil, ctx.Send(SectorSealPreCommit1Failed{xerrors.Errorf("failed to get network version: %w", err)}) } msd := policy.GetMaxProveCommitDuration(actors.VersionForNetwork(nv), sector.SectorType) @@ -297,17 +291,33 @@ func (m *Sealing) handlePreCommitting(ctx statemachine.Context, sector SectorInf depositMinimum := m.tryUpgradeSector(ctx.Context(), params) + collateral, err := m.api.StateMinerPreCommitDepositForPower(ctx.Context(), m.maddr, *params, tok) + if err != nil { + return nil, big.Zero(), nil, xerrors.Errorf("getting initial pledge collateral: %w", err) + } + + deposit := big.Max(depositMinimum, collateral) + + return params, deposit, tok, nil +} + +func (m *Sealing) handlePreCommitting(ctx statemachine.Context, sector SectorInfo) error { + params, deposit, tok, err := m.preCommitParams(ctx, sector) + if err != nil { + return err + } + enc := new(bytes.Buffer) if err := params.MarshalCBOR(enc); err != nil { return ctx.Send(SectorChainPreCommitFailed{xerrors.Errorf("could not serialize pre-commit sector parameters: %w", err)}) } - collateral, err := m.api.StateMinerPreCommitDepositForPower(ctx.Context(), m.maddr, *params, tok) + mi, err := m.api.StateMinerInfo(ctx.Context(), m.maddr, tok) if err != nil { - return xerrors.Errorf("getting initial pledge collateral: %w", err) + log.Errorf("handlePreCommitting: api error, not proceeding: %+v", err) + return nil } - deposit := big.Max(depositMinimum, collateral) goodFunds := big.Add(deposit, m.feeCfg.MaxPreCommitGasFee) from, _, err := m.addrSel(ctx.Context(), mi, api.PreCommitAddr, goodFunds, deposit) @@ -327,6 +337,24 @@ func (m *Sealing) handlePreCommitting(ctx statemachine.Context, sector SectorInf return ctx.Send(SectorPreCommitted{Message: mcid, PreCommitDeposit: deposit, PreCommitInfo: *params}) } +func (m *Sealing) handleSubmitPreCommitBatch(ctx statemachine.Context, sector SectorInfo) error { + if sector.CommD == nil || sector.CommR == nil { + return ctx.Send(SectorCommitFailed{xerrors.Errorf("sector had nil commR or commD")}) + } + + params, deposit, _, err := m.preCommitParams(ctx, sector) + if err != nil { + return err + } + + mcid, err := m.precommiter.AddPreCommit(ctx.Context(), sector, deposit, params) + if err != nil { + return ctx.Send(SectorCommitFailed{xerrors.Errorf("queuing commit for aggregation failed: %w", err)}) + } + + return ctx.Send(SectorCommitAggregateSent{mcid}) +} + func (m *Sealing) handlePreCommitWait(ctx statemachine.Context, sector SectorInfo) error { if sector.PreCommitMessage == nil { return ctx.Send(SectorChainPreCommitFailed{xerrors.Errorf("precommit message was nil")}) From 1946d2ffd423bd70e543788f966275c1604d88d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 18 May 2021 17:37:52 +0200 Subject: [PATCH 21/88] Wire up Precommit Batching --- build/openrpc/full.json.gz | Bin 22810 -> 22809 bytes build/openrpc/miner.json.gz | Bin 7890 -> 7952 bytes build/openrpc/worker.json.gz | Bin 2577 -> 2577 bytes documentation/en/api-methods-miner.md | 23 ++++++++++++++++++++++ extern/storage-sealing/commit_batch.go | 2 +- extern/storage-sealing/fsm.go | 10 ++++++++++ extern/storage-sealing/fsm_events.go | 8 ++++++++ extern/storage-sealing/precommit_batch.go | 2 +- extern/storage-sealing/states_sealing.go | 20 +++++++++++++++++-- 9 files changed, 61 insertions(+), 4 deletions(-) diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index 1aad25356f8daa0c721ab56cf6c7f47c4f4b8fea..a81cb15650063bac74bd3fb4aa2cd03f77604826 100644 GIT binary patch literal 22809 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 A00000 literal 22810 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

-fDhK`xeCY=8+2=BLD= zuArx45gB^3pYqgAVSL&GpQ`=W0@gA^`28Rv=!Xqt`)&ma{-UNlM>&ni%8LAHx&;E@ z@3Aot$o}5l+tFS~0_6Vlc6d+rM}NKE+jIBm{vQ9E?E1UdzrP;`Q)fIyogd#GO?;Q; zDHRhLzcfH8+YjUjw%$(5Q?EUgldC|KHN3s;n)i;=P*xdB1 zw3P;TOlhg1AM_A$dp>e1B-r`d#tbPlutsw1q-O3mDm5um(FsDr5RY%0QqYZ&w?a!U zd(E`hOnc2-=WAve5i>leSMdG}sUz2LLq|0q-PGL5%$G9b$y0Mn>@Bgk#D2Yq{hA~C z3ahXy8HnZ3b&sG2#mYAY^Z5pvgv2ZpE~T$%FTo2#^YW5WEC%iQN@6@rxl{m7mDWfW z0np)ebzbf3EHHWM;+?g*;g|98Dr?+lZjAerBa5y%;jXY6RK)8X@f*ZYFh#{(Wk$JE zEf{;+HInj|byIEjTPO`ih^vqQa6&`E0UbspdJMUAk8{ExK!kg95JZ)t5XO4wn@hT_ z2=rmj-OhG}iH+KzH^@Vfp?0pX#U`47rYu0JHMTG9sNSfiSOo;?M}&;xGd7sVUm~BJ zZMH9}fziMkoGNQ@pE|-78uj9?ZGD83CVu9(28<>!F=onaMqG;N7a8O%}$NMlwLrHZgMpBERBk*?wWBcF zR&KS{qqPLw67WqY;8h9-Oi-Ymo(1h{PQr5y)CR1PC2rBg_aH}!J&=5S#YPKBb+NB% z(bX#B&}7DKY8H@><#G)Jah6uLQ&*)o?{;pnbEhI>e7-rm4k#U#X@h0d5%uzW=L;ss zG$cyfyr9+*f(|88SIDCHI9fX%|IUV|Nd=$Z-|dRd2qvRz+T*$(8IZD>LODB%b}4D2 z05`1^tT!|yMp|E!EVYe7#YBtN;7gMwZTYpy25I-f$p&e+RH<~h*6P4o9kjGM$mA3r z5vcgcWr#bU#T0FY!9gao;8tGi@it6ol zHSH{2mmEaYvHeD-)z4^fCn>ro50MJRE~f1WG8CKhigsBZs`ilM-(?>*g`(+3Q?%r1 zUcsBiN6hn}D~%9`9_e*8kqq+&w9IVra!{YPL)}! zpzr5MlIjaGr0vuLsRkP9cn5gvcnmR-!p>xZ%`Di1MRkz(XB#>2XL~^w4_<{=ZN7}L_UxL@ zq{wHx)9rjfd`5%2^rIefI1Lh^3=Qs3AeJp9BvNHm(Eq*tz1Q#F?)|>^=6AtM(KNY| z$&AqYezadNIVcqfoqBkL2Oc`3f%@E5`4cwmidZr!6}blDrEZF$v%8{s-hsj%coGwx z-qa?zF+M2mc2rL$MKeaJqBwF}q_m3$s_6f!c43Mv*sSE2J3G5oo)kW%-Vx-^_&Q|c z=0Gn~y$a@i{if9bJv<`F)t8Ew;KkQ(rsjJ2erlYTGDX2a{001wjpfCG$q|55$gv5U zh#IR4AXfghMjTLQ#L&m}t5tnl)wflBH&uP72zTnbpg2Q&;2V>wlJmaM1hejlZ=iW* z!IZM)I;=ZSt#WP3aGeljJCSDB z21shWRjZ7!#JZj%eoV;_7aePiwrgCyWXq-!GsxX0%Ne1Z`g;L^L;X3R z!X?CQ3ybj$@u<;Gu8Y&1d{kwu?o1IANy&A9X@L3s&~+0Flpg*EN!;`vkH(#Y zo!!0Evr9VDk8Y6fK?fbp&k(wS98>XpYp>h64nl&CDfL`BBb|dc+wp%1B>Ol;flg#0 zKS9)ND66Sz>U){RY34Ic?FSYr<#S(Z0_jR-tz^KtB`~sA$gI$F4N1MTUuPF^j<`bK zZHbSpZ647yG)hyNZ+rn8jY_H*rUcz{K;-WzJSp>$BVd>d7$PnTw)*6Qn0Oi5G-cfh zCU7pzKnDmwPAAye0-qRCADdQhV8S?p?&_|sYiAEyMt8&VJcR3;PHoK(W}R~JTA{&q z=TcX$I+jIa>m?M1NnZ<2sV*2pg#_;^a1i0%)g3Bp^=q{(R=j4#YgWAWbeQ7nIxS0k z1J*j*)e1dI6D^9F$*QzgeM~#m*0ef9ai2j7s~kZv2{iHZGH6+5Xqlm9hBoA?4Y~T0 zAWEI)X=!_415;pgL*Vwe`lc4ZY3Y~}ok7o=1IPr-k)jUqq#(HHNqVTie~DSZ{_s z4~#;@7=XT@bpeJPfgwVDB1oAFD2Y`X&KP1M)<@Axn^fB78MSz4eesS>D!r;mko6Km zd*`%wPD_I=4YqgAr*M_2vrJlnIlGZ1`-MR9!>!Dk+~GdKB_E`^%YH#oZdnV^!JM*a`v?>eaYUWpu5HBaw;!U%J@^vWU1SV2HEC-%mSB0Zt1}RV?&<4=beD)St>k z@o1ft(1gIft+IJ#cy#ExhrS=sX$y0bO!Z}xut%O-{(i~p*Co&1Xm4p_ zL2iDy>Dc|EU(t_fw}O~vsw?}Ask~wP0h(eOGH(vV(Wm0xe(1Uqt_F$m$pr@--XTST zht%`vOx%+c{tk+*e*N747nr>LvnD(|xBow#j@w)9ZIgpIPr)u~1FlgcX#;C4aIwI}0+$B_E*1({DDZTk!0%OhTE+OT6+dZ<1$6ag z!9elZemew^j3*BPYT3W87O{FJ1|;^nHd_lK&27ZVACU3uA*49JW$c!*uk~n)4BfIw z%Oal;i>%Ut3g)nkexSK>AifnBcw$3VSSskR;emCM{a4AQGh%4b?Q<{(Jb;8jN6d@3 zjIM$+#{K2mmW3kjsTgyn{xjudPvO${1UQ;yCzhS8f}Omp(>N>odV5da!#3fT3Qeqr zxm1c_mC?!(9$gSF)y~C2Z83?zqL+niOgGh(n|0Hn4MHZ4 z-hlFPjtl9dAx=enXA?XHadFF%krdQzu}@S_(AxsHsW_-~s0Rk6z**eLWGzksA}kCL znA1?jWzZkcC+HqJ+?&hZ(!&uY`52Qs`4iJK|jbBcNs3WbjT~uGq0t*W)9_XsP&Z8~LzSI1` zY^d8mL0P}fbNcp4p0eLdJQD<`H=Ejm=V)8xXCb zq5(H|^-)!UBmarFs{mp%=y`}6!nfE2C9z6Tcq#|Nlw?oZ+vp>fD-aToI(NVg1L;2) zAtCh=6U;I6zyNvzmKhn>s_!e{w-IE(53sXpto0PQKoN5lHu3|69ax!{buA3boV^EI zhMn!5?VSg)kG05b^~h{(=G5XIi+fgpdv>>L41LcLKb%7B!GVX46rXGbf=b$HBJJXZ z+;~|Gv>4Qs4`4yart*4m_$DcAp@9@1(P0Yh{K6top^{l@W$g^}u^Ye{0W<*2M}$99 zp^ObWWlqs^>}E}Q(Ylkk8bi%<#9OEJ%v4{Fpnd(O;@2}gx=qB=hNLZz?x{qMdp42Oc6VybKF<;V zjM_zdPuE_8HAx}lO7M3)6f@6)xJCwV!wC(E44Ha%ajDStRF%RcEpL>+929Vf3Gh*X zsk<5rN5)~m0rIIc&b~mzO+U8$%d z?tTK$+qhuC9E@{_$J*~^TjV~glmZa)`SzVO~8=Ie#h zX-1-2l@X>VAd6CA9Y|eMxARFVMo+X}R6lMQqcz`G(`!L`SSB}@{Fomw&ET3-?;1me zWx{f-C?-eFJfYk_EVg@i)5`X=I$XzMBZXqjj)yTbeGm)MVw4YSLTclEtR)2A9OtE{ zV1`pPRItXF)+pi0Vw~L?ql6nYp;M$HYPJG8=?2y#0$FZE<}t|96%#}wc(_=mDfHC+ zR0lng88g%I$dpVJIu7!qG#3%3;S@~Z9a46WV@O5_h~tog2MIXgv`++HQ7vfKZ_$}5 zpA-WFJuu{U_3_Z5oBBkbEOA()$YeACgmRT$1CNN*G>!wr#?*6DamhS7LxDQDl%PI= z9ubHI`O_BQFub zBdPwlsHZu?X_@OJsk$K9Ndj%wI@^iGFc!mD4D*yRj70?&6|4jm)EL7FX10t;MXLTo zC>oXq18vftr0OLa2R4JgFE;;foHt}0QlD5Zzd>Anok#lwxVz<-mS3)fUsjkOUcp(PE&g?doC6p0@_y+Y`P(M)oVrSJVcK z0!o~P#APi{Nu~|+b0S@1kH?S@wssHq1*_H&xnR#VlqR(if#*3C}s0xVVVz)mef)BamIZiZ8@0bV3vbf4z~Nc zLLV+k{4w=B@ujU;mac4mFENx`=Nh-S4=cp8LcCT-ze4FH57;?sdF;=T>5(_`9?s#^ z3o@ie1VRmCahRdoF$$0jS4SuZbHo88bKua)L~e2w0WZ$KxV5P?6H(CPu# zR2a7I=e|x4*yE6gEe(`a-G+wAshVa4AnL>tfb=D!a^*@27+o?b;Q`fXIJsz?-~g%I zN(W72hi6)B40@A(pKT2Aw{;pJeQP>&o2L!R)o&t?`_&h9w`A-GS2v-Ba)UWXh1BH@ zvj`POX6CZGsRW=_G*`A3%_Iu>!=8cRI*ULC%j;+PdrdL4aO#4rZ@oz`(Uo*3y4n`2 z$5+q&-T1;K{*12r(yp^`d()$fuA<5sG;?`Umzv$0jvLZAD|@iA2P=Dcma>P{>Medk z824Jw_O&*$;x<3n^62vSI_m-rCyvL+x$F6exNy)`I8@rysxfDrUuHn5*92u2hGhY# z1)LUeTEJ-mrv;oAa6TU3T$@ZrI?cn+$o;VHh_TK-FpZ}^L$5?d)fg?xW|l>YFBKRx z#)46&*2&%CKa2k?{5=%28&3$W|iL?u}f{M#8OVJl46PCYSv>h>D+N;QLDRl#*>*Zl+RR&;(FM z@bkc8%NlIi?exYp;6vznZ9*QUDz`N1ZweYMy0z%mqTA<)ZZ|y+*Sk6+Tum+8`*f!o zFgM5j`mJ2GY7Q`Uf7UV=NeOm^fGjWQZY@Xq8IG!!wOZC{S?iN#ts5V>;+=t^d@meK z@N(Q$HSu7kOHI7u@*}cVOIB-TwN@5z#gw1=~6&?UR z4mmn>9Ko~yK+WTg=IS@shu&hpheXxpkw}Yu*Bf%>$^+G+N?+=S0}ngk4$bq)NJn^z z2)HteKMtnOc#1kdzCD_B&<00cG4ZM}MpXye^R6&K!5Iua-aNQPNgI_!Y#^g~ zV{+4HqDV`eRv?$vEpR(VeG(PLOy)RIt+;@aSDgBxA~BgoJhLPu&`;>pJ;3Qifv6Dami z>V<@y*N8xm&(n`?aWwfiXc&^HeG2%ug6ZdMewg zCO=3*UDC8OG8-JaukKN?iUJ4ssMB`1;<8ja+_R7*AusbUFR_{a)kLYv4CjTLmNUeR zQ_}^RnvQU4Hbf&$v+C0#bc@hiBJ}k9#oVZgcq(RGhRdtCmr3D>4Yr#2vJXtyT|xv4 zhpMb%3VJn|nhcC;acrWQYI+J{Y}hROc^1kw?lwk!O>356%cgBaAhUVe_85=0l$vJG zk0r*I7_W>N>zj7NQ@q(kd@~)b_I4^9t(2Hu%H3UQ=Gjs&oM~b~CS9qeNV4iqp#N2^ z$2TQ`lZ}|^D001;C@a!cGWuXXMgXeEvoU2zX%m>%N`L|4GlZ0yfX}GbbI3B__~L~1 z$?eCJj|ZSPrlIGm^ywVlMM?^Ahvp1m!Z?EN|9@p#1#8LnGcDQbq{((=)+`ZoIh`rY zb!9OV@ipJx+1-27k$FDc6^c*aBe+7LcuJmm1U)E!bhec)&WBu3*y{}Y-ukui%|tib z*ta1E63j43G(F8~En$+ssVJesWlV8S95|r|IVPiC==)wv6WBsCRcN8a>UDs(Zs{E& z%5%mHB9wGj?U`L(C1d}J?xDkhlav8W6{15BO&EZ@U?#TJC$|C_>P95q2%msz-N*aq9pv0RC`VFG3A|{N56FKp#Gf}V#&<2*P*}Yh5%$I#{WA0Z_ zo!vT%`(rxs!xj<9(v=&Sseb)du&COrvZ&In%bq@l+z$;YQaq@|0m#x7ltt&zL-%;# zA>h!-M34t*vzoS%${1h?+EGr5QpQg(Kp+SSfT95!j>fXa=PNK1v`N!4KcG|WA{UIM z&zUsR`Z7iYIMDNu+s({4m|@R5=#y9Al0r9IUyY#)XdsB7eCLFLwBK^XVt_G%F2F-c zCZk%Ssi^J5@=IU8v@ut%2l?gHkKd+D#QFq)SKtMqvNidEm;uI-uk6?cXh;JDrW5t8 za^|)Dn6^Aak&^JGYD*~%^_>xf{wRPh5+4D{Tuq}m@owk|s;?##sZB=%LH$YSd7>o% z1|D_p7!ck`kiKp=4)i(>UF% zF3o)82vFt56=pDSb&CN~Kjv~@L_^R|}uoQ4x}2 z?XFz7xmX6#FF{CbkjyHvXifFjh$bN0V>}(%!s;Z&!;x$jaTEZCI1@)G-YD^ShzlG@ zzyK)>z$svOBCtftXfdgU32^anh~y^`#Ah_P15$m!N}U9C}VAT?@briYvETC9y4> z6cv(#>Lzo-l&{Sr`QZ;!rbn4&e6M*3fRX2;9rfS4+#8hyiaX<*O6dX*)D& zLD$SAE*eKOxmaxX*2a#@TH#qMJZpt#t?(=aSt|%)t?;ZBp0&cWR(O^euN^VAR(Lh~ z^-55*p&Izzx&SUGNG|deyC^uOo(DM!pm#(=;-U zlIc=)nYc`guJTB|<3(NKw2*GfjeKfy%GQz!5qIB`J$0pm1)qgc%p^oaurz>I}R znpkmor!{acf*xS_9~J&!iUK9@WgPO50mu@$E?cdbHn%JfD68FTogb%GRQpI7MXfY# zMMe-6=eoE#s|y-=h2z*A7~Z$Z*`ot`HpaTG@#1E2rM~p@Y8vFl0QLVVOk}FVqB(b2Ral4$+k_vepWUx_5zqN3j+) zKo0h?H0hP9@hH@yU<#zc-oFuBfTy))EoBAQ8x;1b?ib`j$9g*jSHj?^mj$Rtf=+vM4E@{&(;?jF}6O523P0~yaG65 zc}Wm*S-P@zfq@S9w5?}hL|ti9E+?ypVBn0e*yzQ}t$0zS_3aorcgoQ8ij6KQy$k(l z_4-77LNq_wk3POQ5mRz$f(E zSu#B>@J&t1W)MI`jb(xYG)2K&%MCF}d#hMK(Vr0Ke^HK;vJ$KIPgt29`kE7jI_WO5S*WO|cyoPOwe ziJR06^Ra+vF(jt)A`#F@M0C1;vT2^Hj zq2-AvrDQ^5$bjQPEVi9bXD9~I*Yd}q@?p17+Cu3~hSIO=l(b4_dU4W{R_NM~p8_@2 zvw^fdDlKS7fYDT2cP0r=v)KS`)>BK!Y}V7CLaXYugI6%PLp;**wjx-WCYGdH;trc` z&WLX12Vg+FGF9|VKlCNJI%Q5kXA%itQVEf@fk`<*i{MXB+rO)pEt#`q&cHgn{`8fV zFw*469jr~l)gEqR`VlqLRG<^}Xl=tUclO@YDPh#&o*?M8yjP|gFuz!)Lii>-OtjH} z*hXD0xM8LWCwWfEkOs=l6~F-vxxQmcx1fh5rj|UliU*zDC3PX83nk5>LhruWd$T8= ziw~j=F);S4`B_ z<1pYlW3&`nb~~rU%|6h3`#Qi5x<-M1DQR`l15dwDgUUVr(N%{`Oi&LwoCYP`UqZ$| z`L0;)+%P=I#_2~tRv6$qy^am4MRVWS*03-=OX2Y1;oJKC- z@uqD0sb!n2X&dQ{W-{9HJ>SBaJat^-*^j&%%cEUHC#1nPES?#oZ!8=^qgttBxKyb1 zjR#V7%4@}Qu3^BjgMCQ&Qg|xe&XTaI`877zxe>rh*U5+iA(clk+W3%}MIepOu* z7Zh#?!YZ$itHTFB>1w&@H1cho4pZ$4eY8t~d;=1hYNjp_hIoH*Qso%X`u3Ua-B{e0 zs#bFw&AOY3Tj@%JP^c++FjI-w`wtX*!V>R>VN`2w%*FR6B-&n%f9B=5ne5bFfb9j? zUVtC<0=!{RuFk+j8f_gq98Zy&^tRXCLOaW;Z@+%~Ra%N|sn$LcX&ZV$hSUt)l?}%P z-4*I(;GM>GOuteYfgCl5A8Leb{sx@?bQG z_pUA|*^urAcMyYEw3e-r4raT#X$Zdtk)J|1vZPr;Dp0r!oRpta)bN*gylqDJ36k-n!JhYr~ zR#{Waae4LR2#+odlZ;l4!I-t@s%AvoJ`JSsh7JK#?EDuNNx2oT*XhJ&n2+56&Iq6Z zU_K(&NXxSLMJztO*DEG6erYq9R?xcNYi*#!kv&1s^)PAcd75ow**lDm#wr^#r*G-* zO$l3EC*xcihrc8v;Kn6#QENnXOTR4rTI>{*+RbGiLD|<2dlfms%n!NXT1FrTlxI}haGzupAxU9C-n zIF+RWH*<0(sxL>(lw>b%%$$_zu1yJ6gp*VE`^$w%h11h0wRQ-4i$p~e2^w$Ul7jEaEv$K1$_m!MgTNDhp zhUo_Bl16%fA*DM95Trw-yIUy-ctJ{HU`Xj2x}-ruhHj7=%ApZ~A(Rl z*StY)L!%9N<28pP@gcvp@P2@FiOsz|AH9*gqyxwP@k)$Ac$?mz{MX3RV~q?&)-rx^ zbWRuS?ErAnp?zcr#)~qS;nh)8HLfeIU2Pro&BO;Yoa-h%DTL_@LOVHM!}A7>v-%Uy zY07&;Pc-F?ygB}d{H4d9@k4SGJAdw+?uhs7eWsYT%f9B{scHf2>I!eB3~0PC0$DK#R-Idc>U%Br_4nT66e|#O_=8N6_nblUF>ru zmy9e34aA8?Oj9CY*rC9-?0X}LNmI7&?keGa^xe*}%RBXkT$M_TkY>WGqWzXSJ_nhL z#qWZdbbp9_4=C}u=bPf3O5D~r-#Ra`*%b?XJ65^pXui_4YHn}Dnb7&;QI)5MvT81W zkBl=eF~^Q}!1L(lu1al4$h7I&T^%Hz%oRpmFEtCGyt9)Q1C-46V${zz>F$D7; z^?W=gpt#ov4Oi|^Di2@zE_u0DJNo+VtSdIV~aQ7mw zlHc;i2rR=B7E3>z4*a>}#q&dnN5z*qo;-F!nnK|{0}eWyjMpgl?>eH-?SDkH&oUf zKCT5ieToH+lOVAqnG72MBKhSp$)jBKbkbEfLcBR!eovS7&|0Do~ppu zRA!^#YPuB);~M{q&RJ^VI5-W z_DKR_uWT0;Cy@9*Sz6^(Jd4&-A#x1COh%hC`E$GGu1Y6cXkKM-7OJLw^s<;#@A2Vz zm4(mM#Gi$HPiXs;vlETG(~3I|?nO8}Z=yqh&r5?Au@0T)9YG0l47Wd!W)!kz89)e! z7gp-JrSnm7QY^t?10VugTf>rOJy&dou>fJsvK=+7P5i6LXpc_Cz7VU)s|V$5Ci2NH zWra8j$Tmp{+1xnx5dE0 z``tMgF3*MnD*UU4TJFf1Fe>{nXU(4f!tES1PjruBLKF^*A2bpN|Gi)tf19T`i2LtZ zc*nBQq6DbK0ilg+oedcI%Nb4Kx!5CW(P=6pSELKy6+*F9pszm|(BMZ7>j9ZVDC^m?!3EtsFt?cgZ$c*WPG zcOryB(lK2^xdOjbz`Up+=2i8gsF1Zx`4G>;<=OKMr=#ZHO0i(36SunnUcljdAup{q zRZC@UuDI5y^;)}QwZYpbmg&bn{SRx)A|rjO>uYMPbLNa4S>?ojvLlQUQ;B|iEFiAP zh;3t;z5%h!(HfOqD)(U9Q$>QW5SHE}PLQ(zOGN!SU#Df|Tx6A(`ejiFRdkH#3xPr` z3AU$feE9rmt0^ROi9UDn>CG8|HJdr)y)*4x zxjk||g;1ZcHvq0SZ2CdEh(VhrXsD_E9pI+qK?XGuk^d0`m`1lLi5=SjoVoFwk@atX zxnz0I$i~@oV!#~vq%-^ORJjyHRqOUxINTSRbJNi{qTMqp>H1T1!AS3RXuB-J&ap2V zAq95j7W8^@Q=b&I0mNr`6bEq3oW2fIax6?Vvx0^hA5yz-GZ%X(h3&YL$Q4>?J2TB1 z+Oso84AOQ{hIa>47i2!WUu6KBfxs61PyjFT2Pt-B3X|36*s_9z+*?U|UJw4Xt|DEC zEm1NaC2ct&4(rn7JEp?WM2wbKu_fS2Y|8asYf&PG%iILoVHMD1XP6SVkDiH1320Yh8|N@EodGV^NX_p!&SR3M6@AV62%ID_(vkme4|dN^zI;d!pDQnzo# ztKm@WMD{Z%kXrp^CDrId7hF(`RcKpV{cl?eJIrh$A*&`chhaRc$8KcEVnrI^n$cSe zhLKa1JHM6$Oi%*}&{;KCj~cfAg}lXDEejc&bN>E6pX_^K)`rSKaPgK?Hlu~&g5QV$ z3d`^CP3Zr&KIp!Ei|ZWuEiT-fqr+j}x&qO$s8V#njP!VH3la?+c}$$`eY_Hk2pL~X z!^PZGEpci1_=j<9CNX*6{qpwVWaBYJBhX2Czu;xs;mJFt(`)ywjm`eMFwWE;RL|EX zd{bYY3Yir8cOg1b0kxrM`zc|N{;JLU6!|2cG~zcrpzh9qSZT<-9~PaI@`1)DEtwk$ z)qb9B)3Q6=9=*5Du?j9BDKee#*`3Wo8;#-&htC98RJ=pOmG%Z%5Cj^pyI0#kHyILC z*L7I}4=)l$wY^%m9~W@wzgegM6MGtsiT?5OG=^CG%H>%utmjezgp8y44_nqz9&l1; z`;P0NBhB92&-1JTCv)C&xa1Y$N4&Jvq4%^Ba!@4a!+V#5Yb)wG&iV(3n2Q0Oak@?) z9UU5bSE+%@6;d5eRf=&=LY_D$12Ry2Xe+F0v|)6u^w4)cpN-seE_shmE$KrJ;}`8I zmUkxeg0t>5J}t3Mt%x`HFI^i7$cG%-y9*MVy;R&Az7J-GSr43$vzba5I#3zRRHya{ zW0#mSU-px+b}pEg zBS{8u^N6E1so0*i&W>Jql9e~)inELrp5$SLR1!`|@E^^B(Ca6zfL59TS4l3$I#ZPe z6)2GEYBKOQEF<}DWCS__p!05}SbJfxuZ(Cl$YPq=G$~bLMLfMt7#8X+!{)2ZUaPl@KsV_ z7jK%QSF^(8b0cK)f12KO00uJ0d^sid*gN29FUD?ph`QfPZ`4%NM_(=G$stHh>VI~7 zdqid5tmT`;M zIOA0G)TjG%1$vQVx9WFmtsUU+>YpJcY|uIuGR8hL75gk=vnua0FnSZH#ST;5VR4+k z5cyIw)zs-Ucyxr}#ZMLZGvs}Kmp^?s)Tw8Dw@pj^G+x3}XxCB-3AqgCCWODR+8NLt zb>is%B0TL!)+B|<6Soocz-t=gbjNGDB2jEbzeefgjen3EN0Q?Sd+rno?QjI-fEefO z+^|@;-k#*x~ty*DVrO1ja=5vM%QMcc;xIC!z0*|S0YGHpFmaSJa_MXw8$ZsMjT z@iFPH%EXclNCzO#KV&b}^8lSz!-eEHa3J=tUM?!le>c&ZE!>VXyI}$@oH^+zkdRbk zmVdWL^TF)0ue@ivtmiZkDJiQ68F4mK{M#tAH5mIkFT{NkcZHKL`*10USe?3X~+d0Nvm?F7_oSKbdQnq5MIw>P;cLl z`f^{0cm@m>`DgV}y6G$ck^>&CLh6XFIM{q?yWI&)%x0-VLccuaRL@=dpgv`yZN&J) z6j%+k!=zGp-c^w&!cVc;Xx1QpSGSC$>INf_pDz?D-0$SHdtSfFmB z@b3HqA{BJFRGEZeNhysg|02B^lyE!S_61hORjE4Z+{r&pDpN9*B8dDl%%t{Dg1XK4 zgq;IF|8sK7wY-+EaZ`HMLtFul}xJ>amKhsLfoOt*e-K*nPKAd=`n?BHJ(bhct#^nE-Loq6q+Y$?0 zD@=vS^cESOPIDaC*EQmRrF3PUlfrE)609%Ta$xEdiu*8`*x=jf2(UigJYoP*fbK=e zV7)Fm7)%kr`7@E?^5mH@xFoV5sW6gynWq6ZT3jwx9z!iwn?bwCv(~FHVS!6B57l84 z{U__}<=rnfkr(T^B zL2UL?TRi(_f5~*B#)Q}?a6+iuMiV-{I~eDAVLy)Z@+BH^{FHpw{lr7vr!ngi;bj<+ zIPW(p_LHCh95nhu0V;NWM2zG^hGR`NUWPF(xTwRm_3ck<7YivG8%A&3q4pkKfOlKp zIz|xT8o#%d{MK8y8_(_jmE4a#@0b4_2f6A{x|Poo!x;Ar{nRE&hP!Ycr3GHWZdH3k zjgYqnl_6RZx=i9l1>v$pM1=LA-r9>bZg_iA%G#$dpLuuRfmurI13(%HPKy9kgeQx9 z8_Euo--ffr#jU1g&%byK!Ykz=2QST{T+PxSIT~8jhZq7%DPQN}d{Lr?`bVgyN8;aZ z4GN|ruqm+OOrP?-lsl0SD+vVy%2+#p+%2N1eqbR5xI^sjkuC#`S*iYjs)V`R**$Sg z){8x9|K~5>yD~|iI_sI7grA*++1CS@*`kIY=mR<+CER8KllTl1=(!ZQ9Z${qGA*g5Lc?)LQ6&Z%J~$=+Zp`K9sx+eLunRqC0%fE@;+WT;BF2aqZx=s%!< z&jZ~pqUIRSP90`wklc2jJ613uFs|aNK3i33U@Mtqea#n``4}4T^tY$BfccDMY5V#pF`ZPk`A|fVf(|b77&f+x5(AqVMCKi z{kbJMWKy_{XBP#t-pn&CT`e@baWCVJKTHgwkB)ekkVD2c^~OxNC{2jn;wyHW+yf?m z#+WQ4XB|$xMG0m!fZb82c@~=CPa$545gT~t8WP`@;Z-PAm#Swq6H~Uad`SfNt3oc0 zeyJgPwl`eeCJ(d4mKmY-<||CTf4h#q#T<*xJ3E9zD=_AA5An}ycqF%UIDu_8IfE^! zA;lg?0XKz=x>ID3K$vUg?B-00s4|*Z3R~D#{=g+omO_*w7od2zk0NH?^Gwp!A=1zS zY`6I4XGON}wyB(%KB6udKgvz~njB}XJfc;x!?y2iLQ)}eo9R>|gRzOS7$_-fMzoyt z*@+$)JINc*G}%$rT|7M{pl%k&u=Rcs(ei6|nSj*bC5nD>gG)G~fBlKDd}l6w&P~N7 z&>ggI4*Tj~L5Lwwq^5C1boqTXVv07PA8RA7Hd*qF$L`(MsI#Sh;YelEkVD_I8Or&T z_ZKx~eREGw;7)j;+ZO}Zl?;Rx->hC)ZWYbxAcvwx)umzCojZ%&hz*Sz=OD-idkfFD zI`>Jo`Xv3wC~3zJ8M=u3!&uGSJ>a@oC+p)Xo!(LQK`p_rrL<^w)Iv=Dr&Q6(>FehQ zJAboyAZX5Lc)N?vJ6yD%V(;~`6z;uIHF5d+gKPvL-DJ4$>XkbFVh1xJ)n}pxUFxi- z7Q~67FUY%R=m}*ySwd$~lR>Y;d__1!cn>1N%LAsfuf<<~S=P{zRh#*=J&|NGHn}9= zuCqc0s@K8hkGEQF=QFCPRpP2@J;0|QnAtWW>zyF6Y)$pWOpZxC;H9sV_E;Gxi`a)G zWL6WXZ^VhL44elti$|`;zx0ZxWi|JC)&f)onTR`5w!D1u1{1C$dGX%dBopx0y@ZKg zcyzHA$n<=udzo`jpc%8uU-3tdIA_yg4m$ZGrFH8Xb5e&~efQS%%c)U6k;89HnP`Rx z=@*O%2I@BWV}Ey^DFsqhO}ImrIE}^Iq$1*n?aiM{{z8(NI7-h>EU>OGH<@n|SPnV* zwA>Tm$4GIU+E_6BX37~t6H7NK&9(Gz-&8hc%^TV9IlGWsH3sMf@V;G&#}?q!@>Vo@ zAF)!-S>bWY>%*Kt63>f=vBN?~iW8ZIP*ZYyLK8QfaHbaZ%9;uAL@BJ~Q_U@d*>77r zr^irNJ-*iV!5V&kQloKIN~_)$sq4=EPIkd~v}Yny_JGgn8W{X=;&E}?AAAHChQ_ui zN6MvIuk{guo$6E$LszZx_6*_X#+m2(R$)8NWLm6)#zd#H`bwbfX)0 zO+L;wx6u;JU~iM@(8R2!*joLi$>*WX2sS@d zewM6(s7+Qc)g*z`Z{qj~m@e190{)R)KjLV_)XB;^sh6Ywpl=w%sm+WtD~-u&Q1=Lb z!=4*V>xy|+sVel`{)b~5=&Q*xB|OAc6aS*xHSg5)Tg`g%(P@*}T05_%kFPs>{mE}p z&9<3;L|w>Y>#`42Rs2T2Cr?%y3S$`9QmJN4&ew3uRF|1MuvOdFXU(8{h*rrcd%3Kh zUIPl#E>EAOOK4F21z`H}cy0QP*b})N+W+4JR#_;BB;BgvO=QzDRO&;>)T+n-BQq`~ zpTqeGwOU^o=J}6jMMO-e>pY)d-0|{IU9dH^E$QuW>tKQSBYDpwy-9-j-M7XZdL73Y zPvg9e(4v@G$AW&JlR)wsy92Dp3z(*>3xK#u6vjaey5lbY^RSA%AWr^u*U78eJy4h0 zHQ^*tVLo$>ICv_o-S!Th?lH;!Bs*OY^TpAzGdbu<;nnkHxr8xGozU1;Tg zNg9vpQi`DV3J*?DEd&W_Y+EocSSwG2TgGQ;pNPcFkIbHTmVw3gHCHt}pB z5|Xec0WJwSc0K;@?*QOcBuMZgMUlDfHW3LN0K_@Zx!?eJ)QJPJ>lpf2?{^2?L%pLT zJSY12QKyJnP#^1;gswd8YI3HXPA>G0eg|C-5zK3xbbAM`G1Q+rx((@)n4UiV@~C5A zvzqUa1r365_tc=oJvB`idY<0VP3Rdea;Tv4*I$1v$X7gHVMBZ2X+KRValrz9Kp47H z>aGm>Qi~IK;q)|vU<&VP_e}ixQhNc!%dTIjqe0gt?n~|QEB;DdNLMb_yzT|2(8a*& zt`U6s6+eB&fBp5B-qF34Z3Fi~AM1KYcYq6Q(TkPK#x#34aEOO^-~HEY%r_m~h2F}d z?92JXU+Yq8;OX~?rafZ`ZNTWkdtf^jWbNqlUVktg={Fr#op%FR6PGy9rHGZ71IvRQ zojy2_6(_U*LWAlzPpp6cTOk*k`k1;a$U5V=&;XP@;kBMJo^^D1FFx5hF+A3pfj7Ke`L@f0zBEeoudT>g03R8NqY3sLf-7jD*u1)J+sC!)qbo&2Zd$ z*U~(fQKFdZ9`%U}&xma!%D#h3EPaeumetWSA6`S(Mi@}I_WT@qU}nLO-!OFF0n3^J z<2K+%|J`Te-mbrVDq?mg_ULiFOOHCD(B^LxBqk7;r|h120p^|BL>H)v+a_kpV0n&FO1v8i9e$40WS=N=TDv zhLNoeHQ_gL`n#{hGI25S71Lu_e=Qk8U=iV!J%etF%_rMkc!U?b+6z8v2H?hLZ)=$5;E^-&OrNe z;|a3%?ku(@c5T4l1ioyVZDgb(W~gxloG8tz%0bf;QAbvQ!pAD;<%_K zC641o-#k!J*@h z)vlNDD;9xh&7W6Kqf0<}75-HZY&JwDknRd&gcsU3M3?OSSA6;L`qT73Z%)sC`!xB_ zo73sr|4dG=f6*YmLoUHK1G_umB4+1n9;6y2nn!G?Ie;!Z8o(xR>nmoBv1hSPep^DU z8Njkwi-}<(kM{s!-bcpIyzW=rkbWs)0r_RMJ7BG#9+MWIMs-yBePvl0*vKEo{F4wD zEo(nyb0`T}pF?1IX9Ro5gzm@r+(H;$LFyuS2Q2=wlEfC1%?@cZL%qm0Qlh<)sJ8EZ zd=H;w*V#)Fvdo$*pI@3~t~k+VS!<1Q%UIiGEJ+|QIa7Eeu$4izDb~s+-SXB}d21vI zA!JNd!!SI`BE)O4uV0cJpL+=*a&VR}M|D4qWv7P_U>^Xdx4@Yw}Ts<7F zR&X@98|hC~V>`Ja^)}Mb(%{sgI31! zcSoNfXuwKKhP)=v#8O=wK;$1uiY|!F(y3xX6GL=!P}RwHjD$oP{$I zOS4i-rW8Z72%pc2-(g+^MaUg8-&n-BwQ2!> zij+(Xgtnss{AUr}kS>T!#dRf6Xhf}?7+|*(x zOo9H!^T5Afj^J~(_>c19QE8TFCL)zjw|Pchqu#Rd{;_dh=qt_;jjlEp5N?u@v&pxd zynmdW7lKJ~MDuv8vb$U}i z8{}mP`IeLSj+0Lc+`R_lep%Gyd$Rmknj+(75#BhrwgJCQ1^ikGjY+7)lNGCtX8;?} zItLV7y8wIh4CYZ0P#H0qe42m)>X+#Tg)h!|nui0S+vR^Bs_34ari=Hy%PMOIf5h0B zbyW-c8g9$c7U&M*==sGt=VwBf(B&g&Y^RLmQ*G`%h>h&0_a{{?4WmCeAO1$j=l6f# z|NYya|GtC2{EIs84^KV!)30xg&-cH)c{d&W;=LvBukYOtmv_JYi_R;jYwYMA^JfcJ zl5mBP)c25{dbc$oN)X6iG5pCt^1T(bNXg!;9r;UpFjVsKb1VD z%O7fq8bMc8k_nPuL#amMn=00*mUY4>vUH+}m>HFh#b#Nh_!@_m>sV%{Nt|&?Ot4_B zO543Vdg((`auY{>e0)FdNkShXdWLHfwShiPNrL!pP{aqwGBCeJelvKV(ZMvxXBSdC zKv5)oy?LMkihsf$KZYa1^$B>eA*TEb+yRT>aO&X&He3ii$U(Xs3M8!|Whrp`5Ss;s0V><>U#M^BH?Y4n-+rW1Mlw2q95_vQp ztf{snBB7PHr66ALYzZ)ig?kI+t!!|oK}J6%TMn{IZg%g%8{k#Tz`yV=Fro-h;$~%+ zvjS6y{ha&s+Yiw5zyfA&3;0Nv(4D|U)!1oa6f9uz0mW;FnT3_qjyGQ0X~7TUF`3U) z-a^DmjZFiluyUyBOXYH=DcGp>X^v{haGLXH% z24Uz$YvfN!smU}#a@~c~DyJIK?QBR%NO~$i5j~^iX4S^2X}()w(>p_Ec>q1V1IPk1 z3%(<67_rTE5F$O3q9R`XU@Hh6;YA>VtGW@uQng&)0go1ZUOtCc(1Y%sFmKECCeMBQ z04Xp51rtD**#*Lr@-r2zv|ekmv=+;5SS$f2RszAvYGxsCS*h+7oR{SOAenOGymJM< z@r2D1H3*^QyA`a1H#irdMG<;A(q zbI4jOPyW51kGU&5x;Zi&8`z5|BPKI;*Vmr|WM-8(o2r|=+c^eRq01TeuS%WGTEc-$ zmc1zGd&p!*KU=wO>4kmYN;DvxIe!M%5|Ewgv0tulT|xS?Yoo zA9%?by8_=TI=FS0SjyQpgaT_CIQfOe8_x9EQ;Q@w`DX$#ckLbuRM`VsUUn{GR z4}0PNN~ZADLi-;yw$_G*+R)H0goc!ikEEb0ef$g@dDQ2`wE-2o(%=C_Hq5bD_AAL% zcKL{}MS{=OjJiux%hY-bd9B%`HlpR8Yqp~7>JTvjSI}e9)6f>%3~F23W1HQ!>L%Na z+8Uq~+79(JN;FrzZ+G0bJ8s(@w_Am;+zg6o-5s}6*^XNdQtWej-8X1)fhk9(wFg6f zi}KkLAj~bQj^>QS#mc}mp&43~9nE>2^;`+q!Y;OP95gV9*FWqaS`83s0NEWsKn`#*9Wbu9MnL<==5rc_v__`6dk;nPrr~LoCHKF=&5@ zhC|RLF?jB{4@D&ni|Xq`>y1}iR4kbnT76CH3R>!EB_(#8?iY~D?uH8CG0HQl1?3@` za$p`-Q-H zYD?m2GBq#!%Ou!d_O~ZzfA6@k$nHmrm{R<$rl^R&H6|z_xmHYCG1-ckNFt3B3z`ck z6nZrgi4l-w63IrT$J%08gY0zmz9bn~)Y z60EPvAhBcZn|9gDF6CAZVXB5n8~Ez!teq@)Q4M?(6YAq$Y@=rKB-XMaYfet-h<)kC z&~}lzzG9SJ_9#@sJ4ktQS5>XZ>z7iAl@(yn)K*TCZZ%x1;fgd|i?uD*u3@btbn62c z!oWg**KxPA2yF>{*V}AixP{>shFcikK^T_ZqAQT>1Gx+CxUYf1F)G^u;ADd>`nKrX zqHl}7yNJG$wI%^$R}j<=#fLOXA@7V3t6m5`-fnRWz6IeHgtr93+u7ya2A*3zP(}~r zuE<_N3jz;{9cVC}s!~Q9Bq(ji8c#RcTI8)V*a>COB5{kv2_!!0YG;I?6tn>=dS!t`C&(}8D}RV zR8i}|CS9IHZ(0S^Dxe)yK(KXuT1E6+6jAOXcmlEO`aTh%N{UDT(it+n2DMP@2DMsf zC$&(-?TTCyE>Jdha?G?*dLD~{`320_KzMOFBX0U&XBFU$6Hvw60%%<$x$(m$TSRRU zwS|i$Tu2s$ebJm6OK6@Wu&4tWArTpDh-=zjAchM}JRhHm4yVeXEJu&^!kW3V9Zy3( zG0hY2H8#)Y%7U)uQ9xm1C-i*3d%t2|G}Wu*I|Uq+BbVq>HS1=##bYR0!}3EHJihkM zT6xuu?MP9P+!3L8rePMj&D;_pJJax|XMUWsze*BFLb?2ZkzI)_=I=eEZ|@ywP-rsa;(X--pF^rL zzJy}72vB6hZcs!`0_5~%Hd9V5Z2_srFRq3DTa9&z9|Fo?npM>(;f7W1(?Ao0P2b`#c}S*7&W;l;VCS%C#KW9*=@L*j`S6?d zaA3HQO_R^bU-379C^scEo48OjA+S7c2IuStq8h*4*$do=mf?R4hL<|NY|cLb_x2re zKRF8*nAH|4vIxl>;K(u%6H^#B%Ot(=_}O|lH^zP^?bWCCIb3%2shGhqJ*2Mtnk3YO z2b-dphv~_}8nSI@B0!<_pm`9RjKjhrnk|+d#Krpp-92pvsIi15$M>*k_bV0)f1buT zLPPVYGVt&}D5FkmV}0vMKR-`ecGKt@Ki7Ar*GV9&WoZUjc~Fa(ok=#Z7-x4ITTp53&>u-0Wfx?M z8JspAN|R8O32eflR+3hE14ZI1si}Dzil^lX(GL?yXk{rTOSw^tX)|HEX?~cly41v} zDu_xHtMhgtR@VyCo(YrezCxb?8K?NEBXxu+My64lagA`HJjIGLvyVAd2Cyt>YFq&c z6AoeRt?keUjkwxpfGEEoSER#uUoj;bbU7(1oZWJRyxn zB_~d`NNmEgkspI@X}vRpLAz3n4hbrN~%WzzstO4f!#t@l`y$Xx%ia$e68Q?zUj& z1}MBMIIks;tbVpCv4QhN@YYShvA}}n6`U)Io=-Z##FKXI0H0A-(Lk}$8jKP7g=mN3kMHdQRMsJlv~ z%U&}c?qOZuaa;M6fE|~_+=zz9pnO$J76k>>FkyHH)l2O~q);yKoG8j?*j)ZeAnzd+ z!9re~`AR@rpgR}@+?hvyasG^=d^>P+QWhj8c_95=*I%oj8o&_bq@xR4WvowJo=c;5 z^fO}H=lYnLCgR6c@I#yqJl6k>t=^y0H%kNmx`hAy`DeYO^W~VJ+%+<81)TlC5A5(C zzjcVQWI)>Q^*NA=x^^GS`Wdn%bmjz@2!eP@Ta%z(Oy0yRH^1qR@tiY?^vKsQ>x2n z$EMGxSU-cppEi}x9_JvH@@q^}_5*}mpE}CMXZ&i0%5MmpnKb1`=P)?1p+|x3sEz#E z1xD@}JW>-S5-XKd7 z(P;w)qL0EQn83$q73w~=Z&nVo7{ga1O$hN;N0K|A^CAZXB!l$r3pvB+k6EQ)pwKbZ6A zaNa*TKAs)G`LK5|GLPWFYzU2m6L2^%o(-4nmd9-AA`%5o+m%2{uH-!l~>i1k**lM+XN#*KEwr%hR_B z239@n7&yQ{^nnLsPwxP-z>M|$q@%x^CVyW+54v}(FDLwC@;^U${6X2EVMJIdW*Bx- zrF5d-O6M-Kv8WV`zJL<{cxolv(^?zQD6(ZVA@FaYM^7z_7Qnr`kV1p64SG>&JRb! z*C$8AZB7 zMHrQz6tf^IW#Oq z%*j3RpposVnZ1+jPnDHbk!&TQmKxrCE9;tNIv#9f^XiuFRDBlvQ3pNgbw@sb*UcA{ zB@3pcPvE1f*!fhqAa`NnV4lF0$SzcsH2swcD0t-ny3`A#NVail&p0(d5PeCE+nS7e z+f0O}UM$-0$JV0IZRq8hgkFA}VYD&o2=%0QR|&MZf(~)%FA!L?RswaLj@lBO|4UR6 z)ztuQt*NbStl1@(y%1~GzN+de3lvb{0XLK2+tD*lF(`@gvK`?;U)v1gni<5k_sX_V z8-v>X_@|#_Um0RqeUS zroGx1GeT73rBRn!2e)-_TL(8%U4BZz_sBEYaiEl4oA;Txx6nN!7&AGjf|CgfwRW_x zQ<7U^iIN(HmL;xH!Ni0bC9DhL4bi^wqMmj&IcsqLaJvuw`*E@)ge`dDS2C-1@@G_C zzRs*y44_4eCeu?dIS@W`5M?#I*rqYZFj-K#>izPD&I)QsFqq zJ@qqXmpclGoKisRXdPZ(MeVF(Gq0$oc7isxpm#ERS)g}hb(+&Vk2+7$OMt3CZXREprC==kX9q<45S8g^H*lHd{$J( z`ra?8o{^YfBc%vooQo7I2ySdj-^KY&u>&EyZgat9*OkRce|oxk`u_j`0RR8pfEKl# GjR62v<$f&y literal 7890 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 diff --git a/build/openrpc/worker.json.gz b/build/openrpc/worker.json.gz index 2214ad85af5e8b7f0c47a81106e161959b55c203..5bb69aa4919c86a4700e9fe7a69472082c5d4cc8 100644 GIT binary patch literal 2577 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 network.Version13 { + return ctx.Send(SectorPreCommitBatch{}) + } + } + params, deposit, tok, err := m.preCommitParams(ctx, sector) if err != nil { return err @@ -349,10 +365,10 @@ func (m *Sealing) handleSubmitPreCommitBatch(ctx statemachine.Context, sector Se mcid, err := m.precommiter.AddPreCommit(ctx.Context(), sector, deposit, params) if err != nil { - return ctx.Send(SectorCommitFailed{xerrors.Errorf("queuing commit for aggregation failed: %w", err)}) + return ctx.Send(SectorCommitFailed{xerrors.Errorf("queuing precommit batch failed: %w", err)}) } - return ctx.Send(SectorCommitAggregateSent{mcid}) + return ctx.Send(SectorPreCommitBatchSent{mcid}) } func (m *Sealing) handlePreCommitWait(ctx statemachine.Context, sector SectorInfo) error { From 9690bc882c3ce9309c00efaf39cf190390bc2ee5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 18 May 2021 18:41:42 +0200 Subject: [PATCH 22/88] Test to trigger batching logic --- api/test/window_post.go | 46 ++++++++++++++++++++++++++++++ extern/sector-storage/mock/mock.go | 2 +- node/node_test.go | 6 ++++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/api/test/window_post.go b/api/test/window_post.go index b6804c401..e508fb5c5 100644 --- a/api/test/window_post.go +++ b/api/test/window_post.go @@ -117,6 +117,52 @@ func TestSDRUpgrade(t *testing.T, b APIBuilder, blocktime time.Duration) { <-done } +func TestPledgeBatching(t *testing.T, b APIBuilder, blocktime time.Duration, nSectors int) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + n, sn := b(t, []FullNodeOpts{FullNodeWithLatestActorsAt(-1)}, OneMiner) + client := n[0].FullNode.(*impl.FullNodeAPI) + miner := sn[0] + + addrinfo, err := client.NetAddrsListen(ctx) + if err != nil { + t.Fatal(err) + } + + if err := miner.NetConnect(ctx, addrinfo); err != nil { + t.Fatal(err) + } + build.Clock.Sleep(time.Second) + + mine := int64(1) + done := make(chan struct{}) + go func() { + defer close(done) + for atomic.LoadInt64(&mine) != 0 { + build.Clock.Sleep(blocktime) + if err := sn[0].MineOne(ctx, bminer.MineReq{Done: func(bool, abi.ChainEpoch, error) { + + }}); err != nil { + t.Error(err) + } + } + }() + + for { + h, err := client.ChainHead(ctx) + require.NoError(t, err) + if h.Height() > 10 { + break + } + } + + pledgeSectors(t, ctx, miner, nSectors, 0, nil) + + atomic.StoreInt64(&mine, 0) + <-done +} + func TestPledgeSector(t *testing.T, b APIBuilder, blocktime time.Duration, nSectors int) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/extern/sector-storage/mock/mock.go b/extern/sector-storage/mock/mock.go index 8a70ed7bd..279d50d77 100644 --- a/extern/sector-storage/mock/mock.go +++ b/extern/sector-storage/mock/mock.go @@ -524,7 +524,7 @@ func (m mockVerif) VerifyAggregateSeals(aggregate proof5.AggregateSealVerifyProo return bytes.Equal(aggregate.Proof, out), nil } -func (m mockVerif) AggregateSealProofs(proofType abi.RegisteredSealProof, rap abi.RegisteredAggregationProof, proofs [][]byte) ([]byte, error) { +func (m mockVerif) AggregateSealProofs(aggregateInfo proof5.AggregateSealVerifyProofAndInfos, proofs [][]byte) ([]byte, error) { out := make([]byte, 200) // todo: figure out more real length for pi, proof := range proofs { for i := range proof[:32] { diff --git a/node/node_test.go b/node/node_test.go index 91348647d..5db7e355f 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -148,6 +148,12 @@ func TestPledgeSectors(t *testing.T) { }) } +func TestPledgeBatching(t *testing.T) { + t.Run("100", func(t *testing.T) { + test.TestPledgeBatching(t, builder.MockSbBuilder, 50*time.Millisecond, 100) + }) +} + func TestTapeFix(t *testing.T) { logging.SetLogLevel("miner", "ERROR") logging.SetLogLevel("chainstore", "ERROR") From e400bdf87a7763428a45e33a6b22d69816a56940 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 18 May 2021 18:58:41 +0200 Subject: [PATCH 23/88] Order proofs before aggregation --- extern/sector-storage/mock/mock.go | 14 ++++++++++++++ extern/storage-sealing/commit_batch.go | 9 ++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/extern/sector-storage/mock/mock.go b/extern/sector-storage/mock/mock.go index 279d50d77..c7844befd 100644 --- a/extern/sector-storage/mock/mock.go +++ b/extern/sector-storage/mock/mock.go @@ -516,11 +516,18 @@ func (m mockVerif) VerifyAggregateSeals(aggregate proof5.AggregateSealVerifyProo for pi, svi := range aggregate.Infos { for i := 0; i < 32; i++ { b := svi.UnsealedCID.Bytes()[i] + svi.SealedCID.Bytes()[31-i] - svi.InteractiveRandomness[i]*svi.Randomness[i] // raw proof byte + b *= uint8(pi) // with aggregate index out[i] += b } } + var sis []abi.SectorNumber + for _, info := range aggregate.Infos { + sis = append(sis, info.Number) + } + fmt.Printf("VERSIS %+v\n", sis) + return bytes.Equal(aggregate.Proof, out), nil } @@ -531,6 +538,13 @@ func (m mockVerif) AggregateSealProofs(aggregateInfo proof5.AggregateSealVerifyP out[i] += proof[i] * uint8(pi) } } + + var sis []abi.SectorNumber + for _, info := range aggregateInfo.Infos { + sis = append(sis, info.Number) + } + fmt.Printf("AGGSIS %+v\n", sis) + return out, nil } diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go index 34e386a49..e560ecba7 100644 --- a/extern/storage-sealing/commit_batch.go +++ b/extern/storage-sealing/commit_batch.go @@ -186,10 +186,17 @@ func (b *CommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { for id, p := range b.todo { params.SectorNumbers.Set(uint64(id)) - proofs = append(proofs, p.proof) infos = append(infos, p.info) } + sort.Slice(infos, func(i, j int) bool { + return infos[i].Number < infos[j].Number + }) + + for _, info := range infos { + proofs = append(proofs, b.todo[info.Number].proof) + } + params.AggregateProof, err = b.verif.AggregateSealProofs(proof5.AggregateSealVerifyProofAndInfos{ Miner: 0, SealProof: spt, From 0419c64a06ee6709a21b13e590b4483bb8c53eaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 18 May 2021 19:47:30 +0200 Subject: [PATCH 24/88] CLI for precommit batching --- cmd/lotus-shed/cron-count.go | 99 ++ cmd/lotus-storage-miner/sectors.go | 62 +- documentation/en/cli-lotus-miner.md | 1864 ++++++++++++++++++++++++ extern/storage-sealing/commit_batch.go | 3 +- 4 files changed, 2024 insertions(+), 4 deletions(-) create mode 100644 cmd/lotus-shed/cron-count.go create mode 100644 documentation/en/cli-lotus-miner.md diff --git a/cmd/lotus-shed/cron-count.go b/cmd/lotus-shed/cron-count.go new file mode 100644 index 000000000..622f38791 --- /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.UpgradeTurboHeight+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-storage-miner/sectors.go b/cmd/lotus-storage-miner/sectors.go index 8da491841..2bb44b4f4 100644 --- a/cmd/lotus-storage-miner/sectors.go +++ b/cmd/lotus-storage-miner/sectors.go @@ -45,7 +45,7 @@ var sectorsCmd = &cli.Command{ sectorsStartSealCmd, sectorsSealDelayCmd, sectorsCapacityCollateralCmd, - sectorsPendingCommit, + sectorsBatching, }, } @@ -970,9 +970,18 @@ var sectorsUpdateCmd = &cli.Command{ }, } -var sectorsPendingCommit = &cli.Command{ +var sectorsBatching = &cli.Command{ + Name: "batching", + Usage: "manage batch sector operations", + Subcommands: []*cli.Command{ + sectorsBatchingPendingCommit, + sectorsBatchingPendingPreCommit, + }, +} + +var sectorsBatchingPendingCommit = &cli.Command{ Name: "pending-commit", - Usage: "list sectors waiting in batch queue", + Usage: "list sectors waiting in commit batch queue", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "publish-now", @@ -1017,6 +1026,53 @@ var sectorsPendingCommit = &cli.Command{ }, } +var sectorsBatchingPendingPreCommit = &cli.Command{ + Name: "pending-precommit", + Usage: "list sectors waiting in precommit batch queue", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "publish-now", + Usage: "send a batch now", + }, + }, + Action: func(cctx *cli.Context) error { + api, closer, err := lcli.GetStorageMinerAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := lcli.ReqContext(cctx) + + if cctx.Bool("publish-now") { + cid, err := api.SectorPreCommitFlush(ctx) + if err != nil { + return xerrors.Errorf("flush: %w", err) + } + if cid == nil { + return xerrors.Errorf("no sectors to publish") + } + + fmt.Println("sector batch published: ", cid) + return nil + } + + pending, err := api.SectorPreCommitPending(ctx) + if err != nil { + return xerrors.Errorf("getting pending deals: %w", err) + } + + if len(pending) > 0 { + for _, sector := range pending { + fmt.Println(sector.Number) + } + return nil + } + + fmt.Println("No sectors queued to be committed") + return nil + }, +} + func yesno(b bool) string { if b { return color.GreenString("YES") diff --git a/documentation/en/cli-lotus-miner.md b/documentation/en/cli-lotus-miner.md new file mode 100644 index 000000000..dfa9072c9 --- /dev/null +++ b/documentation/en/cli-lotus-miner.md @@ -0,0 +1,1864 @@ +# lotus-miner +``` +NAME: + lotus-miner - Filecoin decentralized storage network miner + +USAGE: + lotus-miner [global options] command [command options] [arguments...] + +VERSION: + 1.11.0-dev + +COMMANDS: + init Initialize a lotus miner repo + run Start a lotus miner process + stop Stop a running lotus miner + config Output default configuration + backup Create node metadata backup + version Print version + help, h Shows a list of commands or help for one command + CHAIN: + actor manipulate the miner actor + info Print miner info + DEVELOPER: + auth Manage RPC permissions + log Manage logging + wait-api Wait for lotus api to come online + fetch-params Fetch proving parameters + MARKET: + storage-deals Manage storage deals and related configuration + retrieval-deals Manage retrieval deals and related configuration + data-transfers Manage data transfers + NETWORK: + net Manage P2P Network + RETRIEVAL: + pieces interact with the piecestore + STORAGE: + sectors interact with sector store + proving View proving information + storage manage sector storage + sealing interact with sealing pipeline + +GLOBAL OPTIONS: + --actor value, -a value specify other actor to check state for (read only) + --color (default: false) + --miner-repo value, --storagerepo value Specify miner repo path. flag(storagerepo) and env(LOTUS_STORAGE_PATH) are DEPRECATION, will REMOVE SOON (default: "~/.lotusminer") [$LOTUS_MINER_PATH, $LOTUS_STORAGE_PATH] + --help, -h show help (default: false) + --version, -v print the version (default: false) +``` + +## lotus-miner init +``` +NAME: + lotus-miner init - Initialize a lotus miner repo + +USAGE: + lotus-miner init command [command options] [arguments...] + +COMMANDS: + restore Initialize a lotus miner repo from a backup + help, h Shows a list of commands or help for one command + +OPTIONS: + --actor value specify the address of an already created miner actor + --create-worker-key create separate worker key (default: false) + --worker value, -w value worker key to use (overrides --create-worker-key) + --owner value, -o value owner key to use + --sector-size value specify sector size to use (default: "32GiB") + --pre-sealed-sectors value specify set of presealed sectors for starting as a genesis miner + --pre-sealed-metadata value specify the metadata file for the presealed sectors + --nosync don't check full-node sync status (default: false) + --symlink-imported-sectors attempt to symlink to presealed sectors instead of copying them into place (default: false) + --no-local-storage don't use storageminer repo for sector storage (default: false) + --gas-premium value set gas premium for initialization messages in AttoFIL (default: "0") + --from value select which address to send actor creation message from + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +### lotus-miner init restore +``` +NAME: + lotus-miner init restore - Initialize a lotus miner repo from a backup + +USAGE: + lotus-miner init restore [command options] [backupFile] + +OPTIONS: + --nosync don't check full-node sync status (default: false) + --config value config file (config.toml) + --storage-config value storage paths config (storage.json) + --help, -h show help (default: false) + +``` + +## lotus-miner run +``` +NAME: + lotus-miner run - Start a lotus miner process + +USAGE: + lotus-miner run [command options] [arguments...] + +OPTIONS: + --miner-api value 2345 + --enable-gpu-proving enable use of GPU for mining operations (default: true) + --nosync don't check full-node sync status (default: false) + --manage-fdlimit manage open file limit (default: true) + --help, -h show help (default: false) + +``` + +## lotus-miner stop +``` +NAME: + lotus-miner stop - Stop a running lotus miner + +USAGE: + lotus-miner stop [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +## lotus-miner config +``` +NAME: + lotus-miner config - Output default configuration + +USAGE: + lotus-miner config [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +## lotus-miner backup +``` +NAME: + lotus-miner backup - Create node metadata backup + +USAGE: + lotus-miner backup [command options] [backup file path] + +DESCRIPTION: + The backup command writes a copy of node metadata under the specified path + +Online backups: +For security reasons, the daemon must be have LOTUS_BACKUP_BASE_PATH env var set +to a path where backup files are supposed to be saved, and the path specified in +this command must be within this base path + +OPTIONS: + --offline create backup without the node running (default: false) + --help, -h show help (default: false) + +``` + +## lotus-miner version +``` +NAME: + lotus-miner version - Print version + +USAGE: + lotus-miner version [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +## lotus-miner actor +``` +NAME: + lotus-miner actor - manipulate the miner actor + +USAGE: + lotus-miner actor command [command options] [arguments...] + +COMMANDS: + set-addrs set addresses that your miner can be publicly dialed on + withdraw withdraw available balance + repay-debt pay down a miner's debt + set-peer-id set the peer id of your miner + set-owner Set owner address (this command should be invoked twice, first with the old owner as the senderAddress, and then with the new owner) + control Manage control addresses + propose-change-worker Propose a worker address change + confirm-change-worker Confirm a worker address change + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +### lotus-miner actor set-addrs +``` +NAME: + lotus-miner actor set-addrs - set addresses that your miner can be publicly dialed on + +USAGE: + lotus-miner actor set-addrs [command options] [arguments...] + +OPTIONS: + --gas-limit value set gas limit (default: 0) + --unset unset address (default: false) + --help, -h show help (default: false) + +``` + +### lotus-miner actor withdraw +``` +NAME: + lotus-miner actor withdraw - withdraw available balance + +USAGE: + lotus-miner actor withdraw [command options] [amount (FIL)] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus-miner actor repay-debt +``` +NAME: + lotus-miner actor repay-debt - pay down a miner's debt + +USAGE: + lotus-miner actor repay-debt [command options] [amount (FIL)] + +OPTIONS: + --from value optionally specify the account to send funds from + --help, -h show help (default: false) + +``` + +### lotus-miner actor set-peer-id +``` +NAME: + lotus-miner actor set-peer-id - set the peer id of your miner + +USAGE: + lotus-miner actor set-peer-id [command options] [arguments...] + +OPTIONS: + --gas-limit value set gas limit (default: 0) + --help, -h show help (default: false) + +``` + +### lotus-miner actor set-owner +``` +NAME: + lotus-miner actor set-owner - Set owner address (this command should be invoked twice, first with the old owner as the senderAddress, and then with the new owner) + +USAGE: + lotus-miner actor set-owner [command options] [newOwnerAddress senderAddress] + +OPTIONS: + --really-do-it Actually send transaction performing the action (default: false) + --help, -h show help (default: false) + +``` + +### lotus-miner actor control +``` +NAME: + lotus-miner actor control - Manage control addresses + +USAGE: + lotus-miner actor control command [command options] [arguments...] + +COMMANDS: + list Get currently set control addresses + set Set control address(-es) + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +#### lotus-miner actor control list +``` +NAME: + lotus-miner actor control list - Get currently set control addresses + +USAGE: + lotus-miner actor control list [command options] [arguments...] + +OPTIONS: + --verbose (default: false) + --color (default: true) + --help, -h show help (default: false) + +``` + +#### lotus-miner actor control set +``` +NAME: + lotus-miner actor control set - Set control address(-es) + +USAGE: + lotus-miner actor control set [command options] [...address] + +OPTIONS: + --really-do-it Actually send transaction performing the action (default: false) + --help, -h show help (default: false) + +``` + +### lotus-miner actor propose-change-worker +``` +NAME: + lotus-miner actor propose-change-worker - Propose a worker address change + +USAGE: + lotus-miner actor propose-change-worker [command options] [address] + +OPTIONS: + --really-do-it Actually send transaction performing the action (default: false) + --help, -h show help (default: false) + +``` + +### lotus-miner actor confirm-change-worker +``` +NAME: + lotus-miner actor confirm-change-worker - Confirm a worker address change + +USAGE: + lotus-miner actor confirm-change-worker [command options] [address] + +OPTIONS: + --really-do-it Actually send transaction performing the action (default: false) + --help, -h show help (default: false) + +``` + +## lotus-miner info +``` +NAME: + lotus-miner info - Print miner info + +USAGE: + lotus-miner info command [command options] [arguments...] + +COMMANDS: + all dump all related miner info + help, h Shows a list of commands or help for one command + +OPTIONS: + --hide-sectors-info hide sectors info (default: false) + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +### lotus-miner info all +``` +NAME: + lotus-miner info all - dump all related miner info + +USAGE: + lotus-miner info all [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +## lotus-miner auth +``` +NAME: + lotus-miner auth - Manage RPC permissions + +USAGE: + lotus-miner auth command [command options] [arguments...] + +COMMANDS: + create-token Create token + api-info Get token with API info required to connect to this node + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +### lotus-miner auth create-token +``` +NAME: + lotus-miner auth create-token - Create token + +USAGE: + lotus-miner auth create-token [command options] [arguments...] + +OPTIONS: + --perm value permission to assign to the token, one of: read, write, sign, admin + --help, -h show help (default: false) + +``` + +### lotus-miner auth api-info +``` +NAME: + lotus-miner auth api-info - Get token with API info required to connect to this node + +USAGE: + lotus-miner auth api-info [command options] [arguments...] + +OPTIONS: + --perm value permission to assign to the token, one of: read, write, sign, admin + --help, -h show help (default: false) + +``` + +## lotus-miner log +``` +NAME: + lotus-miner log - Manage logging + +USAGE: + lotus-miner log command [command options] [arguments...] + +COMMANDS: + list List log systems + set-level Set log level + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +### lotus-miner log list +``` +NAME: + lotus-miner log list - List log systems + +USAGE: + lotus-miner log list [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus-miner log set-level +``` +NAME: + lotus-miner log set-level - Set log level + +USAGE: + lotus-miner log set-level [command options] [level] + +DESCRIPTION: + Set the log level for logging systems: + + The system flag can be specified multiple times. + + eg) log set-level --system chain --system chainxchg debug + + Available Levels: + debug + info + warn + error + + Environment Variables: + GOLOG_LOG_LEVEL - Default log level for all log systems + GOLOG_LOG_FMT - Change output log format (json, nocolor) + GOLOG_FILE - Write logs to file + GOLOG_OUTPUT - Specify whether to output to file, stderr, stdout or a combination, i.e. file+stderr + + +OPTIONS: + --system value limit to log system + --help, -h show help (default: false) + +``` + +## lotus-miner wait-api +``` +NAME: + lotus-miner wait-api - Wait for lotus api to come online + +USAGE: + lotus-miner wait-api [command options] [arguments...] + +CATEGORY: + DEVELOPER + +OPTIONS: + --help, -h show help (default: false) + +``` + +## lotus-miner fetch-params +``` +NAME: + lotus-miner fetch-params - Fetch proving parameters + +USAGE: + lotus-miner fetch-params [command options] [sectorSize] + +CATEGORY: + DEVELOPER + +OPTIONS: + --help, -h show help (default: false) + +``` + +## lotus-miner storage-deals +``` +NAME: + lotus-miner storage-deals - Manage storage deals and related configuration + +USAGE: + lotus-miner storage-deals command [command options] [arguments...] + +COMMANDS: + import-data Manually import data for a deal + list List all deals for this miner + selection Configure acceptance criteria for storage deal proposals + set-ask Configure the miner's ask + get-ask Print the miner's ask + set-blocklist Set the miner's list of blocklisted piece CIDs + get-blocklist List the contents of the miner's piece CID blocklist + reset-blocklist Remove all entries from the miner's piece CID blocklist + set-seal-duration Set the expected time, in minutes, that you expect sealing sectors to take. Deals that start before this duration will be rejected. + pending-publish list deals waiting in publish queue + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +### lotus-miner storage-deals import-data +``` +NAME: + lotus-miner storage-deals import-data - Manually import data for a deal + +USAGE: + lotus-miner storage-deals import-data [command options] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus-miner storage-deals list +``` +NAME: + lotus-miner storage-deals list - List all deals for this miner + +USAGE: + lotus-miner storage-deals list [command options] [arguments...] + +OPTIONS: + --verbose, -v (default: false) + --watch watch deal updates in real-time, rather than a one time list (default: false) + --help, -h show help (default: false) + +``` + +### lotus-miner storage-deals selection +``` +NAME: + lotus-miner storage-deals selection - Configure acceptance criteria for storage deal proposals + +USAGE: + lotus-miner storage-deals selection command [command options] [arguments...] + +COMMANDS: + list List storage deal proposal selection criteria + reset Reset storage deal proposal selection criteria to default values + reject Configure criteria which necessitate automatic rejection + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +#### lotus-miner storage-deals selection list +``` +NAME: + lotus-miner storage-deals selection list - List storage deal proposal selection criteria + +USAGE: + lotus-miner storage-deals selection list [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +#### lotus-miner storage-deals selection reset +``` +NAME: + lotus-miner storage-deals selection reset - Reset storage deal proposal selection criteria to default values + +USAGE: + lotus-miner storage-deals selection reset [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +#### lotus-miner storage-deals selection reject +``` +NAME: + lotus-miner storage-deals selection reject - Configure criteria which necessitate automatic rejection + +USAGE: + lotus-miner storage-deals selection reject [command options] [arguments...] + +OPTIONS: + --online (default: false) + --offline (default: false) + --verified (default: false) + --unverified (default: false) + --help, -h show help (default: false) + +``` + +### lotus-miner storage-deals set-ask +``` +NAME: + lotus-miner storage-deals set-ask - Configure the miner's ask + +USAGE: + lotus-miner storage-deals set-ask [command options] [arguments...] + +OPTIONS: + --price PRICE Set the price of the ask for unverified deals (specified as FIL / GiB / Epoch) to PRICE. + --verified-price PRICE Set the price of the ask for verified deals (specified as FIL / GiB / Epoch) to PRICE + --min-piece-size SIZE Set minimum piece size (w/bit-padding, in bytes) in ask to SIZE (default: 256B) + --max-piece-size SIZE Set maximum piece size (w/bit-padding, in bytes) in ask to SIZE (default: miner sector size) + --help, -h show help (default: false) + +``` + +### lotus-miner storage-deals get-ask +``` +NAME: + lotus-miner storage-deals get-ask - Print the miner's ask + +USAGE: + lotus-miner storage-deals get-ask [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus-miner storage-deals set-blocklist +``` +NAME: + lotus-miner storage-deals set-blocklist - Set the miner's list of blocklisted piece CIDs + +USAGE: + lotus-miner storage-deals set-blocklist [command options] [ (optional, will read from stdin if omitted)] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus-miner storage-deals get-blocklist +``` +NAME: + lotus-miner storage-deals get-blocklist - List the contents of the miner's piece CID blocklist + +USAGE: + lotus-miner storage-deals get-blocklist [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus-miner storage-deals reset-blocklist +``` +NAME: + lotus-miner storage-deals reset-blocklist - Remove all entries from the miner's piece CID blocklist + +USAGE: + lotus-miner storage-deals reset-blocklist [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus-miner storage-deals set-seal-duration +``` +NAME: + lotus-miner storage-deals set-seal-duration - Set the expected time, in minutes, that you expect sealing sectors to take. Deals that start before this duration will be rejected. + +USAGE: + lotus-miner storage-deals set-seal-duration [command options] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus-miner storage-deals pending-publish +``` +NAME: + lotus-miner storage-deals pending-publish - list deals waiting in publish queue + +USAGE: + lotus-miner storage-deals pending-publish [command options] [arguments...] + +OPTIONS: + --publish-now send a publish message now (default: false) + --help, -h show help (default: false) + +``` + +## lotus-miner retrieval-deals +``` +NAME: + lotus-miner retrieval-deals - Manage retrieval deals and related configuration + +USAGE: + lotus-miner retrieval-deals command [command options] [arguments...] + +COMMANDS: + selection Configure acceptance criteria for retrieval deal proposals + list List all active retrieval deals for this miner + set-ask Configure the provider's retrieval ask + get-ask Get the provider's current retrieval ask + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +### lotus-miner retrieval-deals selection +``` +NAME: + lotus-miner retrieval-deals selection - Configure acceptance criteria for retrieval deal proposals + +USAGE: + lotus-miner retrieval-deals selection command [command options] [arguments...] + +COMMANDS: + list List retrieval deal proposal selection criteria + reset Reset retrieval deal proposal selection criteria to default values + reject Configure criteria which necessitate automatic rejection + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +#### lotus-miner retrieval-deals selection list +``` +NAME: + lotus-miner retrieval-deals selection list - List retrieval deal proposal selection criteria + +USAGE: + lotus-miner retrieval-deals selection list [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +#### lotus-miner retrieval-deals selection reset +``` +NAME: + lotus-miner retrieval-deals selection reset - Reset retrieval deal proposal selection criteria to default values + +USAGE: + lotus-miner retrieval-deals selection reset [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +#### lotus-miner retrieval-deals selection reject +``` +NAME: + lotus-miner retrieval-deals selection reject - Configure criteria which necessitate automatic rejection + +USAGE: + lotus-miner retrieval-deals selection reject [command options] [arguments...] + +OPTIONS: + --online (default: false) + --offline (default: false) + --help, -h show help (default: false) + +``` + +### lotus-miner retrieval-deals list +``` +NAME: + lotus-miner retrieval-deals list - List all active retrieval deals for this miner + +USAGE: + lotus-miner retrieval-deals list [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus-miner retrieval-deals set-ask +``` +NAME: + lotus-miner retrieval-deals set-ask - Configure the provider's retrieval ask + +USAGE: + lotus-miner retrieval-deals set-ask [command options] [arguments...] + +OPTIONS: + --price value Set the price of the ask for retrievals (FIL/GiB) + --unseal-price value Set the price to unseal + --payment-interval value Set the payment interval (in bytes) for retrieval (default: 1MiB) + --payment-interval-increase value Set the payment interval increase (in bytes) for retrieval (default: 1MiB) + --help, -h show help (default: false) + +``` + +### lotus-miner retrieval-deals get-ask +``` +NAME: + lotus-miner retrieval-deals get-ask - Get the provider's current retrieval ask + +USAGE: + lotus-miner retrieval-deals get-ask [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +## lotus-miner data-transfers +``` +NAME: + lotus-miner data-transfers - Manage data transfers + +USAGE: + lotus-miner data-transfers command [command options] [arguments...] + +COMMANDS: + list List ongoing data transfers for this miner + restart Force restart a stalled data transfer + cancel Force cancel a data transfer + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +### lotus-miner data-transfers list +``` +NAME: + lotus-miner data-transfers list - List ongoing data transfers for this miner + +USAGE: + lotus-miner data-transfers list [command options] [arguments...] + +OPTIONS: + --verbose, -v print verbose transfer details (default: false) + --color use color in display output (default: true) + --completed show completed data transfers (default: false) + --watch watch deal updates in real-time, rather than a one time list (default: false) + --show-failed show failed/cancelled transfers (default: false) + --help, -h show help (default: false) + +``` + +### lotus-miner data-transfers restart +``` +NAME: + lotus-miner data-transfers restart - Force restart a stalled data transfer + +USAGE: + lotus-miner data-transfers restart [command options] [arguments...] + +OPTIONS: + --peerid value narrow to transfer with specific peer + --initiator specify only transfers where peer is/is not initiator (default: false) + --help, -h show help (default: false) + +``` + +### lotus-miner data-transfers cancel +``` +NAME: + lotus-miner data-transfers cancel - Force cancel a data transfer + +USAGE: + lotus-miner data-transfers cancel [command options] [arguments...] + +OPTIONS: + --peerid value narrow to transfer with specific peer + --initiator specify only transfers where peer is/is not initiator (default: false) + --cancel-timeout value time to wait for cancel to be sent to client (default: 5s) + --help, -h show help (default: false) + +``` + +## lotus-miner net +``` +NAME: + lotus-miner net - Manage P2P Network + +USAGE: + lotus-miner net command [command options] [arguments...] + +COMMANDS: + peers Print peers + connect Connect to a peer + listen List listen addresses + id Get node identity + findpeer Find the addresses of a given peerID + scores Print peers' pubsub scores + reachability Print information about reachability from the internet + bandwidth Print bandwidth usage information + block Manage network connection gating rules + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +### lotus-miner net peers +``` +NAME: + lotus-miner net peers - Print peers + +USAGE: + lotus-miner net peers [command options] [arguments...] + +OPTIONS: + --agent, -a Print agent name (default: false) + --extended, -x Print extended peer information in json (default: false) + --help, -h show help (default: false) + +``` + +### lotus-miner net connect +``` +NAME: + lotus-miner net connect - Connect to a peer + +USAGE: + lotus-miner net connect [command options] [peerMultiaddr|minerActorAddress] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus-miner net listen +``` +NAME: + lotus-miner net listen - List listen addresses + +USAGE: + lotus-miner net listen [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus-miner net id +``` +NAME: + lotus-miner net id - Get node identity + +USAGE: + lotus-miner net id [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus-miner net findpeer +``` +NAME: + lotus-miner net findpeer - Find the addresses of a given peerID + +USAGE: + lotus-miner net findpeer [command options] [peerId] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus-miner net scores +``` +NAME: + lotus-miner net scores - Print peers' pubsub scores + +USAGE: + lotus-miner net scores [command options] [arguments...] + +OPTIONS: + --extended, -x print extended peer scores in json (default: false) + --help, -h show help (default: false) + +``` + +### lotus-miner net reachability +``` +NAME: + lotus-miner net reachability - Print information about reachability from the internet + +USAGE: + lotus-miner net reachability [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus-miner net bandwidth +``` +NAME: + lotus-miner net bandwidth - Print bandwidth usage information + +USAGE: + lotus-miner net bandwidth [command options] [arguments...] + +OPTIONS: + --by-peer list bandwidth usage by peer (default: false) + --by-protocol list bandwidth usage by protocol (default: false) + --help, -h show help (default: false) + +``` + +### lotus-miner net block +``` +NAME: + lotus-miner net block - Manage network connection gating rules + +USAGE: + lotus-miner net block command [command options] [arguments...] + +COMMANDS: + add Add connection gating rules + remove Remove connection gating rules + list list connection gating rules + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +#### lotus-miner net block add +``` +NAME: + lotus-miner net block add - Add connection gating rules + +USAGE: + lotus-miner net block add command [command options] [arguments...] + +COMMANDS: + peer Block a peer + ip Block an IP address + subnet Block an IP subnet + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +##### lotus-miner net block add peer +``` +NAME: + lotus-miner net block add peer - Block a peer + +USAGE: + lotus-miner net block add peer [command options] ... + +OPTIONS: + --help, -h show help (default: false) + +``` + +##### lotus-miner net block add ip +``` +NAME: + lotus-miner net block add ip - Block an IP address + +USAGE: + lotus-miner net block add ip [command options] ... + +OPTIONS: + --help, -h show help (default: false) + +``` + +##### lotus-miner net block add subnet +``` +NAME: + lotus-miner net block add subnet - Block an IP subnet + +USAGE: + lotus-miner net block add subnet [command options] ... + +OPTIONS: + --help, -h show help (default: false) + +``` + +#### lotus-miner net block remove +``` +NAME: + lotus-miner net block remove - Remove connection gating rules + +USAGE: + lotus-miner net block remove command [command options] [arguments...] + +COMMANDS: + peer Unblock a peer + ip Unblock an IP address + subnet Unblock an IP subnet + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +##### lotus-miner net block remove peer +``` +NAME: + lotus-miner net block remove peer - Unblock a peer + +USAGE: + lotus-miner net block remove peer [command options] ... + +OPTIONS: + --help, -h show help (default: false) + +``` + +##### lotus-miner net block remove ip +``` +NAME: + lotus-miner net block remove ip - Unblock an IP address + +USAGE: + lotus-miner net block remove ip [command options] ... + +OPTIONS: + --help, -h show help (default: false) + +``` + +##### lotus-miner net block remove subnet +``` +NAME: + lotus-miner net block remove subnet - Unblock an IP subnet + +USAGE: + lotus-miner net block remove subnet [command options] ... + +OPTIONS: + --help, -h show help (default: false) + +``` + +#### lotus-miner net block list +``` +NAME: + lotus-miner net block list - list connection gating rules + +USAGE: + lotus-miner net block list [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +## lotus-miner pieces +``` +NAME: + lotus-miner pieces - interact with the piecestore + +USAGE: + lotus-miner pieces command [command options] [arguments...] + +DESCRIPTION: + The piecestore is a database that tracks and manages data that is made available to the retrieval market + +COMMANDS: + list-pieces list registered pieces + list-cids list registered payload CIDs + piece-info get registered information for a given piece CID + cid-info get registered information for a given payload CID + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +### lotus-miner pieces list-pieces +``` +NAME: + lotus-miner pieces list-pieces - list registered pieces + +USAGE: + lotus-miner pieces list-pieces [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus-miner pieces list-cids +``` +NAME: + lotus-miner pieces list-cids - list registered payload CIDs + +USAGE: + lotus-miner pieces list-cids [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus-miner pieces piece-info +``` +NAME: + lotus-miner pieces piece-info - get registered information for a given piece CID + +USAGE: + lotus-miner pieces piece-info [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus-miner pieces cid-info +``` +NAME: + lotus-miner pieces cid-info - get registered information for a given payload CID + +USAGE: + lotus-miner pieces cid-info [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +## lotus-miner sectors +``` +NAME: + lotus-miner sectors - interact with sector store + +USAGE: + lotus-miner sectors command [command options] [arguments...] + +COMMANDS: + status Get the seal status of a sector by its number + list List sectors + refs List References to sectors + update-state ADVANCED: manually update the state of a sector, this may aid in error recovery + pledge store random data in a sector + extend Extend sector expiration + terminate Terminate sector on-chain then remove (WARNING: This means losing power and collateral for the removed sector) + remove Forcefully remove a sector (WARNING: This means losing power and collateral for the removed sector (use 'terminate' for lower penalty)) + mark-for-upgrade Mark a committed capacity sector for replacement by a sector with deals + seal Manually start sealing a sector (filling any unused space with junk) + set-seal-delay Set the time, in minutes, that a new sector waits for deals before sealing starts + get-cc-collateral Get the collateral required to pledge a committed capacity sector + batching manage batch sector operations + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +### lotus-miner sectors status +``` +NAME: + lotus-miner sectors status - Get the seal status of a sector by its number + +USAGE: + lotus-miner sectors status [command options] + +OPTIONS: + --log display event log (default: false) + --on-chain-info show sector on chain info (default: false) + --help, -h show help (default: false) + +``` + +### lotus-miner sectors list +``` +NAME: + lotus-miner sectors list - List sectors + +USAGE: + lotus-miner sectors list [command options] [arguments...] + +OPTIONS: + --show-removed show removed sectors (default: false) + --color, -c (default: true) + --fast don't show on-chain info for better performance (default: false) + --events display number of events the sector has received (default: false) + --seal-time display how long it took for the sector to be sealed (default: false) + --states value filter sectors by a comma-separated list of states + --help, -h show help (default: false) + +``` + +### lotus-miner sectors refs +``` +NAME: + lotus-miner sectors refs - List References to sectors + +USAGE: + lotus-miner sectors refs [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus-miner sectors update-state +``` +NAME: + lotus-miner sectors update-state - ADVANCED: manually update the state of a sector, this may aid in error recovery + +USAGE: + lotus-miner sectors update-state [command options] + +OPTIONS: + --really-do-it pass this flag if you know what you are doing (default: false) + --help, -h show help (default: false) + +``` + +### lotus-miner sectors pledge +``` +NAME: + lotus-miner sectors pledge - store random data in a sector + +USAGE: + lotus-miner sectors pledge [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus-miner sectors extend +``` +NAME: + lotus-miner sectors extend - Extend sector expiration + +USAGE: + lotus-miner sectors extend [command options] + +OPTIONS: + --new-expiration value new expiration epoch (default: 0) + --v1-sectors renews all v1 sectors up to the maximum possible lifetime (default: false) + --tolerance value when extending v1 sectors, don't try to extend sectors by fewer than this number of epochs (default: 20160) + --expiration-cutoff value when extending v1 sectors, skip sectors whose current expiration is more than epochs from now (infinity if unspecified) (default: 0) + + --help, -h show help (default: false) + +``` + +### lotus-miner sectors terminate +``` +NAME: + lotus-miner sectors terminate - Terminate sector on-chain then remove (WARNING: This means losing power and collateral for the removed sector) + +USAGE: + lotus-miner sectors terminate command [command options] + +COMMANDS: + flush Send a terminate message if there are sectors queued for termination + pending List sector numbers of sectors pending termination + help, h Shows a list of commands or help for one command + +OPTIONS: + --really-do-it pass this flag if you know what you are doing (default: false) + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +#### lotus-miner sectors terminate flush +``` +NAME: + lotus-miner sectors terminate flush - Send a terminate message if there are sectors queued for termination + +USAGE: + lotus-miner sectors terminate flush [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +#### lotus-miner sectors terminate pending +``` +NAME: + lotus-miner sectors terminate pending - List sector numbers of sectors pending termination + +USAGE: + lotus-miner sectors terminate pending [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus-miner sectors remove +``` +NAME: + lotus-miner sectors remove - Forcefully remove a sector (WARNING: This means losing power and collateral for the removed sector (use 'terminate' for lower penalty)) + +USAGE: + lotus-miner sectors remove [command options] + +OPTIONS: + --really-do-it pass this flag if you know what you are doing (default: false) + --help, -h show help (default: false) + +``` + +### lotus-miner sectors mark-for-upgrade +``` +NAME: + lotus-miner sectors mark-for-upgrade - Mark a committed capacity sector for replacement by a sector with deals + +USAGE: + lotus-miner sectors mark-for-upgrade [command options] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus-miner sectors seal +``` +NAME: + lotus-miner sectors seal - Manually start sealing a sector (filling any unused space with junk) + +USAGE: + lotus-miner sectors seal [command options] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus-miner sectors set-seal-delay +``` +NAME: + lotus-miner sectors set-seal-delay - Set the time, in minutes, that a new sector waits for deals before sealing starts + +USAGE: + lotus-miner sectors set-seal-delay [command options] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus-miner sectors get-cc-collateral +``` +NAME: + lotus-miner sectors get-cc-collateral - Get the collateral required to pledge a committed capacity sector + +USAGE: + lotus-miner sectors get-cc-collateral [command options] [arguments...] + +OPTIONS: + --expiration value the epoch when the sector will expire (default: 0) + --help, -h show help (default: false) + +``` + +### lotus-miner sectors batching +``` +NAME: + lotus-miner sectors batching - manage batch sector operations + +USAGE: + lotus-miner sectors batching command [command options] [arguments...] + +COMMANDS: + pending-commit list sectors waiting in commit batch queue + pending-precommit list sectors waiting in precommit batch queue + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +#### lotus-miner sectors batching pending-commit +``` +NAME: + lotus-miner sectors batching pending-commit - list sectors waiting in commit batch queue + +USAGE: + lotus-miner sectors batching pending-commit [command options] [arguments...] + +OPTIONS: + --publish-now send a batch now (default: false) + --help, -h show help (default: false) + +``` + +#### lotus-miner sectors batching pending-precommit +``` +NAME: + lotus-miner sectors batching pending-precommit - list sectors waiting in precommit batch queue + +USAGE: + lotus-miner sectors batching pending-precommit [command options] [arguments...] + +OPTIONS: + --publish-now send a batch now (default: false) + --help, -h show help (default: false) + +``` + +## lotus-miner proving +``` +NAME: + lotus-miner proving - View proving information + +USAGE: + lotus-miner proving command [command options] [arguments...] + +COMMANDS: + info View current state information + deadlines View the current proving period deadlines information + deadline View the current proving period deadline information by its index + faults View the currently known proving faulty sectors information + check Check sectors provable + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +### lotus-miner proving info +``` +NAME: + lotus-miner proving info - View current state information + +USAGE: + lotus-miner proving info [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus-miner proving deadlines +``` +NAME: + lotus-miner proving deadlines - View the current proving period deadlines information + +USAGE: + lotus-miner proving deadlines [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus-miner proving deadline +``` +NAME: + lotus-miner proving deadline - View the current proving period deadline information by its index + +USAGE: + lotus-miner proving deadline [command options] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus-miner proving faults +``` +NAME: + lotus-miner proving faults - View the currently known proving faulty sectors information + +USAGE: + lotus-miner proving faults [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus-miner proving check +``` +NAME: + lotus-miner proving check - Check sectors provable + +USAGE: + lotus-miner proving check [command options] + +OPTIONS: + --only-bad print only bad sectors (default: false) + --slow run slower checks (default: false) + --help, -h show help (default: false) + +``` + +## lotus-miner storage +``` +NAME: + lotus-miner storage - manage sector storage + +USAGE: + lotus-miner storage command [command options] [arguments...] + +DESCRIPTION: + Sectors can be stored across many filesystem paths. These +commands provide ways to manage the storage the miner will used to store sectors +long term for proving (references as 'store') as well as how sectors will be +stored while moving through the sealing pipeline (references as 'seal'). + +COMMANDS: + attach attach local storage path + list list local storage paths + find find sector in the storage system + cleanup trigger cleanup actions + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +### lotus-miner storage attach +``` +NAME: + lotus-miner storage attach - attach local storage path + +USAGE: + lotus-miner storage attach [command options] [arguments...] + +DESCRIPTION: + Storage can be attached to the miner using this command. The storage volume +list is stored local to the miner in $LOTUS_MINER_PATH/storage.json. We do not +recommend manually modifying this value without further understanding of the +storage system. + +Each storage volume contains a configuration file which describes the +capabilities of the volume. When the '--init' flag is provided, this file will +be created using the additional flags. + +Weight +A high weight value means data will be more likely to be stored in this path + +Seal +Data for the sealing process will be stored here + +Store +Finalized sectors that will be moved here for long term storage and be proven +over time + + +OPTIONS: + --init initialize the path first (default: false) + --weight value (for init) path weight (default: 10) + --seal (for init) use path for sealing (default: false) + --store (for init) use path for long-term storage (default: false) + --max-storage value (for init) limit storage space for sectors (expensive for very large paths!) + --help, -h show help (default: false) + +``` + +### lotus-miner storage list +``` +NAME: + lotus-miner storage list - list local storage paths + +USAGE: + lotus-miner storage list command [command options] [arguments...] + +COMMANDS: + sectors get list of all sector files + help, h Shows a list of commands or help for one command + +OPTIONS: + --color (default: false) + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +#### lotus-miner storage list sectors +``` +NAME: + lotus-miner storage list sectors - get list of all sector files + +USAGE: + lotus-miner storage list sectors [command options] [arguments...] + +OPTIONS: + --color (default: true) + --help, -h show help (default: false) + +``` + +### lotus-miner storage find +``` +NAME: + lotus-miner storage find - find sector in the storage system + +USAGE: + lotus-miner storage find [command options] [sector number] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus-miner storage cleanup +``` +NAME: + lotus-miner storage cleanup - trigger cleanup actions + +USAGE: + lotus-miner storage cleanup [command options] [arguments...] + +OPTIONS: + --removed cleanup remaining files from removed sectors (default: true) + --help, -h show help (default: false) + +``` + +## lotus-miner sealing +``` +NAME: + lotus-miner sealing - interact with sealing pipeline + +USAGE: + lotus-miner sealing command [command options] [arguments...] + +COMMANDS: + jobs list running jobs + workers list workers + sched-diag Dump internal scheduler state + abort Abort a running job + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +### lotus-miner sealing jobs +``` +NAME: + lotus-miner sealing jobs - list running jobs + +USAGE: + lotus-miner sealing jobs [command options] [arguments...] + +OPTIONS: + --color (default: false) + --show-ret-done show returned but not consumed calls (default: false) + --help, -h show help (default: false) + +``` + +### lotus-miner sealing workers +``` +NAME: + lotus-miner sealing workers - list workers + +USAGE: + lotus-miner sealing workers [command options] [arguments...] + +OPTIONS: + --color (default: false) + --help, -h show help (default: false) + +``` + +### lotus-miner sealing sched-diag +``` +NAME: + lotus-miner sealing sched-diag - Dump internal scheduler state + +USAGE: + lotus-miner sealing sched-diag [command options] [arguments...] + +OPTIONS: + --force-sched (default: false) + --help, -h show help (default: false) + +``` + +### lotus-miner sealing abort +``` +NAME: + lotus-miner sealing abort - Abort a running job + +USAGE: + lotus-miner sealing abort [command options] [callid] + +OPTIONS: + --help, -h show help (default: false) + +``` diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go index e560ecba7..060a92fff 100644 --- a/extern/storage-sealing/commit_batch.go +++ b/extern/storage-sealing/commit_batch.go @@ -20,6 +20,7 @@ import ( "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" ) @@ -333,7 +334,7 @@ func (b *CommitBatcher) Stop(ctx context.Context) error { } func getSectorDeadline(curEpoch abi.ChainEpoch, si SectorInfo) time.Time { - deadlineEpoch := si.TicketEpoch + deadlineEpoch := si.TicketEpoch + policy.MaxPreCommitRandomnessLookback for _, p := range si.Pieces { if p.DealInfo == nil { continue From dd393b470faf1eea850946b1a3b42faefac4e689 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 18 May 2021 20:34:23 +0200 Subject: [PATCH 25/88] Fix aggregation inputs --- api/test/window_post.go | 2 +- extern/sector-storage/mock/mock.go | 2 +- extern/storage-sealing/commit_batch.go | 10 +++++++--- extern/storage-sealing/states_sealing.go | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/api/test/window_post.go b/api/test/window_post.go index e508fb5c5..df520c63d 100644 --- a/api/test/window_post.go +++ b/api/test/window_post.go @@ -121,7 +121,7 @@ func TestPledgeBatching(t *testing.T, b APIBuilder, blocktime time.Duration, nSe ctx, cancel := context.WithCancel(context.Background()) defer cancel() - n, sn := b(t, []FullNodeOpts{FullNodeWithLatestActorsAt(-1)}, OneMiner) + n, sn := b(t, []FullNodeOpts{FullNodeWithLatestActorsAt(-1)}, OneMiner) client := n[0].FullNode.(*impl.FullNodeAPI) miner := sn[0] diff --git a/extern/sector-storage/mock/mock.go b/extern/sector-storage/mock/mock.go index c7844befd..bb968b474 100644 --- a/extern/sector-storage/mock/mock.go +++ b/extern/sector-storage/mock/mock.go @@ -517,7 +517,7 @@ func (m mockVerif) VerifyAggregateSeals(aggregate proof5.AggregateSealVerifyProo for i := 0; i < 32; i++ { b := svi.UnsealedCID.Bytes()[i] + svi.SealedCID.Bytes()[31-i] - svi.InteractiveRandomness[i]*svi.Randomness[i] // raw proof byte - b *= uint8(pi) // with aggregate index + b *= uint8(pi) // with aggregate index out[i] += b } } diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go index 060a92fff..f086c2ad6 100644 --- a/extern/storage-sealing/commit_batch.go +++ b/extern/storage-sealing/commit_batch.go @@ -181,7 +181,6 @@ func (b *CommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { return nil, nil } - spt := b.todo[0].spt proofs := make([][]byte, 0, total) infos := make([]proof5.AggregateSealVerifyInfo, 0, total) @@ -198,9 +197,14 @@ func (b *CommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { proofs = append(proofs, b.todo[info.Number].proof) } + mid, err := address.IDFromAddress(b.maddr) + if err != nil { + return nil, xerrors.Errorf("getting miner id: %w", err) + } + params.AggregateProof, err = b.verif.AggregateSealProofs(proof5.AggregateSealVerifyProofAndInfos{ - Miner: 0, - SealProof: spt, + Miner: abi.ActorID(mid), + SealProof: b.todo[infos[0].Number].spt, AggregateProof: arp, Infos: infos, }, proofs) diff --git a/extern/storage-sealing/states_sealing.go b/extern/storage-sealing/states_sealing.go index 19eb31e64..bec9ad51b 100644 --- a/extern/storage-sealing/states_sealing.go +++ b/extern/storage-sealing/states_sealing.go @@ -3,7 +3,6 @@ package sealing import ( "bytes" "context" - "github.com/ipfs/go-cid" "golang.org/x/xerrors" @@ -590,6 +589,7 @@ func (m *Sealing) handleSubmitCommitAggregate(ctx statemachine.Context, sector S UnsealedCID: *sector.CommD, }, proof: sector.Proof, // todo: this correct?? + spt: sector.SectorType, }) if err != nil { return ctx.Send(SectorCommitFailed{xerrors.Errorf("queuing commit for aggregation failed: %w", err)}) From 5112b9fe2b0529ac62bdf7f368c71b8507fa92ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 18 May 2021 21:04:47 +0200 Subject: [PATCH 26/88] Lower default batch slack --- chain/actors/policy/policy.go | 2 +- node/config/def.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/chain/actors/policy/policy.go b/chain/actors/policy/policy.go index 113544c05..1a95c4635 100644 --- a/chain/actors/policy/policy.go +++ b/chain/actors/policy/policy.go @@ -39,7 +39,7 @@ const ( ChainFinality = miner5.ChainFinality SealRandomnessLookback = ChainFinality PaychSettleDelay = paych5.SettleDelay - MaxPreCommitRandomnessLookback = builtin5.EpochsInDay + SealRandomnessLookback + MaxPreCommitRandomnessLookback = builtin5.EpochsInDay + SealRandomnessLookback // todo fix ) // SetSupportedProofTypes sets supported proof types, across all actor versions. diff --git a/node/config/def.go b/node/config/def.go index 207419c6c..0bc4a81e8 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -258,13 +258,13 @@ func DefaultStorageMiner() *StorageMiner { MinPreCommitBatch: 1, // we must have at least one proof to aggregate MaxPreCommitBatch: 204, // todo max? PreCommitBatchWait: Duration(24 * time.Hour), // this can be up to 6 days - PreCommitBatchSlack: Duration(8 * time.Hour), + PreCommitBatchSlack: Duration(3 * time.Hour), AggregateCommits: true, MinCommitBatch: 1, // we must have at least one proof to aggregate MaxCommitBatch: 204, // this is the maximum aggregation per FIP13 CommitBatchWait: Duration(24 * time.Hour), // this can be up to 6 days - CommitBatchSlack: Duration(8 * time.Hour), + CommitBatchSlack: Duration(1 * time.Hour), TerminateBatchMin: 1, TerminateBatchMax: 100, From 2a0c0e379b9adc35fbde1a903267b25aff81b159 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 19 May 2021 14:32:41 +0200 Subject: [PATCH 27/88] Working default batching config --- node/config/def.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/node/config/def.go b/node/config/def.go index 0bc4a81e8..c0372a1e3 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -6,6 +6,8 @@ import ( "github.com/ipfs/go-cid" + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" + "github.com/filecoin-project/lotus/chain/types" sectorstorage "github.com/filecoin-project/lotus/extern/sector-storage" ) @@ -256,13 +258,13 @@ func DefaultStorageMiner() *StorageMiner { BatchPreCommits: true, MinPreCommitBatch: 1, // we must have at least one proof to aggregate - MaxPreCommitBatch: 204, // todo max? + MaxPreCommitBatch: miner5.PreCommitSectorBatchMaxSize, // PreCommitBatchWait: Duration(24 * time.Hour), // this can be up to 6 days PreCommitBatchSlack: Duration(3 * time.Hour), AggregateCommits: true, MinCommitBatch: 1, // we must have at least one proof to aggregate - MaxCommitBatch: 204, // this is the maximum aggregation per FIP13 + MaxCommitBatch: miner5.MaxAggregatedSectors, // this is the maximum aggregation per FIP13 CommitBatchWait: Duration(24 * time.Hour), // this can be up to 6 days CommitBatchSlack: Duration(1 * time.Hour), From eafaf6d23620d4dd28780446a6ab6afdb07a9b1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 19 May 2021 14:33:15 +0200 Subject: [PATCH 28/88] Don't block on batching in node tests --- api/test/window_post.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/api/test/window_post.go b/api/test/window_post.go index df520c63d..767aff4d6 100644 --- a/api/test/window_post.go +++ b/api/test/window_post.go @@ -234,10 +234,16 @@ func pledgeSectors(t *testing.T, ctx context.Context, miner TestStorageNode, n, } for len(toCheck) > 0 { + pcb, err := miner.SectorPreCommitFlush(ctx) + require.NoError(t, err) + if pcb != nil { + fmt.Printf("PRECOMMIT BATCH: %s\n", *pcb) + } + cb, err := miner.SectorCommitFlush(ctx) require.NoError(t, err) if cb != nil { - fmt.Printf("BATCH: %s\n", *cb) + fmt.Printf("COMMIT BATCH: %s\n", *cb) } for n := range toCheck { From a5677d1b7a4099bd39bd17bd8a80aea98da207bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 19 May 2021 15:20:23 +0200 Subject: [PATCH 29/88] ffiwrapper: Separate Prover interface --- chain/gen/gen.go | 4 ---- cmd/lotus-bench/caching_verifier.go | 4 ---- .../sector-storage/ffiwrapper/prover_cgo.go | 18 +++++++++++++++++ extern/sector-storage/ffiwrapper/types.go | 6 +++++- .../sector-storage/ffiwrapper/verifier_cgo.go | 4 ---- extern/sector-storage/mock/mock.go | 20 ++++++++++--------- extern/storage-sealing/commit_batch.go | 8 ++++---- extern/storage-sealing/sealing.go | 4 ++-- node/builder.go | 1 + node/modules/storageminer.go | 4 +++- node/test/builder.go | 3 +++ storage/miner.go | 6 ++++-- 12 files changed, 51 insertions(+), 31 deletions(-) create mode 100644 extern/sector-storage/ffiwrapper/prover_cgo.go diff --git a/chain/gen/gen.go b/chain/gen/gen.go index b9173d781..27feaeaaa 100644 --- a/chain/gen/gen.go +++ b/chain/gen/gen.go @@ -693,10 +693,6 @@ func (m genFakeVerifier) VerifyAggregateSeals(aggregate proof5.AggregateSealVeri panic("not supported") } -func (m genFakeVerifier) AggregateSealProofs(ai proof5.AggregateSealVerifyProofAndInfos, proofs [][]byte) ([]byte, error) { - panic("not supported") -} - func (m genFakeVerifier) VerifyWinningPoSt(ctx context.Context, info proof5.WinningPoStVerifyInfo) (bool, error) { panic("not supported") } diff --git a/cmd/lotus-bench/caching_verifier.go b/cmd/lotus-bench/caching_verifier.go index 55786c585..f4cc0f837 100644 --- a/cmd/lotus-bench/caching_verifier.go +++ b/cmd/lotus-bench/caching_verifier.go @@ -101,8 +101,4 @@ func (cv cachingVerifier) VerifyAggregateSeals(aggregate proof5.AggregateSealVer return cv.backend.VerifyAggregateSeals(aggregate) } -func (cv cachingVerifier) AggregateSealProofs(proofType abi.RegisteredSealProof, rap abi.RegisteredAggregationProof, proofs [][]byte) ([]byte, error) { - return cv.backend.AggregateSealProofs(proofType, rap, proofs) -} - var _ ffiwrapper.Verifier = (*cachingVerifier)(nil) diff --git a/extern/sector-storage/ffiwrapper/prover_cgo.go b/extern/sector-storage/ffiwrapper/prover_cgo.go new file mode 100644 index 000000000..3ad73c81c --- /dev/null +++ b/extern/sector-storage/ffiwrapper/prover_cgo.go @@ -0,0 +1,18 @@ +//+build cgo + +package ffiwrapper + +import ( + ffi "github.com/filecoin-project/filecoin-ffi" + proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" +) + +var ProofProver = proofProver{} + +var _ Prover = ProofProver + +type proofProver struct{} + +func (v proofProver) AggregateSealProofs(aggregateInfo proof5.AggregateSealVerifyProofAndInfos, proofs [][]byte) ([]byte, error) { + return ffi.AggregateSealProofs(aggregateInfo, proofs) +} diff --git a/extern/sector-storage/ffiwrapper/types.go b/extern/sector-storage/ffiwrapper/types.go index 99efa7521..a5b2fdf1f 100644 --- a/extern/sector-storage/ffiwrapper/types.go +++ b/extern/sector-storage/ffiwrapper/types.go @@ -40,8 +40,12 @@ type Verifier interface { VerifyWindowPoSt(ctx context.Context, info proof5.WindowPoStVerifyInfo) (bool, error) GenerateWinningPoStSectorChallenge(context.Context, abi.RegisteredPoStProof, abi.ActorID, abi.PoStRandomness, uint64) ([]uint64, error) +} + +// Prover contains cheap proving-related methods +type Prover interface { + // TODO: move GenerateWinningPoStSectorChallenge from the Verifier interface to here - // cheap, makes no sense to put this on the storage interface AggregateSealProofs(aggregateInfo proof5.AggregateSealVerifyProofAndInfos, proofs [][]byte) ([]byte, error) } diff --git a/extern/sector-storage/ffiwrapper/verifier_cgo.go b/extern/sector-storage/ffiwrapper/verifier_cgo.go index 650155305..95724bb7c 100644 --- a/extern/sector-storage/ffiwrapper/verifier_cgo.go +++ b/extern/sector-storage/ffiwrapper/verifier_cgo.go @@ -139,7 +139,3 @@ func (proofVerifier) GenerateWinningPoStSectorChallenge(ctx context.Context, pro randomness[31] &= 0x3f return ffi.GenerateWinningPoStSectorChallenge(proofType, minerID, randomness, eligibleSectorCount) } - -func (v proofVerifier) AggregateSealProofs(aggregateInfo proof5.AggregateSealVerifyProofAndInfos, proofs [][]byte) ([]byte, error) { - return ffi.AggregateSealProofs(aggregateInfo, proofs) -} diff --git a/extern/sector-storage/mock/mock.go b/extern/sector-storage/mock/mock.go index bb968b474..bdd9e14cd 100644 --- a/extern/sector-storage/mock/mock.go +++ b/extern/sector-storage/mock/mock.go @@ -34,7 +34,7 @@ type SectorMgr struct { lk sync.Mutex } -type mockVerif struct{} +type mockVerifProver struct{} func NewMockSectorMgr(genesisSectors []abi.SectorID) *SectorMgr { sectors := make(map[abi.SectorID]*sectorState) @@ -490,7 +490,7 @@ func (mgr *SectorMgr) ReturnFetch(ctx context.Context, callID storiface.CallID, panic("not supported") } -func (m mockVerif) VerifySeal(svi proof5.SealVerifyInfo) (bool, error) { +func (m mockVerifProver) VerifySeal(svi proof5.SealVerifyInfo) (bool, error) { plen, err := svi.SealProof.ProofSize() if err != nil { return false, err @@ -511,7 +511,7 @@ func (m mockVerif) VerifySeal(svi proof5.SealVerifyInfo) (bool, error) { return true, nil } -func (m mockVerif) VerifyAggregateSeals(aggregate proof5.AggregateSealVerifyProofAndInfos) (bool, error) { +func (m mockVerifProver) VerifyAggregateSeals(aggregate proof5.AggregateSealVerifyProofAndInfos) (bool, error) { out := make([]byte, 200) for pi, svi := range aggregate.Infos { for i := 0; i < 32; i++ { @@ -531,7 +531,7 @@ func (m mockVerif) VerifyAggregateSeals(aggregate proof5.AggregateSealVerifyProo return bytes.Equal(aggregate.Proof, out), nil } -func (m mockVerif) AggregateSealProofs(aggregateInfo proof5.AggregateSealVerifyProofAndInfos, proofs [][]byte) ([]byte, error) { +func (m mockVerifProver) AggregateSealProofs(aggregateInfo proof5.AggregateSealVerifyProofAndInfos, proofs [][]byte) ([]byte, error) { out := make([]byte, 200) // todo: figure out more real length for pi, proof := range proofs { for i := range proof[:32] { @@ -548,12 +548,12 @@ func (m mockVerif) AggregateSealProofs(aggregateInfo proof5.AggregateSealVerifyP return out, nil } -func (m mockVerif) VerifyWinningPoSt(ctx context.Context, info proof5.WinningPoStVerifyInfo) (bool, error) { +func (m mockVerifProver) VerifyWinningPoSt(ctx context.Context, info proof5.WinningPoStVerifyInfo) (bool, error) { info.Randomness[31] &= 0x3f return true, nil } -func (m mockVerif) VerifyWindowPoSt(ctx context.Context, info proof5.WindowPoStVerifyInfo) (bool, error) { +func (m mockVerifProver) VerifyWindowPoSt(ctx context.Context, info proof5.WindowPoStVerifyInfo) (bool, error) { if len(info.Proofs) != 1 { return false, xerrors.Errorf("expected 1 proof entry") } @@ -567,15 +567,17 @@ func (m mockVerif) VerifyWindowPoSt(ctx context.Context, info proof5.WindowPoStV return true, nil } -func (m mockVerif) GenerateDataCommitment(pt abi.RegisteredSealProof, pieces []abi.PieceInfo) (cid.Cid, error) { +func (m mockVerifProver) GenerateDataCommitment(pt abi.RegisteredSealProof, pieces []abi.PieceInfo) (cid.Cid, error) { return ffiwrapper.GenerateUnsealedCID(pt, pieces) } -func (m mockVerif) GenerateWinningPoStSectorChallenge(ctx context.Context, proofType abi.RegisteredPoStProof, minerID abi.ActorID, randomness abi.PoStRandomness, eligibleSectorCount uint64) ([]uint64, error) { +func (m mockVerifProver) GenerateWinningPoStSectorChallenge(ctx context.Context, proofType abi.RegisteredPoStProof, minerID abi.ActorID, randomness abi.PoStRandomness, eligibleSectorCount uint64) ([]uint64, error) { return []uint64{0}, nil } -var MockVerifier = mockVerif{} +var MockVerifier = mockVerifProver{} +var MockProver = mockVerifProver{} var _ storage.Sealer = &SectorMgr{} var _ ffiwrapper.Verifier = MockVerifier +var _ ffiwrapper.Prover = MockProver diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go index f086c2ad6..72a56e797 100644 --- a/extern/storage-sealing/commit_batch.go +++ b/extern/storage-sealing/commit_batch.go @@ -46,7 +46,7 @@ type CommitBatcher struct { addrSel AddrSel feeCfg FeeConfig getConfig GetSealingConfigFunc - verif ffiwrapper.Verifier + prover ffiwrapper.Prover deadlines map[abi.SectorNumber]time.Time todo map[abi.SectorNumber]AggregateInput @@ -57,7 +57,7 @@ type CommitBatcher struct { lk sync.Mutex } -func NewCommitBatcher(mctx context.Context, maddr address.Address, api CommitBatcherApi, addrSel AddrSel, feeCfg FeeConfig, getConfig GetSealingConfigFunc, verif ffiwrapper.Verifier) *CommitBatcher { +func NewCommitBatcher(mctx context.Context, maddr address.Address, api CommitBatcherApi, addrSel AddrSel, feeCfg FeeConfig, getConfig GetSealingConfigFunc, prov ffiwrapper.Prover) *CommitBatcher { b := &CommitBatcher{ api: api, maddr: maddr, @@ -65,7 +65,7 @@ func NewCommitBatcher(mctx context.Context, maddr address.Address, api CommitBat addrSel: addrSel, feeCfg: feeCfg, getConfig: getConfig, - verif: verif, + prover: prov, deadlines: map[abi.SectorNumber]time.Time{}, todo: map[abi.SectorNumber]AggregateInput{}, @@ -202,7 +202,7 @@ func (b *CommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { return nil, xerrors.Errorf("getting miner id: %w", err) } - params.AggregateProof, err = b.verif.AggregateSealProofs(proof5.AggregateSealVerifyProofAndInfos{ + params.AggregateProof, err = b.prover.AggregateSealProofs(proof5.AggregateSealVerifyProofAndInfos{ Miner: abi.ActorID(mid), SealProof: b.todo[infos[0].Number].spt, AggregateProof: arp, diff --git a/extern/storage-sealing/sealing.go b/extern/storage-sealing/sealing.go index ede281e39..fc452cc6f 100644 --- a/extern/storage-sealing/sealing.go +++ b/extern/storage-sealing/sealing.go @@ -132,7 +132,7 @@ type pendingPiece struct { accepted func(abi.SectorNumber, abi.UnpaddedPieceSize, error) } -func New(api SealingAPI, fc FeeConfig, events Events, maddr address.Address, ds datastore.Batching, sealer sectorstorage.SectorManager, sc SectorIDCounter, verif ffiwrapper.Verifier, pcp PreCommitPolicy, gc GetSealingConfigFunc, notifee SectorStateNotifee, as AddrSel) *Sealing { +func New(api SealingAPI, fc FeeConfig, events Events, maddr address.Address, ds datastore.Batching, sealer sectorstorage.SectorManager, sc SectorIDCounter, verif ffiwrapper.Verifier, prov ffiwrapper.Prover, pcp PreCommitPolicy, gc GetSealingConfigFunc, notifee SectorStateNotifee, as AddrSel) *Sealing { s := &Sealing{ api: api, feeCfg: fc, @@ -155,7 +155,7 @@ func New(api SealingAPI, fc FeeConfig, events Events, maddr address.Address, ds terminator: NewTerminationBatcher(context.TODO(), maddr, api, as, fc, gc), precommiter: NewPreCommitBatcher(context.TODO(), maddr, api, as, fc, gc), - commiter: NewCommitBatcher(context.TODO(), maddr, api, as, fc, gc, verif), + commiter: NewCommitBatcher(context.TODO(), maddr, api, as, fc, gc, prov), getConfig: gc, dealInfo: &CurrentDealInfoManager{api}, diff --git a/node/builder.go b/node/builder.go index c884b169b..ce00fc18d 100644 --- a/node/builder.go +++ b/node/builder.go @@ -379,6 +379,7 @@ var MinerNode = Options( // Sector storage: Proofs Override(new(ffiwrapper.Verifier), ffiwrapper.ProofVerifier), + Override(new(ffiwrapper.Prover), ffiwrapper.ProofProver), Override(new(storage2.Prover), From(new(sectorstorage.SectorManager))), // Sealing diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index 8a9a99175..122bec519 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -202,6 +202,7 @@ type StorageMinerParams struct { Sealer sectorstorage.SectorManager SectorIDCounter sealing.SectorIDCounter Verifier ffiwrapper.Verifier + Prover ffiwrapper.Prover GetSealingConfigFn dtypes.GetSealingConfigFunc Journal journal.Journal AddrSel *storage.AddressSelector @@ -218,6 +219,7 @@ func StorageMiner(fc config.MinerFeeConfig) func(params StorageMinerParams) (*st h = params.Host sc = params.SectorIDCounter verif = params.Verifier + prover = params.Prover gsd = params.GetSealingConfigFn j = params.Journal as = params.AddrSel @@ -235,7 +237,7 @@ func StorageMiner(fc config.MinerFeeConfig) func(params StorageMinerParams) (*st return nil, err } - sm, err := storage.NewMiner(api, maddr, h, ds, sealer, sc, verif, gsd, fc, j, as) + sm, err := storage.NewMiner(api, maddr, h, ds, sealer, sc, verif, prover, gsd, fc, j, as) if err != nil { return nil, err } diff --git a/node/test/builder.go b/node/test/builder.go index cd0ecc55b..174f07592 100644 --- a/node/test/builder.go +++ b/node/test/builder.go @@ -463,6 +463,7 @@ func mockSbBuilderOpts(t *testing.T, fullOpts []test.FullNodeOpts, storage []tes node.Test(), node.Override(new(ffiwrapper.Verifier), mock.MockVerifier), + node.Override(new(ffiwrapper.Prover), mock.MockProver), // so that we subscribe to pubsub topics immediately node.Override(new(dtypes.Bootstrapper), dtypes.Bootstrapper(true)), @@ -486,6 +487,7 @@ func mockSbBuilderOpts(t *testing.T, fullOpts []test.FullNodeOpts, storage []tes return mock.NewMockSectorMgr(nil), nil }), node.Override(new(ffiwrapper.Verifier), mock.MockVerifier), + node.Override(new(ffiwrapper.Prover), mock.MockProver), node.Unset(new(*sectorstorage.Manager)), )) } @@ -524,6 +526,7 @@ func mockSbBuilderOpts(t *testing.T, fullOpts []test.FullNodeOpts, storage []tes return mock.NewMockSectorMgr(sectors), nil }), node.Override(new(ffiwrapper.Verifier), mock.MockVerifier), + node.Override(new(ffiwrapper.Prover), mock.MockProver), node.Unset(new(*sectorstorage.Manager)), opts, )) diff --git a/storage/miner.go b/storage/miner.go index 52be4d7b8..1c1a9a0bc 100644 --- a/storage/miner.go +++ b/storage/miner.go @@ -48,6 +48,7 @@ type Miner struct { ds datastore.Batching sc sealing.SectorIDCounter verif ffiwrapper.Verifier + prover ffiwrapper.Prover addrSel *AddressSelector maddr address.Address @@ -116,7 +117,7 @@ type storageMinerApi interface { WalletHas(context.Context, address.Address) (bool, error) } -func NewMiner(api storageMinerApi, maddr address.Address, h host.Host, ds datastore.Batching, sealer sectorstorage.SectorManager, sc sealing.SectorIDCounter, verif ffiwrapper.Verifier, gsd dtypes.GetSealingConfigFunc, feeCfg config.MinerFeeConfig, journal journal.Journal, as *AddressSelector) (*Miner, error) { +func NewMiner(api storageMinerApi, maddr address.Address, h host.Host, ds datastore.Batching, sealer sectorstorage.SectorManager, sc sealing.SectorIDCounter, verif ffiwrapper.Verifier, prover ffiwrapper.Prover, gsd dtypes.GetSealingConfigFunc, feeCfg config.MinerFeeConfig, journal journal.Journal, as *AddressSelector) (*Miner, error) { m := &Miner{ api: api, feeCfg: feeCfg, @@ -125,6 +126,7 @@ func NewMiner(api storageMinerApi, maddr address.Address, h host.Host, ds datast ds: ds, sc: sc, verif: verif, + prover: prover, addrSel: as, maddr: maddr, @@ -161,7 +163,7 @@ func (m *Miner) Run(ctx context.Context) error { return m.addrSel.AddressFor(ctx, m.api, mi, use, goodFunds, minFunds) } - m.sealing = sealing.New(adaptedAPI, fc, NewEventsAdapter(evts), m.maddr, m.ds, m.sealer, m.sc, m.verif, &pcp, sealing.GetSealingConfigFunc(m.getSealConfig), m.handleSealingNotifications, as) + m.sealing = sealing.New(adaptedAPI, fc, NewEventsAdapter(evts), m.maddr, m.ds, m.sealer, m.sc, m.verif, m.prover, &pcp, sealing.GetSealingConfigFunc(m.getSealConfig), m.handleSealingNotifications, as) go m.sealing.Run(ctx) //nolint:errcheck // logged intside the function From 2a1b359edec34da00ef748c435e5118c5c871626 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 19 May 2021 15:20:34 +0200 Subject: [PATCH 30/88] config: fmt --- node/config/def.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/node/config/def.go b/node/config/def.go index c0372a1e3..c0aa82a59 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -257,15 +257,15 @@ func DefaultStorageMiner() *StorageMiner { AlwaysKeepUnsealedCopy: true, BatchPreCommits: true, - MinPreCommitBatch: 1, // we must have at least one proof to aggregate - MaxPreCommitBatch: miner5.PreCommitSectorBatchMaxSize, // - PreCommitBatchWait: Duration(24 * time.Hour), // this can be up to 6 days + MinPreCommitBatch: 1, // we must have at least one proof to aggregate + MaxPreCommitBatch: miner5.PreCommitSectorBatchMaxSize, // + PreCommitBatchWait: Duration(24 * time.Hour), // this can be up to 6 days PreCommitBatchSlack: Duration(3 * time.Hour), AggregateCommits: true, - MinCommitBatch: 1, // we must have at least one proof to aggregate - MaxCommitBatch: miner5.MaxAggregatedSectors, // this is the maximum aggregation per FIP13 - CommitBatchWait: Duration(24 * time.Hour), // this can be up to 6 days + MinCommitBatch: 1, // we must have at least one proof to aggregate + MaxCommitBatch: miner5.MaxAggregatedSectors, // this is the maximum aggregation per FIP13 + CommitBatchWait: Duration(24 * time.Hour), // this can be up to 6 days CommitBatchSlack: Duration(1 * time.Hour), TerminateBatchMin: 1, From e088c71b9a3473996ddd6b52746d90b76b8f8523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 19 May 2021 20:07:20 +0200 Subject: [PATCH 31/88] marketadapter: Handle batch sealing messages --- api/test/deals.go | 2 + api/test/window_post.go | 26 +++--- .../sector-storage/ffiwrapper/sealer_test.go | 4 +- .../storageadapter/ondealsectorcommitted.go | 79 ++++++++++++++----- 4 files changed, 79 insertions(+), 32 deletions(-) diff --git a/api/test/deals.go b/api/test/deals.go index 7a9454bae..e3432ff0d 100644 --- a/api/test/deals.go +++ b/api/test/deals.go @@ -435,6 +435,8 @@ func startSealingWaiting(t *testing.T, ctx context.Context, miner TestStorageNod require.NoError(t, miner.SectorStartSealing(ctx, snum)) } } + + flushSealingBatches(t, ctx, miner) } func testRetrieval(t *testing.T, ctx context.Context, client api.FullNode, fcid cid.Cid, piece *cid.Cid, carExport bool, data []byte) { diff --git a/api/test/window_post.go b/api/test/window_post.go index 767aff4d6..48fe3fd6c 100644 --- a/api/test/window_post.go +++ b/api/test/window_post.go @@ -201,6 +201,20 @@ func TestPledgeSector(t *testing.T, b APIBuilder, blocktime time.Duration, nSect <-done } +func flushSealingBatches(t *testing.T, ctx context.Context, miner TestStorageNode) { + pcb, err := miner.SectorPreCommitFlush(ctx) + require.NoError(t, err) + if pcb != nil { + fmt.Printf("PRECOMMIT BATCH: %s\n", *pcb) + } + + cb, err := miner.SectorCommitFlush(ctx) + require.NoError(t, err) + if cb != nil { + fmt.Printf("COMMIT BATCH: %s\n", *cb) + } +} + func pledgeSectors(t *testing.T, ctx context.Context, miner TestStorageNode, n, existing int, blockNotif <-chan struct{}) { for i := 0; i < n; i++ { if i%3 == 0 && blockNotif != nil { @@ -234,17 +248,7 @@ func pledgeSectors(t *testing.T, ctx context.Context, miner TestStorageNode, n, } for len(toCheck) > 0 { - pcb, err := miner.SectorPreCommitFlush(ctx) - require.NoError(t, err) - if pcb != nil { - fmt.Printf("PRECOMMIT BATCH: %s\n", *pcb) - } - - cb, err := miner.SectorCommitFlush(ctx) - require.NoError(t, err) - if cb != nil { - fmt.Printf("COMMIT BATCH: %s\n", *cb) - } + flushSealingBatches(t, ctx, miner) for n := range toCheck { st, err := miner.SectorsStatus(ctx, n, false) diff --git a/extern/sector-storage/ffiwrapper/sealer_test.go b/extern/sector-storage/ffiwrapper/sealer_test.go index 172641bf7..df657f097 100644 --- a/extern/sector-storage/ffiwrapper/sealer_test.go +++ b/extern/sector-storage/ffiwrapper/sealer_test.go @@ -542,12 +542,12 @@ func TestSealAndVerifyAggregate(t *testing.T) { aggStart := time.Now() - avi.Proof, err = ProofVerifier.AggregateSealProofs(avi, toAggregate) + avi.Proof, err = ProofProver.AggregateSealProofs(avi, toAggregate) require.NoError(t, err) aggDone := time.Now() - _, err = ProofVerifier.AggregateSealProofs(avi, toAggregate) + _, err = ProofProver.AggregateSealProofs(avi, toAggregate) require.NoError(t, err) aggHot := time.Now() diff --git a/markets/storageadapter/ondealsectorcommitted.go b/markets/storageadapter/ondealsectorcommitted.go index b5f9c7510..f9ae201b3 100644 --- a/markets/storageadapter/ondealsectorcommitted.go +++ b/markets/storageadapter/ondealsectorcommitted.go @@ -12,6 +12,7 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-fil-markets/storagemarket" "github.com/filecoin-project/go-state-types/abi" + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors/builtin/market" @@ -109,7 +110,7 @@ func (mgr *SectorCommittedManager) OnDealSectorPreCommitted(ctx context.Context, // Watch for a pre-commit message to the provider. matchEvent := func(msg *types.Message) (bool, error) { - matched := msg.To == provider && msg.Method == miner.Methods.PreCommitSector + matched := msg.To == provider && (msg.Method == miner.Methods.PreCommitSector || msg.Method == miner.Methods.PreCommitSectorBatch) return matched, nil } @@ -137,12 +138,6 @@ func (mgr *SectorCommittedManager) OnDealSectorPreCommitted(ctx context.Context, return true, nil } - // Extract the message parameters - var params miner.SectorPreCommitInfo - if err := params.UnmarshalCBOR(bytes.NewReader(msg.Params)); err != nil { - return false, xerrors.Errorf("unmarshal pre commit: %w", err) - } - // When there is a reorg, the deal ID may change, so get the // current deal ID from the publish message CID res, err := mgr.dealInfo.GetCurrentDealInfo(ctx, ts.Key().Bytes(), &proposal, publishCid) @@ -150,13 +145,40 @@ func (mgr *SectorCommittedManager) OnDealSectorPreCommitted(ctx context.Context, return false, err } - // Check through the deal IDs associated with this message - for _, did := range params.DealIDs { - if did == res.DealID { - // Found the deal ID in this message. Callback with the sector ID. - cb(params.SectorNumber, false, nil) - return false, nil + // Extract the message parameters + switch msg.Method { + case miner.Methods.PreCommitSector: + var params miner.SectorPreCommitInfo + if err := params.UnmarshalCBOR(bytes.NewReader(msg.Params)); err != nil { + return false, xerrors.Errorf("unmarshal pre commit: %w", err) } + + // Check through the deal IDs associated with this message + for _, did := range params.DealIDs { + if did == res.DealID { + // Found the deal ID in this message. Callback with the sector ID. + cb(params.SectorNumber, false, nil) + return false, nil + } + } + case miner.Methods.PreCommitSectorBatch: + var params miner5.PreCommitSectorBatchParams + if err := params.UnmarshalCBOR(bytes.NewReader(msg.Params)); err != nil { + return false, xerrors.Errorf("unmarshal pre commit: %w", err) + } + + for _, precommit := range params.Sectors { + // Check through the deal IDs associated with this message + for _, did := range precommit.DealIDs { + if did == res.DealID { + // Found the deal ID in this message. Callback with the sector ID. + cb(precommit.SectorNumber, false, nil) + return false, nil + } + } + } + default: + return false, xerrors.Errorf("unexpected method %d", msg.Method) } // Didn't find the deal ID in this message, so keep looking @@ -207,16 +229,35 @@ func (mgr *SectorCommittedManager) OnDealSectorCommitted(ctx context.Context, pr // Match a prove-commit sent to the provider with the given sector number matchEvent := func(msg *types.Message) (matched bool, err error) { - if msg.To != provider || msg.Method != miner.Methods.ProveCommitSector { + if msg.To != provider { return false, nil } - var params miner.ProveCommitSectorParams - if err := params.UnmarshalCBOR(bytes.NewReader(msg.Params)); err != nil { - return false, xerrors.Errorf("failed to unmarshal prove commit sector params: %w", err) - } + switch msg.Method { + case miner.Methods.ProveCommitSector: + var params miner.ProveCommitSectorParams + if err := params.UnmarshalCBOR(bytes.NewReader(msg.Params)); err != nil { + return false, xerrors.Errorf("failed to unmarshal prove commit sector params: %w", err) + } - return params.SectorNumber == sectorNumber, nil + return params.SectorNumber == sectorNumber, nil + + case miner.Methods.ProveCommitAggregate: + var params miner5.ProveCommitAggregateParams + if err := params.UnmarshalCBOR(bytes.NewReader(msg.Params)); err != nil { + return false, xerrors.Errorf("failed to unmarshal prove commit sector params: %w", err) + } + + set, err := params.SectorNumbers.IsSet(uint64(sectorNumber)) + if err != nil { + return false, xerrors.Errorf("checking if sectorNumber is set in commit aggregate message: %w", err) + } + + return set, nil + + default: + return false, nil + } } // The deal must be accepted by the deal proposal start epoch, so timeout From 51139361c73d94b936a126cba0bc6e90bf998eb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 19 May 2021 20:34:50 +0200 Subject: [PATCH 32/88] sealing: Handle full batches correctly --- extern/storage-sealing/commit_batch.go | 7 ++++++- extern/storage-sealing/precommit_batch.go | 5 +++++ extern/storage-sealing/terminate_batch.go | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go index 72a56e797..f1cbb15ad 100644 --- a/extern/storage-sealing/commit_batch.go +++ b/extern/storage-sealing/commit_batch.go @@ -194,6 +194,11 @@ func (b *CommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { }) for _, info := range infos { + if len(infos) >= cfg.MaxCommitBatch { + log.Infow("commit batch full") + break + } + proofs = append(proofs, b.todo[info.Number].proof) } @@ -232,7 +237,7 @@ func (b *CommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { return nil, xerrors.Errorf("sending message failed: %w", err) } - log.Infow("Sent ProveCommitAggregate message", "cid", mcid, "from", from, "sectors", total) + log.Infow("Sent ProveCommitAggregate message", "cid", mcid, "from", from, "todo", total, "sectors", len(infos)) err = params.SectorNumbers.ForEach(func(us uint64) error { sn := abi.SectorNumber(us) diff --git a/extern/storage-sealing/precommit_batch.go b/extern/storage-sealing/precommit_batch.go index 4430fdda9..f16e5c5ee 100644 --- a/extern/storage-sealing/precommit_batch.go +++ b/extern/storage-sealing/precommit_batch.go @@ -172,6 +172,11 @@ func (b *PreCommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { deposit := big.Zero() for _, p := range b.todo { + if len(params.Sectors) >= cfg.MaxPreCommitBatch { + log.Infow("precommit batch full") + break + } + params.Sectors = append(params.Sectors, p.pci) deposit = big.Add(deposit, p.deposit) } diff --git a/extern/storage-sealing/terminate_batch.go b/extern/storage-sealing/terminate_batch.go index 2bb2dc76a..d545f443f 100644 --- a/extern/storage-sealing/terminate_batch.go +++ b/extern/storage-sealing/terminate_batch.go @@ -183,7 +183,7 @@ func (b *TerminateBatcher) processBatch(notif, after bool) (*cid.Cid, error) { Sectors: toTerminate, }) - if total >= uint64(miner.AddressedSectorsMax) { + if total >= uint64(miner.AddressedSectorsMax) || total >= cfg.TerminateBatchMax { break } From 44bf9bf903a71e590e92182d2f1201957b1ce59f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 20 May 2021 10:39:00 +0200 Subject: [PATCH 33/88] tests: Better state logging in pledgeSectors --- api/test/window_post.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/api/test/window_post.go b/api/test/window_post.go index 48fe3fd6c..1e8090f11 100644 --- a/api/test/window_post.go +++ b/api/test/window_post.go @@ -247,12 +247,15 @@ func pledgeSectors(t *testing.T, ctx context.Context, miner TestStorageNode, n, toCheck[number] = struct{}{} } + for len(toCheck) > 0 { flushSealingBatches(t, ctx, miner) + states := map[api.SectorState]int{} for n := range toCheck { st, err := miner.SectorsStatus(ctx, n, false) require.NoError(t, err) + states[st.State]++ if st.State == api.SectorState(sealing.Proving) { delete(toCheck, n) } @@ -261,8 +264,9 @@ func pledgeSectors(t *testing.T, ctx context.Context, miner TestStorageNode, n, } } + build.Clock.Sleep(100 * time.Millisecond) - fmt.Printf("WaitSeal: %d\n", len(s)) + fmt.Printf("WaitSeal: %d %+v\n", len(s), states) } } From 7edffcd37b00e497b16b29a52c0e66bac0587ea0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 20 May 2021 10:55:01 +0200 Subject: [PATCH 34/88] Update ffi --- extern/filecoin-ffi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/filecoin-ffi b/extern/filecoin-ffi index 178ac15a5..58771ba4d 160000 --- a/extern/filecoin-ffi +++ b/extern/filecoin-ffi @@ -1 +1 @@ -Subproject commit 178ac15a537626cac07d8af68bffc603011c0310 +Subproject commit 58771ba4d942badc306925160a945022ad335161 From 678812f35b8056ce8fae47c475193964517c72f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 20 May 2021 11:33:03 +0200 Subject: [PATCH 35/88] gofmt, lint --- api/test/window_post.go | 2 -- chain/actors/policy/policy.go | 2 +- cmd/lotus-shed/main.go | 1 + go.sum | 3 --- 4 files changed, 2 insertions(+), 6 deletions(-) diff --git a/api/test/window_post.go b/api/test/window_post.go index 1e8090f11..04e709b86 100644 --- a/api/test/window_post.go +++ b/api/test/window_post.go @@ -247,7 +247,6 @@ func pledgeSectors(t *testing.T, ctx context.Context, miner TestStorageNode, n, toCheck[number] = struct{}{} } - for len(toCheck) > 0 { flushSealingBatches(t, ctx, miner) @@ -264,7 +263,6 @@ func pledgeSectors(t *testing.T, ctx context.Context, miner TestStorageNode, n, } } - build.Clock.Sleep(100 * time.Millisecond) fmt.Printf("WaitSeal: %d %+v\n", len(s), states) } diff --git a/chain/actors/policy/policy.go b/chain/actors/policy/policy.go index 1a95c4635..113544c05 100644 --- a/chain/actors/policy/policy.go +++ b/chain/actors/policy/policy.go @@ -39,7 +39,7 @@ const ( ChainFinality = miner5.ChainFinality SealRandomnessLookback = ChainFinality PaychSettleDelay = paych5.SettleDelay - MaxPreCommitRandomnessLookback = builtin5.EpochsInDay + SealRandomnessLookback // todo fix + MaxPreCommitRandomnessLookback = builtin5.EpochsInDay + SealRandomnessLookback ) // SetSupportedProofTypes sets supported proof types, across all actor versions. diff --git a/cmd/lotus-shed/main.go b/cmd/lotus-shed/main.go index ebe4f014a..0e0a13a55 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, diff --git a/go.sum b/go.sum index c281e0fdb..3ef546ba4 100644 --- a/go.sum +++ b/go.sum @@ -293,7 +293,6 @@ github.com/filecoin-project/go-state-types v0.0.0-20200903145444-247639ffa6ad/go github.com/filecoin-project/go-state-types v0.0.0-20200904021452-1883f36ca2f4/go.mod h1:IQ0MBPnonv35CJHtWSN3YY1Hz2gkPru1Q9qoaYLxx9I= github.com/filecoin-project/go-state-types v0.0.0-20200928172055-2df22083d8ab/go.mod h1:ezYnPf0bNkTsDibL/psSz5dy4B5awOJ/E7P2Saeep8g= github.com/filecoin-project/go-state-types v0.0.0-20201102161440-c8033295a1fc/go.mod h1:ezYnPf0bNkTsDibL/psSz5dy4B5awOJ/E7P2Saeep8g= -github.com/filecoin-project/go-state-types v0.1.0 h1:9r2HCSMMCmyMfGyMKxQtv0GKp6VT/m5GgVk8EhYbLJU= github.com/filecoin-project/go-state-types v0.1.0/go.mod h1:ezYnPf0bNkTsDibL/psSz5dy4B5awOJ/E7P2Saeep8g= github.com/filecoin-project/go-state-types v0.1.1-0.20210506134452-99b279731c48 h1:Jc4OprDp3bRDxbsrXNHPwJabZJM3iDy+ri8/1e0ZnX4= github.com/filecoin-project/go-state-types v0.1.1-0.20210506134452-99b279731c48/go.mod h1:ezYnPf0bNkTsDibL/psSz5dy4B5awOJ/E7P2Saeep8g= @@ -310,10 +309,8 @@ github.com/filecoin-project/specs-actors v0.9.13 h1:rUEOQouefi9fuVY/2HOroROJlZbO github.com/filecoin-project/specs-actors v0.9.13/go.mod h1:TS1AW/7LbG+615j4NsjMK1qlpAwaFsG9w0V2tg2gSao= github.com/filecoin-project/specs-actors/v2 v2.0.1/go.mod h1:v2NZVYinNIKA9acEMBm5wWXxqv5+frFEbekBFemYghY= github.com/filecoin-project/specs-actors/v2 v2.3.2/go.mod h1:UuJQLoTx/HPvvWeqlIFmC/ywlOLHNe8SNQ3OunFbu2Y= -github.com/filecoin-project/specs-actors/v2 v2.3.4/go.mod h1:UuJQLoTx/HPvvWeqlIFmC/ywlOLHNe8SNQ3OunFbu2Y= github.com/filecoin-project/specs-actors/v2 v2.3.5-0.20210114162132-5b58b773f4fb h1:orr/sMzrDZUPAveRE+paBdu1kScIUO5zm+HYeh+VlhA= github.com/filecoin-project/specs-actors/v2 v2.3.5-0.20210114162132-5b58b773f4fb/go.mod h1:LljnY2Mn2homxZsmokJZCpRuhOPxfXhvcek5gWkmqAc= -github.com/filecoin-project/specs-actors/v3 v3.0.4-0.20210227000520-b3317b86f4d1/go.mod h1:oMcmEed6B7H/wHabM3RQphTIhq0ibAKsbpYs+bQ/uxQ= github.com/filecoin-project/specs-actors/v3 v3.1.0 h1:s4qiPw8pgypqBGAy853u/zdZJ7K9cTZdM1rTiSonHrg= github.com/filecoin-project/specs-actors/v3 v3.1.0/go.mod h1:mpynccOLlIRy0QnR008BwYBwT9fen+sPR13MA1VmMww= github.com/filecoin-project/specs-actors/v4 v4.0.0 h1:vMALksY5G3J5rj3q9rbcyB+f4Tk1xrLqSgdB3jOok4s= From bb889a5976ed8828014fba7e5f46667ab63c10a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 20 May 2021 12:11:11 +0200 Subject: [PATCH 36/88] sealing: Fix max commit batch size check --- extern/storage-sealing/commit_batch.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go index f1cbb15ad..53f572712 100644 --- a/extern/storage-sealing/commit_batch.go +++ b/extern/storage-sealing/commit_batch.go @@ -185,6 +185,11 @@ func (b *CommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { infos := make([]proof5.AggregateSealVerifyInfo, 0, total) for id, p := range b.todo { + if len(infos) >= cfg.MaxCommitBatch { + log.Infow("commit batch full") + break + } + params.SectorNumbers.Set(uint64(id)) infos = append(infos, p.info) } @@ -194,11 +199,6 @@ func (b *CommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { }) for _, info := range infos { - if len(infos) >= cfg.MaxCommitBatch { - log.Infow("commit batch full") - break - } - proofs = append(proofs, b.todo[info.Number].proof) } From d71334cc2416768ca05c27e08680f85bde451811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 20 May 2021 18:04:07 +0200 Subject: [PATCH 37/88] Address self-review --- api/test/pledge.go | 258 ++++++++++++++++++ api/test/window_post.go | 242 ---------------- .../misc/actors_version_checklist.md | 2 +- extern/storage-sealing/states_sealing.go | 5 +- storage/wdpost_run_test.go | 4 - 5 files changed, 262 insertions(+), 249 deletions(-) create mode 100644 api/test/pledge.go diff --git a/api/test/pledge.go b/api/test/pledge.go new file mode 100644 index 000000000..49df97ea8 --- /dev/null +++ b/api/test/pledge.go @@ -0,0 +1,258 @@ +package test + +import ( + "context" + "fmt" + "sort" + "strings" + "sync/atomic" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/network" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/build" + sealing "github.com/filecoin-project/lotus/extern/storage-sealing" + bminer "github.com/filecoin-project/lotus/miner" + "github.com/filecoin-project/lotus/node/impl" +) + +func TestSDRUpgrade(t *testing.T, b APIBuilder, blocktime time.Duration) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + n, sn := b(t, []FullNodeOpts{FullNodeWithSDRAt(500, 1000)}, OneMiner) + client := n[0].FullNode.(*impl.FullNodeAPI) + miner := sn[0] + + addrinfo, err := client.NetAddrsListen(ctx) + if err != nil { + t.Fatal(err) + } + + if err := miner.NetConnect(ctx, addrinfo); err != nil { + t.Fatal(err) + } + build.Clock.Sleep(time.Second) + + pledge := make(chan struct{}) + mine := int64(1) + done := make(chan struct{}) + go func() { + defer close(done) + round := 0 + for atomic.LoadInt64(&mine) != 0 { + build.Clock.Sleep(blocktime) + if err := sn[0].MineOne(ctx, bminer.MineReq{Done: func(bool, abi.ChainEpoch, error) { + + }}); err != nil { + t.Error(err) + } + + // 3 sealing rounds: before, during after. + if round >= 3 { + continue + } + + head, err := client.ChainHead(ctx) + assert.NoError(t, err) + + // rounds happen every 100 blocks, with a 50 block offset. + if head.Height() >= abi.ChainEpoch(round*500+50) { + round++ + pledge <- struct{}{} + + ver, err := client.StateNetworkVersion(ctx, head.Key()) + assert.NoError(t, err) + switch round { + case 1: + assert.Equal(t, network.Version6, ver) + case 2: + assert.Equal(t, network.Version7, ver) + case 3: + assert.Equal(t, network.Version8, ver) + } + } + + } + }() + + // before. + pledgeSectors(t, ctx, miner, 9, 0, pledge) + + s, err := miner.SectorsList(ctx) + require.NoError(t, err) + sort.Slice(s, func(i, j int) bool { + return s[i] < s[j] + }) + + for i, id := range s { + info, err := miner.SectorsStatus(ctx, id, true) + require.NoError(t, err) + expectProof := abi.RegisteredSealProof_StackedDrg2KiBV1 + if i >= 3 { + // after + expectProof = abi.RegisteredSealProof_StackedDrg2KiBV1_1 + } + assert.Equal(t, expectProof, info.SealProof, "sector %d, id %d", i, id) + } + + atomic.StoreInt64(&mine, 0) + <-done +} + +func TestPledgeBatching(t *testing.T, b APIBuilder, blocktime time.Duration, nSectors int) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + n, sn := b(t, []FullNodeOpts{FullNodeWithLatestActorsAt(-1)}, OneMiner) + client := n[0].FullNode.(*impl.FullNodeAPI) + miner := sn[0] + + addrinfo, err := client.NetAddrsListen(ctx) + if err != nil { + t.Fatal(err) + } + + if err := miner.NetConnect(ctx, addrinfo); err != nil { + t.Fatal(err) + } + build.Clock.Sleep(time.Second) + + mine := int64(1) + done := make(chan struct{}) + go func() { + defer close(done) + for atomic.LoadInt64(&mine) != 0 { + build.Clock.Sleep(blocktime) + if err := sn[0].MineOne(ctx, bminer.MineReq{Done: func(bool, abi.ChainEpoch, error) { + + }}); err != nil { + t.Error(err) + } + } + }() + + for { + h, err := client.ChainHead(ctx) + require.NoError(t, err) + if h.Height() > 10 { + break + } + } + + pledgeSectors(t, ctx, miner, nSectors, 0, nil) + + atomic.StoreInt64(&mine, 0) + <-done +} + +func TestPledgeSector(t *testing.T, b APIBuilder, blocktime time.Duration, nSectors int) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + n, sn := b(t, OneFull, OneMiner) + client := n[0].FullNode.(*impl.FullNodeAPI) + miner := sn[0] + + addrinfo, err := client.NetAddrsListen(ctx) + if err != nil { + t.Fatal(err) + } + + if err := miner.NetConnect(ctx, addrinfo); err != nil { + t.Fatal(err) + } + build.Clock.Sleep(time.Second) + + mine := int64(1) + done := make(chan struct{}) + go func() { + defer close(done) + for atomic.LoadInt64(&mine) != 0 { + build.Clock.Sleep(blocktime) + if err := sn[0].MineOne(ctx, bminer.MineReq{Done: func(bool, abi.ChainEpoch, error) { + + }}); err != nil { + t.Error(err) + } + } + }() + + pledgeSectors(t, ctx, miner, nSectors, 0, nil) + + atomic.StoreInt64(&mine, 0) + <-done +} + +func flushSealingBatches(t *testing.T, ctx context.Context, miner TestStorageNode) { + pcb, err := miner.SectorPreCommitFlush(ctx) + require.NoError(t, err) + if pcb != nil { + fmt.Printf("PRECOMMIT BATCH: %s\n", *pcb) + } + + cb, err := miner.SectorCommitFlush(ctx) + require.NoError(t, err) + if cb != nil { + fmt.Printf("COMMIT BATCH: %s\n", *cb) + } +} + +func pledgeSectors(t *testing.T, ctx context.Context, miner TestStorageNode, n, existing int, blockNotif <-chan struct{}) { + for i := 0; i < n; i++ { + if i%3 == 0 && blockNotif != nil { + <-blockNotif + log.Errorf("WAIT") + } + log.Errorf("PLEDGING %d", i) + _, err := miner.PledgeSector(ctx) + require.NoError(t, err) + } + + for { + s, err := miner.SectorsList(ctx) // Note - the test builder doesn't import genesis sectors into FSM + require.NoError(t, err) + fmt.Printf("Sectors: %d\n", len(s)) + if len(s) >= n+existing { + break + } + + build.Clock.Sleep(100 * time.Millisecond) + } + + fmt.Printf("All sectors is fsm\n") + + s, err := miner.SectorsList(ctx) + require.NoError(t, err) + + toCheck := map[abi.SectorNumber]struct{}{} + for _, number := range s { + toCheck[number] = struct{}{} + } + + for len(toCheck) > 0 { + flushSealingBatches(t, ctx, miner) + + states := map[api.SectorState]int{} + for n := range toCheck { + st, err := miner.SectorsStatus(ctx, n, false) + require.NoError(t, err) + states[st.State]++ + if st.State == api.SectorState(sealing.Proving) { + delete(toCheck, n) + } + if strings.Contains(string(st.State), "Fail") { + t.Fatal("sector in a failed state", st.State) + } + } + + build.Clock.Sleep(100 * time.Millisecond) + fmt.Printf("WaitSeal: %d %+v\n", len(s), states) + } +} diff --git a/api/test/window_post.go b/api/test/window_post.go index 04e709b86..128a22641 100644 --- a/api/test/window_post.go +++ b/api/test/window_post.go @@ -3,14 +3,9 @@ package test import ( "context" "fmt" - "sort" - "sync/atomic" - - "strings" "testing" "time" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/filecoin-project/go-address" @@ -18,7 +13,6 @@ import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/go-state-types/dline" - "github.com/filecoin-project/go-state-types/network" "github.com/filecoin-project/lotus/extern/sector-storage/mock" sealing "github.com/filecoin-project/lotus/extern/storage-sealing" proof3 "github.com/filecoin-project/specs-actors/v3/actors/runtime/proof" @@ -29,245 +23,9 @@ import ( "github.com/filecoin-project/lotus/chain/actors" minerActor "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/types" - bminer "github.com/filecoin-project/lotus/miner" "github.com/filecoin-project/lotus/node/impl" ) -func TestSDRUpgrade(t *testing.T, b APIBuilder, blocktime time.Duration) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - n, sn := b(t, []FullNodeOpts{FullNodeWithSDRAt(500, 1000)}, OneMiner) - client := n[0].FullNode.(*impl.FullNodeAPI) - miner := sn[0] - - addrinfo, err := client.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - - if err := miner.NetConnect(ctx, addrinfo); err != nil { - t.Fatal(err) - } - build.Clock.Sleep(time.Second) - - pledge := make(chan struct{}) - mine := int64(1) - done := make(chan struct{}) - go func() { - defer close(done) - round := 0 - for atomic.LoadInt64(&mine) != 0 { - build.Clock.Sleep(blocktime) - if err := sn[0].MineOne(ctx, bminer.MineReq{Done: func(bool, abi.ChainEpoch, error) { - - }}); err != nil { - t.Error(err) - } - - // 3 sealing rounds: before, during after. - if round >= 3 { - continue - } - - head, err := client.ChainHead(ctx) - assert.NoError(t, err) - - // rounds happen every 100 blocks, with a 50 block offset. - if head.Height() >= abi.ChainEpoch(round*500+50) { - round++ - pledge <- struct{}{} - - ver, err := client.StateNetworkVersion(ctx, head.Key()) - assert.NoError(t, err) - switch round { - case 1: - assert.Equal(t, network.Version6, ver) - case 2: - assert.Equal(t, network.Version7, ver) - case 3: - assert.Equal(t, network.Version8, ver) - } - } - - } - }() - - // before. - pledgeSectors(t, ctx, miner, 9, 0, pledge) - - s, err := miner.SectorsList(ctx) - require.NoError(t, err) - sort.Slice(s, func(i, j int) bool { - return s[i] < s[j] - }) - - for i, id := range s { - info, err := miner.SectorsStatus(ctx, id, true) - require.NoError(t, err) - expectProof := abi.RegisteredSealProof_StackedDrg2KiBV1 - if i >= 3 { - // after - expectProof = abi.RegisteredSealProof_StackedDrg2KiBV1_1 - } - assert.Equal(t, expectProof, info.SealProof, "sector %d, id %d", i, id) - } - - atomic.StoreInt64(&mine, 0) - <-done -} - -func TestPledgeBatching(t *testing.T, b APIBuilder, blocktime time.Duration, nSectors int) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - n, sn := b(t, []FullNodeOpts{FullNodeWithLatestActorsAt(-1)}, OneMiner) - client := n[0].FullNode.(*impl.FullNodeAPI) - miner := sn[0] - - addrinfo, err := client.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - - if err := miner.NetConnect(ctx, addrinfo); err != nil { - t.Fatal(err) - } - build.Clock.Sleep(time.Second) - - mine := int64(1) - done := make(chan struct{}) - go func() { - defer close(done) - for atomic.LoadInt64(&mine) != 0 { - build.Clock.Sleep(blocktime) - if err := sn[0].MineOne(ctx, bminer.MineReq{Done: func(bool, abi.ChainEpoch, error) { - - }}); err != nil { - t.Error(err) - } - } - }() - - for { - h, err := client.ChainHead(ctx) - require.NoError(t, err) - if h.Height() > 10 { - break - } - } - - pledgeSectors(t, ctx, miner, nSectors, 0, nil) - - atomic.StoreInt64(&mine, 0) - <-done -} - -func TestPledgeSector(t *testing.T, b APIBuilder, blocktime time.Duration, nSectors int) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - n, sn := b(t, OneFull, OneMiner) - client := n[0].FullNode.(*impl.FullNodeAPI) - miner := sn[0] - - addrinfo, err := client.NetAddrsListen(ctx) - if err != nil { - t.Fatal(err) - } - - if err := miner.NetConnect(ctx, addrinfo); err != nil { - t.Fatal(err) - } - build.Clock.Sleep(time.Second) - - mine := int64(1) - done := make(chan struct{}) - go func() { - defer close(done) - for atomic.LoadInt64(&mine) != 0 { - build.Clock.Sleep(blocktime) - if err := sn[0].MineOne(ctx, bminer.MineReq{Done: func(bool, abi.ChainEpoch, error) { - - }}); err != nil { - t.Error(err) - } - } - }() - - pledgeSectors(t, ctx, miner, nSectors, 0, nil) - - atomic.StoreInt64(&mine, 0) - <-done -} - -func flushSealingBatches(t *testing.T, ctx context.Context, miner TestStorageNode) { - pcb, err := miner.SectorPreCommitFlush(ctx) - require.NoError(t, err) - if pcb != nil { - fmt.Printf("PRECOMMIT BATCH: %s\n", *pcb) - } - - cb, err := miner.SectorCommitFlush(ctx) - require.NoError(t, err) - if cb != nil { - fmt.Printf("COMMIT BATCH: %s\n", *cb) - } -} - -func pledgeSectors(t *testing.T, ctx context.Context, miner TestStorageNode, n, existing int, blockNotif <-chan struct{}) { - for i := 0; i < n; i++ { - if i%3 == 0 && blockNotif != nil { - <-blockNotif - log.Errorf("WAIT") - } - log.Errorf("PLEDGING %d", i) - _, err := miner.PledgeSector(ctx) - require.NoError(t, err) - } - - for { - s, err := miner.SectorsList(ctx) // Note - the test builder doesn't import genesis sectors into FSM - require.NoError(t, err) - fmt.Printf("Sectors: %d\n", len(s)) - if len(s) >= n+existing { - break - } - - build.Clock.Sleep(100 * time.Millisecond) - } - - fmt.Printf("All sectors is fsm\n") - - s, err := miner.SectorsList(ctx) - require.NoError(t, err) - - toCheck := map[abi.SectorNumber]struct{}{} - for _, number := range s { - toCheck[number] = struct{}{} - } - - for len(toCheck) > 0 { - flushSealingBatches(t, ctx, miner) - - states := map[api.SectorState]int{} - for n := range toCheck { - st, err := miner.SectorsStatus(ctx, n, false) - require.NoError(t, err) - states[st.State]++ - if st.State == api.SectorState(sealing.Proving) { - delete(toCheck, n) - } - if strings.Contains(string(st.State), "Fail") { - t.Fatal("sector in a failed state", st.State) - } - } - - build.Clock.Sleep(100 * time.Millisecond) - fmt.Printf("WaitSeal: %d %+v\n", len(s), states) - } -} - func TestWindowPost(t *testing.T, b APIBuilder, blocktime time.Duration, nSectors int) { for _, height := range []abi.ChainEpoch{ -1, // before diff --git a/documentation/misc/actors_version_checklist.md b/documentation/misc/actors_version_checklist.md index 6c06441ab..7931acbcc 100644 --- a/documentation/misc/actors_version_checklist.md +++ b/documentation/misc/actors_version_checklist.md @@ -8,7 +8,7 @@ - [ ] Register in `chain/vm/invoker.go` - [ ] Register in `chain/vm/mkactor.go` - [ ] Update `chain/types/state.go` -- [ ] Update `chain/state/statetree.go` +- [ ] Update `chain/state/statetree.go` (New / Load) - [ ] Update `chain/stmgr/forks.go` - [ ] Schedule - [ ] Migration diff --git a/extern/storage-sealing/states_sealing.go b/extern/storage-sealing/states_sealing.go index bec9ad51b..391951dea 100644 --- a/extern/storage-sealing/states_sealing.go +++ b/extern/storage-sealing/states_sealing.go @@ -3,6 +3,7 @@ package sealing import ( "bytes" "context" + "github.com/ipfs/go-cid" "golang.org/x/xerrors" @@ -318,7 +319,7 @@ func (m *Sealing) handlePreCommitting(ctx statemachine.Context, sector SectorInf } params, deposit, tok, err := m.preCommitParams(ctx, sector) - if err != nil { + if params == nil || err != nil { return err } @@ -358,7 +359,7 @@ func (m *Sealing) handleSubmitPreCommitBatch(ctx statemachine.Context, sector Se } params, deposit, _, err := m.preCommitParams(ctx, sector) - if err != nil { + if params == nil || err != nil { return err } diff --git a/storage/wdpost_run_test.go b/storage/wdpost_run_test.go index 2e412880a..5117e718a 100644 --- a/storage/wdpost_run_test.go +++ b/storage/wdpost_run_test.go @@ -145,10 +145,6 @@ func (m mockVerif) VerifyWindowPoSt(ctx context.Context, info proof2.WindowPoStV return true, nil } -func (m mockVerif) AggregateSealProofs(proofType abi.RegisteredSealProof, rap abi.RegisteredAggregationProof, proofs [][]byte) ([]byte, error) { - panic("implement me") -} - func (m mockVerif) VerifyAggregateSeals(aggregate proof5.AggregateSealVerifyProofAndInfos) (bool, error) { panic("implement me") } From f3fda4ae7f53ea44cae3bfb17cd8f5753727e961 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 20 May 2021 18:22:55 +0200 Subject: [PATCH 38/88] Better asserts in TestPledgeBatching --- api/test/pledge.go | 48 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/api/test/pledge.go b/api/test/pledge.go index 49df97ea8..202b24515 100644 --- a/api/test/pledge.go +++ b/api/test/pledge.go @@ -146,7 +146,43 @@ func TestPledgeBatching(t *testing.T, b APIBuilder, blocktime time.Duration, nSe } } - pledgeSectors(t, ctx, miner, nSectors, 0, nil) + toCheck := startPledge(t, ctx, miner, nSectors, 0, nil) + + for len(toCheck) > 0 { + states := map[api.SectorState]int{} + + for n := range toCheck { + st, err := miner.SectorsStatus(ctx, n, false) + require.NoError(t, err) + states[st.State]++ + if st.State == api.SectorState(sealing.Proving) { + delete(toCheck, n) + } + if strings.Contains(string(st.State), "Fail") { + t.Fatal("sector in a failed state", st.State) + } + } + if states[api.SectorState(sealing.SubmitPreCommitBatch)] == nSectors || + (states[api.SectorState(sealing.SubmitPreCommitBatch)] > 0 && states[api.SectorState(sealing.PreCommit1)] == 0 && states[api.SectorState(sealing.PreCommit2)] == 0) { + pcb, err := miner.SectorPreCommitFlush(ctx) + require.NoError(t, err) + if pcb != nil { + fmt.Printf("PRECOMMIT BATCH: %s\n", *pcb) + } + } + + if states[api.SectorState(sealing.SubmitCommitAggregate)] == nSectors || + (states[api.SectorState(sealing.SubmitCommitAggregate)] > 0 && states[api.SectorState(sealing.WaitSeed)] == 0 && states[api.SectorState(sealing.Committing)] == 0) { + cb, err := miner.SectorCommitFlush(ctx) + require.NoError(t, err) + if cb != nil { + fmt.Printf("COMMIT BATCH: %s\n", *cb) + } + } + + build.Clock.Sleep(100 * time.Millisecond) + fmt.Printf("WaitSeal: %d %+v\n", len(toCheck), states) + } atomic.StoreInt64(&mine, 0) <-done @@ -204,7 +240,7 @@ func flushSealingBatches(t *testing.T, ctx context.Context, miner TestStorageNod } } -func pledgeSectors(t *testing.T, ctx context.Context, miner TestStorageNode, n, existing int, blockNotif <-chan struct{}) { +func startPledge(t *testing.T, ctx context.Context, miner TestStorageNode, n, existing int, blockNotif <-chan struct{}) map[abi.SectorNumber]struct{} { for i := 0; i < n; i++ { if i%3 == 0 && blockNotif != nil { <-blockNotif @@ -236,6 +272,12 @@ func pledgeSectors(t *testing.T, ctx context.Context, miner TestStorageNode, n, toCheck[number] = struct{}{} } + return toCheck +} + +func pledgeSectors(t *testing.T, ctx context.Context, miner TestStorageNode, n, existing int, blockNotif <-chan struct{}) { + toCheck := startPledge(t, ctx, miner, n, existing, blockNotif) + for len(toCheck) > 0 { flushSealingBatches(t, ctx, miner) @@ -253,6 +295,6 @@ func pledgeSectors(t *testing.T, ctx context.Context, miner TestStorageNode, n, } build.Clock.Sleep(100 * time.Millisecond) - fmt.Printf("WaitSeal: %d %+v\n", len(s), states) + fmt.Printf("WaitSeal: %d %+v\n", len(toCheck), states) } } From 13ff6ed462b7cf6f316f0345dec841215ec2c23b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 20 May 2021 18:38:33 +0200 Subject: [PATCH 39/88] Test pledging before nv13 upgrade --- api/test/pledge.go | 89 ++++++++++++++++++++++++++++++++++++++++++++++ node/node_test.go | 3 ++ 2 files changed, 92 insertions(+) diff --git a/api/test/pledge.go b/api/test/pledge.go index 202b24515..8752ad4ac 100644 --- a/api/test/pledge.go +++ b/api/test/pledge.go @@ -17,8 +17,10 @@ import ( "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/stmgr" sealing "github.com/filecoin-project/lotus/extern/storage-sealing" bminer "github.com/filecoin-project/lotus/miner" + "github.com/filecoin-project/lotus/node" "github.com/filecoin-project/lotus/node/impl" ) @@ -188,6 +190,93 @@ func TestPledgeBatching(t *testing.T, b APIBuilder, blocktime time.Duration, nSe <-done } +func TestPledgeBeforeNv13(t *testing.T, b APIBuilder, blocktime time.Duration, nSectors int) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + n, sn := b(t, []FullNodeOpts{ + { + Opts: func(nodes []TestNode) node.Option { + return node.Override(new(stmgr.UpgradeSchedule), stmgr.UpgradeSchedule{{ + Network: network.Version9, + Height: 1, + Migration: stmgr.UpgradeActorsV2, + }, { + Network: network.Version10, + Height: 2, + Migration: stmgr.UpgradeActorsV3, + }, { + Network: network.Version12, + Height: 3, + Migration: stmgr.UpgradeActorsV4, + }, { + Network: network.Version13, + Height: 1000000000, + Migration: stmgr.UpgradeActorsV5, + }}) + }, + }, + }, OneMiner) + client := n[0].FullNode.(*impl.FullNodeAPI) + miner := sn[0] + + addrinfo, err := client.NetAddrsListen(ctx) + if err != nil { + t.Fatal(err) + } + + if err := miner.NetConnect(ctx, addrinfo); err != nil { + t.Fatal(err) + } + build.Clock.Sleep(time.Second) + + mine := int64(1) + done := make(chan struct{}) + go func() { + defer close(done) + for atomic.LoadInt64(&mine) != 0 { + build.Clock.Sleep(blocktime) + if err := sn[0].MineOne(ctx, bminer.MineReq{Done: func(bool, abi.ChainEpoch, error) { + + }}); err != nil { + t.Error(err) + } + } + }() + + for { + h, err := client.ChainHead(ctx) + require.NoError(t, err) + if h.Height() > 10 { + break + } + } + + toCheck := startPledge(t, ctx, miner, nSectors, 0, nil) + + for len(toCheck) > 0 { + states := map[api.SectorState]int{} + + for n := range toCheck { + st, err := miner.SectorsStatus(ctx, n, false) + require.NoError(t, err) + states[st.State]++ + if st.State == api.SectorState(sealing.Proving) { + delete(toCheck, n) + } + if strings.Contains(string(st.State), "Fail") { + t.Fatal("sector in a failed state", st.State) + } + } + + build.Clock.Sleep(100 * time.Millisecond) + fmt.Printf("WaitSeal: %d %+v\n", len(toCheck), states) + } + + atomic.StoreInt64(&mine, 0) + <-done +} + func TestPledgeSector(t *testing.T, b APIBuilder, blocktime time.Duration, nSectors int) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/node/node_test.go b/node/node_test.go index 5db7e355f..1cc089b1e 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -152,6 +152,9 @@ func TestPledgeBatching(t *testing.T) { t.Run("100", func(t *testing.T) { test.TestPledgeBatching(t, builder.MockSbBuilder, 50*time.Millisecond, 100) }) + t.Run("100-before-nv13", func(t *testing.T) { + test.TestPledgeBeforeNv13(t, builder.MockSbBuilder, 50*time.Millisecond, 100) + }) } func TestTapeFix(t *testing.T) { From e3255a06ea2a2d844c0053121c4286a87159f8db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 20 May 2021 18:39:59 +0200 Subject: [PATCH 40/88] sealing: Add missing states to SubmitPreCommitBatch planner --- extern/storage-sealing/fsm.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/extern/storage-sealing/fsm.go b/extern/storage-sealing/fsm.go index d249685b0..594f9f2f5 100644 --- a/extern/storage-sealing/fsm.go +++ b/extern/storage-sealing/fsm.go @@ -72,8 +72,8 @@ var fsmPlanners = map[SectorState]func(events []statemachine.Event, state *Secto ), PreCommitting: planOne( on(SectorPreCommitBatch{}, SubmitPreCommitBatch), - on(SectorSealPreCommit1Failed{}, SealPreCommit1Failed), on(SectorPreCommitted{}, PreCommitWait), + on(SectorSealPreCommit1Failed{}, SealPreCommit1Failed), on(SectorChainPreCommitFailed{}, PreCommitFailed), on(SectorPreCommitLanded{}, WaitSeed), on(SectorDealsExpired{}, DealsExpired), @@ -81,6 +81,11 @@ var fsmPlanners = map[SectorState]func(events []statemachine.Event, state *Secto ), SubmitPreCommitBatch: planOne( on(SectorPreCommitBatchSent{}, PreCommitBatchWait), + on(SectorSealPreCommit1Failed{}, SealPreCommit1Failed), + on(SectorChainPreCommitFailed{}, PreCommitFailed), + on(SectorPreCommitLanded{}, WaitSeed), + on(SectorDealsExpired{}, DealsExpired), + on(SectorInvalidDealIDs{}, RecoverDealIDs), ), PreCommitBatchWait: planOne( on(SectorChainPreCommitFailed{}, PreCommitFailed), @@ -99,8 +104,8 @@ var fsmPlanners = map[SectorState]func(events []statemachine.Event, state *Secto Committing: planCommitting, SubmitCommit: planOne( on(SectorCommitSubmitted{}, CommitWait), - on(SectorCommitFailed{}, CommitFailed), on(SectorSubmitCommitAggregate{}, SubmitCommitAggregate), + on(SectorCommitFailed{}, CommitFailed), ), SubmitCommitAggregate: planOne( on(SectorCommitAggregateSent{}, CommitWait), From f5409845b574eab91b356ac97fda920ffa3d9b98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 25 May 2021 16:07:45 +0200 Subject: [PATCH 41/88] Some review addressing --- api/api_storage.go | 7 +- build/openrpc/miner.json.gz | Bin 7952 -> 7988 bytes chain/vm/gas.go | 4 +- chain/vm/gas_v0.go | 3 +- cmd/lotus-shed/cron-count.go | 99 ------------- cmd/lotus-shed/main.go | 1 - documentation/en/api-methods-miner.md | 3 + extern/sector-storage/mock/mock.go | 2 - .../storageadapter/ondealsectorcommitted.go | 131 ++++++++++-------- 9 files changed, 86 insertions(+), 164 deletions(-) delete mode 100644 cmd/lotus-shed/cron-count.go diff --git a/api/api_storage.go b/api/api_storage.go index f9a5ccdd8..202b40f93 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -85,8 +85,11 @@ type StorageMiner interface { SectorPreCommitFlush(ctx context.Context) (*cid.Cid, error) //perm:admin // SectorPreCommitPending returns a list of pending PreCommit sectors to be sent in the next batch message SectorPreCommitPending(ctx context.Context) ([]abi.SectorID, error) //perm:admin - SectorCommitFlush(ctx context.Context) (*cid.Cid, error) //perm:admin - SectorCommitPending(ctx context.Context) ([]abi.SectorID, error) //perm:admin + // SectorCommitFlush immediately sends a Commit message with sectors aggregated for Commit. + // Returns null if message wasn't sent + SectorCommitFlush(ctx context.Context) (*cid.Cid, error) //perm:admin + // SectorCommitPending returns a list of pending Commit sectors to be sent in the next aggregate message + SectorCommitPending(ctx context.Context) ([]abi.SectorID, error) //perm:admin // WorkerConnect tells the node to connect to workers RPC WorkerConnect(context.Context, string) error //perm:admin retry:true diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index 75dd3a2a0e7257a3b6de8dd031de46c68a3e691d..1ddc27f12930edce042af42f23c095dc7e9e6964 100644 GIT binary patch delta 3281 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 P00960{(w}jgN^|Jx?OLq delta 3245 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 diff --git a/chain/vm/gas.go b/chain/vm/gas.go index 5ecc42345..67dd7677e 100644 --- a/chain/vm/gas.go +++ b/chain/vm/gas.go @@ -75,7 +75,7 @@ type Pricelist interface { OnHashing(dataSize int) GasCharge OnComputeUnsealedSectorCid(proofType abi.RegisteredSealProof, pieces []abi.PieceInfo) GasCharge OnVerifySeal(info proof5.SealVerifyInfo) GasCharge - OnVerifyAggregateSeals() GasCharge + OnVerifyAggregateSeals(aggregate proof5.AggregateSealVerifyProofAndInfos) GasCharge OnVerifyPost(info proof5.WindowPoStVerifyInfo) GasCharge OnVerifyConsensusFault() GasCharge } @@ -282,7 +282,7 @@ func (ps pricedSyscalls) BatchVerifySeals(inp map[address.Address][]proof5.SealV } func (ps pricedSyscalls) VerifyAggregateSeals(aggregate proof5.AggregateSealVerifyProofAndInfos) error { - ps.chargeGas(ps.pl.OnVerifyAggregateSeals()) + ps.chargeGas(ps.pl.OnVerifyAggregateSeals(aggregate)) defer ps.chargeGas(gasOnActorExec) return ps.under.VerifyAggregateSeals(aggregate) diff --git a/chain/vm/gas_v0.go b/chain/vm/gas_v0.go index d54760b69..c90ebaa73 100644 --- a/chain/vm/gas_v0.go +++ b/chain/vm/gas_v0.go @@ -4,6 +4,7 @@ import ( "fmt" proof2 "github.com/filecoin-project/specs-actors/v2/actors/runtime/proof" + proof5 "github.com/filecoin-project/specs-actors/v5/actors/runtime/proof" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" @@ -187,7 +188,7 @@ func (pl *pricelistV0) OnVerifySeal(info proof2.SealVerifyInfo) GasCharge { } // OnVerifyAggregateSeals -func (pl *pricelistV0) OnVerifyAggregateSeals() GasCharge { +func (pl *pricelistV0) OnVerifyAggregateSeals(aggregate proof5.AggregateSealVerifyProofAndInfos) GasCharge { // TODO: this needs more cost tunning return newGasCharge("OnVerifyAggregateSeals", pl.verifyAggregateSealBase, 0) } diff --git a/cmd/lotus-shed/cron-count.go b/cmd/lotus-shed/cron-count.go deleted file mode 100644 index 622f38791..000000000 --- a/cmd/lotus-shed/cron-count.go +++ /dev/null @@ -1,99 +0,0 @@ -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.UpgradeTurboHeight+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 0e0a13a55..ebe4f014a 100644 --- a/cmd/lotus-shed/main.go +++ b/cmd/lotus-shed/main.go @@ -20,7 +20,6 @@ func main() { base32Cmd, base16Cmd, bitFieldCmd, - cronWcCmd, frozenMinersCmd, keyinfoCmd, jwtCmd, diff --git a/documentation/en/api-methods-miner.md b/documentation/en/api-methods-miner.md index 4622d3e53..1e8e7cbc5 100644 --- a/documentation/en/api-methods-miner.md +++ b/documentation/en/api-methods-miner.md @@ -1561,6 +1561,8 @@ Response: `{}` ### SectorCommitFlush +SectorCommitFlush immediately sends a Commit message with sectors aggregated for Commit. +Returns null if message wasn't sent Perms: admin @@ -1570,6 +1572,7 @@ Inputs: `null` Response: `null` ### SectorCommitPending +SectorCommitPending returns a list of pending Commit sectors to be sent in the next aggregate message Perms: admin diff --git a/extern/sector-storage/mock/mock.go b/extern/sector-storage/mock/mock.go index bdd9e14cd..eac75d176 100644 --- a/extern/sector-storage/mock/mock.go +++ b/extern/sector-storage/mock/mock.go @@ -526,7 +526,6 @@ func (m mockVerifProver) VerifyAggregateSeals(aggregate proof5.AggregateSealVeri for _, info := range aggregate.Infos { sis = append(sis, info.Number) } - fmt.Printf("VERSIS %+v\n", sis) return bytes.Equal(aggregate.Proof, out), nil } @@ -543,7 +542,6 @@ func (m mockVerifProver) AggregateSealProofs(aggregateInfo proof5.AggregateSealV for _, info := range aggregateInfo.Infos { sis = append(sis, info.Number) } - fmt.Printf("AGGSIS %+v\n", sis) return out, nil } diff --git a/markets/storageadapter/ondealsectorcommitted.go b/markets/storageadapter/ondealsectorcommitted.go index f9ae201b3..31bc0b8bf 100644 --- a/markets/storageadapter/ondealsectorcommitted.go +++ b/markets/storageadapter/ondealsectorcommitted.go @@ -146,39 +146,13 @@ func (mgr *SectorCommittedManager) OnDealSectorPreCommitted(ctx context.Context, } // Extract the message parameters - switch msg.Method { - case miner.Methods.PreCommitSector: - var params miner.SectorPreCommitInfo - if err := params.UnmarshalCBOR(bytes.NewReader(msg.Params)); err != nil { - return false, xerrors.Errorf("unmarshal pre commit: %w", err) - } + sn, err := dealSectorInPreCommitMsg(msg, res) + if err != nil { + return false, err + } - // Check through the deal IDs associated with this message - for _, did := range params.DealIDs { - if did == res.DealID { - // Found the deal ID in this message. Callback with the sector ID. - cb(params.SectorNumber, false, nil) - return false, nil - } - } - case miner.Methods.PreCommitSectorBatch: - var params miner5.PreCommitSectorBatchParams - if err := params.UnmarshalCBOR(bytes.NewReader(msg.Params)); err != nil { - return false, xerrors.Errorf("unmarshal pre commit: %w", err) - } - - for _, precommit := range params.Sectors { - // Check through the deal IDs associated with this message - for _, did := range precommit.DealIDs { - if did == res.DealID { - // Found the deal ID in this message. Callback with the sector ID. - cb(precommit.SectorNumber, false, nil) - return false, nil - } - } - } - default: - return false, xerrors.Errorf("unexpected method %d", msg.Method) + if sn != nil { + cb(*sn, false, nil) } // Didn't find the deal ID in this message, so keep looking @@ -233,31 +207,7 @@ func (mgr *SectorCommittedManager) OnDealSectorCommitted(ctx context.Context, pr return false, nil } - switch msg.Method { - case miner.Methods.ProveCommitSector: - var params miner.ProveCommitSectorParams - if err := params.UnmarshalCBOR(bytes.NewReader(msg.Params)); err != nil { - return false, xerrors.Errorf("failed to unmarshal prove commit sector params: %w", err) - } - - return params.SectorNumber == sectorNumber, nil - - case miner.Methods.ProveCommitAggregate: - var params miner5.ProveCommitAggregateParams - if err := params.UnmarshalCBOR(bytes.NewReader(msg.Params)); err != nil { - return false, xerrors.Errorf("failed to unmarshal prove commit sector params: %w", err) - } - - set, err := params.SectorNumbers.IsSet(uint64(sectorNumber)) - if err != nil { - return false, xerrors.Errorf("checking if sectorNumber is set in commit aggregate message: %w", err) - } - - return set, nil - - default: - return false, nil - } + return sectorInCommitMsg(msg, sectorNumber) } // The deal must be accepted by the deal proposal start epoch, so timeout @@ -314,6 +264,73 @@ func (mgr *SectorCommittedManager) OnDealSectorCommitted(ctx context.Context, pr return nil } +// dealSectorInPreCommitMsg tries to find a sector containing the specified deal +func dealSectorInPreCommitMsg(msg *types.Message, res sealing.CurrentDealInfo) (*abi.SectorNumber, error) { + switch msg.Method { + case miner.Methods.PreCommitSector: + var params miner.SectorPreCommitInfo + if err := params.UnmarshalCBOR(bytes.NewReader(msg.Params)); err != nil { + return nil, xerrors.Errorf("unmarshal pre commit: %w", err) + } + + // Check through the deal IDs associated with this message + for _, did := range params.DealIDs { + if did == res.DealID { + // Found the deal ID in this message. Callback with the sector ID. + return ¶ms.SectorNumber, nil + } + } + case miner.Methods.PreCommitSectorBatch: + var params miner5.PreCommitSectorBatchParams + if err := params.UnmarshalCBOR(bytes.NewReader(msg.Params)); err != nil { + return nil, xerrors.Errorf("unmarshal pre commit: %w", err) + } + + for _, precommit := range params.Sectors { + // Check through the deal IDs associated with this message + for _, did := range precommit.DealIDs { + if did == res.DealID { + // Found the deal ID in this message. Callback with the sector ID. + return &precommit.SectorNumber, nil + } + } + } + default: + return nil, xerrors.Errorf("unexpected method %d", msg.Method) + } + + return nil, nil +} + +// sectorInCommitMsg checks if the provided message commits specified sector +func sectorInCommitMsg(msg *types.Message, sectorNumber abi.SectorNumber) (bool, error) { + switch msg.Method { + case miner.Methods.ProveCommitSector: + var params miner.ProveCommitSectorParams + if err := params.UnmarshalCBOR(bytes.NewReader(msg.Params)); err != nil { + return false, xerrors.Errorf("failed to unmarshal prove commit sector params: %w", err) + } + + return params.SectorNumber == sectorNumber, nil + + case miner.Methods.ProveCommitAggregate: + var params miner5.ProveCommitAggregateParams + if err := params.UnmarshalCBOR(bytes.NewReader(msg.Params)); err != nil { + return false, xerrors.Errorf("failed to unmarshal prove commit sector params: %w", err) + } + + set, err := params.SectorNumbers.IsSet(uint64(sectorNumber)) + if err != nil { + return false, xerrors.Errorf("checking if sectorNumber is set in commit aggregate message: %w", err) + } + + return set, nil + + default: + return false, nil + } +} + func (mgr *SectorCommittedManager) checkIfDealAlreadyActive(ctx context.Context, ts *types.TipSet, proposal *market.DealProposal, publishCid cid.Cid) (sealing.CurrentDealInfo, bool, error) { res, err := mgr.dealInfo.GetCurrentDealInfo(ctx, ts.Key().Bytes(), proposal, publishCid) if err != nil { From d90ab2b81fea5ba1d5d0e91134d1f843d99f20e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 25 May 2021 16:20:49 +0200 Subject: [PATCH 42/88] config: Document batching fields --- node/config/def.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/node/config/def.go b/node/config/def.go index c0aa82a59..1c82e9b17 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -84,16 +84,24 @@ type SealingConfig struct { AlwaysKeepUnsealedCopy bool + // enable / disable precommit batching (takes effect after nv13) BatchPreCommits bool + // maximum precommit batch size - batches will be sent immediately above this size MaxPreCommitBatch int MinPreCommitBatch int + // how long to wait before submitting a batch after crossing the minimum batch size PreCommitBatchWait Duration + // time buffer for forceful batch submission before sectors in batch would start expiring PreCommitBatchSlack Duration + // enable / disable commit aggregation (takes effect after nv13) AggregateCommits bool + // maximum batched commit size - batches will be sent immediately above this size MinCommitBatch int MaxCommitBatch int + // how long to wait before submitting a batch after crossing the minimum batch size CommitBatchWait Duration + // time buffer for forceful batch submission before sectors in batch would start expiring CommitBatchSlack Duration TerminateBatchMax uint64 From 04658e1cae27781599592558925fa52a102681f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 25 May 2021 16:26:59 +0200 Subject: [PATCH 43/88] fix lint --- extern/sector-storage/mock/mock.go | 10 ---------- node/config/def.go | 14 +++++++------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/extern/sector-storage/mock/mock.go b/extern/sector-storage/mock/mock.go index eac75d176..a622f8ccb 100644 --- a/extern/sector-storage/mock/mock.go +++ b/extern/sector-storage/mock/mock.go @@ -522,11 +522,6 @@ func (m mockVerifProver) VerifyAggregateSeals(aggregate proof5.AggregateSealVeri } } - var sis []abi.SectorNumber - for _, info := range aggregate.Infos { - sis = append(sis, info.Number) - } - return bytes.Equal(aggregate.Proof, out), nil } @@ -538,11 +533,6 @@ func (m mockVerifProver) AggregateSealProofs(aggregateInfo proof5.AggregateSealV } } - var sis []abi.SectorNumber - for _, info := range aggregateInfo.Infos { - sis = append(sis, info.Number) - } - return out, nil } diff --git a/node/config/def.go b/node/config/def.go index 1c82e9b17..988b0a6c9 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -85,22 +85,22 @@ type SealingConfig struct { AlwaysKeepUnsealedCopy bool // enable / disable precommit batching (takes effect after nv13) - BatchPreCommits bool + BatchPreCommits bool // maximum precommit batch size - batches will be sent immediately above this size - MaxPreCommitBatch int - MinPreCommitBatch int + MaxPreCommitBatch int + MinPreCommitBatch int // how long to wait before submitting a batch after crossing the minimum batch size - PreCommitBatchWait Duration + PreCommitBatchWait Duration // time buffer for forceful batch submission before sectors in batch would start expiring PreCommitBatchSlack Duration // enable / disable commit aggregation (takes effect after nv13) AggregateCommits bool // maximum batched commit size - batches will be sent immediately above this size - MinCommitBatch int - MaxCommitBatch int + MinCommitBatch int + MaxCommitBatch int // how long to wait before submitting a batch after crossing the minimum batch size - CommitBatchWait Duration + CommitBatchWait Duration // time buffer for forceful batch submission before sectors in batch would start expiring CommitBatchSlack Duration From 7bd0fcbb24294ebccaaa061a001f7ce95519039c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 25 May 2021 16:47:42 +0200 Subject: [PATCH 44/88] sealing: Don't start batch timers with empty batches --- extern/storage-sealing/commit_batch.go | 14 +++++++++----- extern/storage-sealing/precommit_batch.go | 14 +++++++++----- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go index 53f572712..88c8bdf37 100644 --- a/extern/storage-sealing/commit_batch.go +++ b/extern/storage-sealing/commit_batch.go @@ -105,7 +105,7 @@ func (b *CommitBatcher) run() { return case <-b.notify: sendAboveMax = true - case <-time.After(b.batchWait(cfg.CommitBatchWait, cfg.CommitBatchSlack)): + case <-b.batchWait(cfg.CommitBatchWait, cfg.CommitBatchSlack): sendAboveMin = true case fr := <-b.force: // user triggered forceRes = fr @@ -119,12 +119,16 @@ func (b *CommitBatcher) run() { } } -func (b *CommitBatcher) batchWait(maxWait, slack time.Duration) time.Duration { +func (b *CommitBatcher) batchWait(maxWait, slack time.Duration) <-chan time.Time { now := time.Now() b.lk.Lock() defer b.lk.Unlock() + if len(b.todo) == 0 { + return nil + } + var deadline time.Time for sn := range b.todo { sectorDeadline := b.deadlines[sn] @@ -140,12 +144,12 @@ func (b *CommitBatcher) batchWait(maxWait, slack time.Duration) time.Duration { } if deadline.IsZero() { - return maxWait + return time.After(maxWait) } deadline = deadline.Add(-slack) if deadline.Before(now) { - return time.Nanosecond // can't return 0 + return time.After(time.Nanosecond) // can't return 0 } wait := deadline.Sub(now) @@ -153,7 +157,7 @@ func (b *CommitBatcher) batchWait(maxWait, slack time.Duration) time.Duration { wait = maxWait } - return wait + return time.After(wait) } func (b *CommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { diff --git a/extern/storage-sealing/precommit_batch.go b/extern/storage-sealing/precommit_batch.go index f16e5c5ee..69ba47c60 100644 --- a/extern/storage-sealing/precommit_batch.go +++ b/extern/storage-sealing/precommit_batch.go @@ -95,7 +95,7 @@ func (b *PreCommitBatcher) run() { return case <-b.notify: sendAboveMax = true - case <-time.After(b.batchWait(cfg.PreCommitBatchWait, cfg.PreCommitBatchSlack)): + case <-b.batchWait(cfg.PreCommitBatchWait, cfg.PreCommitBatchSlack): sendAboveMin = true case fr := <-b.force: // user triggered forceRes = fr @@ -109,12 +109,16 @@ func (b *PreCommitBatcher) run() { } } -func (b *PreCommitBatcher) batchWait(maxWait, slack time.Duration) time.Duration { +func (b *PreCommitBatcher) batchWait(maxWait, slack time.Duration) <-chan time.Time { now := time.Now() b.lk.Lock() defer b.lk.Unlock() + if len(b.todo) == 0 { + return nil + } + var deadline time.Time for sn := range b.todo { sectorDeadline := b.deadlines[sn] @@ -130,12 +134,12 @@ func (b *PreCommitBatcher) batchWait(maxWait, slack time.Duration) time.Duration } if deadline.IsZero() { - return maxWait + return time.After(maxWait) } deadline = deadline.Add(-slack) if deadline.Before(now) { - return time.Nanosecond // can't return 0 + return time.After(time.Nanosecond) // can't return 0 } wait := deadline.Sub(now) @@ -143,7 +147,7 @@ func (b *PreCommitBatcher) batchWait(maxWait, slack time.Duration) time.Duration wait = maxWait } - return wait + return time.After(wait) } func (b *PreCommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { From 4c6c9a0edbd9ac9162e08c24ec1b26c198b82cca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 25 May 2021 21:27:32 +0200 Subject: [PATCH 45/88] Fix windowpost/deadline tests --- api/test/deadlines.go | 2 +- api/test/test.go | 25 +++++++++++++++++++++++++ api/test/window_post.go | 4 ++-- node/node_test.go | 4 ++++ 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/api/test/deadlines.go b/api/test/deadlines.go index 8060c18f3..68186f0e1 100644 --- a/api/test/deadlines.go +++ b/api/test/deadlines.go @@ -61,7 +61,7 @@ func TestDeadlineToggling(t *testing.T, b APIBuilder, blocktime time.Duration) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - n, sn := b(t, []FullNodeOpts{FullNodeWithLatestActorsAt(upgradeH)}, OneMiner) + n, sn := b(t, []FullNodeOpts{FullNodeWithV4ActorsAt(upgradeH)}, OneMiner) client := n[0].FullNode.(*impl.FullNodeAPI) minerA := sn[0] diff --git a/api/test/test.go b/api/test/test.go index 44530191b..c064ca6fb 100644 --- a/api/test/test.go +++ b/api/test/test.go @@ -169,6 +169,31 @@ var FullNodeWithSDRAt = func(calico, persian abi.ChainEpoch) FullNodeOpts { } } +var FullNodeWithV4ActorsAt = func(upgradeHeight abi.ChainEpoch) FullNodeOpts { + if upgradeHeight == -1 { + upgradeHeight = 3 + } + + return FullNodeOpts{ + Opts: func(nodes []TestNode) node.Option { + return node.Override(new(stmgr.UpgradeSchedule), stmgr.UpgradeSchedule{{ + // prepare for upgrade. + Network: network.Version9, + Height: 1, + Migration: stmgr.UpgradeActorsV2, + }, { + Network: network.Version10, + Height: 2, + Migration: stmgr.UpgradeActorsV3, + }, { + Network: network.Version12, + Height: upgradeHeight, + Migration: stmgr.UpgradeActorsV4, + }}) + }, + } +} + var MineNext = miner.MineReq{ InjectNulls: 0, Done: func(bool, abi.ChainEpoch, error) {}, diff --git a/api/test/window_post.go b/api/test/window_post.go index 128a22641..e6e3ff2a3 100644 --- a/api/test/window_post.go +++ b/api/test/window_post.go @@ -439,7 +439,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{ - FullNodeWithLatestActorsAt(-1), + FullNodeWithV4ActorsAt(-1), }, []StorageMiner{ {Full: 0, Preseal: PresealGenesis}, {Full: 0}, @@ -722,7 +722,7 @@ func TestWindowPostDisputeFails(t *testing.T, b APIBuilder, blocktime time.Durat ctx, cancel := context.WithCancel(context.Background()) defer cancel() - n, sn := b(t, []FullNodeOpts{FullNodeWithLatestActorsAt(-1)}, OneMiner) + n, sn := b(t, []FullNodeOpts{FullNodeWithV4ActorsAt(-1)}, OneMiner) client := n[0].FullNode.(*impl.FullNodeAPI) miner := sn[0] diff --git a/node/node_test.go b/node/node_test.go index 1cc089b1e..5ae15fd8c 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -173,6 +173,7 @@ func TestWindowedPost(t *testing.T) { } logging.SetLogLevel("miner", "ERROR") + logging.SetLogLevel("gen", "ERROR") logging.SetLogLevel("chainstore", "ERROR") logging.SetLogLevel("chain", "ERROR") logging.SetLogLevel("sub", "ERROR") @@ -221,6 +222,7 @@ func TestWindowPostDispute(t *testing.T) { t.Skip("this takes a few minutes, set LOTUS_TEST_WINDOW_POST=1 to run") } logging.SetLogLevel("miner", "ERROR") + logging.SetLogLevel("gen", "ERROR") logging.SetLogLevel("chainstore", "ERROR") logging.SetLogLevel("chain", "ERROR") logging.SetLogLevel("sub", "ERROR") @@ -234,6 +236,7 @@ func TestWindowPostDisputeFails(t *testing.T) { t.Skip("this takes a few minutes, set LOTUS_TEST_WINDOW_POST=1 to run") } logging.SetLogLevel("miner", "ERROR") + logging.SetLogLevel("gen", "ERROR") logging.SetLogLevel("chainstore", "ERROR") logging.SetLogLevel("chain", "ERROR") logging.SetLogLevel("sub", "ERROR") @@ -247,6 +250,7 @@ func TestDeadlineToggling(t *testing.T) { t.Skip("this takes a few minutes, set LOTUS_TEST_DEADLINE_TOGGLING=1 to run") } logging.SetLogLevel("miner", "ERROR") + logging.SetLogLevel("gen", "ERROR") logging.SetLogLevel("chainstore", "ERROR") logging.SetLogLevel("chain", "ERROR") logging.SetLogLevel("sub", "ERROR") From fc76a09b192b208b927ae4fe0dad9f0963d44d9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 26 May 2021 09:58:57 +0200 Subject: [PATCH 46/88] mock: Use real aggregate lengths --- extern/sector-storage/mock/mock.go | 33 ++++++++++++++++++++++++-- extern/storage-sealing/commit_batch.go | 3 +-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/extern/sector-storage/mock/mock.go b/extern/sector-storage/mock/mock.go index a622f8ccb..52496f836 100644 --- a/extern/sector-storage/mock/mock.go +++ b/extern/sector-storage/mock/mock.go @@ -512,7 +512,7 @@ func (m mockVerifProver) VerifySeal(svi proof5.SealVerifyInfo) (bool, error) { } func (m mockVerifProver) VerifyAggregateSeals(aggregate proof5.AggregateSealVerifyProofAndInfos) (bool, error) { - out := make([]byte, 200) + out := make([]byte, m.aggLen(len(aggregate.Infos))) for pi, svi := range aggregate.Infos { for i := 0; i < 32; i++ { b := svi.UnsealedCID.Bytes()[i] + svi.SealedCID.Bytes()[31-i] - svi.InteractiveRandomness[i]*svi.Randomness[i] // raw proof byte @@ -526,7 +526,7 @@ func (m mockVerifProver) VerifyAggregateSeals(aggregate proof5.AggregateSealVeri } func (m mockVerifProver) AggregateSealProofs(aggregateInfo proof5.AggregateSealVerifyProofAndInfos, proofs [][]byte) ([]byte, error) { - out := make([]byte, 200) // todo: figure out more real length + out := make([]byte, m.aggLen(len(aggregateInfo.Infos))) // todo: figure out more real length for pi, proof := range proofs { for i := range proof[:32] { out[i] += proof[i] * uint8(pi) @@ -536,6 +536,35 @@ func (m mockVerifProver) AggregateSealProofs(aggregateInfo proof5.AggregateSealV return out, nil } +func (m mockVerifProver) aggLen(nproofs int) int { + switch { + case nproofs <= 8: + return 11220 + case nproofs <= 16: + return 14196 + case nproofs <= 32: + return 17172 + case nproofs <= 64: + return 20148 + case nproofs <= 128: + return 23124 + case nproofs <= 256: + return 26100 + case nproofs <= 512: + return 29076 + case nproofs <= 1024: + return 32052 + case nproofs <= 2048: + return 35028 + case nproofs <= 4096: + return 38004 + case nproofs <= 8192: + return 40980 + default: + panic("too many proofs") + } +} + func (m mockVerifProver) VerifyWinningPoSt(ctx context.Context, info proof5.WinningPoStVerifyInfo) (bool, error) { info.Randomness[31] &= 0x3f return true, nil diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go index 88c8bdf37..845400ccf 100644 --- a/extern/storage-sealing/commit_batch.go +++ b/extern/storage-sealing/commit_batch.go @@ -33,8 +33,7 @@ type CommitBatcherApi interface { } type AggregateInput struct { - spt abi.RegisteredSealProof - // TODO: Something changed in actors, I think this now needs to be AggregateSealVerifyProofAndInfos todo ?? + spt abi.RegisteredSealProof info proof5.AggregateSealVerifyInfo proof []byte } From 21b4741e302e61cf7a994a4c5dc58cd49917fe50 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Wed, 26 May 2021 19:03:46 -0400 Subject: [PATCH 47/88] Fix randomness fetching around null blocks --- chain/gen/gen.go | 40 +++++--- chain/gen/genesis/miners.go | 16 +++- chain/store/store.go | 40 ++++++-- chain/store/store_test.go | 2 +- chain/sync_test.go | 180 +++++++++++++++++++++++++++++------- chain/vm/runtime.go | 22 ++++- chain/vm/vm.go | 6 +- conformance/rand_fixed.go | 12 ++- conformance/rand_record.go | 21 ++++- conformance/rand_replay.go | 33 ++++++- node/impl/full/chain.go | 16 +++- 11 files changed, 314 insertions(+), 74 deletions(-) diff --git a/chain/gen/gen.go b/chain/gen/gen.go index 27feaeaaa..319b7ac29 100644 --- a/chain/gen/gen.go +++ b/chain/gen/gen.go @@ -75,9 +75,10 @@ type ChainGen struct { w *wallet.LocalWallet - eppProvs map[address.Address]WinningPoStProver - Miners []address.Address - receivers []address.Address + eppProvs map[address.Address]WinningPoStProver + Miners []address.Address + receivers []address.Address + // a SecP address banker address.Address bankerNonce uint64 @@ -110,7 +111,7 @@ var DefaultRemainderAccountActor = genesis.Actor{ Meta: remAccMeta.ActorMeta(), } -func NewGeneratorWithSectors(numSectors int) (*ChainGen, error) { +func NewGeneratorWithSectorsAndUpgradeSchedule(numSectors int, us stmgr.UpgradeSchedule) (*ChainGen, error) { j := journal.NilJournal() // TODO: we really shouldn't modify a global variable here. policy.SetSupportedProofTypes(abi.RegisteredSealProof_StackedDrg2KiBV1) @@ -244,7 +245,10 @@ func NewGeneratorWithSectors(numSectors int) (*ChainGen, error) { mgen[genesis2.MinerAddress(uint64(i))] = &wppProvider{} } - sm := stmgr.NewStateManager(cs) + sm, err := stmgr.NewStateManagerWithUpgradeSchedule(cs, us) + if err != nil { + return nil, xerrors.Errorf("initing stmgr: %w", err) + } miners := []address.Address{maddr1, maddr2} @@ -282,6 +286,14 @@ func NewGenerator() (*ChainGen, error) { return NewGeneratorWithSectors(1) } +func NewGeneratorWithSectors(numSectors int) (*ChainGen, error) { + return NewGeneratorWithSectorsAndUpgradeSchedule(numSectors, stmgr.DefaultUpgradeSchedule()) +} + +func NewGeneratorWithUpgradeSchedule(us stmgr.UpgradeSchedule) (*ChainGen, error) { + return NewGeneratorWithSectorsAndUpgradeSchedule(1, us) +} + func (cg *ChainGen) StateManager() *stmgr.StateManager { return cg.sm } @@ -384,7 +396,7 @@ type MinedTipSet struct { } func (cg *ChainGen) NextTipSet() (*MinedTipSet, error) { - mts, err := cg.NextTipSetFromMiners(cg.CurTipset.TipSet(), cg.Miners) + mts, err := cg.NextTipSetFromMiners(cg.CurTipset.TipSet(), cg.Miners, 0) if err != nil { return nil, err } @@ -397,7 +409,7 @@ func (cg *ChainGen) SetWinningPoStProver(m address.Address, wpp WinningPoStProve cg.eppProvs[m] = wpp } -func (cg *ChainGen) NextTipSetFromMiners(base *types.TipSet, miners []address.Address) (*MinedTipSet, error) { +func (cg *ChainGen) NextTipSetFromMiners(base *types.TipSet, miners []address.Address, nulls abi.ChainEpoch) (*MinedTipSet, error) { ms, err := cg.GetMessages(cg) if err != nil { return nil, xerrors.Errorf("get random messages: %w", err) @@ -408,21 +420,23 @@ func (cg *ChainGen) NextTipSetFromMiners(base *types.TipSet, miners []address.Ad msgs[i] = ms } - fts, err := cg.NextTipSetFromMinersWithMessages(base, miners, msgs) + fts, err := cg.NextTipSetFromMinersWithMessagesAndNulls(base, miners, msgs, nulls) if err != nil { return nil, err } + cg.CurTipset = fts + return &MinedTipSet{ TipSet: fts, Messages: ms, }, nil } -func (cg *ChainGen) NextTipSetFromMinersWithMessages(base *types.TipSet, miners []address.Address, msgs [][]*types.SignedMessage) (*store.FullTipSet, error) { +func (cg *ChainGen) NextTipSetFromMinersWithMessagesAndNulls(base *types.TipSet, miners []address.Address, msgs [][]*types.SignedMessage, nulls abi.ChainEpoch) (*store.FullTipSet, error) { var blks []*types.FullBlock - for round := base.Height() + 1; len(blks) == 0; round++ { + for round := base.Height() + nulls + 1; len(blks) == 0; round++ { for mi, m := range miners { bvals, et, ticket, err := cg.nextBlockProof(context.TODO(), base, m, round) if err != nil { @@ -455,6 +469,8 @@ func (cg *ChainGen) NextTipSetFromMinersWithMessages(base *types.TipSet, miners return nil, err } + cg.CurTipset = fts + return fts, nil } @@ -574,7 +590,7 @@ func (mca mca) ChainGetRandomnessFromTickets(ctx context.Context, tsk types.TipS return nil, xerrors.Errorf("loading tipset key: %w", err) } - return mca.sm.ChainStore().GetChainRandomness(ctx, pts.Cids(), personalization, randEpoch, entropy) + return mca.sm.ChainStore().GetChainRandomnessLookingBack(ctx, pts.Cids(), personalization, randEpoch, entropy) } func (mca mca) ChainGetRandomnessFromBeacon(ctx context.Context, tsk types.TipSetKey, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) (abi.Randomness, error) { @@ -583,7 +599,7 @@ func (mca mca) ChainGetRandomnessFromBeacon(ctx context.Context, tsk types.TipSe return nil, xerrors.Errorf("loading tipset key: %w", err) } - return mca.sm.ChainStore().GetBeaconRandomness(ctx, pts.Cids(), personalization, randEpoch, entropy) + return mca.sm.ChainStore().GetBeaconRandomnessLookingBack(ctx, pts.Cids(), personalization, randEpoch, entropy) } func (mca mca) MinerGetBaseInfo(ctx context.Context, maddr address.Address, epoch abi.ChainEpoch, tsk types.TipSetKey) (*api.MiningBaseInfo, error) { diff --git a/chain/gen/genesis/miners.go b/chain/gen/genesis/miners.go index be33560e5..86fd16812 100644 --- a/chain/gen/genesis/miners.go +++ b/chain/gen/genesis/miners.go @@ -335,13 +335,25 @@ func SetupStorageMiners(ctx context.Context, cs *store.ChainStore, sroot cid.Cid // TODO: copied from actors test harness, deduplicate or remove from here type fakeRand struct{} -func (fr *fakeRand) GetChainRandomness(ctx context.Context, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) ([]byte, error) { +func (fr *fakeRand) GetChainRandomnessLookingForward(ctx context.Context, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) ([]byte, error) { out := make([]byte, 32) _, _ = rand.New(rand.NewSource(int64(randEpoch * 1000))).Read(out) //nolint return out, nil } -func (fr *fakeRand) GetBeaconRandomness(ctx context.Context, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) ([]byte, error) { +func (fr *fakeRand) GetChainRandomnessLookingBack(ctx context.Context, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) ([]byte, error) { + out := make([]byte, 32) + _, _ = rand.New(rand.NewSource(int64(randEpoch * 1000))).Read(out) //nolint + return out, nil +} + +func (fr *fakeRand) GetBeaconRandomnessLookingForward(ctx context.Context, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) ([]byte, error) { + out := make([]byte, 32) + _, _ = rand.New(rand.NewSource(int64(randEpoch))).Read(out) //nolint + return out, nil +} + +func (fr *fakeRand) GetBeaconRandomnessLookingBack(ctx context.Context, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) ([]byte, error) { out := make([]byte, 32) _, _ = rand.New(rand.NewSource(int64(randEpoch))).Read(out) //nolint return out, nil diff --git a/chain/store/store.go b/chain/store/store.go index 7ebe31ec4..dfde93fc7 100644 --- a/chain/store/store.go +++ b/chain/store/store.go @@ -1280,7 +1280,15 @@ func DrawRandomness(rbase []byte, pers crypto.DomainSeparationTag, round abi.Cha return h.Sum(nil), nil } -func (cs *ChainStore) GetBeaconRandomness(ctx context.Context, blks []cid.Cid, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { +func (cs *ChainStore) GetBeaconRandomnessLookingBack(ctx context.Context, blks []cid.Cid, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { + return cs.GetBeaconRandomness(ctx, blks, pers, round, entropy, true) +} + +func (cs *ChainStore) GetBeaconRandomnessLookingForward(ctx context.Context, blks []cid.Cid, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { + return cs.GetBeaconRandomness(ctx, blks, pers, round, entropy, false) +} + +func (cs *ChainStore) GetBeaconRandomness(ctx context.Context, blks []cid.Cid, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte, lookback bool) ([]byte, error) { _, span := trace.StartSpan(ctx, "store.GetBeaconRandomness") defer span.End() span.AddAttributes(trace.Int64Attribute("round", int64(round))) @@ -1299,7 +1307,7 @@ func (cs *ChainStore) GetBeaconRandomness(ctx context.Context, blks []cid.Cid, p searchHeight = 0 } - randTs, err := cs.GetTipsetByHeight(ctx, searchHeight, ts, true) + randTs, err := cs.GetTipsetByHeight(ctx, searchHeight, ts, lookback) if err != nil { return nil, err } @@ -1314,7 +1322,15 @@ func (cs *ChainStore) GetBeaconRandomness(ctx context.Context, blks []cid.Cid, p return DrawRandomness(be.Data, pers, round, entropy) } -func (cs *ChainStore) GetChainRandomness(ctx context.Context, blks []cid.Cid, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { +func (cs *ChainStore) GetChainRandomnessLookingBack(ctx context.Context, blks []cid.Cid, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { + return cs.GetChainRandomness(ctx, blks, pers, round, entropy, true) +} + +func (cs *ChainStore) GetChainRandomnessLookingForward(ctx context.Context, blks []cid.Cid, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { + return cs.GetChainRandomness(ctx, blks, pers, round, entropy, false) +} + +func (cs *ChainStore) GetChainRandomness(ctx context.Context, blks []cid.Cid, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte, lookback bool) ([]byte, error) { _, span := trace.StartSpan(ctx, "store.GetChainRandomness") defer span.End() span.AddAttributes(trace.Int64Attribute("round", int64(round))) @@ -1333,7 +1349,7 @@ func (cs *ChainStore) GetChainRandomness(ctx context.Context, blks []cid.Cid, pe searchHeight = 0 } - randTs, err := cs.GetTipsetByHeight(ctx, searchHeight, ts, true) + randTs, err := cs.GetTipsetByHeight(ctx, searchHeight, ts, lookback) if err != nil { return nil, err } @@ -1608,12 +1624,20 @@ func NewChainRand(cs *ChainStore, blks []cid.Cid) vm.Rand { } } -func (cr *chainRand) GetChainRandomness(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { - return cr.cs.GetChainRandomness(ctx, cr.blks, pers, round, entropy) +func (cr *chainRand) GetChainRandomnessLookingBack(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { + return cr.cs.GetChainRandomnessLookingBack(ctx, cr.blks, pers, round, entropy) } -func (cr *chainRand) GetBeaconRandomness(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { - return cr.cs.GetBeaconRandomness(ctx, cr.blks, pers, round, entropy) +func (cr *chainRand) GetChainRandomnessLookingForward(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { + return cr.cs.GetChainRandomnessLookingForward(ctx, cr.blks, pers, round, entropy) +} + +func (cr *chainRand) GetBeaconRandomnessLookingBack(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { + return cr.cs.GetBeaconRandomnessLookingBack(ctx, cr.blks, pers, round, entropy) +} + +func (cr *chainRand) GetBeaconRandomnessLookingForward(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { + return cr.cs.GetBeaconRandomnessLookingForward(ctx, cr.blks, pers, round, entropy) } func (cs *ChainStore) GetTipSetFromKey(tsk types.TipSetKey) (*types.TipSet, error) { diff --git a/chain/store/store_test.go b/chain/store/store_test.go index 51e2e08d0..62a0430e3 100644 --- a/chain/store/store_test.go +++ b/chain/store/store_test.go @@ -76,7 +76,7 @@ func BenchmarkGetRandomness(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - _, err := cs.GetChainRandomness(context.TODO(), last.Cids(), crypto.DomainSeparationTag_SealRandomness, 500, nil) + _, err := cs.GetChainRandomnessLookingBack(context.TODO(), last.Cids(), crypto.DomainSeparationTag_SealRandomness, 500, nil) if err != nil { b.Fatal(err) } diff --git a/chain/sync_test.go b/chain/sync_test.go index 9570eda32..2e5a5f3cf 100644 --- a/chain/sync_test.go +++ b/chain/sync_test.go @@ -7,6 +7,12 @@ import ( "testing" "time" + "github.com/filecoin-project/go-state-types/crypto" + + "github.com/filecoin-project/go-state-types/network" + + "github.com/filecoin-project/lotus/chain/stmgr" + "github.com/ipfs/go-cid" ds "github.com/ipfs/go-datastore" @@ -101,7 +107,7 @@ func prepSyncTest(t testing.TB, h int) *syncTestUtil { g: g, } - tu.addSourceNode(h) + tu.addSourceNode(stmgr.DefaultUpgradeSchedule(), h) //tu.checkHeight("source", source, h) // separate logs @@ -110,6 +116,53 @@ func prepSyncTest(t testing.TB, h int) *syncTestUtil { return tu } +func prepSyncTestWithV5Height(t testing.TB, h int, v5height abi.ChainEpoch) *syncTestUtil { + logging.SetLogLevel("*", "INFO") + + us := stmgr.UpgradeSchedule{{ + // prepare for upgrade. + Network: network.Version9, + Height: 1, + Migration: stmgr.UpgradeActorsV2, + }, { + Network: network.Version10, + Height: 2, + Migration: stmgr.UpgradeActorsV3, + }, { + Network: network.Version12, + Height: 3, + Migration: stmgr.UpgradeActorsV4, + }, { + Network: network.Version13, + Height: v5height, + Migration: stmgr.UpgradeActorsV5, + }} + + g, err := gen.NewGeneratorWithUpgradeSchedule(us) + + if err != nil { + t.Fatalf("%+v", err) + } + + ctx, cancel := context.WithCancel(context.Background()) + + tu := &syncTestUtil{ + t: t, + ctx: ctx, + cancel: cancel, + + mn: mocknet.New(ctx), + g: g, + } + + tu.addSourceNode(us, h) + //tu.checkHeight("source", source, h) + + // separate logs + fmt.Println("\x1b[31m///////////////////////////////////////////////////\x1b[39b") + return tu +} + func (tu *syncTestUtil) Shutdown() { tu.cancel() } @@ -174,7 +227,7 @@ func (tu *syncTestUtil) pushTsExpectErr(to int, fts *store.FullTipSet, experr bo } } -func (tu *syncTestUtil) mineOnBlock(blk *store.FullTipSet, to int, miners []int, wait, fail bool, msgs [][]*types.SignedMessage) *store.FullTipSet { +func (tu *syncTestUtil) mineOnBlock(blk *store.FullTipSet, to int, miners []int, wait, fail bool, msgs [][]*types.SignedMessage, nulls abi.ChainEpoch) *store.FullTipSet { if miners == nil { for i := range tu.g.Miners { miners = append(miners, i) @@ -191,10 +244,10 @@ func (tu *syncTestUtil) mineOnBlock(blk *store.FullTipSet, to int, miners []int, var nts *store.FullTipSet var err error if msgs != nil { - nts, err = tu.g.NextTipSetFromMinersWithMessages(blk.TipSet(), maddrs, msgs) + nts, err = tu.g.NextTipSetFromMinersWithMessagesAndNulls(blk.TipSet(), maddrs, msgs, 0) require.NoError(tu.t, err) } else { - mt, err := tu.g.NextTipSetFromMiners(blk.TipSet(), maddrs) + mt, err := tu.g.NextTipSetFromMiners(blk.TipSet(), maddrs, nulls) require.NoError(tu.t, err) nts = mt.TipSet } @@ -209,11 +262,11 @@ func (tu *syncTestUtil) mineOnBlock(blk *store.FullTipSet, to int, miners []int, } func (tu *syncTestUtil) mineNewBlock(src int, miners []int) { - mts := tu.mineOnBlock(tu.g.CurTipset, src, miners, true, false, nil) + mts := tu.mineOnBlock(tu.g.CurTipset, src, miners, true, false, nil, 0) tu.g.CurTipset = mts } -func (tu *syncTestUtil) addSourceNode(gen int) { +func (tu *syncTestUtil) addSourceNode(us stmgr.UpgradeSchedule, gen int) { if tu.genesis != nil { tu.t.Fatal("source node already exists") } @@ -229,6 +282,7 @@ func (tu *syncTestUtil) addSourceNode(gen int) { node.Test(), node.Override(new(modules.Genesis), modules.LoadGenesis(genesis)), + node.Override(new(stmgr.UpgradeSchedule), us), ) require.NoError(tu.t, err) tu.t.Cleanup(func() { _ = stop(context.Background()) }) @@ -442,7 +496,7 @@ func TestSyncBadTimestamp(t *testing.T) { fmt.Println("BASE: ", base.Cids()) tu.printHeads() - a1 := tu.mineOnBlock(base, 0, nil, false, true, nil) + a1 := tu.mineOnBlock(base, 0, nil, false, true, nil, 0) tu.g.Timestamper = nil require.NoError(t, tu.g.ResyncBankerNonce(a1.TipSet())) @@ -451,7 +505,7 @@ func TestSyncBadTimestamp(t *testing.T) { fmt.Println("After mine bad block!") tu.printHeads() - a2 := tu.mineOnBlock(base, 0, nil, true, false, nil) + a2 := tu.mineOnBlock(base, 0, nil, true, false, nil, 0) tu.waitUntilSync(0, client) @@ -495,7 +549,7 @@ func TestSyncBadWinningPoSt(t *testing.T) { tu.g.SetWinningPoStProver(tu.g.Miners[1], &badWpp{}) // now ensure that new blocks are not accepted - tu.mineOnBlock(base, client, nil, false, true, nil) + tu.mineOnBlock(base, client, nil, false, true, nil, 0) } func (tu *syncTestUtil) loadChainToNode(to int) { @@ -540,16 +594,16 @@ func TestSyncFork(t *testing.T) { fmt.Println("Mining base: ", base.TipSet().Cids(), base.TipSet().Height()) // The two nodes fork at this point into 'a' and 'b' - a1 := tu.mineOnBlock(base, p1, []int{0}, true, false, nil) - a := tu.mineOnBlock(a1, p1, []int{0}, true, false, nil) - a = tu.mineOnBlock(a, p1, []int{0}, true, false, nil) + a1 := tu.mineOnBlock(base, p1, []int{0}, true, false, nil, 0) + a := tu.mineOnBlock(a1, p1, []int{0}, true, false, nil, 0) + a = tu.mineOnBlock(a, p1, []int{0}, true, false, nil, 0) require.NoError(t, tu.g.ResyncBankerNonce(a1.TipSet())) // chain B will now be heaviest - b := tu.mineOnBlock(base, p2, []int{1}, true, false, nil) - b = tu.mineOnBlock(b, p2, []int{1}, true, false, nil) - b = tu.mineOnBlock(b, p2, []int{1}, true, false, nil) - b = tu.mineOnBlock(b, p2, []int{1}, true, false, nil) + b := tu.mineOnBlock(base, p2, []int{1}, true, false, nil, 0) + b = tu.mineOnBlock(b, p2, []int{1}, true, false, nil, 0) + b = tu.mineOnBlock(b, p2, []int{1}, true, false, nil, 0) + b = tu.mineOnBlock(b, p2, []int{1}, true, false, nil, 0) fmt.Println("A: ", a.Cids(), a.TipSet().Height()) fmt.Println("B: ", b.Cids(), b.TipSet().Height()) @@ -611,13 +665,13 @@ func TestDuplicateNonce(t *testing.T) { msgs[k] = []*types.SignedMessage{makeMsg(tu.g.Miners[k])} } - ts1 := tu.mineOnBlock(base, 0, []int{0, 1}, true, false, msgs) + ts1 := tu.mineOnBlock(base, 0, []int{0, 1}, true, false, msgs, 0) tu.waitUntilSyncTarget(0, ts1.TipSet()) // mine another tipset - ts2 := tu.mineOnBlock(ts1, 0, []int{0, 1}, true, false, make([][]*types.SignedMessage, 2)) + ts2 := tu.mineOnBlock(ts1, 0, []int{0, 1}, true, false, make([][]*types.SignedMessage, 2), 0) tu.waitUntilSyncTarget(0, ts2.TipSet()) var includedMsg cid.Cid @@ -668,11 +722,15 @@ func TestBadNonce(t *testing.T) { base := tu.g.CurTipset + // Get the banker from computed tipset state, not the parent. + st, _, err := tu.g.StateManager().TipSetState(context.TODO(), base.TipSet()) + require.NoError(t, err) + ba, err := tu.g.StateManager().LoadActorRaw(context.TODO(), tu.g.Banker(), st) + require.NoError(t, err) + // Produce a message from the banker with a bad nonce makeBadMsg := func() *types.SignedMessage { - ba, err := tu.nds[0].StateGetActor(context.TODO(), tu.g.Banker(), base.TipSet().Key()) - require.NoError(t, err) msg := types.Message{ To: tu.g.Banker(), From: tu.g.Banker(), @@ -700,7 +758,7 @@ func TestBadNonce(t *testing.T) { msgs := make([][]*types.SignedMessage, 1) msgs[0] = []*types.SignedMessage{makeBadMsg()} - tu.mineOnBlock(base, 0, []int{0}, true, true, msgs) + tu.mineOnBlock(base, 0, []int{0}, true, true, msgs, 0) } func BenchmarkSyncBasic(b *testing.B) { @@ -765,19 +823,19 @@ func TestSyncCheckpointHead(t *testing.T) { fmt.Println("Mining base: ", base.TipSet().Cids(), base.TipSet().Height()) // The two nodes fork at this point into 'a' and 'b' - a1 := tu.mineOnBlock(base, p1, []int{0}, true, false, nil) - a := tu.mineOnBlock(a1, p1, []int{0}, true, false, nil) - a = tu.mineOnBlock(a, p1, []int{0}, true, false, nil) + a1 := tu.mineOnBlock(base, p1, []int{0}, true, false, nil, 0) + a := tu.mineOnBlock(a1, p1, []int{0}, true, false, nil, 0) + a = tu.mineOnBlock(a, p1, []int{0}, true, false, nil, 0) tu.waitUntilSyncTarget(p1, a.TipSet()) tu.checkpointTs(p1, a.TipSet().Key()) require.NoError(t, tu.g.ResyncBankerNonce(a1.TipSet())) // chain B will now be heaviest - b := tu.mineOnBlock(base, p2, []int{1}, true, false, nil) - b = tu.mineOnBlock(b, p2, []int{1}, true, false, nil) - b = tu.mineOnBlock(b, p2, []int{1}, true, false, nil) - b = tu.mineOnBlock(b, p2, []int{1}, true, false, nil) + b := tu.mineOnBlock(base, p2, []int{1}, true, false, nil, 0) + b = tu.mineOnBlock(b, p2, []int{1}, true, false, nil, 0) + b = tu.mineOnBlock(b, p2, []int{1}, true, false, nil, 0) + b = tu.mineOnBlock(b, p2, []int{1}, true, false, nil, 0) fmt.Println("A: ", a.Cids(), a.TipSet().Height()) fmt.Println("B: ", b.Cids(), b.TipSet().Height()) @@ -807,19 +865,19 @@ func TestSyncCheckpointEarlierThanHead(t *testing.T) { fmt.Println("Mining base: ", base.TipSet().Cids(), base.TipSet().Height()) // The two nodes fork at this point into 'a' and 'b' - a1 := tu.mineOnBlock(base, p1, []int{0}, true, false, nil) - a := tu.mineOnBlock(a1, p1, []int{0}, true, false, nil) - a = tu.mineOnBlock(a, p1, []int{0}, true, false, nil) + a1 := tu.mineOnBlock(base, p1, []int{0}, true, false, nil, 0) + a := tu.mineOnBlock(a1, p1, []int{0}, true, false, nil, 0) + a = tu.mineOnBlock(a, p1, []int{0}, true, false, nil, 0) tu.waitUntilSyncTarget(p1, a.TipSet()) tu.checkpointTs(p1, a1.TipSet().Key()) require.NoError(t, tu.g.ResyncBankerNonce(a1.TipSet())) // chain B will now be heaviest - b := tu.mineOnBlock(base, p2, []int{1}, true, false, nil) - b = tu.mineOnBlock(b, p2, []int{1}, true, false, nil) - b = tu.mineOnBlock(b, p2, []int{1}, true, false, nil) - b = tu.mineOnBlock(b, p2, []int{1}, true, false, nil) + b := tu.mineOnBlock(base, p2, []int{1}, true, false, nil, 0) + b = tu.mineOnBlock(b, p2, []int{1}, true, false, nil, 0) + b = tu.mineOnBlock(b, p2, []int{1}, true, false, nil, 0) + b = tu.mineOnBlock(b, p2, []int{1}, true, false, nil, 0) fmt.Println("A: ", a.Cids(), a.TipSet().Height()) fmt.Println("B: ", b.Cids(), b.TipSet().Height()) @@ -833,3 +891,55 @@ func TestSyncCheckpointEarlierThanHead(t *testing.T) { require.Equal(tu.t, p1Head, a.TipSet()) tu.assertBad(p1, b.TipSet()) } + +func TestDrandNull(t *testing.T) { + H := 10 + v5h := abi.ChainEpoch(50) + build.UpgradeHyperdriveHeight = v5h + tu := prepSyncTestWithV5Height(t, H, v5h) + + entropy := []byte{0, 2, 3, 4} + // arbitrarily chosen + pers := crypto.DomainSeparationTag_WinningPoStChallengeSeed + + beforeNull := tu.g.CurTipset + afterNull := tu.mineOnBlock(beforeNull, 0, nil, false, false, nil, 2) + nullHeight := beforeNull.TipSet().Height() + 1 + if afterNull.TipSet().Height() == nullHeight { + t.Fatal("didn't inject nulls as expected") + } + + rand, err := tu.nds[0].ChainGetRandomnessFromBeacon(tu.ctx, afterNull.TipSet().Key(), pers, nullHeight, entropy) + require.NoError(t, err) + + // calculate the expected randomness based on the beacon BEFORE the null + expectedBE := beforeNull.Blocks[0].Header.BeaconEntries + expectedRand, err := store.DrawRandomness(expectedBE[len(expectedBE)-1].Data, pers, nullHeight, entropy) + require.NoError(t, err) + + require.Equal(t, []byte(rand), expectedRand) + + // zoom zoom to past the v5 upgrade by injecting many many nulls + postUpgrade := tu.mineOnBlock(afterNull, 0, nil, false, false, nil, v5h) + nv, err := tu.nds[0].StateNetworkVersion(tu.ctx, types.EmptyTSK) + require.NoError(t, err) + if nv != network.Version13 { + t.Fatal("expect to be v13 by now") + } + + afterNull = tu.mineOnBlock(postUpgrade, 0, nil, false, false, nil, 2) + nullHeight = postUpgrade.TipSet().Height() + 1 + if afterNull.TipSet().Height() == nullHeight { + t.Fatal("didn't inject nulls as expected") + } + + rand, err = tu.nds[0].ChainGetRandomnessFromBeacon(tu.ctx, afterNull.TipSet().Key(), pers, nullHeight, entropy) + require.NoError(t, err) + + // calculate the expected randomness based on the beacon AFTER the null + expectedBE = afterNull.Blocks[0].Header.BeaconEntries + expectedRand, err = store.DrawRandomness(expectedBE[len(expectedBE)-1].Data, pers, nullHeight, entropy) + require.NoError(t, err) + + require.Equal(t, []byte(rand), expectedRand) +} diff --git a/chain/vm/runtime.go b/chain/vm/runtime.go index a3e2f293f..f89bd9f40 100644 --- a/chain/vm/runtime.go +++ b/chain/vm/runtime.go @@ -208,17 +208,31 @@ func (rt *Runtime) GetActorCodeCID(addr address.Address) (ret cid.Cid, ok bool) } func (rt *Runtime) GetRandomnessFromTickets(personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) abi.Randomness { - res, err := rt.vm.rand.GetChainRandomness(rt.ctx, personalization, randEpoch, entropy) + var err error + var res []byte + if rt.vm.GetNtwkVersion(rt.ctx, randEpoch) >= network.Version13 { + res, err = rt.vm.rand.GetChainRandomnessLookingForward(rt.ctx, personalization, randEpoch, entropy) + } else { + res, err = rt.vm.rand.GetChainRandomnessLookingBack(rt.ctx, personalization, randEpoch, entropy) + } + if err != nil { - panic(aerrors.Fatalf("could not get randomness: %s", err)) + panic(aerrors.Fatalf("could not get ticket randomness: %s", err)) } return res } func (rt *Runtime) GetRandomnessFromBeacon(personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) abi.Randomness { - res, err := rt.vm.rand.GetBeaconRandomness(rt.ctx, personalization, randEpoch, entropy) + var err error + var res []byte + if rt.vm.GetNtwkVersion(rt.ctx, randEpoch) >= network.Version13 { + res, err = rt.vm.rand.GetBeaconRandomnessLookingForward(rt.ctx, personalization, randEpoch, entropy) + } else { + res, err = rt.vm.rand.GetBeaconRandomnessLookingBack(rt.ctx, personalization, randEpoch, entropy) + } + if err != nil { - panic(aerrors.Fatalf("could not get randomness: %s", err)) + panic(aerrors.Fatalf("could not get beacon randomness: %s", err)) } return res } diff --git a/chain/vm/vm.go b/chain/vm/vm.go index afc74e744..88ec93a80 100644 --- a/chain/vm/vm.go +++ b/chain/vm/vm.go @@ -255,8 +255,10 @@ func NewVM(ctx context.Context, opts *VMOpts) (*VM, error) { } type Rand interface { - GetChainRandomness(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) - GetBeaconRandomness(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) + GetChainRandomnessLookingBack(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) + GetChainRandomnessLookingForward(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) + GetBeaconRandomnessLookingBack(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) + GetBeaconRandomnessLookingForward(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) } type ApplyRet struct { diff --git a/conformance/rand_fixed.go b/conformance/rand_fixed.go index d356b53d0..f15910e1d 100644 --- a/conformance/rand_fixed.go +++ b/conformance/rand_fixed.go @@ -19,10 +19,18 @@ func NewFixedRand() vm.Rand { return &fixedRand{} } -func (r *fixedRand) GetChainRandomness(_ context.Context, _ crypto.DomainSeparationTag, _ abi.ChainEpoch, _ []byte) ([]byte, error) { +func (r *fixedRand) GetChainRandomnessLookingForward(_ context.Context, _ crypto.DomainSeparationTag, _ abi.ChainEpoch, _ []byte) ([]byte, error) { return []byte("i_am_random_____i_am_random_____"), nil // 32 bytes. } -func (r *fixedRand) GetBeaconRandomness(_ context.Context, _ crypto.DomainSeparationTag, _ abi.ChainEpoch, _ []byte) ([]byte, error) { +func (r *fixedRand) GetChainRandomnessLookingBack(_ context.Context, _ crypto.DomainSeparationTag, _ abi.ChainEpoch, _ []byte) ([]byte, error) { + return []byte("i_am_random_____i_am_random_____"), nil // 32 bytes. +} + +func (r *fixedRand) GetBeaconRandomnessLookingForward(_ context.Context, _ crypto.DomainSeparationTag, _ abi.ChainEpoch, _ []byte) ([]byte, error) { + return []byte("i_am_random_____i_am_random_____"), nil // 32 bytes. +} + +func (r *fixedRand) GetBeaconRandomnessLookingBack(_ context.Context, _ crypto.DomainSeparationTag, _ abi.ChainEpoch, _ []byte) ([]byte, error) { return []byte("i_am_random_____i_am_random_____"), nil // 32 bytes. } diff --git a/conformance/rand_record.go b/conformance/rand_record.go index 6f6d064dc..5b4985fef 100644 --- a/conformance/rand_record.go +++ b/conformance/rand_record.go @@ -45,8 +45,17 @@ func (r *RecordingRand) loadHead() { r.head = head.Key() } -func (r *RecordingRand) GetChainRandomness(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { +func (r *RecordingRand) GetChainRandomnessLookingForward(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { + return r.getChainRandomness(ctx, pers, round, entropy) +} + +func (r *RecordingRand) GetChainRandomnessLookingBack(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { + return r.getChainRandomness(ctx, pers, round, entropy) +} + +func (r *RecordingRand) getChainRandomness(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { r.once.Do(r.loadHead) + // FullNode's ChainGetRandomnessFromTickets handles whether we should be looking forward or back ret, err := r.api.ChainGetRandomnessFromTickets(ctx, r.head, pers, round, entropy) if err != nil { return ret, err @@ -70,7 +79,15 @@ func (r *RecordingRand) GetChainRandomness(ctx context.Context, pers crypto.Doma return ret, err } -func (r *RecordingRand) GetBeaconRandomness(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { +func (r *RecordingRand) GetBeaconRandomnessLookingForward(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { + return r.getBeaconRandomness(ctx, pers, round, entropy) +} + +func (r *RecordingRand) GetBeaconRandomnessLookingBack(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { + return r.getBeaconRandomness(ctx, pers, round, entropy) +} + +func (r *RecordingRand) getBeaconRandomness(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { r.once.Do(r.loadHead) ret, err := r.api.ChainGetRandomnessFromBeacon(ctx, r.head, pers, round, entropy) if err != nil { diff --git a/conformance/rand_replay.go b/conformance/rand_replay.go index 1b73e5a08..faae1d090 100644 --- a/conformance/rand_replay.go +++ b/conformance/rand_replay.go @@ -43,7 +43,15 @@ func (r *ReplayingRand) match(requested schema.RandomnessRule) ([]byte, bool) { return nil, false } -func (r *ReplayingRand) GetChainRandomness(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { +func (r *ReplayingRand) GetChainRandomnessLookingForward(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { + return r.getChainRandomness(ctx, pers, round, entropy, false) +} + +func (r *ReplayingRand) GetChainRandomnessLookingBack(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { + return r.getChainRandomness(ctx, pers, round, entropy, true) +} + +func (r *ReplayingRand) getChainRandomness(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte, lookback bool) ([]byte, error) { rule := schema.RandomnessRule{ Kind: schema.RandomnessChain, DomainSeparationTag: int64(pers), @@ -57,10 +65,23 @@ func (r *ReplayingRand) GetChainRandomness(ctx context.Context, pers crypto.Doma } r.reporter.Logf("returning fallback chain randomness: dst=%d, epoch=%d, entropy=%x", pers, round, entropy) - return r.fallback.GetChainRandomness(ctx, pers, round, entropy) + + if lookback { + return r.fallback.GetChainRandomnessLookingBack(ctx, pers, round, entropy) + } + + return r.fallback.GetChainRandomnessLookingForward(ctx, pers, round, entropy) } -func (r *ReplayingRand) GetBeaconRandomness(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { +func (r *ReplayingRand) GetBeaconRandomnessLookingForward(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { + return r.getBeaconRandomness(ctx, pers, round, entropy, false) +} + +func (r *ReplayingRand) GetBeaconRandomnessLookingBack(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte) ([]byte, error) { + return r.getBeaconRandomness(ctx, pers, round, entropy, true) +} + +func (r *ReplayingRand) getBeaconRandomness(ctx context.Context, pers crypto.DomainSeparationTag, round abi.ChainEpoch, entropy []byte, lookback bool) ([]byte, error) { rule := schema.RandomnessRule{ Kind: schema.RandomnessBeacon, DomainSeparationTag: int64(pers), @@ -74,6 +95,10 @@ func (r *ReplayingRand) GetBeaconRandomness(ctx context.Context, pers crypto.Dom } r.reporter.Logf("returning fallback beacon randomness: dst=%d, epoch=%d, entropy=%x", pers, round, entropy) - return r.fallback.GetBeaconRandomness(ctx, pers, round, entropy) + if lookback { + return r.fallback.GetBeaconRandomnessLookingBack(ctx, pers, round, entropy) + } + + return r.fallback.GetBeaconRandomnessLookingForward(ctx, pers, round, entropy) } diff --git a/node/impl/full/chain.go b/node/impl/full/chain.go index c45932195..845c0ce60 100644 --- a/node/impl/full/chain.go +++ b/node/impl/full/chain.go @@ -10,6 +10,8 @@ import ( "strings" "sync" + "github.com/filecoin-project/lotus/build" + "go.uber.org/fx" "golang.org/x/xerrors" @@ -95,7 +97,12 @@ func (a *ChainAPI) ChainGetRandomnessFromTickets(ctx context.Context, tsk types. return nil, xerrors.Errorf("loading tipset key: %w", err) } - return a.Chain.GetChainRandomness(ctx, pts.Cids(), personalization, randEpoch, entropy) + // Doing this here is slightly nicer than doing it in the chainstore directly, but it's still bad for ChainAPI to reason about network upgrades + if randEpoch > build.UpgradeHyperdriveHeight { + return a.Chain.GetChainRandomnessLookingForward(ctx, pts.Cids(), personalization, randEpoch, entropy) + } + + return a.Chain.GetChainRandomnessLookingBack(ctx, pts.Cids(), personalization, randEpoch, entropy) } func (a *ChainAPI) ChainGetRandomnessFromBeacon(ctx context.Context, tsk types.TipSetKey, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) (abi.Randomness, error) { @@ -104,7 +111,12 @@ func (a *ChainAPI) ChainGetRandomnessFromBeacon(ctx context.Context, tsk types.T return nil, xerrors.Errorf("loading tipset key: %w", err) } - return a.Chain.GetBeaconRandomness(ctx, pts.Cids(), personalization, randEpoch, entropy) + // Doing this here is slightly nicer than doing it in the chainstore directly, but it's still bad for ChainAPI to reason about network upgrades + if randEpoch > build.UpgradeHyperdriveHeight { + return a.Chain.GetBeaconRandomnessLookingForward(ctx, pts.Cids(), personalization, randEpoch, entropy) + } + + return a.Chain.GetBeaconRandomnessLookingBack(ctx, pts.Cids(), personalization, randEpoch, entropy) } func (a *ChainAPI) ChainGetBlock(ctx context.Context, msg cid.Cid) (*types.BlockHeader, error) { From cb59daf3c154f773c14efb5caddfecf7c33d935a Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Thu, 27 May 2021 17:44:41 +0200 Subject: [PATCH 48/88] Introduce gas prices for aggregate verifications Signed-off-by: Jakub Sztandera --- chain/vm/gas.go | 31 ++++++++++++++++++++++++++-- chain/vm/gas_v0.go | 45 ++++++++++++++++++++++++++++++++++++----- chain/vm/gas_v0_test.go | 32 +++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 7 deletions(-) create mode 100644 chain/vm/gas_v0_test.go diff --git a/chain/vm/gas.go b/chain/vm/gas.go index 67dd7677e..c860ce9a0 100644 --- a/chain/vm/gas.go +++ b/chain/vm/gas.go @@ -160,8 +160,35 @@ var prices = map[abi.ChainEpoch]Pricelist{ hashingBase: 31355, computeUnsealedSectorCidBase: 98647, - verifySealBase: 2000, // TODO gas , it VerifySeal syscall is not used - verifyAggregateSealBase: 400_000_000, // TODO (~40ms, I think) + verifySealBase: 2000, // TODO gas, it VerifySeal syscall is not used + + verifyAggregateSealPer: map[abi.RegisteredSealProof]int64{ + abi.RegisteredSealProof_StackedDrg32GiBV1_1: 449900, + abi.RegisteredSealProof_StackedDrg64GiBV1_1: 359272, + }, + verifyAggregateSealSteps: map[abi.RegisteredSealProof]stepCost{ + abi.RegisteredSealProof_StackedDrg32GiBV1_1: { + {4, 103994170}, + {7, 112356810}, + {13, 122912610}, + {26, 137559930}, + {52, 162039100}, + {103, 210960780}, + {205, 318351180}, + {410, 528274980}, + }, + abi.RegisteredSealProof_StackedDrg64GiBV1_1: { + {4, 102581240}, + {7, 110803030}, + {13, 120803700}, + {26, 134642130}, + {52, 157357890}, + {103, 203017690}, + {205, 304253590}, + {410, 509880640}, + }, + }, + verifyPostLookup: map[abi.RegisteredPoStProof]scalingCost{ abi.RegisteredPoStProof_StackedDrgWindow512MiBV1: { flat: 117680921, diff --git a/chain/vm/gas_v0.go b/chain/vm/gas_v0.go index c90ebaa73..13c5fdd86 100644 --- a/chain/vm/gas_v0.go +++ b/chain/vm/gas_v0.go @@ -18,6 +18,28 @@ type scalingCost struct { scale int64 } +type stepCost []step + +type step struct { + start int64 + cost int64 +} + +func (sc stepCost) Lookup(x int64) int64 { + i := 0 + for ; i < len(sc); i++ { + if sc[i].start > x { + break + } + } + i-- // look at previous item + if i < 0 { + return 0 + } + + return sc[i].cost +} + type pricelistV0 struct { computeGasMulti int64 storageGasMulti int64 @@ -93,9 +115,12 @@ type pricelistV0 struct { computeUnsealedSectorCidBase int64 verifySealBase int64 verifyAggregateSealBase int64 - verifyPostLookup map[abi.RegisteredPoStProof]scalingCost - verifyPostDiscount bool - verifyConsensusFault int64 + verifyAggregateSealPer map[abi.RegisteredSealProof]int64 + verifyAggregateSealSteps map[abi.RegisteredSealProof]stepCost + + verifyPostLookup map[abi.RegisteredPoStProof]scalingCost + verifyPostDiscount bool + verifyConsensusFault int64 } var _ Pricelist = (*pricelistV0)(nil) @@ -189,8 +214,18 @@ func (pl *pricelistV0) OnVerifySeal(info proof2.SealVerifyInfo) GasCharge { // OnVerifyAggregateSeals func (pl *pricelistV0) OnVerifyAggregateSeals(aggregate proof5.AggregateSealVerifyProofAndInfos) GasCharge { - // TODO: this needs more cost tunning - return newGasCharge("OnVerifyAggregateSeals", pl.verifyAggregateSealBase, 0) + proofType := aggregate.SealProof + perProof, ok := pl.verifyAggregateSealPer[proofType] + if !ok { + perProof = pl.verifyAggregateSealPer[abi.RegisteredSealProof_StackedDrg32GiBV1_1] + } + + step, ok := pl.verifyAggregateSealSteps[proofType] + if !ok { + step = pl.verifyAggregateSealSteps[abi.RegisteredSealProof_StackedDrg32GiBV1_1] + } + num := int64(len(aggregate.Infos)) + return newGasCharge("OnVerifyAggregateSeals", perProof*num+step.Lookup(num), 0) } // OnVerifyPost diff --git a/chain/vm/gas_v0_test.go b/chain/vm/gas_v0_test.go new file mode 100644 index 000000000..447e4f70c --- /dev/null +++ b/chain/vm/gas_v0_test.go @@ -0,0 +1,32 @@ +package vm + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestStepGasCost(t *testing.T) { + s := stepCost{ + {4, 103994170}, + {7, 112356810}, + {13, 122912610}, + {26, 137559930}, + {52, 162039100}, + {103, 210960780}, + {205, 318351180}, + {410, 528274980}, + } + + assert.EqualValues(t, 0, s.Lookup(0)) + assert.EqualValues(t, 0, s.Lookup(3)) + assert.EqualValues(t, 103994170, s.Lookup(4)) + assert.EqualValues(t, 103994170, s.Lookup(6)) + assert.EqualValues(t, 112356810, s.Lookup(7)) + assert.EqualValues(t, 210960780, s.Lookup(103)) + assert.EqualValues(t, 210960780, s.Lookup(204)) + assert.EqualValues(t, 318351180, s.Lookup(205)) + assert.EqualValues(t, 318351180, s.Lookup(409)) + assert.EqualValues(t, 528274980, s.Lookup(410)) + assert.EqualValues(t, 528274980, s.Lookup(10000000000)) +} From 10b931312bce2649227ef11a1ecb5c8a493898f8 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Thu, 27 May 2021 13:42:42 -0400 Subject: [PATCH 49/88] Fix edgecase in tipset skipcache --- chain/store/index.go | 3 +++ chain/sync_test.go | 3 +++ 2 files changed, 6 insertions(+) diff --git a/chain/store/index.go b/chain/store/index.go index a9da994af..324fb7a63 100644 --- a/chain/store/index.go +++ b/chain/store/index.go @@ -107,6 +107,9 @@ func (ci *ChainIndex) fillCache(tsk types.TipSetKey) (*lbEntry, error) { } rheight -= ci.skipLength + if rheight < 0 { + rheight = 0 + } var skipTarget *types.TipSet if parent.Height() < rheight { diff --git a/chain/sync_test.go b/chain/sync_test.go index 2e5a5f3cf..095b224ad 100644 --- a/chain/sync_test.go +++ b/chain/sync_test.go @@ -895,6 +895,7 @@ func TestSyncCheckpointEarlierThanHead(t *testing.T) { func TestDrandNull(t *testing.T) { H := 10 v5h := abi.ChainEpoch(50) + ov5h := build.UpgradeHyperdriveHeight build.UpgradeHyperdriveHeight = v5h tu := prepSyncTestWithV5Height(t, H, v5h) @@ -942,4 +943,6 @@ func TestDrandNull(t *testing.T) { require.NoError(t, err) require.Equal(t, []byte(rand), expectedRand) + build.UpgradeHyperdriveHeight = ov5h + } From fba5c65ffdddc9de8959255f594ce3c2bdae02d9 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Fri, 28 May 2021 12:35:48 -0400 Subject: [PATCH 50/88] Extend the default deal start epoch delay --- node/impl/client/client.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/node/impl/client/client.go b/node/impl/client/client.go index cdef4d02b..c2987ddd2 100644 --- a/node/impl/client/client.go +++ b/node/impl/client/client.go @@ -62,7 +62,8 @@ import ( var DefaultHashFunction = uint64(mh.BLAKE2B_MIN + 31) -const dealStartBufferHours uint64 = 49 +// 8 days ~= SealDuration + PreCommit + MaxProveCommitDuration + 8 hour buffer +const dealStartBufferHours uint64 = 8 * 24 type API struct { fx.In From 8003a8a2d0a236974eaa68c97e9dd966a666d1ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Sun, 30 May 2021 17:20:14 +0200 Subject: [PATCH 51/88] events: Fix handling of multiple matched events per epoch --- chain/events/events_called.go | 12 ++++--- chain/events/events_test.go | 59 +++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/chain/events/events_called.go b/chain/events/events_called.go index 7f39e9038..e84484480 100644 --- a/chain/events/events_called.go +++ b/chain/events/events_called.go @@ -142,8 +142,10 @@ func (e *hcEvents) processHeadChangeEvent(rev, app []*types.TipSet) error { // Queue up calls until there have been enough blocks to reach // confidence on the message calls - for tid, data := range newCalls { - e.queueForConfidence(tid, data, nil, ts) + for tid, calls := range newCalls { + for _, data := range calls { + e.queueForConfidence(tid, data, nil, ts) + } } for at := e.lastTs.Height(); at <= ts.Height(); at++ { @@ -472,7 +474,7 @@ func newMessageEvents(ctx context.Context, hcAPI headChangeAPI, cs eventAPI) mes } // Check if there are any new actor calls -func (me *messageEvents) checkNewCalls(ts *types.TipSet) (map[triggerID]eventData, error) { +func (me *messageEvents) checkNewCalls(ts *types.TipSet) (map[triggerID][]eventData, error) { pts, err := me.cs.ChainGetTipSet(me.ctx, ts.Parents()) // we actually care about messages in the parent tipset here if err != nil { log.Errorf("getting parent tipset in checkNewCalls: %s", err) @@ -483,7 +485,7 @@ func (me *messageEvents) checkNewCalls(ts *types.TipSet) (map[triggerID]eventDat defer me.lk.RUnlock() // For each message in the tipset - res := make(map[triggerID]eventData) + res := make(map[triggerID][]eventData) me.messagesForTs(pts, func(msg *types.Message) { // TODO: provide receipts @@ -498,7 +500,7 @@ func (me *messageEvents) checkNewCalls(ts *types.TipSet) (map[triggerID]eventDat // If there was a match, include the message in the results for the // trigger if matched { - res[tid] = msg + res[tid] = append(res[tid], msg) } } }) diff --git a/chain/events/events_test.go b/chain/events/events_test.go index 3957f425c..b5cbb0594 100644 --- a/chain/events/events_test.go +++ b/chain/events/events_test.go @@ -1323,3 +1323,62 @@ func TestStateChangedTimeout(t *testing.T) { fcs.advance(0, 5, nil) require.False(t, called) } + +func TestCalledMultiplePerEpoch(t *testing.T) { + fcs := &fakeCS{ + t: t, + h: 1, + + msgs: map[cid.Cid]fakeMsg{}, + blkMsgs: map[cid.Cid]cid.Cid{}, + tsc: newTSCache(2*build.ForkLengthThreshold, nil), + } + require.NoError(t, fcs.tsc.add(fcs.makeTs(t, nil, 1, dummyCid))) + + events := NewEvents(context.Background(), fcs) + + t0123, err := address.NewFromString("t0123") + require.NoError(t, err) + + at := 0 + + err = events.Called(func(ts *types.TipSet) (d bool, m bool, e error) { + return false, true, nil + }, func(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH abi.ChainEpoch) (bool, error) { + switch at { + case 0: + require.Equal(t, uint64(1), msg.Nonce) + require.Equal(t, abi.ChainEpoch(4), ts.Height()) + case 1: + require.Equal(t, uint64(2), msg.Nonce) + require.Equal(t, abi.ChainEpoch(4), ts.Height()) + default: + t.Fatal("apply should only get called twice, at: ", at) + } + at++ + return true, nil + }, func(_ context.Context, ts *types.TipSet) error { + switch at { + case 2: + require.Equal(t, abi.ChainEpoch(4), ts.Height()) + case 3: + require.Equal(t, abi.ChainEpoch(4), ts.Height()) + default: + t.Fatal("revert should only get called twice, at: ", at) + } + at++ + return nil + }, 3, 20, matchAddrMethod(t0123, 5)) + require.NoError(t, err) + + fcs.advance(0, 10, map[int]cid.Cid{ + 1: fcs.fakeMsgs(fakeMsg{ + bmsgs: []*types.Message{ + {To: t0123, From: t0123, Method: 5, Nonce: 1}, + {To: t0123, From: t0123, Method: 5, Nonce: 2}, + }, + }), + }) + + fcs.advance(9, 1, nil) +} From 7fca1c1ee7a1a0c804b753fb6d39d4514154571b Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Thu, 27 May 2021 12:06:15 -0400 Subject: [PATCH 52/88] Implement FIP-0015 --- api/test/window_post.go | 154 ++++++++++++++++++++++++++++++++++++++++ build/params_mainnet.go | 2 +- chain/vm/vm.go | 39 +++++----- node/node_test.go | 28 ++++++++ 4 files changed, 205 insertions(+), 18 deletions(-) diff --git a/api/test/window_post.go b/api/test/window_post.go index e6e3ff2a3..2d3302d64 100644 --- a/api/test/window_post.go +++ b/api/test/window_post.go @@ -6,6 +6,8 @@ import ( "testing" "time" + "github.com/filecoin-project/go-state-types/big" + "github.com/stretchr/testify/require" "github.com/filecoin-project/go-address" @@ -846,3 +848,155 @@ waitForProof: require.Contains(t, err.Error(), "failed to dispute valid post (RetCode=16)") } } + +func TestWindowPostBaseFeeNoBurn(t *testing.T, b APIBuilder, blocktime time.Duration) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + och := build.UpgradeClausHeight + build.UpgradeClausHeight = 10 + n, sn := b(t, DefaultFullOpts(1), OneMiner) + + client := n[0].FullNode.(*impl.FullNodeAPI) + miner := sn[0] + + { + addrinfo, err := client.NetAddrsListen(ctx) + if err != nil { + t.Fatal(err) + } + + if err := miner.NetConnect(ctx, addrinfo); err != nil { + t.Fatal(err) + } + } + + maddr, err := miner.ActorAddress(ctx) + require.NoError(t, err) + + mi, err := client.StateMinerInfo(ctx, maddr, types.EmptyTSK) + require.NoError(t, err) + + build.Clock.Sleep(time.Second) + + done := make(chan struct{}) + go func() { + defer close(done) + for ctx.Err() == nil { + build.Clock.Sleep(blocktime) + if err := miner.MineOne(ctx, MineNext); err != nil { + if ctx.Err() != nil { + // context was canceled, ignore the error. + return + } + t.Error(err) + } + } + }() + defer func() { + cancel() + <-done + }() + + pledgeSectors(t, ctx, miner, 10, 0, nil) + wact, err := client.StateGetActor(ctx, mi.Worker, types.EmptyTSK) + require.NoError(t, err) + en := wact.Nonce + + // wait for a new message to be sent from worker address, it will be a PoSt + +waitForProof: + for { + wact, err := client.StateGetActor(ctx, mi.Worker, types.EmptyTSK) + require.NoError(t, err) + if wact.Nonce > en { + break waitForProof + } + + build.Clock.Sleep(blocktime) + } + + slm, err := client.StateListMessages(ctx, &api.MessageMatch{To: maddr}, types.EmptyTSK, 0) + require.NoError(t, err) + + pmr, err := client.StateReplay(ctx, types.EmptyTSK, slm[0]) + require.NoError(t, err) + + require.Equal(t, pmr.GasCost.BaseFeeBurn, big.Zero()) + + build.UpgradeClausHeight = och +} + +func TestWindowPostBaseFeeBurn(t *testing.T, b APIBuilder, blocktime time.Duration) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + n, sn := b(t, []FullNodeOpts{FullNodeWithLatestActorsAt(-1)}, OneMiner) + + client := n[0].FullNode.(*impl.FullNodeAPI) + miner := sn[0] + + { + addrinfo, err := client.NetAddrsListen(ctx) + if err != nil { + t.Fatal(err) + } + + if err := miner.NetConnect(ctx, addrinfo); err != nil { + t.Fatal(err) + } + } + + maddr, err := miner.ActorAddress(ctx) + require.NoError(t, err) + + mi, err := client.StateMinerInfo(ctx, maddr, types.EmptyTSK) + require.NoError(t, err) + + build.Clock.Sleep(time.Second) + + done := make(chan struct{}) + go func() { + defer close(done) + for ctx.Err() == nil { + build.Clock.Sleep(blocktime) + if err := miner.MineOne(ctx, MineNext); err != nil { + if ctx.Err() != nil { + // context was canceled, ignore the error. + return + } + t.Error(err) + } + } + }() + defer func() { + cancel() + <-done + }() + + pledgeSectors(t, ctx, miner, 10, 0, nil) + wact, err := client.StateGetActor(ctx, mi.Worker, types.EmptyTSK) + require.NoError(t, err) + en := wact.Nonce + + // wait for a new message to be sent from worker address, it will be a PoSt + +waitForProof: + for { + wact, err := client.StateGetActor(ctx, mi.Worker, types.EmptyTSK) + require.NoError(t, err) + if wact.Nonce > en { + break waitForProof + } + + build.Clock.Sleep(blocktime) + } + + slm, err := client.StateListMessages(ctx, &api.MessageMatch{To: maddr}, types.EmptyTSK, 0) + require.NoError(t, err) + + pmr, err := client.StateReplay(ctx, types.EmptyTSK, slm[0]) + require.NoError(t, err) + + require.NotEqual(t, pmr.GasCost.BaseFeeBurn, big.Zero()) +} diff --git a/build/params_mainnet.go b/build/params_mainnet.go index 5c3171a27..2d708f9e4 100644 --- a/build/params_mainnet.go +++ b/build/params_mainnet.go @@ -51,7 +51,7 @@ const UpgradePersianHeight = UpgradeCalicoHeight + (builtin2.EpochsInHour * 60) const UpgradeOrangeHeight = 336458 // 2020-12-22T02:00:00Z -const UpgradeClausHeight = 343200 +var UpgradeClausHeight = abi.ChainEpoch(343200) // 2021-03-04T00:00:30Z const UpgradeTrustHeight = 550321 diff --git a/chain/vm/vm.go b/chain/vm/vm.go index afc74e744..9874ea7da 100644 --- a/chain/vm/vm.go +++ b/chain/vm/vm.go @@ -566,7 +566,7 @@ func (vm *VM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet, gasUsed = 0 } - burn, err := vm.ShouldBurn(st, msg, errcode) + burn, err := vm.ShouldBurn(ctx, st, msg, errcode) if err != nil { return nil, xerrors.Errorf("deciding whether should burn failed: %w", err) } @@ -609,26 +609,31 @@ func (vm *VM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet, }, nil } -func (vm *VM) ShouldBurn(st *state.StateTree, msg *types.Message, errcode exitcode.ExitCode) (bool, error) { - // Check to see if we should burn funds. We avoid burning on successful - // window post. This won't catch _indirect_ window post calls, but this - // is the best we can get for now. - if vm.blockHeight > build.UpgradeClausHeight && errcode == exitcode.Ok && msg.Method == miner.Methods.SubmitWindowedPoSt { - // Ok, we've checked the _method_, but we still need to check - // the target actor. It would be nice if we could just look at - // the trace, but I'm not sure if that's safe? - if toActor, err := st.GetActor(msg.To); err != nil { - // If the actor wasn't found, we probably deleted it or something. Move on. - if !xerrors.Is(err, types.ErrActorNotFound) { - // Otherwise, this should never fail and something is very wrong. - return false, xerrors.Errorf("failed to lookup target actor: %w", err) +func (vm *VM) ShouldBurn(ctx context.Context, st *state.StateTree, msg *types.Message, errcode exitcode.ExitCode) (bool, error) { + if vm.ntwkVersion(ctx, vm.blockHeight) <= network.Version12 { + // Check to see if we should burn funds. We avoid burning on successful + // window post. This won't catch _indirect_ window post calls, but this + // is the best we can get for now. + if vm.blockHeight > build.UpgradeClausHeight && errcode == exitcode.Ok && msg.Method == miner.Methods.SubmitWindowedPoSt { + // Ok, we've checked the _method_, but we still need to check + // the target actor. It would be nice if we could just look at + // the trace, but I'm not sure if that's safe? + if toActor, err := st.GetActor(msg.To); err != nil { + // If the actor wasn't found, we probably deleted it or something. Move on. + if !xerrors.Is(err, types.ErrActorNotFound) { + // Otherwise, this should never fail and something is very wrong. + return false, xerrors.Errorf("failed to lookup target actor: %w", err) + } + } else if builtin.IsStorageMinerActor(toActor.Code) { + // Ok, this is a storage miner and we've processed a window post. Remove the burn. + return false, nil } - } else if builtin.IsStorageMinerActor(toActor.Code) { - // Ok, this is a storage miner and we've processed a window post. Remove the burn. - return false, nil } + + return true, nil } + // Any "don't burn" rules from Network v13 onwards go here, for now we always return true return true, nil } diff --git a/node/node_test.go b/node/node_test.go index 5ae15fd8c..933a0f614 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -245,6 +245,34 @@ func TestWindowPostDisputeFails(t *testing.T) { test.TestWindowPostDisputeFails(t, builder.MockSbBuilder, 2*time.Millisecond) } +func TestWindowPostBaseFeeNoBurn(t *testing.T) { + if os.Getenv("LOTUS_TEST_WINDOW_POST") != "1" { + t.Skip("this takes a few minutes, set LOTUS_TEST_WINDOW_POST=1 to run") + } + logging.SetLogLevel("miner", "ERROR") + logging.SetLogLevel("gen", "ERROR") + logging.SetLogLevel("chainstore", "ERROR") + logging.SetLogLevel("chain", "ERROR") + logging.SetLogLevel("sub", "ERROR") + logging.SetLogLevel("storageminer", "ERROR") + + test.TestWindowPostBaseFeeNoBurn(t, builder.MockSbBuilder, 2*time.Millisecond) +} + +func TestWindowPostBaseFeeBurn(t *testing.T) { + if os.Getenv("LOTUS_TEST_WINDOW_POST") != "1" { + t.Skip("this takes a few minutes, set LOTUS_TEST_WINDOW_POST=1 to run") + } + logging.SetLogLevel("miner", "ERROR") + logging.SetLogLevel("gen", "ERROR") + logging.SetLogLevel("chainstore", "ERROR") + logging.SetLogLevel("chain", "ERROR") + logging.SetLogLevel("sub", "ERROR") + logging.SetLogLevel("storageminer", "ERROR") + + test.TestWindowPostBaseFeeBurn(t, builder.MockSbBuilder, 2*time.Millisecond) +} + func TestDeadlineToggling(t *testing.T) { if os.Getenv("LOTUS_TEST_DEADLINE_TOGGLING") != "1" { t.Skip("this takes a few minutes, set LOTUS_TEST_DEADLINE_TOGGLING=1 to run") From 61554cf3e019ae00abb6bf2d92e4beda0a9331d8 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Thu, 27 May 2021 15:54:31 -0400 Subject: [PATCH 53/88] Update to latest actors --- chain/actors/policy/policy.go | 7 +------ chain/actors/policy/policy.go.template | 10 +++++++--- chain/vm/runtime.go | 4 ++++ extern/storage-sealing/precommit_batch.go | 2 +- go.mod | 2 +- go.sum | 4 ++-- 6 files changed, 16 insertions(+), 13 deletions(-) diff --git a/chain/actors/policy/policy.go b/chain/actors/policy/policy.go index 113544c05..4d115f783 100644 --- a/chain/actors/policy/policy.go +++ b/chain/actors/policy/policy.go @@ -60,8 +60,6 @@ func SetSupportedProofTypes(types ...abi.RegisteredSealProof) { miner4.PreCommitSealProofTypesV7 = make(map[abi.RegisteredSealProof]struct{}, len(types)*2) miner4.PreCommitSealProofTypesV8 = make(map[abi.RegisteredSealProof]struct{}, len(types)) - miner5.PreCommitSealProofTypesV0 = make(map[abi.RegisteredSealProof]struct{}, len(types)) - miner5.PreCommitSealProofTypesV7 = make(map[abi.RegisteredSealProof]struct{}, len(types)*2) miner5.PreCommitSealProofTypesV8 = make(map[abi.RegisteredSealProof]struct{}, len(types)) AddSupportedProofTypes(types...) @@ -93,9 +91,6 @@ func AddSupportedProofTypes(types ...abi.RegisteredSealProof) { miner4.PreCommitSealProofTypesV7[t+abi.RegisteredSealProof_StackedDrg2KiBV1_1] = struct{}{} miner4.PreCommitSealProofTypesV8[t+abi.RegisteredSealProof_StackedDrg2KiBV1_1] = struct{}{} - miner5.PreCommitSealProofTypesV0[t] = struct{}{} - miner5.PreCommitSealProofTypesV7[t] = struct{}{} - miner5.PreCommitSealProofTypesV7[t+abi.RegisteredSealProof_StackedDrg2KiBV1_1] = struct{}{} miner5.PreCommitSealProofTypesV8[t+abi.RegisteredSealProof_StackedDrg2KiBV1_1] = struct{}{} } @@ -308,7 +303,7 @@ func GetDefaultAggregationProof() abi.RegisteredAggregationProof { func GetSectorMaxLifetime(proof abi.RegisteredSealProof, nwVer network.Version) abi.ChainEpoch { if nwVer <= network.Version10 { - return builtin5.SealProofPoliciesV0[proof].SectorMaxLifetime + return builtin4.SealProofPoliciesV0[proof].SectorMaxLifetime } return builtin5.SealProofPoliciesV11[proof].SectorMaxLifetime diff --git a/chain/actors/policy/policy.go.template b/chain/actors/policy/policy.go.template index f5055c423..aef1081cb 100644 --- a/chain/actors/policy/policy.go.template +++ b/chain/actors/policy/policy.go.template @@ -31,10 +31,12 @@ func SetSupportedProofTypes(types ...abi.RegisteredSealProof) { {{range .versions}} {{if (eq . 0)}} miner{{.}}.SupportedProofTypes = make(map[abi.RegisteredSealProof]struct{}, len(types)) - {{else}} + {{else if (le . 4)}} miner{{.}}.PreCommitSealProofTypesV0 = make(map[abi.RegisteredSealProof]struct{}, len(types)) miner{{.}}.PreCommitSealProofTypesV7 = make(map[abi.RegisteredSealProof]struct{}, len(types)*2) miner{{.}}.PreCommitSealProofTypesV8 = make(map[abi.RegisteredSealProof]struct{}, len(types)) + {{else}} + miner{{.}}.PreCommitSealProofTypesV8 = make(map[abi.RegisteredSealProof]struct{}, len(types)) {{end}} {{end}} @@ -53,11 +55,13 @@ func AddSupportedProofTypes(types ...abi.RegisteredSealProof) { {{range .versions}} {{if (eq . 0)}} miner{{.}}.SupportedProofTypes[t] = struct{}{} - {{else}} + {{else if (le . 4)}} miner{{.}}.PreCommitSealProofTypesV0[t] = struct{}{} miner{{.}}.PreCommitSealProofTypesV7[t] = struct{}{} miner{{.}}.PreCommitSealProofTypesV7[t+abi.RegisteredSealProof_StackedDrg2KiBV1_1] = struct{}{} miner{{.}}.PreCommitSealProofTypesV8[t+abi.RegisteredSealProof_StackedDrg2KiBV1_1] = struct{}{} + {{else}} + miner{{.}}.PreCommitSealProofTypesV8[t+abi.RegisteredSealProof_StackedDrg2KiBV1_1] = struct{}{} {{end}} {{end}} } @@ -203,7 +207,7 @@ func GetDefaultAggregationProof() abi.RegisteredAggregationProof { func GetSectorMaxLifetime(proof abi.RegisteredSealProof, nwVer network.Version) abi.ChainEpoch { if nwVer <= network.Version10 { - return builtin{{.latestVersion}}.SealProofPoliciesV0[proof].SectorMaxLifetime + return builtin4.SealProofPoliciesV0[proof].SectorMaxLifetime } return builtin{{.latestVersion}}.SealProofPoliciesV11[proof].SectorMaxLifetime diff --git a/chain/vm/runtime.go b/chain/vm/runtime.go index f89bd9f40..00c04ceb8 100644 --- a/chain/vm/runtime.go +++ b/chain/vm/runtime.go @@ -81,6 +81,10 @@ type Runtime struct { lastGasCharge *types.GasTrace } +func (rt *Runtime) BaseFee() abi.TokenAmount { + return rt.vm.baseFee +} + func (rt *Runtime) NetworkVersion() network.Version { return rt.vm.GetNtwkVersion(rt.ctx, rt.CurrEpoch()) } diff --git a/extern/storage-sealing/precommit_batch.go b/extern/storage-sealing/precommit_batch.go index 69ba47c60..bce8e21d5 100644 --- a/extern/storage-sealing/precommit_batch.go +++ b/extern/storage-sealing/precommit_batch.go @@ -181,7 +181,7 @@ func (b *PreCommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { break } - params.Sectors = append(params.Sectors, p.pci) + params.Sectors = append(params.Sectors, *p.pci) deposit = big.Add(deposit, p.deposit) } diff --git a/go.mod b/go.mod index 98e9d3691..9a2dd0f19 100644 --- a/go.mod +++ b/go.mod @@ -47,7 +47,7 @@ require ( github.com/filecoin-project/specs-actors/v2 v2.3.5-0.20210114162132-5b58b773f4fb github.com/filecoin-project/specs-actors/v3 v3.1.0 github.com/filecoin-project/specs-actors/v4 v4.0.0 - github.com/filecoin-project/specs-actors/v5 v5.0.0-20210517165532-c7cff61d07fb + github.com/filecoin-project/specs-actors/v5 v5.0.0-20210528202914-a9f9f95f5e93 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 diff --git a/go.sum b/go.sum index 3ef546ba4..34d21605f 100644 --- a/go.sum +++ b/go.sum @@ -316,8 +316,8 @@ github.com/filecoin-project/specs-actors/v3 v3.1.0/go.mod h1:mpynccOLlIRy0QnR008 github.com/filecoin-project/specs-actors/v4 v4.0.0 h1:vMALksY5G3J5rj3q9rbcyB+f4Tk1xrLqSgdB3jOok4s= github.com/filecoin-project/specs-actors/v4 v4.0.0/go.mod h1:TkHXf/l7Wyw4ZejyXIPS2rK8bBO0rdwhTZyQQgaglng= github.com/filecoin-project/specs-actors/v5 v5.0.0-20210512015452-4fe3889fff57/go.mod h1:283yBMMUSDB2abcjP/hhrwTkhb9h3sfM6KGrep/ZlBI= -github.com/filecoin-project/specs-actors/v5 v5.0.0-20210517165532-c7cff61d07fb h1:818gGdeEC+7aHGl2X7ptdtYuqoEgRsY3jwz+DvUYUFk= -github.com/filecoin-project/specs-actors/v5 v5.0.0-20210517165532-c7cff61d07fb/go.mod h1:283yBMMUSDB2abcjP/hhrwTkhb9h3sfM6KGrep/ZlBI= +github.com/filecoin-project/specs-actors/v5 v5.0.0-20210528202914-a9f9f95f5e93 h1:PZ5pLy4dZVgL+fXgvSVtPOYhfEYUzEYYVEz7IfG8e5U= +github.com/filecoin-project/specs-actors/v5 v5.0.0-20210528202914-a9f9f95f5e93/go.mod h1:kSDmoQuO8jlhMVzKNoesbhka1e6gHKcLQjKm9mE9Qhw= github.com/filecoin-project/specs-storage v0.1.1-0.20201105051918-5188d9774506 h1:Ur/l2+6qN+lQiqjozWWc5p9UDaAMDZKTlDS98oRnlIw= github.com/filecoin-project/specs-storage v0.1.1-0.20201105051918-5188d9774506/go.mod h1:nJRRM7Aa9XVvygr3W9k6xGF46RWzr2zxF/iGoAIfA/g= github.com/filecoin-project/test-vectors/schema v0.0.5 h1:w3zHQhzM4pYxJDl21avXjOKBLF8egrvwUwjpT8TquDg= From 92f544d96c8b58ec082364cbaf5de2ade09cb219 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Wed, 28 Apr 2021 19:24:09 -0400 Subject: [PATCH 54/88] Add verifreg utils to CLI --- cli/cmd.go | 1 + cli/filplus.go | 277 ++++ cmd/lotus-shed/verifreg.go | 30 +- documentation/en/cli-lotus.md | 2762 +++++++++++++++++++++++++++++++++ 4 files changed, 3060 insertions(+), 10 deletions(-) create mode 100644 cli/filplus.go create mode 100644 documentation/en/cli-lotus.md diff --git a/cli/cmd.go b/cli/cmd.go index efbb3b990..bd7588206 100644 --- a/cli/cmd.go +++ b/cli/cmd.go @@ -70,6 +70,7 @@ var Commands = []*cli.Command{ WithCategory("basic", walletCmd), WithCategory("basic", clientCmd), WithCategory("basic", multisigCmd), + WithCategory("basic", filplusCmd), WithCategory("basic", paychCmd), WithCategory("developer", AuthCmd), WithCategory("developer", MpoolCmd), diff --git a/cli/filplus.go b/cli/filplus.go new file mode 100644 index 000000000..b98ac4ab8 --- /dev/null +++ b/cli/filplus.go @@ -0,0 +1,277 @@ +package cli + +import ( + "context" + "fmt" + + "github.com/filecoin-project/lotus/api" + + verifreg4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/verifreg" + + "github.com/filecoin-project/go-state-types/big" + + "github.com/urfave/cli/v2" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/blockstore" + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/actors" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/actors/builtin/verifreg" + "github.com/filecoin-project/lotus/chain/types" + cbor "github.com/ipfs/go-ipld-cbor" +) + +var filplusCmd = &cli.Command{ + Name: "filplus", + Usage: "Interact with the verified registry actor used by Filplus", + Flags: []cli.Flag{}, + Subcommands: []*cli.Command{ + filplusVerifyClientCmd, + filplusListNotariesCmd, + filplusListClientsCmd, + filplusCheckClientCmd, + filplusCheckNotaryCmd, + }, +} + +var filplusVerifyClientCmd = &cli.Command{ + Name: "grant-datacap", + Usage: "give allowance to the specified verified client address", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "from", + Usage: "specify your notary address to send the message from", + Required: true, + }, + }, + Action: func(cctx *cli.Context) error { + froms := cctx.String("from") + if froms == "" { + return fmt.Errorf("must specify from address with --from") + } + + fromk, err := address.NewFromString(froms) + if err != nil { + return err + } + + if cctx.Args().Len() != 2 { + return fmt.Errorf("must specify two arguments: address and allowance") + } + + target, err := address.NewFromString(cctx.Args().Get(0)) + if err != nil { + return err + } + + allowance, err := types.BigFromString(cctx.Args().Get(1)) + if err != nil { + return err + } + + api, closer, err := GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := ReqContext(cctx) + + found, dcap, err := checkNotary(ctx, api, fromk) + if err != nil { + return err + } + + if !found { + return xerrors.New("sender address must be a notary") + } + + if dcap.Cmp(allowance.Int) < 0 { + return xerrors.Errorf("cannot allot more allowance than notary data cap: %s < %s", dcap, allowance) + } + + // TODO: This should be abstracted over actor versions + params, err := actors.SerializeParams(&verifreg4.AddVerifiedClientParams{Address: target, Allowance: allowance}) + if err != nil { + return err + } + + msg := &types.Message{ + To: verifreg.Address, + From: fromk, + Method: verifreg.Methods.AddVerifiedClient, + Params: params, + } + + smsg, err := api.MpoolPushMessage(ctx, msg, nil) + if err != nil { + return err + } + + fmt.Printf("message sent, now waiting on cid: %s\n", smsg.Cid()) + + mwait, err := api.StateWaitMsg(ctx, smsg.Cid(), build.MessageConfidence) + if err != nil { + return err + } + + if mwait.Receipt.ExitCode != 0 { + return fmt.Errorf("failed to add verified client: %d", mwait.Receipt.ExitCode) + } + + return nil + }, +} + +var filplusListNotariesCmd = &cli.Command{ + Name: "list-notaries", + Usage: "list all notaries", + Action: func(cctx *cli.Context) error { + api, closer, err := GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := ReqContext(cctx) + + act, err := api.StateGetActor(ctx, verifreg.Address, types.EmptyTSK) + if err != nil { + return err + } + + apibs := blockstore.NewAPIBlockstore(api) + store := adt.WrapStore(ctx, cbor.NewCborStore(apibs)) + + st, err := verifreg.Load(store, act) + if err != nil { + return err + } + return st.ForEachVerifier(func(addr address.Address, dcap abi.StoragePower) error { + _, err := fmt.Printf("%s: %s\n", addr, dcap) + return err + }) + }, +} + +var filplusListClientsCmd = &cli.Command{ + Name: "list-clients", + Usage: "list all verified clients", + Action: func(cctx *cli.Context) error { + api, closer, err := GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := ReqContext(cctx) + + act, err := api.StateGetActor(ctx, verifreg.Address, types.EmptyTSK) + if err != nil { + return err + } + + apibs := blockstore.NewAPIBlockstore(api) + store := adt.WrapStore(ctx, cbor.NewCborStore(apibs)) + + st, err := verifreg.Load(store, act) + if err != nil { + return err + } + return st.ForEachClient(func(addr address.Address, dcap abi.StoragePower) error { + _, err := fmt.Printf("%s: %s\n", addr, dcap) + return err + }) + }, +} + +var filplusCheckClientCmd = &cli.Command{ + Name: "check-client-datacap", + Usage: "check verified client remaining bytes", + Action: func(cctx *cli.Context) error { + if !cctx.Args().Present() { + return fmt.Errorf("must specify client address to check") + } + + caddr, err := address.NewFromString(cctx.Args().First()) + if err != nil { + return err + } + + api, closer, err := GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := ReqContext(cctx) + + dcap, err := api.StateVerifiedClientStatus(ctx, caddr, types.EmptyTSK) + if err != nil { + return err + } + if dcap == nil { + return xerrors.Errorf("client %s is not a verified client", err) + } + + fmt.Println(*dcap) + + return nil + }, +} + +var filplusCheckNotaryCmd = &cli.Command{ + Name: "check-notaries-datacap", + Usage: "check notaries remaining bytes", + Action: func(cctx *cli.Context) error { + if !cctx.Args().Present() { + return fmt.Errorf("must specify notary address to check") + } + + vaddr, err := address.NewFromString(cctx.Args().First()) + if err != nil { + return err + } + + api, closer, err := GetFullNodeAPI(cctx) + if err != nil { + return err + } + defer closer() + ctx := ReqContext(cctx) + + found, dcap, err := checkNotary(ctx, api, vaddr) + if err != nil { + return err + } + if !found { + return fmt.Errorf("not found") + } + + fmt.Println(dcap) + + return nil + }, +} + +func checkNotary(ctx context.Context, api api.FullNode, vaddr address.Address) (bool, abi.StoragePower, error) { + vid, err := api.StateLookupID(ctx, vaddr, types.EmptyTSK) + if err != nil { + return false, big.Zero(), err + } + + act, err := api.StateGetActor(ctx, verifreg.Address, types.EmptyTSK) + if err != nil { + return false, big.Zero(), err + } + + apibs := blockstore.NewAPIBlockstore(api) + store := adt.WrapStore(ctx, cbor.NewCborStore(apibs)) + + st, err := verifreg.Load(store, act) + if err != nil { + return false, big.Zero(), err + } + + return st.VerifierDataCap(vid) +} diff --git a/cmd/lotus-shed/verifreg.go b/cmd/lotus-shed/verifreg.go index 426827ad2..988de5d53 100644 --- a/cmd/lotus-shed/verifreg.go +++ b/cmd/lotus-shed/verifreg.go @@ -102,8 +102,9 @@ var verifRegAddVerifierCmd = &cli.Command{ } var verifRegVerifyClientCmd = &cli.Command{ - Name: "verify-client", - Usage: "make a given account a verified client", + Name: "verify-client", + Usage: "make a given account a verified client", + Hidden: true, Flags: []cli.Flag{ &cli.StringFlag{ Name: "from", @@ -111,6 +112,7 @@ var verifRegVerifyClientCmd = &cli.Command{ }, }, Action: func(cctx *cli.Context) error { + fmt.Println("DEPRECATED: This behavior is being moved to `lotus verifreg`") froms := cctx.String("from") if froms == "" { return fmt.Errorf("must specify from address with --from") @@ -175,9 +177,11 @@ var verifRegVerifyClientCmd = &cli.Command{ } var verifRegListVerifiersCmd = &cli.Command{ - Name: "list-verifiers", - Usage: "list all verifiers", + Name: "list-verifiers", + Usage: "list all verifiers", + Hidden: true, Action: func(cctx *cli.Context) error { + fmt.Println("DEPRECATED: This behavior is being moved to `lotus verifreg`") api, closer, err := lcli.GetFullNodeAPI(cctx) if err != nil { return err @@ -205,9 +209,11 @@ var verifRegListVerifiersCmd = &cli.Command{ } var verifRegListClientsCmd = &cli.Command{ - Name: "list-clients", - Usage: "list all verified clients", + Name: "list-clients", + Usage: "list all verified clients", + Hidden: true, Action: func(cctx *cli.Context) error { + fmt.Println("DEPRECATED: This behavior is being moved to `lotus verifreg`") api, closer, err := lcli.GetFullNodeAPI(cctx) if err != nil { return err @@ -235,9 +241,11 @@ var verifRegListClientsCmd = &cli.Command{ } var verifRegCheckClientCmd = &cli.Command{ - Name: "check-client", - Usage: "check verified client remaining bytes", + Name: "check-client", + Usage: "check verified client remaining bytes", + Hidden: true, Action: func(cctx *cli.Context) error { + fmt.Println("DEPRECATED: This behavior is being moved to `lotus verifreg`") if !cctx.Args().Present() { return fmt.Errorf("must specify client address to check") } @@ -269,9 +277,11 @@ var verifRegCheckClientCmd = &cli.Command{ } var verifRegCheckVerifierCmd = &cli.Command{ - Name: "check-verifier", - Usage: "check verifiers remaining bytes", + Name: "check-verifier", + Usage: "check verifiers remaining bytes", + Hidden: true, Action: func(cctx *cli.Context) error { + fmt.Println("DEPRECATED: This behavior is being moved to `lotus verifreg`") if !cctx.Args().Present() { return fmt.Errorf("must specify verifier address to check") } diff --git a/documentation/en/cli-lotus.md b/documentation/en/cli-lotus.md new file mode 100644 index 000000000..82c8dfad3 --- /dev/null +++ b/documentation/en/cli-lotus.md @@ -0,0 +1,2762 @@ +# lotus +``` +NAME: + lotus - Filecoin decentralized storage network client + +USAGE: + lotus [global options] command [command options] [arguments...] + +VERSION: + 1.11.0-dev + +COMMANDS: + daemon Start a lotus daemon process + backup Create node metadata backup + version Print version + help, h Shows a list of commands or help for one command + BASIC: + send Send funds between accounts + wallet Manage wallet + client Make deals, store data, retrieve data + msig Interact with a multisig wallet + verifreg Interact with the verified registry actor + paych Manage payment channels + DEVELOPER: + auth Manage RPC permissions + mpool Manage message pool + state Interact with and query filecoin chain state + chain Interact with filecoin blockchain + log Manage logging + wait-api Wait for lotus api to come online + fetch-params Fetch proving parameters + NETWORK: + net Manage P2P Network + sync Inspect or interact with the chain syncer + +GLOBAL OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) +``` + +## lotus daemon +``` +NAME: + lotus daemon - Start a lotus daemon process + +USAGE: + lotus daemon command [command options] [arguments...] + +COMMANDS: + stop Stop a running lotus daemon + help, h Shows a list of commands or help for one command + +OPTIONS: + --api value (default: "1234") + --genesis value genesis file to use for first node run + --bootstrap (default: true) + --import-chain value on first run, load chain from given file or url and validate + --import-snapshot value import chain state from a given chain export file or url + --halt-after-import halt the process after importing chain from file (default: false) + --pprof value specify name of file for writing cpu profile to + --profile value specify type of node + --manage-fdlimit manage open file limit (default: true) + --config value specify path of config file to use + --api-max-req-size value maximum API request size accepted by the JSON RPC server (default: 0) + --restore value restore from backup file + --restore-config value config file to use when restoring from backup + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +### lotus daemon stop +``` +NAME: + lotus daemon stop - Stop a running lotus daemon + +USAGE: + lotus daemon stop [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +## lotus backup +``` +NAME: + lotus backup - Create node metadata backup + +USAGE: + lotus backup [command options] [backup file path] + +DESCRIPTION: + The backup command writes a copy of node metadata under the specified path + +Online backups: +For security reasons, the daemon must be have LOTUS_BACKUP_BASE_PATH env var set +to a path where backup files are supposed to be saved, and the path specified in +this command must be within this base path + +OPTIONS: + --offline create backup without the node running (default: false) + --help, -h show help (default: false) + +``` + +## lotus version +``` +NAME: + lotus version - Print version + +USAGE: + lotus version [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +## lotus send +``` +NAME: + lotus send - Send funds between accounts + +USAGE: + lotus send [command options] [targetAddress] [amount] + +CATEGORY: + BASIC + +OPTIONS: + --from value optionally specify the account to send funds from + --gas-premium value specify gas price to use in AttoFIL (default: "0") + --gas-feecap value specify gas fee cap to use in AttoFIL (default: "0") + --gas-limit value specify gas limit (default: 0) + --nonce value specify the nonce to use (default: 0) + --method value specify method to invoke (default: 0) + --params-json value specify invocation parameters in json + --params-hex value specify invocation parameters in hex + --force must be specified for the action to take effect if maybe SysErrInsufficientFunds etc (default: false) + --help, -h show help (default: false) + +``` + +## lotus wallet +``` +NAME: + lotus wallet - Manage wallet + +USAGE: + lotus wallet command [command options] [arguments...] + +COMMANDS: + new Generate a new key of the given type + list List wallet address + balance Get account balance + export export keys + import import keys + default Get default wallet address + set-default Set default wallet address + sign sign a message + verify verify the signature of a message + delete Delete an account from the wallet + market Interact with market balances + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +### lotus wallet new +``` +NAME: + lotus wallet new - Generate a new key of the given type + +USAGE: + lotus wallet new [command options] [bls|secp256k1 (default secp256k1)] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus wallet list +``` +NAME: + lotus wallet list - List wallet address + +USAGE: + lotus wallet list [command options] [arguments...] + +OPTIONS: + --addr-only, -a Only print addresses (default: false) + --id, -i Output ID addresses (default: false) + --market, -m Output market balances (default: false) + --help, -h show help (default: false) + +``` + +### lotus wallet balance +``` +NAME: + lotus wallet balance - Get account balance + +USAGE: + lotus wallet balance [command options] [address] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus wallet export +``` +NAME: + lotus wallet export - export keys + +USAGE: + lotus wallet export [command options] [address] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus wallet import +``` +NAME: + lotus wallet import - import keys + +USAGE: + lotus wallet import [command options] [ (optional, will read from stdin if omitted)] + +OPTIONS: + --format value specify input format for key (default: "hex-lotus") + --as-default import the given key as your new default key (default: false) + --help, -h show help (default: false) + +``` + +### lotus wallet default +``` +NAME: + lotus wallet default - Get default wallet address + +USAGE: + lotus wallet default [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus wallet set-default +``` +NAME: + lotus wallet set-default - Set default wallet address + +USAGE: + lotus wallet set-default [command options] [address] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus wallet sign +``` +NAME: + lotus wallet sign - sign a message + +USAGE: + lotus wallet sign [command options] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus wallet verify +``` +NAME: + lotus wallet verify - verify the signature of a message + +USAGE: + lotus wallet verify [command options] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus wallet delete +``` +NAME: + lotus wallet delete - Delete an account from the wallet + +USAGE: + lotus wallet delete [command options]

+ +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus wallet market +``` +NAME: + lotus wallet market - Interact with market balances + +USAGE: + lotus wallet market command [command options] [arguments...] + +COMMANDS: + withdraw Withdraw funds from the Storage Market Actor + add Add funds to the Storage Market Actor + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +#### lotus wallet market withdraw +``` +NAME: + lotus wallet market withdraw - Withdraw funds from the Storage Market Actor + +USAGE: + lotus wallet market withdraw [command options] [amount (FIL) optional, otherwise will withdraw max available] + +OPTIONS: + --wallet value, -w value Specify address to withdraw funds to, otherwise it will use the default wallet address + --address value, -a value Market address to withdraw from (account or miner actor address, defaults to --wallet address) + --help, -h show help (default: false) + +``` + +#### lotus wallet market add +``` +NAME: + lotus wallet market add - Add funds to the Storage Market Actor + +USAGE: + lotus wallet market add [command options] + +OPTIONS: + --from value, -f value Specify address to move funds from, otherwise it will use the default wallet address + --address value, -a value Market address to move funds to (account or miner actor address, defaults to --from address) + --help, -h show help (default: false) + +``` + +## lotus client +``` +NAME: + lotus client - Make deals, store data, retrieve data + +USAGE: + lotus client command [command options] [arguments...] + +COMMANDS: + help, h Shows a list of commands or help for one command + DATA: + import Import data + drop Remove import + local List locally imported data + stat Print information about a locally stored file (piece size, etc) + RETRIEVAL: + find Find data in the network + retrieve Retrieve data from network + cancel-retrieval Cancel a retrieval deal by deal ID; this also cancels the associated transfer + STORAGE: + deal Initialize storage deal with a miner + query-ask Find a miners ask + list-deals List storage market deals + get-deal Print detailed deal information + list-asks List asks for top miners + deal-stats Print statistics about local storage deals + inspect-deal Inspect detailed information about deal's lifecycle and the various stages it goes through + UTIL: + commP Calculate the piece-cid (commP) of a CAR file + generate-car Generate a car file from input + balances Print storage market client balances + list-transfers List ongoing data transfers for deals + restart-transfer Force restart a stalled data transfer + cancel-transfer Force cancel a data transfer + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +### lotus client import +``` +NAME: + lotus client import - Import data + +USAGE: + lotus client import [command options] [inputPath] + +CATEGORY: + DATA + +OPTIONS: + --car import from a car file instead of a regular file (default: false) + --quiet, -q Output root CID only (default: false) + --help, -h show help (default: false) + +``` + +### lotus client drop +``` +NAME: + lotus client drop - Remove import + +USAGE: + lotus client drop [command options] [import ID...] + +CATEGORY: + DATA + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus client local +``` +NAME: + lotus client local - List locally imported data + +USAGE: + lotus client local [command options] [arguments...] + +CATEGORY: + DATA + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus client stat +``` +NAME: + lotus client stat - Print information about a locally stored file (piece size, etc) + +USAGE: + lotus client stat [command options] + +CATEGORY: + DATA + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus client find +``` +NAME: + lotus client find - Find data in the network + +USAGE: + lotus client find [command options] [dataCid] + +CATEGORY: + RETRIEVAL + +OPTIONS: + --pieceCid value require data to be retrieved from a specific Piece CID + --help, -h show help (default: false) + +``` + +### lotus client retrieve +``` +NAME: + lotus client retrieve - Retrieve data from network + +USAGE: + lotus client retrieve [command options] [dataCid outputPath] + +CATEGORY: + RETRIEVAL + +OPTIONS: + --from value address to send transactions from + --car export to a car file instead of a regular file (default: false) + --miner value miner address for retrieval, if not present it'll use local discovery + --maxPrice value maximum price the client is willing to consider (default: 0.01 FIL) + --pieceCid value require data to be retrieved from a specific Piece CID + --allow-local (default: false) + --help, -h show help (default: false) + +``` + +### lotus client cancel-retrieval +``` +NAME: + lotus client cancel-retrieval - Cancel a retrieval deal by deal ID; this also cancels the associated transfer + +USAGE: + lotus client cancel-retrieval [command options] [arguments...] + +CATEGORY: + RETRIEVAL + +OPTIONS: + --deal-id value specify retrieval deal by deal ID (default: 0) + --help, -h show help (default: false) + +``` + +### lotus client deal +``` +NAME: + lotus client deal - Initialize storage deal with a miner + +USAGE: + lotus client deal [command options] [dataCid miner price duration] + +CATEGORY: + STORAGE + +DESCRIPTION: + Make a deal with a miner. +dataCid comes from running 'lotus client import'. +miner is the address of the miner you wish to make a deal with. +price is measured in FIL/GB/Epoch. Miners usually don't accept a bid +lower than their advertised ask. You can check a miners listed price +with 'lotus client query-ask '. +duration is how long the miner should store the data for, in blocks. +The minimum value is 518400 (6 months). + +OPTIONS: + --manual-piece-cid value manually specify piece commitment for data (dataCid must be to a car file) + --manual-piece-size value if manually specifying piece cid, used to specify size (dataCid must be to a car file) (default: 0) + --from value specify address to fund the deal with + --start-epoch value specify the epoch that the deal should start at (default: -1) + --fast-retrieval indicates that data should be available for fast retrieval (default: true) + --verified-deal indicate that the deal counts towards verified client total (default: true if client is verified, false otherwise) + --provider-collateral value specify the requested provider collateral the miner should put up + --help, -h show help (default: false) + +``` + +### lotus client query-ask +``` +NAME: + lotus client query-ask - Find a miners ask + +USAGE: + lotus client query-ask [command options] [minerAddress] + +CATEGORY: + STORAGE + +OPTIONS: + --peerid value specify peer ID of node to make query against + --size value data size in bytes (default: 0) + --duration value deal duration (default: 0) + --help, -h show help (default: false) + +``` + +### lotus client list-deals +``` +NAME: + lotus client list-deals - List storage market deals + +USAGE: + lotus client list-deals [command options] [arguments...] + +CATEGORY: + STORAGE + +OPTIONS: + --verbose, -v print verbose deal details (default: false) + --color use color in display output (default: true) + --show-failed show failed/failing deals (default: false) + --watch watch deal updates in real-time, rather than a one time list (default: false) + --help, -h show help (default: false) + +``` + +### lotus client get-deal +``` +NAME: + lotus client get-deal - Print detailed deal information + +USAGE: + lotus client get-deal [command options] [arguments...] + +CATEGORY: + STORAGE + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus client list-asks +``` +NAME: + lotus client list-asks - List asks for top miners + +USAGE: + lotus client list-asks [command options] [arguments...] + +CATEGORY: + STORAGE + +OPTIONS: + --by-ping sort by ping (default: false) + --output-format value Either 'text' or 'csv' (default: "text") + --help, -h show help (default: false) + +``` + +### lotus client deal-stats +``` +NAME: + lotus client deal-stats - Print statistics about local storage deals + +USAGE: + lotus client deal-stats [command options] [arguments...] + +CATEGORY: + STORAGE + +OPTIONS: + --newer-than value (default: 0s) + --help, -h show help (default: false) + +``` + +### lotus client inspect-deal +``` +NAME: + lotus client inspect-deal - Inspect detailed information about deal's lifecycle and the various stages it goes through + +USAGE: + lotus client inspect-deal [command options] [arguments...] + +CATEGORY: + STORAGE + +OPTIONS: + --deal-id value (default: 0) + --proposal-cid value + --help, -h show help (default: false) + +``` + +### lotus client commP +``` +NAME: + lotus client commP - Calculate the piece-cid (commP) of a CAR file + +USAGE: + lotus client commP [command options] [inputFile] + +CATEGORY: + UTIL + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus client generate-car +``` +NAME: + lotus client generate-car - Generate a car file from input + +USAGE: + lotus client generate-car [command options] [inputPath outputPath] + +CATEGORY: + UTIL + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus client balances +``` +NAME: + lotus client balances - Print storage market client balances + +USAGE: + lotus client balances [command options] [arguments...] + +CATEGORY: + UTIL + +OPTIONS: + --client value specify storage client address + --help, -h show help (default: false) + +``` + +### lotus client list-transfers +``` +NAME: + lotus client list-transfers - List ongoing data transfers for deals + +USAGE: + lotus client list-transfers [command options] [arguments...] + +CATEGORY: + UTIL + +OPTIONS: + --verbose, -v print verbose transfer details (default: false) + --color use color in display output (default: true) + --completed show completed data transfers (default: false) + --watch watch deal updates in real-time, rather than a one time list (default: false) + --show-failed show failed/cancelled transfers (default: false) + --help, -h show help (default: false) + +``` + +### lotus client restart-transfer +``` +NAME: + lotus client restart-transfer - Force restart a stalled data transfer + +USAGE: + lotus client restart-transfer [command options] [arguments...] + +CATEGORY: + UTIL + +OPTIONS: + --peerid value narrow to transfer with specific peer + --initiator specify only transfers where peer is/is not initiator (default: true) + --help, -h show help (default: false) + +``` + +### lotus client cancel-transfer +``` +NAME: + lotus client cancel-transfer - Force cancel a data transfer + +USAGE: + lotus client cancel-transfer [command options] [arguments...] + +CATEGORY: + UTIL + +OPTIONS: + --peerid value narrow to transfer with specific peer + --initiator specify only transfers where peer is/is not initiator (default: true) + --cancel-timeout value time to wait for cancel to be sent to storage provider (default: 5s) + --help, -h show help (default: false) + +``` + +## lotus msig +``` +NAME: + lotus msig - Interact with a multisig wallet + +USAGE: + lotus msig command [command options] [arguments...] + +COMMANDS: + create Create a new multisig wallet + inspect Inspect a multisig wallet + propose Propose a multisig transaction + propose-remove Propose to remove a signer + approve Approve a multisig message + add-propose Propose to add a signer + add-approve Approve a message to add a signer + add-cancel Cancel a message to add a signer + swap-propose Propose to swap signers + swap-approve Approve a message to swap signers + swap-cancel Cancel a message to swap signers + lock-propose Propose to lock up some balance + lock-approve Approve a message to lock up some balance + lock-cancel Cancel a message to lock up some balance + vested Gets the amount vested in an msig between two epochs + propose-threshold Propose setting a different signing threshold on the account + help, h Shows a list of commands or help for one command + +OPTIONS: + --confidence value number of block confirmations to wait for (default: 5) + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +### lotus msig create +``` +NAME: + lotus msig create - Create a new multisig wallet + +USAGE: + lotus msig create [command options] [address1 address2 ...] + +OPTIONS: + --required value number of required approvals (uses number of signers provided if omitted) (default: 0) + --value value initial funds to give to multisig (default: "0") + --duration value length of the period over which funds unlock (default: "0") + --from value account to send the create message from + --help, -h show help (default: false) + +``` + +### lotus msig inspect +``` +NAME: + lotus msig inspect - Inspect a multisig wallet + +USAGE: + lotus msig inspect [command options] [address] + +OPTIONS: + --vesting Include vesting details (default: false) + --decode-params Decode parameters of transaction proposals (default: false) + --help, -h show help (default: false) + +``` + +### lotus msig propose +``` +NAME: + lotus msig propose - Propose a multisig transaction + +USAGE: + lotus msig propose [command options] [multisigAddress destinationAddress value (optional)] + +OPTIONS: + --from value account to send the propose message from + --help, -h show help (default: false) + +``` + +### lotus msig propose-remove +``` +NAME: + lotus msig propose-remove - Propose to remove a signer + +USAGE: + lotus msig propose-remove [command options] [multisigAddress signer] + +OPTIONS: + --decrease-threshold whether the number of required signers should be decreased (default: false) + --from value account to send the propose message from + --help, -h show help (default: false) + +``` + +### lotus msig approve +``` +NAME: + lotus msig approve - Approve a multisig message + +USAGE: + lotus msig approve [command options] [proposerAddress destination value [methodId methodParams]] + +OPTIONS: + --from value account to send the approve message from + --help, -h show help (default: false) + +``` + +### lotus msig add-propose +``` +NAME: + lotus msig add-propose - Propose to add a signer + +USAGE: + lotus msig add-propose [command options] [multisigAddress signer] + +OPTIONS: + --increase-threshold whether the number of required signers should be increased (default: false) + --from value account to send the propose message from + --help, -h show help (default: false) + +``` + +### lotus msig add-approve +``` +NAME: + lotus msig add-approve - Approve a message to add a signer + +USAGE: + lotus msig add-approve [command options] [multisigAddress proposerAddress txId newAddress increaseThreshold] + +OPTIONS: + --from value account to send the approve message from + --help, -h show help (default: false) + +``` + +### lotus msig add-cancel +``` +NAME: + lotus msig add-cancel - Cancel a message to add a signer + +USAGE: + lotus msig add-cancel [command options] [multisigAddress txId newAddress increaseThreshold] + +OPTIONS: + --from value account to send the approve message from + --help, -h show help (default: false) + +``` + +### lotus msig swap-propose +``` +NAME: + lotus msig swap-propose - Propose to swap signers + +USAGE: + lotus msig swap-propose [command options] [multisigAddress oldAddress newAddress] + +OPTIONS: + --from value account to send the approve message from + --help, -h show help (default: false) + +``` + +### lotus msig swap-approve +``` +NAME: + lotus msig swap-approve - Approve a message to swap signers + +USAGE: + lotus msig swap-approve [command options] [multisigAddress proposerAddress txId oldAddress newAddress] + +OPTIONS: + --from value account to send the approve message from + --help, -h show help (default: false) + +``` + +### lotus msig swap-cancel +``` +NAME: + lotus msig swap-cancel - Cancel a message to swap signers + +USAGE: + lotus msig swap-cancel [command options] [multisigAddress txId oldAddress newAddress] + +OPTIONS: + --from value account to send the approve message from + --help, -h show help (default: false) + +``` + +### lotus msig lock-propose +``` +NAME: + lotus msig lock-propose - Propose to lock up some balance + +USAGE: + lotus msig lock-propose [command options] [multisigAddress startEpoch unlockDuration amount] + +OPTIONS: + --from value account to send the propose message from + --help, -h show help (default: false) + +``` + +### lotus msig lock-approve +``` +NAME: + lotus msig lock-approve - Approve a message to lock up some balance + +USAGE: + lotus msig lock-approve [command options] [multisigAddress proposerAddress txId startEpoch unlockDuration amount] + +OPTIONS: + --from value account to send the approve message from + --help, -h show help (default: false) + +``` + +### lotus msig lock-cancel +``` +NAME: + lotus msig lock-cancel - Cancel a message to lock up some balance + +USAGE: + lotus msig lock-cancel [command options] [multisigAddress txId startEpoch unlockDuration amount] + +OPTIONS: + --from value account to send the cancel message from + --help, -h show help (default: false) + +``` + +### lotus msig vested +``` +NAME: + lotus msig vested - Gets the amount vested in an msig between two epochs + +USAGE: + lotus msig vested [command options] [multisigAddress] + +OPTIONS: + --start-epoch value start epoch to measure vesting from (default: 0) + --end-epoch value end epoch to stop measure vesting at (default: -1) + --help, -h show help (default: false) + +``` + +### lotus msig propose-threshold +``` +NAME: + lotus msig propose-threshold - Propose setting a different signing threshold on the account + +USAGE: + lotus msig propose-threshold [command options] + +OPTIONS: + --from value account to send the proposal from + --help, -h show help (default: false) + +``` + +## lotus verifreg +``` +NAME: + lotus verifreg - Interact with the verified registry actor + +USAGE: + lotus verifreg command [command options] [arguments...] + +COMMANDS: + verify-client give allowance to the specified verified client address + list-verifiers list all verifiers + list-clients list all verified clients + check-client check verified client remaining bytes + check-verifier check verifiers remaining bytes + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +### lotus verifreg verify-client +``` +NAME: + lotus verifreg verify-client - give allowance to the specified verified client address + +USAGE: + lotus verifreg verify-client [command options] [arguments...] + +OPTIONS: + --from value specify your verifier address to send the message from + --help, -h show help (default: false) + +``` + +### lotus verifreg list-verifiers +``` +NAME: + lotus verifreg list-verifiers - list all verifiers + +USAGE: + lotus verifreg list-verifiers [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus verifreg list-clients +``` +NAME: + lotus verifreg list-clients - list all verified clients + +USAGE: + lotus verifreg list-clients [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus verifreg check-client +``` +NAME: + lotus verifreg check-client - check verified client remaining bytes + +USAGE: + lotus verifreg check-client [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus verifreg check-verifier +``` +NAME: + lotus verifreg check-verifier - check verifiers remaining bytes + +USAGE: + lotus verifreg check-verifier [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +## lotus paych +``` +NAME: + lotus paych - Manage payment channels + +USAGE: + lotus paych command [command options] [arguments...] + +COMMANDS: + add-funds Add funds to the payment channel between fromAddress and toAddress. Creates the payment channel if it doesn't already exist. + list List all locally registered payment channels + voucher Interact with payment channel vouchers + settle Settle a payment channel + status Show the status of an outbound payment channel + status-by-from-to Show the status of an active outbound payment channel by from/to addresses + collect Collect funds for a payment channel + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +### lotus paych add-funds +``` +NAME: + lotus paych add-funds - Add funds to the payment channel between fromAddress and toAddress. Creates the payment channel if it doesn't already exist. + +USAGE: + lotus paych add-funds [command options] [fromAddress toAddress amount] + +OPTIONS: + --restart-retrievals restart stalled retrieval deals on this payment channel (default: true) + --help, -h show help (default: false) + +``` + +### lotus paych list +``` +NAME: + lotus paych list - List all locally registered payment channels + +USAGE: + lotus paych list [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus paych voucher +``` +NAME: + lotus paych voucher - Interact with payment channel vouchers + +USAGE: + lotus paych voucher command [command options] [arguments...] + +COMMANDS: + create Create a signed payment channel voucher + check Check validity of payment channel voucher + add Add payment channel voucher to local datastore + list List stored vouchers for a given payment channel + best-spendable Print vouchers with highest value that is currently spendable for each lane + submit Submit voucher to chain to update payment channel state + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +#### lotus paych voucher create +``` +NAME: + lotus paych voucher create - Create a signed payment channel voucher + +USAGE: + lotus paych voucher create [command options] [channelAddress amount] + +OPTIONS: + --lane value specify payment channel lane to use (default: 0) + --help, -h show help (default: false) + +``` + +#### lotus paych voucher check +``` +NAME: + lotus paych voucher check - Check validity of payment channel voucher + +USAGE: + lotus paych voucher check [command options] [channelAddress voucher] + +OPTIONS: + --help, -h show help (default: false) + +``` + +#### lotus paych voucher add +``` +NAME: + lotus paych voucher add - Add payment channel voucher to local datastore + +USAGE: + lotus paych voucher add [command options] [channelAddress voucher] + +OPTIONS: + --help, -h show help (default: false) + +``` + +#### lotus paych voucher list +``` +NAME: + lotus paych voucher list - List stored vouchers for a given payment channel + +USAGE: + lotus paych voucher list [command options] [channelAddress] + +OPTIONS: + --export Print voucher as serialized string (default: false) + --help, -h show help (default: false) + +``` + +#### lotus paych voucher best-spendable +``` +NAME: + lotus paych voucher best-spendable - Print vouchers with highest value that is currently spendable for each lane + +USAGE: + lotus paych voucher best-spendable [command options] [channelAddress] + +OPTIONS: + --export Print voucher as serialized string (default: false) + --help, -h show help (default: false) + +``` + +#### lotus paych voucher submit +``` +NAME: + lotus paych voucher submit - Submit voucher to chain to update payment channel state + +USAGE: + lotus paych voucher submit [command options] [channelAddress voucher] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus paych settle +``` +NAME: + lotus paych settle - Settle a payment channel + +USAGE: + lotus paych settle [command options] [channelAddress] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus paych status +``` +NAME: + lotus paych status - Show the status of an outbound payment channel + +USAGE: + lotus paych status [command options] [channelAddress] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus paych status-by-from-to +``` +NAME: + lotus paych status-by-from-to - Show the status of an active outbound payment channel by from/to addresses + +USAGE: + lotus paych status-by-from-to [command options] [fromAddress toAddress] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus paych collect +``` +NAME: + lotus paych collect - Collect funds for a payment channel + +USAGE: + lotus paych collect [command options] [channelAddress] + +OPTIONS: + --help, -h show help (default: false) + +``` + +## lotus auth +``` +NAME: + lotus auth - Manage RPC permissions + +USAGE: + lotus auth command [command options] [arguments...] + +COMMANDS: + create-token Create token + api-info Get token with API info required to connect to this node + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +### lotus auth create-token +``` +NAME: + lotus auth create-token - Create token + +USAGE: + lotus auth create-token [command options] [arguments...] + +OPTIONS: + --perm value permission to assign to the token, one of: read, write, sign, admin + --help, -h show help (default: false) + +``` + +### lotus auth api-info +``` +NAME: + lotus auth api-info - Get token with API info required to connect to this node + +USAGE: + lotus auth api-info [command options] [arguments...] + +OPTIONS: + --perm value permission to assign to the token, one of: read, write, sign, admin + --help, -h show help (default: false) + +``` + +## lotus mpool +``` +NAME: + lotus mpool - Manage message pool + +USAGE: + lotus mpool command [command options] [arguments...] + +COMMANDS: + pending Get pending messages + sub Subscribe to mpool changes + stat print mempool stats + replace replace a message in the mempool + find find a message in the mempool + config get or set current mpool configuration + gas-perf Check gas performance of messages in mempool + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +### lotus mpool pending +``` +NAME: + lotus mpool pending - Get pending messages + +USAGE: + lotus mpool pending [command options] [arguments...] + +OPTIONS: + --local print pending messages for addresses in local wallet only (default: false) + --cids only print cids of messages in output (default: false) + --to value return messages to a given address + --from value return messages from a given address + --help, -h show help (default: false) + +``` + +### lotus mpool sub +``` +NAME: + lotus mpool sub - Subscribe to mpool changes + +USAGE: + lotus mpool sub [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus mpool stat +``` +NAME: + lotus mpool stat - print mempool stats + +USAGE: + lotus mpool stat [command options] [arguments...] + +OPTIONS: + --local print stats for addresses in local wallet only (default: false) + --basefee-lookback value number of blocks to look back for minimum basefee (default: 60) + --help, -h show help (default: false) + +``` + +### lotus mpool replace +``` +NAME: + lotus mpool replace - replace a message in the mempool + +USAGE: + lotus mpool replace [command options] | + +OPTIONS: + --gas-feecap value gas feecap for new message (burn and pay to miner, attoFIL/GasUnit) + --gas-premium value gas price for new message (pay to miner, attoFIL/GasUnit) + --gas-limit value gas limit for new message (GasUnit) (default: 0) + --auto automatically reprice the specified message (default: false) + --max-fee value Spend up to X attoFIL for this message (applicable for auto mode) + --help, -h show help (default: false) + +``` + +### lotus mpool find +``` +NAME: + lotus mpool find - find a message in the mempool + +USAGE: + lotus mpool find [command options] [arguments...] + +OPTIONS: + --from value search for messages with given 'from' address + --to value search for messages with given 'to' address + --method value search for messages with given method (default: 0) + --help, -h show help (default: false) + +``` + +### lotus mpool config +``` +NAME: + lotus mpool config - get or set current mpool configuration + +USAGE: + lotus mpool config [command options] [new-config] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus mpool gas-perf +``` +NAME: + lotus mpool gas-perf - Check gas performance of messages in mempool + +USAGE: + lotus mpool gas-perf [command options] [arguments...] + +OPTIONS: + --all print gas performance for all mempool messages (default only prints for local) (default: false) + --help, -h show help (default: false) + +``` + +## lotus state +``` +NAME: + lotus state - Interact with and query filecoin chain state + +USAGE: + lotus state command [command options] [arguments...] + +COMMANDS: + power Query network or miner power + sectors Query the sector set of a miner + active-sectors Query the active sector set of a miner + list-actors list all actors in the network + list-miners list all miners in the network + circulating-supply Get the exact current circulating supply of Filecoin + sector Get miner sector info + get-actor Print actor information + lookup Find corresponding ID address + replay Replay a particular message + sector-size Look up miners sector size + read-state View a json representation of an actors state + list-messages list messages on chain matching given criteria + compute-state Perform state computations + call Invoke a method on an actor locally + get-deal View on-chain deal info + wait-msg Wait for a message to appear on chain + search-msg Search to see whether a message has appeared on chain + miner-info Retrieve miner information + market Inspect the storage market actor + exec-trace Get the execution trace of a given message + network-version Returns the network version + miner-proving-deadline Retrieve information about a given miner's proving deadline + help, h Shows a list of commands or help for one command + +OPTIONS: + --tipset value specify tipset to call method on (pass comma separated array of cids) + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +### lotus state power +``` +NAME: + lotus state power - Query network or miner power + +USAGE: + lotus state power [command options] [ (optional)] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus state sectors +``` +NAME: + lotus state sectors - Query the sector set of a miner + +USAGE: + lotus state sectors [command options] [minerAddress] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus state active-sectors +``` +NAME: + lotus state active-sectors - Query the active sector set of a miner + +USAGE: + lotus state active-sectors [command options] [minerAddress] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus state list-actors +``` +NAME: + lotus state list-actors - list all actors in the network + +USAGE: + lotus state list-actors [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus state list-miners +``` +NAME: + lotus state list-miners - list all miners in the network + +USAGE: + lotus state list-miners [command options] [arguments...] + +OPTIONS: + --sort-by value criteria to sort miners by (none, num-deals) + --help, -h show help (default: false) + +``` + +### lotus state circulating-supply +``` +NAME: + lotus state circulating-supply - Get the exact current circulating supply of Filecoin + +USAGE: + lotus state circulating-supply [command options] [arguments...] + +OPTIONS: + --vm-supply calculates the approximation of the circulating supply used internally by the VM (instead of the exact amount) (default: false) + --help, -h show help (default: false) + +``` + +### lotus state sector +``` +NAME: + lotus state sector - Get miner sector info + +USAGE: + lotus state sector [command options] [minerAddress] [sectorNumber] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus state get-actor +``` +NAME: + lotus state get-actor - Print actor information + +USAGE: + lotus state get-actor [command options] [actorrAddress] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus state lookup +``` +NAME: + lotus state lookup - Find corresponding ID address + +USAGE: + lotus state lookup [command options] [address] + +OPTIONS: + --reverse, -r Perform reverse lookup (default: false) + --help, -h show help (default: false) + +``` + +### lotus state replay +``` +NAME: + lotus state replay - Replay a particular message + +USAGE: + lotus state replay [command options] + +OPTIONS: + --show-trace print out full execution trace for given message (default: false) + --detailed-gas print out detailed gas costs for given message (default: false) + --help, -h show help (default: false) + +``` + +### lotus state sector-size +``` +NAME: + lotus state sector-size - Look up miners sector size + +USAGE: + lotus state sector-size [command options] [minerAddress] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus state read-state +``` +NAME: + lotus state read-state - View a json representation of an actors state + +USAGE: + lotus state read-state [command options] [actorAddress] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus state list-messages +``` +NAME: + lotus state list-messages - list messages on chain matching given criteria + +USAGE: + lotus state list-messages [command options] [arguments...] + +OPTIONS: + --to value return messages to a given address + --from value return messages from a given address + --toheight value don't look before given block height (default: 0) + --cids print message CIDs instead of messages (default: false) + --help, -h show help (default: false) + +``` + +### lotus state compute-state +``` +NAME: + lotus state compute-state - Perform state computations + +USAGE: + lotus state compute-state [command options] [arguments...] + +OPTIONS: + --vm-height value set the height that the vm will see (default: 0) + --apply-mpool-messages apply messages from the mempool to the computed state (default: false) + --show-trace print out full execution trace for given tipset (default: false) + --html generate html report (default: false) + --json generate json output (default: false) + --compute-state-output value a json file containing pre-existing compute-state output, to generate html reports without rerunning state changes + --no-timing don't show timing information in html traces (default: false) + --help, -h show help (default: false) + +``` + +### lotus state call +``` +NAME: + lotus state call - Invoke a method on an actor locally + +USAGE: + lotus state call [command options] [toAddress methodId (optional)] + +OPTIONS: + --from value (default: "f00") + --value value specify value field for invocation (default: "0") + --ret value specify how to parse output (auto, raw, addr, big) (default: "auto") + --help, -h show help (default: false) + +``` + +### lotus state get-deal +``` +NAME: + lotus state get-deal - View on-chain deal info + +USAGE: + lotus state get-deal [command options] [dealId] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus state wait-msg +``` +NAME: + lotus state wait-msg - Wait for a message to appear on chain + +USAGE: + lotus state wait-msg [command options] [messageCid] + +OPTIONS: + --timeout value (default: "10m") + --help, -h show help (default: false) + +``` + +### lotus state search-msg +``` +NAME: + lotus state search-msg - Search to see whether a message has appeared on chain + +USAGE: + lotus state search-msg [command options] [messageCid] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus state miner-info +``` +NAME: + lotus state miner-info - Retrieve miner information + +USAGE: + lotus state miner-info [command options] [minerAddress] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus state market +``` +NAME: + lotus state market - Inspect the storage market actor + +USAGE: + lotus state market command [command options] [arguments...] + +COMMANDS: + balance Get the market balance (locked and escrowed) for a given account + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +#### lotus state market balance +``` +NAME: + lotus state market balance - Get the market balance (locked and escrowed) for a given account + +USAGE: + lotus state market balance [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus state exec-trace +``` +NAME: + lotus state exec-trace - Get the execution trace of a given message + +USAGE: + lotus state exec-trace [command options] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus state network-version +``` +NAME: + lotus state network-version - Returns the network version + +USAGE: + lotus state network-version [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus state miner-proving-deadline +``` +NAME: + lotus state miner-proving-deadline - Retrieve information about a given miner's proving deadline + +USAGE: + lotus state miner-proving-deadline [command options] [minerAddress] + +OPTIONS: + --help, -h show help (default: false) + +``` + +## lotus chain +``` +NAME: + lotus chain - Interact with filecoin blockchain + +USAGE: + lotus chain command [command options] [arguments...] + +COMMANDS: + head Print chain head + getblock Get a block and print its details + read-obj Read the raw bytes of an object + delete-obj Delete an object from the chain blockstore + stat-obj Collect size and ipld link counts for objs + getmessage Get and print a message by its cid + sethead manually set the local nodes head tipset (Caution: normally only used for recovery) + list, love View a segment of the chain + get Get chain DAG node by path + bisect bisect chain for an event + export export chain to a car file + slash-consensus Report consensus fault + gas-price Estimate gas prices + inspect-usage Inspect block space usage of a given tipset + decode decode various types + encode encode various types + disputer interact with the window post disputer + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +### lotus chain head +``` +NAME: + lotus chain head - Print chain head + +USAGE: + lotus chain head [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus chain getblock +``` +NAME: + lotus chain getblock - Get a block and print its details + +USAGE: + lotus chain getblock [command options] [blockCid] + +OPTIONS: + --raw print just the raw block header (default: false) + --help, -h show help (default: false) + +``` + +### lotus chain read-obj +``` +NAME: + lotus chain read-obj - Read the raw bytes of an object + +USAGE: + lotus chain read-obj [command options] [objectCid] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus chain delete-obj +``` +NAME: + lotus chain delete-obj - Delete an object from the chain blockstore + +USAGE: + lotus chain delete-obj [command options] [objectCid] + +DESCRIPTION: + WARNING: Removing wrong objects from the chain blockstore may lead to sync issues + +OPTIONS: + --really-do-it (default: false) + --help, -h show help (default: false) + +``` + +### lotus chain stat-obj +``` +NAME: + lotus chain stat-obj - Collect size and ipld link counts for objs + +USAGE: + lotus chain stat-obj [command options] [cid] + +DESCRIPTION: + Collect object size and ipld link count for an object. + + When a base is provided it will be walked first, and all links visisted + will be ignored when the passed in object is walked. + + +OPTIONS: + --base value ignore links found in this obj + --help, -h show help (default: false) + +``` + +### lotus chain getmessage +``` +NAME: + lotus chain getmessage - Get and print a message by its cid + +USAGE: + lotus chain getmessage [command options] [messageCid] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus chain sethead +``` +NAME: + lotus chain sethead - manually set the local nodes head tipset (Caution: normally only used for recovery) + +USAGE: + lotus chain sethead [command options] [tipsetkey] + +OPTIONS: + --genesis reset head to genesis (default: false) + --epoch value reset head to given epoch (default: 0) + --help, -h show help (default: false) + +``` + +#### lotus chain list, love +``` +``` + +### lotus chain get +``` +NAME: + lotus chain get - Get chain DAG node by path + +USAGE: + lotus chain get [command options] [path] + +DESCRIPTION: + Get ipld node under a specified path: + + lotus chain get /ipfs/[cid]/some/path + + Path prefixes: + - /ipfs/[cid], /ipld/[cid] - traverse IPLD path + - /pstate - traverse from head.ParentStateRoot + + Note: + You can use special path elements to traverse through some data structures: + - /ipfs/[cid]/@H:elem - get 'elem' from hamt + - /ipfs/[cid]/@Hi:123 - get varint elem 123 from hamt + - /ipfs/[cid]/@Hu:123 - get uvarint elem 123 from hamt + - /ipfs/[cid]/@Ha:t01 - get element under Addr(t01).Bytes + - /ipfs/[cid]/@A:10 - get 10th amt element + - .../@Ha:t01/@state - get pretty map-based actor state + + List of --as-type types: + - raw + - block + - message + - smessage, signedmessage + - actor + - amt + - hamt-epoch + - hamt-address + - cronevent + - account-state + + +OPTIONS: + --as-type value specify type to interpret output as + --verbose (default: false) + --tipset value specify tipset for /pstate (pass comma separated array of cids) + --help, -h show help (default: false) + +``` + +### lotus chain bisect +``` +NAME: + lotus chain bisect - bisect chain for an event + +USAGE: + lotus chain bisect [command options] [minHeight maxHeight path shellCommand ] + +DESCRIPTION: + Bisect the chain state tree: + + lotus chain bisect [min height] [max height] '1/2/3/state/path' 'shell command' 'args' + + Returns the first tipset in which condition is true + v + [start] FFFFFFFTTT [end] + + Example: find height at which deal ID 100 000 appeared + - lotus chain bisect 1 32000 '@Ha:t03/1' jq -e '.[2] > 100000' + + For special path elements see 'chain get' help + + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus chain export +``` +NAME: + lotus chain export - export chain to a car file + +USAGE: + lotus chain export [command options] [outputPath] + +OPTIONS: + --tipset value + --recent-stateroots value specify the number of recent state roots to include in the export (default: 0) + --skip-old-msgs (default: false) + --help, -h show help (default: false) + +``` + +### lotus chain slash-consensus +``` +NAME: + lotus chain slash-consensus - Report consensus fault + +USAGE: + lotus chain slash-consensus [command options] [blockCid1 blockCid2] + +OPTIONS: + --from value optionally specify the account to report consensus from + --extra value Extra block cid + --help, -h show help (default: false) + +``` + +### lotus chain gas-price +``` +NAME: + lotus chain gas-price - Estimate gas prices + +USAGE: + lotus chain gas-price [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus chain inspect-usage +``` +NAME: + lotus chain inspect-usage - Inspect block space usage of a given tipset + +USAGE: + lotus chain inspect-usage [command options] [arguments...] + +OPTIONS: + --tipset value specify tipset to view block space usage of (default: "@head") + --length value length of chain to inspect block space usage for (default: 1) + --num-results value number of results to print per category (default: 10) + --help, -h show help (default: false) + +``` + +### lotus chain decode +``` +NAME: + lotus chain decode - decode various types + +USAGE: + lotus chain decode command [command options] [arguments...] + +COMMANDS: + params Decode message params + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +#### lotus chain decode params +``` +NAME: + lotus chain decode params - Decode message params + +USAGE: + lotus chain decode params [command options] [toAddr method params] + +OPTIONS: + --tipset value + --encoding value specify input encoding to parse (default: "base64") + --help, -h show help (default: false) + +``` + +### lotus chain encode +``` +NAME: + lotus chain encode - encode various types + +USAGE: + lotus chain encode command [command options] [arguments...] + +COMMANDS: + params Encodes the given JSON params + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +#### lotus chain encode params +``` +NAME: + lotus chain encode params - Encodes the given JSON params + +USAGE: + lotus chain encode params [command options] [toAddr method params] + +OPTIONS: + --tipset value + --encoding value specify input encoding to parse (default: "base64") + --help, -h show help (default: false) + +``` + +### lotus chain disputer +``` +NAME: + lotus chain disputer - interact with the window post disputer + +USAGE: + lotus chain disputer command [command options] [arguments...] + +COMMANDS: + start Start the window post disputer + dispute Send a specific DisputeWindowedPoSt message + help, h Shows a list of commands or help for one command + +OPTIONS: + --max-fee value Spend up to X FIL per DisputeWindowedPoSt message + --from value optionally specify the account to send messages from + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +#### lotus chain disputer start +``` +NAME: + lotus chain disputer start - Start the window post disputer + +USAGE: + lotus chain disputer start [command options] [minerAddress] + +OPTIONS: + --start-epoch value only start disputing PoSts after this epoch (default: 0) + --help, -h show help (default: false) + +``` + +#### lotus chain disputer dispute +``` +NAME: + lotus chain disputer dispute - Send a specific DisputeWindowedPoSt message + +USAGE: + lotus chain disputer dispute [command options] [minerAddress index postIndex] + +OPTIONS: + --help, -h show help (default: false) + +``` + +## lotus log +``` +NAME: + lotus log - Manage logging + +USAGE: + lotus log command [command options] [arguments...] + +COMMANDS: + list List log systems + set-level Set log level + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +### lotus log list +``` +NAME: + lotus log list - List log systems + +USAGE: + lotus log list [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus log set-level +``` +NAME: + lotus log set-level - Set log level + +USAGE: + lotus log set-level [command options] [level] + +DESCRIPTION: + Set the log level for logging systems: + + The system flag can be specified multiple times. + + eg) log set-level --system chain --system chainxchg debug + + Available Levels: + debug + info + warn + error + + Environment Variables: + GOLOG_LOG_LEVEL - Default log level for all log systems + GOLOG_LOG_FMT - Change output log format (json, nocolor) + GOLOG_FILE - Write logs to file + GOLOG_OUTPUT - Specify whether to output to file, stderr, stdout or a combination, i.e. file+stderr + + +OPTIONS: + --system value limit to log system + --help, -h show help (default: false) + +``` + +## lotus wait-api +``` +NAME: + lotus wait-api - Wait for lotus api to come online + +USAGE: + lotus wait-api [command options] [arguments...] + +CATEGORY: + DEVELOPER + +OPTIONS: + --help, -h show help (default: false) + +``` + +## lotus fetch-params +``` +NAME: + lotus fetch-params - Fetch proving parameters + +USAGE: + lotus fetch-params [command options] [sectorSize] + +CATEGORY: + DEVELOPER + +OPTIONS: + --help, -h show help (default: false) + +``` + +## lotus net +``` +NAME: + lotus net - Manage P2P Network + +USAGE: + lotus net command [command options] [arguments...] + +COMMANDS: + peers Print peers + connect Connect to a peer + listen List listen addresses + id Get node identity + findpeer Find the addresses of a given peerID + scores Print peers' pubsub scores + reachability Print information about reachability from the internet + bandwidth Print bandwidth usage information + block Manage network connection gating rules + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +### lotus net peers +``` +NAME: + lotus net peers - Print peers + +USAGE: + lotus net peers [command options] [arguments...] + +OPTIONS: + --agent, -a Print agent name (default: false) + --extended, -x Print extended peer information in json (default: false) + --help, -h show help (default: false) + +``` + +### lotus net connect +``` +NAME: + lotus net connect - Connect to a peer + +USAGE: + lotus net connect [command options] [peerMultiaddr|minerActorAddress] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus net listen +``` +NAME: + lotus net listen - List listen addresses + +USAGE: + lotus net listen [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus net id +``` +NAME: + lotus net id - Get node identity + +USAGE: + lotus net id [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus net findpeer +``` +NAME: + lotus net findpeer - Find the addresses of a given peerID + +USAGE: + lotus net findpeer [command options] [peerId] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus net scores +``` +NAME: + lotus net scores - Print peers' pubsub scores + +USAGE: + lotus net scores [command options] [arguments...] + +OPTIONS: + --extended, -x print extended peer scores in json (default: false) + --help, -h show help (default: false) + +``` + +### lotus net reachability +``` +NAME: + lotus net reachability - Print information about reachability from the internet + +USAGE: + lotus net reachability [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus net bandwidth +``` +NAME: + lotus net bandwidth - Print bandwidth usage information + +USAGE: + lotus net bandwidth [command options] [arguments...] + +OPTIONS: + --by-peer list bandwidth usage by peer (default: false) + --by-protocol list bandwidth usage by protocol (default: false) + --help, -h show help (default: false) + +``` + +### lotus net block +``` +NAME: + lotus net block - Manage network connection gating rules + +USAGE: + lotus net block command [command options] [arguments...] + +COMMANDS: + add Add connection gating rules + remove Remove connection gating rules + list list connection gating rules + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +#### lotus net block add +``` +NAME: + lotus net block add - Add connection gating rules + +USAGE: + lotus net block add command [command options] [arguments...] + +COMMANDS: + peer Block a peer + ip Block an IP address + subnet Block an IP subnet + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +##### lotus net block add peer +``` +NAME: + lotus net block add peer - Block a peer + +USAGE: + lotus net block add peer [command options] ... + +OPTIONS: + --help, -h show help (default: false) + +``` + +##### lotus net block add ip +``` +NAME: + lotus net block add ip - Block an IP address + +USAGE: + lotus net block add ip [command options] ... + +OPTIONS: + --help, -h show help (default: false) + +``` + +##### lotus net block add subnet +``` +NAME: + lotus net block add subnet - Block an IP subnet + +USAGE: + lotus net block add subnet [command options] ... + +OPTIONS: + --help, -h show help (default: false) + +``` + +#### lotus net block remove +``` +NAME: + lotus net block remove - Remove connection gating rules + +USAGE: + lotus net block remove command [command options] [arguments...] + +COMMANDS: + peer Unblock a peer + ip Unblock an IP address + subnet Unblock an IP subnet + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +##### lotus net block remove peer +``` +NAME: + lotus net block remove peer - Unblock a peer + +USAGE: + lotus net block remove peer [command options] ... + +OPTIONS: + --help, -h show help (default: false) + +``` + +##### lotus net block remove ip +``` +NAME: + lotus net block remove ip - Unblock an IP address + +USAGE: + lotus net block remove ip [command options] ... + +OPTIONS: + --help, -h show help (default: false) + +``` + +##### lotus net block remove subnet +``` +NAME: + lotus net block remove subnet - Unblock an IP subnet + +USAGE: + lotus net block remove subnet [command options] ... + +OPTIONS: + --help, -h show help (default: false) + +``` + +#### lotus net block list +``` +NAME: + lotus net block list - list connection gating rules + +USAGE: + lotus net block list [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +## lotus sync +``` +NAME: + lotus sync - Inspect or interact with the chain syncer + +USAGE: + lotus sync command [command options] [arguments...] + +COMMANDS: + status check sync status + wait Wait for sync to be complete + mark-bad Mark the given block as bad, will prevent syncing to a chain that contains it + unmark-bad Unmark the given block as bad, makes it possible to sync to a chain containing it + check-bad check if the given block was marked bad, and for what reason + checkpoint mark a certain tipset as checkpointed; the node will never fork away from this tipset + help, h Shows a list of commands or help for one command + +OPTIONS: + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + +### lotus sync status +``` +NAME: + lotus sync status - check sync status + +USAGE: + lotus sync status [command options] [arguments...] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus sync wait +``` +NAME: + lotus sync wait - Wait for sync to be complete + +USAGE: + lotus sync wait [command options] [arguments...] + +OPTIONS: + --watch don't exit after node is synced (default: false) + --help, -h show help (default: false) + +``` + +### lotus sync mark-bad +``` +NAME: + lotus sync mark-bad - Mark the given block as bad, will prevent syncing to a chain that contains it + +USAGE: + lotus sync mark-bad [command options] [blockCid] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus sync unmark-bad +``` +NAME: + lotus sync unmark-bad - Unmark the given block as bad, makes it possible to sync to a chain containing it + +USAGE: + lotus sync unmark-bad [command options] [blockCid] + +OPTIONS: + --all drop the entire bad block cache (default: false) + --help, -h show help (default: false) + +``` + +### lotus sync check-bad +``` +NAME: + lotus sync check-bad - check if the given block was marked bad, and for what reason + +USAGE: + lotus sync check-bad [command options] [blockCid] + +OPTIONS: + --help, -h show help (default: false) + +``` + +### lotus sync checkpoint +``` +NAME: + lotus sync checkpoint - mark a certain tipset as checkpointed; the node will never fork away from this tipset + +USAGE: + lotus sync checkpoint [command options] [tipsetKey] + +OPTIONS: + --epoch value checkpoint the tipset at the given epoch (default: 0) + --help, -h show help (default: false) + +``` From 4bf03b485f5bd9928a950b47580313bbce2b05ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Mon, 31 May 2021 21:40:21 +0200 Subject: [PATCH 55/88] mod tidy --- go.sum | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/go.sum b/go.sum index 2095180dc..701a7f1cf 100644 --- a/go.sum +++ b/go.sum @@ -321,15 +321,13 @@ github.com/filecoin-project/specs-actors/v2 v2.3.5 h1:PbT4tPlSXZ8sRgajhb4D8AOEmi github.com/filecoin-project/specs-actors/v2 v2.3.5/go.mod h1:LljnY2Mn2homxZsmokJZCpRuhOPxfXhvcek5gWkmqAc= github.com/filecoin-project/specs-actors/v3 v3.1.0 h1:s4qiPw8pgypqBGAy853u/zdZJ7K9cTZdM1rTiSonHrg= github.com/filecoin-project/specs-actors/v3 v3.1.0/go.mod h1:mpynccOLlIRy0QnR008BwYBwT9fen+sPR13MA1VmMww= -github.com/filecoin-project/specs-actors/v4 v4.0.0 h1:vMALksY5G3J5rj3q9rbcyB+f4Tk1xrLqSgdB3jOok4s= -github.com/filecoin-project/specs-actors/v4 v4.0.0/go.mod h1:TkHXf/l7Wyw4ZejyXIPS2rK8bBO0rdwhTZyQQgaglng= -github.com/filecoin-project/specs-actors/v5 v5.0.0-20210512015452-4fe3889fff57/go.mod h1:283yBMMUSDB2abcjP/hhrwTkhb9h3sfM6KGrep/ZlBI= -github.com/filecoin-project/specs-actors/v5 v5.0.0-20210517165532-c7cff61d07fb h1:818gGdeEC+7aHGl2X7ptdtYuqoEgRsY3jwz+DvUYUFk= -github.com/filecoin-project/specs-actors/v5 v5.0.0-20210517165532-c7cff61d07fb/go.mod h1:283yBMMUSDB2abcjP/hhrwTkhb9h3sfM6KGrep/ZlBI= github.com/filecoin-project/specs-actors/v3 v3.1.1 h1:BE8fsns1GnEOxt1DTE5LxBK2FThXtWmCChgcJoHTg0E= github.com/filecoin-project/specs-actors/v3 v3.1.1/go.mod h1:mpynccOLlIRy0QnR008BwYBwT9fen+sPR13MA1VmMww= +github.com/filecoin-project/specs-actors/v4 v4.0.0 h1:vMALksY5G3J5rj3q9rbcyB+f4Tk1xrLqSgdB3jOok4s= +github.com/filecoin-project/specs-actors/v4 v4.0.0/go.mod h1:TkHXf/l7Wyw4ZejyXIPS2rK8bBO0rdwhTZyQQgaglng= github.com/filecoin-project/specs-actors/v4 v4.0.1 h1:AiWrtvJZ63MHGe6rn7tPu4nSUY8bA1KDNszqJaD5+Fg= github.com/filecoin-project/specs-actors/v4 v4.0.1/go.mod h1:TkHXf/l7Wyw4ZejyXIPS2rK8bBO0rdwhTZyQQgaglng= +github.com/filecoin-project/specs-actors/v5 v5.0.0-20210512015452-4fe3889fff57/go.mod h1:283yBMMUSDB2abcjP/hhrwTkhb9h3sfM6KGrep/ZlBI= github.com/filecoin-project/specs-actors/v5 v5.0.0-20210528202914-a9f9f95f5e93 h1:PZ5pLy4dZVgL+fXgvSVtPOYhfEYUzEYYVEz7IfG8e5U= github.com/filecoin-project/specs-actors/v5 v5.0.0-20210528202914-a9f9f95f5e93/go.mod h1:kSDmoQuO8jlhMVzKNoesbhka1e6gHKcLQjKm9mE9Qhw= github.com/filecoin-project/specs-storage v0.1.1-0.20201105051918-5188d9774506 h1:Ur/l2+6qN+lQiqjozWWc5p9UDaAMDZKTlDS98oRnlIw= From 3671f2a6ff1848add5bd59ff616fe9655689dbb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Mon, 31 May 2021 21:43:21 +0200 Subject: [PATCH 56/88] fix 2k build --- build/params_2k.go | 2 ++ chain/store/checkpoint_test.go | 4 ++-- cli/filplus.go | 2 +- cmd/lotus-shed/cron-count.go | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/build/params_2k.go b/build/params_2k.go index 3e107b4ed..387d2da0b 100644 --- a/build/params_2k.go +++ b/build/params_2k.go @@ -39,6 +39,8 @@ var UpgradeNorwegianHeight = abi.ChainEpoch(-13) var UpgradeTurboHeight = abi.ChainEpoch(-14) +var UpgradeHyperdriveHeight = abi.ChainEpoch(-15) + var DrandSchedule = map[abi.ChainEpoch]DrandEnum{ 0: DrandMainnet, } diff --git a/chain/store/checkpoint_test.go b/chain/store/checkpoint_test.go index 320b76797..81bbab6ea 100644 --- a/chain/store/checkpoint_test.go +++ b/chain/store/checkpoint_test.go @@ -18,7 +18,7 @@ func TestChainCheckpoint(t *testing.T) { // Let the first miner mine some blocks. last := cg.CurTipset.TipSet() for i := 0; i < 4; i++ { - ts, err := cg.NextTipSetFromMiners(last, cg.Miners[:1]) + ts, err := cg.NextTipSetFromMiners(last, cg.Miners[:1], 0) require.NoError(t, err) last = ts.TipSet.TipSet() @@ -57,7 +57,7 @@ func TestChainCheckpoint(t *testing.T) { // Let the second miner miner mine a fork last = checkpointParents for i := 0; i < 4; i++ { - ts, err := cg.NextTipSetFromMiners(last, cg.Miners[1:]) + ts, err := cg.NextTipSetFromMiners(last, cg.Miners[1:], 0) require.NoError(t, err) last = ts.TipSet.TipSet() diff --git a/cli/filplus.go b/cli/filplus.go index 9a6fa2ccf..53dc5092b 100644 --- a/cli/filplus.go +++ b/cli/filplus.go @@ -3,7 +3,6 @@ package cli import ( "context" "fmt" - "github.com/filecoin-project/lotus/api/v0api" verifreg4 "github.com/filecoin-project/specs-actors/v4/actors/builtin/verifreg" @@ -15,6 +14,7 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/api/v0api" "github.com/filecoin-project/lotus/blockstore" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors" diff --git a/cmd/lotus-shed/cron-count.go b/cmd/lotus-shed/cron-count.go index 79fd6ec42..622f38791 100644 --- a/cmd/lotus-shed/cron-count.go +++ b/cmd/lotus-shed/cron-count.go @@ -60,7 +60,7 @@ func findDeadlineCrons(c *cli.Context) (map[address.Address]struct{}, error) { // 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 { + if ts.Height() <= build.UpgradeTurboHeight+1 { activeMiners[mAddr] = struct{}{} continue } From 8d991283f41b60cb1fdf4c73e46e6ad8a11f8532 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Thu, 6 May 2021 23:51:42 -0400 Subject: [PATCH 57/88] Resolve to ID addresses when handling message selection --- chain/messagepool/messagepool.go | 71 +++++++++-- chain/messagepool/provider.go | 2 + chain/messagepool/selection.go | 10 +- chain/store/store.go | 28 ++++- chain/sync.go | 22 +++- chain/sync_test.go | 111 +++++++++++++++++- .../misc/actors_version_checklist.md | 2 +- 7 files changed, 220 insertions(+), 26 deletions(-) diff --git a/chain/messagepool/messagepool.go b/chain/messagepool/messagepool.go index c2566ae24..299634c6f 100644 --- a/chain/messagepool/messagepool.go +++ b/chain/messagepool/messagepool.go @@ -126,8 +126,10 @@ type MessagePool struct { republished map[cid.Cid]struct{} + // only pubkey addresses localAddrs map[address.Address]struct{} + // only pubkey addresses pending map[address.Address]*msgSet curTsLk sync.Mutex // DO NOT LOCK INSIDE lk @@ -443,7 +445,14 @@ func (mp *MessagePool) runLoop() { } func (mp *MessagePool) addLocal(m *types.SignedMessage) error { - mp.localAddrs[m.Message.From] = struct{}{} + // TODO: Is context.TODO() safe here? Idk how Go works. + sk, err := mp.api.StateAccountKey(context.TODO(), m.Message.From, mp.curTs) + if err != nil { + log.Debugf("mpooladdlocal failed to resolve sender: %s", err) + return err + } + + mp.localAddrs[sk] = struct{}{} msgb, err := m.Serialize() if err != nil { @@ -475,7 +484,7 @@ func (mp *MessagePool) verifyMsgBeforeAdd(m *types.SignedMessage, curTs *types.T return false, xerrors.Errorf("message will not be included in a block: %w", err) } - // this checks if the GasFeeCap is suffisciently high for inclusion in the next 20 blocks + // this checks if the GasFeeCap is sufficiently high for inclusion in the next 20 blocks // if the GasFeeCap is too low, we soft reject the message (Ignore in pubsub) and rely // on republish to push it through later, if the baseFee has fallen. // this is a defensive check that stops minimum baseFee spam attacks from overloading validation @@ -645,7 +654,14 @@ func (mp *MessagePool) checkBalance(m *types.SignedMessage, curTs *types.TipSet) // add Value for soft failure check //requiredFunds = types.BigAdd(requiredFunds, m.Message.Value) - mset, ok := mp.pending[m.Message.From] + // TODO: Is context.TODO() safe here? Idk how Go works. + sk, err := mp.api.StateAccountKey(context.TODO(), m.Message.From, mp.curTs) + if err != nil { + log.Debugf("mpoolcheckbalance failed to resolve sender: %s", err) + return err + } + + mset, ok := mp.pending[sk] if ok { requiredFunds = types.BigAdd(requiredFunds, mset.getRequiredFunds(m.Message.Nonce)) } @@ -752,15 +768,22 @@ func (mp *MessagePool) addLocked(m *types.SignedMessage, strict, untrusted bool) return err } - mset, ok := mp.pending[m.Message.From] + // TODO: Is context.TODO() safe here? Idk how Go works. + sk, err := mp.api.StateAccountKey(context.TODO(), m.Message.From, mp.curTs) + if err != nil { + log.Debugf("mpooladd failed to resolve sender: %s", err) + return err + } + + mset, ok := mp.pending[sk] if !ok { - nonce, err := mp.getStateNonce(m.Message.From, mp.curTs) + nonce, err := mp.getStateNonce(sk, mp.curTs) if err != nil { return xerrors.Errorf("failed to get initial actor nonce: %w", err) } mset = newMsgSet(nonce) - mp.pending[m.Message.From] = mset + mp.pending[sk] = mset } incr, err := mset.add(m, mp, strict, untrusted) @@ -811,7 +834,14 @@ func (mp *MessagePool) getNonceLocked(addr address.Address, curTs *types.TipSet) return 0, err } - mset, ok := mp.pending[addr] + // TODO: Is context.TODO() safe here? Idk how Go works. + sk, err := mp.api.StateAccountKey(context.TODO(), addr, mp.curTs) + if err != nil { + log.Debugf("mpoolgetnonce failed to resolve sender: %s", err) + return 0, err + } + + mset, ok := mp.pending[sk] if ok { if stateNonce > mset.nextNonce { log.Errorf("state nonce was larger than mset.nextNonce (%d > %d)", stateNonce, mset.nextNonce) @@ -891,7 +921,14 @@ func (mp *MessagePool) Remove(from address.Address, nonce uint64, applied bool) } func (mp *MessagePool) remove(from address.Address, nonce uint64, applied bool) { - mset, ok := mp.pending[from] + // TODO: Is context.TODO() safe here? Idk how Go works. + sk, err := mp.api.StateAccountKey(context.TODO(), from, mp.curTs) + if err != nil { + log.Debugf("mpoolremove failed to resolve sender: %s", err) + return + } + + mset, ok := mp.pending[sk] if !ok { return } @@ -949,7 +986,14 @@ func (mp *MessagePool) PendingFor(a address.Address) ([]*types.SignedMessage, *t } func (mp *MessagePool) pendingFor(a address.Address) []*types.SignedMessage { - mset := mp.pending[a] + // TODO: Is context.TODO() safe here? Idk how Go works. + sk, err := mp.api.StateAccountKey(context.TODO(), a, mp.curTs) + if err != nil { + log.Debugf("mpoolpendingfor failed to resolve sender: %s", err) + return nil + } + + mset := mp.pending[sk] if mset == nil || len(mset.msgs) == 0 { return nil } @@ -1303,7 +1347,14 @@ func (mp *MessagePool) loadLocal() error { log.Errorf("adding local message: %+v", err) } - mp.localAddrs[sm.Message.From] = struct{}{} + // TODO: Is context.TODO() safe here? Idk how Go works. + sk, err := mp.api.StateAccountKey(context.TODO(), sm.Message.From, mp.curTs) + if err != nil { + log.Debugf("mpoolloadLocal failed to resolve sender: %s", err) + return err + } + + mp.localAddrs[sk] = struct{}{} } return nil diff --git a/chain/messagepool/provider.go b/chain/messagepool/provider.go index 5a6c751bc..75a2efe91 100644 --- a/chain/messagepool/provider.go +++ b/chain/messagepool/provider.go @@ -37,6 +37,8 @@ type mpoolProvider struct { ps *pubsub.PubSub } +var _ Provider = (*mpoolProvider)(nil) + func NewProvider(sm *stmgr.StateManager, ps *pubsub.PubSub) Provider { return &mpoolProvider{sm: sm, ps: ps} } diff --git a/chain/messagepool/selection.go b/chain/messagepool/selection.go index 6c9d506ef..af450645f 100644 --- a/chain/messagepool/selection.go +++ b/chain/messagepool/selection.go @@ -543,10 +543,16 @@ func (mp *MessagePool) selectPriorityMessages(pending map[address.Address]map[ui var chains []*msgChain priority := mpCfg.PriorityAddrs for _, actor := range priority { - mset, ok := pending[actor] + pk, err := mp.api.StateAccountKey(context.TODO(), actor, mp.curTs) + if err != nil { + log.Debugf("mpooladdlocal failed to resolve sender: %s", err) + return nil, gasLimit + } + + mset, ok := pending[pk] if ok { // remove actor from pending set as we are already processed these messages - delete(pending, actor) + delete(pending, pk) // create chains for the priority actor next := mp.createMessageChains(actor, mset, baseFee, ts) chains = append(chains, next...) diff --git a/chain/store/store.go b/chain/store/store.go index dfde93fc7..5414b12fe 100644 --- a/chain/store/store.go +++ b/chain/store/store.go @@ -12,6 +12,8 @@ import ( "strings" "sync" + "github.com/filecoin-project/lotus/chain/state" + "golang.org/x/sync/errgroup" "github.com/filecoin-project/go-state-types/crypto" @@ -1008,17 +1010,33 @@ type BlockMessages struct { func (cs *ChainStore) BlockMsgsForTipset(ts *types.TipSet) ([]BlockMessages, error) { applied := make(map[address.Address]uint64) + cst := cbor.NewCborStore(cs.stateBlockstore) + st, err := state.LoadStateTree(cst, ts.Blocks()[0].ParentStateRoot) + if err != nil { + return nil, xerrors.Errorf("failed to load state tree") + } + selectMsg := func(m *types.Message) (bool, error) { - // The first match for a sender is guaranteed to have correct nonce -- the block isn't valid otherwise - if _, ok := applied[m.From]; !ok { - applied[m.From] = m.Nonce + var sender address.Address + if ts.Height() >= build.UpgradeHyperdriveHeight { + sender, err = st.LookupID(m.From) + if err != nil { + return false, err + } + } else { + sender = m.From } - if applied[m.From] != m.Nonce { + // The first match for a sender is guaranteed to have correct nonce -- the block isn't valid otherwise + if _, ok := applied[sender]; !ok { + applied[sender] = m.Nonce + } + + if applied[sender] != m.Nonce { return false, nil } - applied[m.From]++ + applied[sender]++ return true, nil } diff --git a/chain/sync.go b/chain/sync.go index 66c9c18bd..d908f3fd8 100644 --- a/chain/sync.go +++ b/chain/sync.go @@ -1084,9 +1084,19 @@ func (syncer *Syncer) checkBlockMessages(ctx context.Context, b *types.FullBlock // Phase 2: (Partial) semantic validation: // the sender exists and is an account actor, and the nonces make sense - if _, ok := nonces[m.From]; !ok { + var sender address.Address + if syncer.sm.GetNtwkVersion(ctx, b.Header.Height) >= network.Version13 { + sender, err = st.LookupID(m.From) + if err != nil { + return err + } + } else { + sender = m.From + } + + if _, ok := nonces[sender]; !ok { // `GetActor` does not validate that this is an account actor. - act, err := st.GetActor(m.From) + act, err := st.GetActor(sender) if err != nil { return xerrors.Errorf("failed to get actor: %w", err) } @@ -1094,13 +1104,13 @@ func (syncer *Syncer) checkBlockMessages(ctx context.Context, b *types.FullBlock if !builtin.IsAccountActor(act.Code) { return xerrors.New("Sender must be an account actor") } - nonces[m.From] = act.Nonce + nonces[sender] = act.Nonce } - if nonces[m.From] != m.Nonce { - return xerrors.Errorf("wrong nonce (exp: %d, got: %d)", nonces[m.From], m.Nonce) + if nonces[sender] != m.Nonce { + return xerrors.Errorf("wrong nonce (exp: %d, got: %d)", nonces[sender], m.Nonce) } - nonces[m.From]++ + nonces[sender]++ return nil } diff --git a/chain/sync_test.go b/chain/sync_test.go index 095b224ad..4aa3f6fdf 100644 --- a/chain/sync_test.go +++ b/chain/sync_test.go @@ -10,7 +10,6 @@ import ( "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/go-state-types/network" - "github.com/filecoin-project/lotus/chain/stmgr" "github.com/ipfs/go-cid" @@ -108,6 +107,7 @@ func prepSyncTest(t testing.TB, h int) *syncTestUtil { } tu.addSourceNode(stmgr.DefaultUpgradeSchedule(), h) + //tu.checkHeight("source", source, h) // separate logs @@ -730,7 +730,6 @@ func TestBadNonce(t *testing.T) { // Produce a message from the banker with a bad nonce makeBadMsg := func() *types.SignedMessage { - msg := types.Message{ To: tu.g.Banker(), From: tu.g.Banker(), @@ -761,6 +760,114 @@ func TestBadNonce(t *testing.T) { tu.mineOnBlock(base, 0, []int{0}, true, true, msgs, 0) } +// This test introduces a block that has 2 messages, with the same sender, and same nonce. +// One of the messages uses the sender's robust address, the other uses the ID address. +// Such a block is invalid and should not sync. +func TestMismatchedNoncesRobustID(t *testing.T) { + v5h := abi.ChainEpoch(4) + tu := prepSyncTestWithV5Height(t, int(v5h+5), v5h) + + base := tu.g.CurTipset + + // Get the banker from computed tipset state, not the parent. + st, _, err := tu.g.StateManager().TipSetState(context.TODO(), base.TipSet()) + require.NoError(t, err) + ba, err := tu.g.StateManager().LoadActorRaw(context.TODO(), tu.g.Banker(), st) + require.NoError(t, err) + + // Produce a message from the banker + makeMsg := func(id bool) *types.SignedMessage { + sender := tu.g.Banker() + if id { + s, err := tu.nds[0].StateLookupID(context.TODO(), sender, base.TipSet().Key()) + require.NoError(t, err) + sender = s + } + + msg := types.Message{ + To: tu.g.Banker(), + From: sender, + + Nonce: ba.Nonce, + + Value: types.NewInt(1), + + Method: 0, + + GasLimit: 100_000_000, + GasFeeCap: types.NewInt(0), + GasPremium: types.NewInt(0), + } + + sig, err := tu.g.Wallet().WalletSign(context.TODO(), tu.g.Banker(), msg.Cid().Bytes(), api.MsgMeta{}) + require.NoError(t, err) + + return &types.SignedMessage{ + Message: msg, + Signature: *sig, + } + } + + msgs := make([][]*types.SignedMessage, 1) + msgs[0] = []*types.SignedMessage{makeMsg(false), makeMsg(true)} + + tu.mineOnBlock(base, 0, []int{0}, true, true, msgs, 0) +} + +// This test introduces a block that has 2 messages, with the same sender, and nonces N and N+1 (so both can be included in a block) +// One of the messages uses the sender's robust address, the other uses the ID address. +// Such a block is valid and should sync. +func TestMatchedNoncesRobustID(t *testing.T) { + v5h := abi.ChainEpoch(4) + tu := prepSyncTestWithV5Height(t, int(v5h+5), v5h) + + base := tu.g.CurTipset + + // Get the banker from computed tipset state, not the parent. + st, _, err := tu.g.StateManager().TipSetState(context.TODO(), base.TipSet()) + require.NoError(t, err) + ba, err := tu.g.StateManager().LoadActorRaw(context.TODO(), tu.g.Banker(), st) + require.NoError(t, err) + + // Produce a message from the banker with specified nonce + makeMsg := func(n uint64, id bool) *types.SignedMessage { + sender := tu.g.Banker() + if id { + s, err := tu.nds[0].StateLookupID(context.TODO(), sender, base.TipSet().Key()) + require.NoError(t, err) + sender = s + } + + msg := types.Message{ + To: tu.g.Banker(), + From: sender, + + Nonce: n, + + Value: types.NewInt(1), + + Method: 0, + + GasLimit: 100_000_000, + GasFeeCap: types.NewInt(0), + GasPremium: types.NewInt(0), + } + + sig, err := tu.g.Wallet().WalletSign(context.TODO(), tu.g.Banker(), msg.Cid().Bytes(), api.MsgMeta{}) + require.NoError(t, err) + + return &types.SignedMessage{ + Message: msg, + Signature: *sig, + } + } + + msgs := make([][]*types.SignedMessage, 1) + msgs[0] = []*types.SignedMessage{makeMsg(ba.Nonce, false), makeMsg(ba.Nonce+1, true)} + + tu.mineOnBlock(base, 0, []int{0}, true, false, msgs, 0) +} + func BenchmarkSyncBasic(b *testing.B) { for i := 0; i < b.N; i++ { runSyncBenchLength(b, 100) diff --git a/documentation/misc/actors_version_checklist.md b/documentation/misc/actors_version_checklist.md index 7931acbcc..769e9da42 100644 --- a/documentation/misc/actors_version_checklist.md +++ b/documentation/misc/actors_version_checklist.md @@ -12,6 +12,6 @@ - [ ] Update `chain/stmgr/forks.go` - [ ] Schedule - [ ] Migration -- [ ] Update upgrade schedule in `api/test/test.go` +- [ ] Update upgrade schedule in `api/test/test.go` and `chain/sync_test.go` - [ ] Update `NewestNetworkVersion` in `build/params_shared_vals.go` - [ ] Register in init in `chain/stmgr/utils.go` From 1f03a618f9abfcb107575e929d4a467f40391d5f Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Tue, 18 May 2021 14:56:42 -0400 Subject: [PATCH 58/88] Plumb contexts through --- chain/messagepool/messagepool.go | 103 ++++++++++------------ chain/messagepool/messagepool_test.go | 46 +++++----- chain/messagepool/pruning.go | 2 +- chain/messagepool/repub_test.go | 4 +- chain/messagepool/selection_test.go | 4 +- chain/messagesigner/messagesigner.go | 8 +- chain/messagesigner/messagesigner_test.go | 4 +- chain/sub/incoming.go | 2 +- node/impl/full/gas.go | 2 +- node/impl/full/mpool.go | 12 +-- node/modules/chain.go | 3 +- node/modules/mpoolnonceapi.go | 2 +- 12 files changed, 94 insertions(+), 98 deletions(-) diff --git a/chain/messagepool/messagepool.go b/chain/messagepool/messagepool.go index 299634c6f..0c8569a1f 100644 --- a/chain/messagepool/messagepool.go +++ b/chain/messagepool/messagepool.go @@ -331,7 +331,7 @@ func (ms *msgSet) getRequiredFunds(nonce uint64) types.BigInt { return types.BigInt{Int: requiredFunds} } -func New(api Provider, ds dtypes.MetadataDS, netName dtypes.NetworkName, j journal.Journal) (*MessagePool, error) { +func New(ctx context.Context, api Provider, ds dtypes.MetadataDS, netName dtypes.NetworkName, j journal.Journal) (*MessagePool, error) { cache, _ := lru.New2Q(build.BlsSignatureCacheSize) verifcache, _ := lru.New2Q(build.VerifSigCacheSize) @@ -375,7 +375,7 @@ func New(api Provider, ds dtypes.MetadataDS, netName dtypes.NetworkName, j journ // load the current tipset and subscribe to head changes _before_ loading local messages mp.curTs = api.SubscribeHeadChanges(func(rev, app []*types.TipSet) error { - err := mp.HeadChange(rev, app) + err := mp.HeadChange(ctx, rev, app) if err != nil { log.Errorf("mpool head notif handler error: %+v", err) } @@ -386,7 +386,7 @@ func New(api Provider, ds dtypes.MetadataDS, netName dtypes.NetworkName, j journ mp.lk.Lock() go func() { - err := mp.loadLocal() + err := mp.loadLocal(ctx) mp.lk.Unlock() mp.curTsLk.Unlock() @@ -444,9 +444,8 @@ func (mp *MessagePool) runLoop() { } } -func (mp *MessagePool) addLocal(m *types.SignedMessage) error { - // TODO: Is context.TODO() safe here? Idk how Go works. - sk, err := mp.api.StateAccountKey(context.TODO(), m.Message.From, mp.curTs) +func (mp *MessagePool) addLocal(ctx context.Context, m *types.SignedMessage) error { + sk, err := mp.api.StateAccountKey(ctx, m.Message.From, mp.curTs) if err != nil { log.Debugf("mpooladdlocal failed to resolve sender: %s", err) return err @@ -519,7 +518,7 @@ func (mp *MessagePool) verifyMsgBeforeAdd(m *types.SignedMessage, curTs *types.T return publish, nil } -func (mp *MessagePool) Push(m *types.SignedMessage) (cid.Cid, error) { +func (mp *MessagePool) Push(ctx context.Context, m *types.SignedMessage) (cid.Cid, error) { err := mp.checkMessage(m) if err != nil { return cid.Undef, err @@ -532,7 +531,7 @@ func (mp *MessagePool) Push(m *types.SignedMessage) (cid.Cid, error) { }() mp.curTsLk.Lock() - publish, err := mp.addTs(m, mp.curTs, true, false) + publish, err := mp.addTs(ctx, m, mp.curTs, true, false) if err != nil { mp.curTsLk.Unlock() return cid.Undef, err @@ -585,7 +584,7 @@ func (mp *MessagePool) checkMessage(m *types.SignedMessage) error { return nil } -func (mp *MessagePool) Add(m *types.SignedMessage) error { +func (mp *MessagePool) Add(ctx context.Context, m *types.SignedMessage) error { err := mp.checkMessage(m) if err != nil { return err @@ -600,7 +599,7 @@ func (mp *MessagePool) Add(m *types.SignedMessage) error { mp.curTsLk.Lock() defer mp.curTsLk.Unlock() - _, err = mp.addTs(m, mp.curTs, false, false) + _, err = mp.addTs(ctx, m, mp.curTs, false, false) return err } @@ -640,7 +639,7 @@ func (mp *MessagePool) VerifyMsgSig(m *types.SignedMessage) error { return nil } -func (mp *MessagePool) checkBalance(m *types.SignedMessage, curTs *types.TipSet) error { +func (mp *MessagePool) checkBalance(ctx context.Context, m *types.SignedMessage, curTs *types.TipSet) error { balance, err := mp.getStateBalance(m.Message.From, curTs) if err != nil { return xerrors.Errorf("failed to check sender balance: %s: %w", err, ErrSoftValidationFailure) @@ -654,8 +653,7 @@ func (mp *MessagePool) checkBalance(m *types.SignedMessage, curTs *types.TipSet) // add Value for soft failure check //requiredFunds = types.BigAdd(requiredFunds, m.Message.Value) - // TODO: Is context.TODO() safe here? Idk how Go works. - sk, err := mp.api.StateAccountKey(context.TODO(), m.Message.From, mp.curTs) + sk, err := mp.api.StateAccountKey(ctx, m.Message.From, mp.curTs) if err != nil { log.Debugf("mpoolcheckbalance failed to resolve sender: %s", err) return err @@ -675,7 +673,7 @@ func (mp *MessagePool) checkBalance(m *types.SignedMessage, curTs *types.TipSet) return nil } -func (mp *MessagePool) addTs(m *types.SignedMessage, curTs *types.TipSet, local, untrusted bool) (bool, error) { +func (mp *MessagePool) addTs(ctx context.Context, m *types.SignedMessage, curTs *types.TipSet, local, untrusted bool) (bool, error) { snonce, err := mp.getStateNonce(m.Message.From, curTs) if err != nil { return false, xerrors.Errorf("failed to look up actor state nonce: %s: %w", err, ErrSoftValidationFailure) @@ -693,17 +691,17 @@ func (mp *MessagePool) addTs(m *types.SignedMessage, curTs *types.TipSet, local, return false, err } - if err := mp.checkBalance(m, curTs); err != nil { + if err := mp.checkBalance(ctx, m, curTs); err != nil { return false, err } - err = mp.addLocked(m, !local, untrusted) + err = mp.addLocked(ctx, m, !local, untrusted) if err != nil { return false, err } if local { - err = mp.addLocal(m) + err = mp.addLocal(ctx, m) if err != nil { return false, xerrors.Errorf("error persisting local message: %w", err) } @@ -712,7 +710,7 @@ func (mp *MessagePool) addTs(m *types.SignedMessage, curTs *types.TipSet, local, return publish, nil } -func (mp *MessagePool) addLoaded(m *types.SignedMessage) error { +func (mp *MessagePool) addLoaded(ctx context.Context, m *types.SignedMessage) error { err := mp.checkMessage(m) if err != nil { return err @@ -738,21 +736,21 @@ func (mp *MessagePool) addLoaded(m *types.SignedMessage) error { return err } - if err := mp.checkBalance(m, curTs); err != nil { + if err := mp.checkBalance(ctx, m, curTs); err != nil { return err } - return mp.addLocked(m, false, false) + return mp.addLocked(ctx, m, false, false) } -func (mp *MessagePool) addSkipChecks(m *types.SignedMessage) error { +func (mp *MessagePool) addSkipChecks(ctx context.Context, m *types.SignedMessage) error { mp.lk.Lock() defer mp.lk.Unlock() - return mp.addLocked(m, false, false) + return mp.addLocked(ctx, m, false, false) } -func (mp *MessagePool) addLocked(m *types.SignedMessage, strict, untrusted bool) error { +func (mp *MessagePool) addLocked(ctx context.Context, m *types.SignedMessage, strict, untrusted bool) error { log.Debugf("mpooladd: %s %d", m.Message.From, m.Message.Nonce) if m.Signature.Type == crypto.SigTypeBLS { mp.blsSigCache.Add(m.Cid(), m.Signature) @@ -768,8 +766,7 @@ func (mp *MessagePool) addLocked(m *types.SignedMessage, strict, untrusted bool) return err } - // TODO: Is context.TODO() safe here? Idk how Go works. - sk, err := mp.api.StateAccountKey(context.TODO(), m.Message.From, mp.curTs) + sk, err := mp.api.StateAccountKey(ctx, m.Message.From, mp.curTs) if err != nil { log.Debugf("mpooladd failed to resolve sender: %s", err) return err @@ -818,24 +815,23 @@ func (mp *MessagePool) addLocked(m *types.SignedMessage, strict, untrusted bool) return nil } -func (mp *MessagePool) GetNonce(addr address.Address) (uint64, error) { +func (mp *MessagePool) GetNonce(ctx context.Context, addr address.Address) (uint64, error) { mp.curTsLk.Lock() defer mp.curTsLk.Unlock() mp.lk.Lock() defer mp.lk.Unlock() - return mp.getNonceLocked(addr, mp.curTs) + return mp.getNonceLocked(ctx, addr, mp.curTs) } -func (mp *MessagePool) getNonceLocked(addr address.Address, curTs *types.TipSet) (uint64, error) { +func (mp *MessagePool) getNonceLocked(ctx context.Context, addr address.Address, curTs *types.TipSet) (uint64, error) { stateNonce, err := mp.getStateNonce(addr, curTs) // sanity check if err != nil { return 0, err } - // TODO: Is context.TODO() safe here? Idk how Go works. - sk, err := mp.api.StateAccountKey(context.TODO(), addr, mp.curTs) + sk, err := mp.api.StateAccountKey(ctx, addr, mp.curTs) if err != nil { log.Debugf("mpoolgetnonce failed to resolve sender: %s", err) return 0, err @@ -878,7 +874,7 @@ func (mp *MessagePool) getStateBalance(addr address.Address, ts *types.TipSet) ( // - strict checks are enabled // - extra strict add checks are used when adding the messages to the msgSet // that means: no nonce gaps, at most 10 pending messages for the actor -func (mp *MessagePool) PushUntrusted(m *types.SignedMessage) (cid.Cid, error) { +func (mp *MessagePool) PushUntrusted(ctx context.Context, m *types.SignedMessage) (cid.Cid, error) { err := mp.checkMessage(m) if err != nil { return cid.Undef, err @@ -891,7 +887,7 @@ func (mp *MessagePool) PushUntrusted(m *types.SignedMessage) (cid.Cid, error) { }() mp.curTsLk.Lock() - publish, err := mp.addTs(m, mp.curTs, true, true) + publish, err := mp.addTs(ctx, m, mp.curTs, true, true) if err != nil { mp.curTsLk.Unlock() return cid.Undef, err @@ -913,16 +909,15 @@ func (mp *MessagePool) PushUntrusted(m *types.SignedMessage) (cid.Cid, error) { return m.Cid(), nil } -func (mp *MessagePool) Remove(from address.Address, nonce uint64, applied bool) { +func (mp *MessagePool) Remove(ctx context.Context, from address.Address, nonce uint64, applied bool) { mp.lk.Lock() defer mp.lk.Unlock() - mp.remove(from, nonce, applied) + mp.remove(ctx, from, nonce, applied) } -func (mp *MessagePool) remove(from address.Address, nonce uint64, applied bool) { - // TODO: Is context.TODO() safe here? Idk how Go works. - sk, err := mp.api.StateAccountKey(context.TODO(), from, mp.curTs) +func (mp *MessagePool) remove(ctx context.Context, from address.Address, nonce uint64, applied bool) { + sk, err := mp.api.StateAccountKey(ctx, from, mp.curTs) if err != nil { log.Debugf("mpoolremove failed to resolve sender: %s", err) return @@ -957,37 +952,36 @@ func (mp *MessagePool) remove(from address.Address, nonce uint64, applied bool) } } -func (mp *MessagePool) Pending() ([]*types.SignedMessage, *types.TipSet) { +func (mp *MessagePool) Pending(ctx context.Context) ([]*types.SignedMessage, *types.TipSet) { mp.curTsLk.Lock() defer mp.curTsLk.Unlock() mp.lk.Lock() defer mp.lk.Unlock() - return mp.allPending() + return mp.allPending(ctx) } -func (mp *MessagePool) allPending() ([]*types.SignedMessage, *types.TipSet) { +func (mp *MessagePool) allPending(ctx context.Context) ([]*types.SignedMessage, *types.TipSet) { out := make([]*types.SignedMessage, 0) for a := range mp.pending { - out = append(out, mp.pendingFor(a)...) + out = append(out, mp.pendingFor(ctx, a)...) } return out, mp.curTs } -func (mp *MessagePool) PendingFor(a address.Address) ([]*types.SignedMessage, *types.TipSet) { +func (mp *MessagePool) PendingFor(ctx context.Context, a address.Address) ([]*types.SignedMessage, *types.TipSet) { mp.curTsLk.Lock() defer mp.curTsLk.Unlock() mp.lk.Lock() defer mp.lk.Unlock() - return mp.pendingFor(a), mp.curTs + return mp.pendingFor(ctx, a), mp.curTs } -func (mp *MessagePool) pendingFor(a address.Address) []*types.SignedMessage { - // TODO: Is context.TODO() safe here? Idk how Go works. - sk, err := mp.api.StateAccountKey(context.TODO(), a, mp.curTs) +func (mp *MessagePool) pendingFor(ctx context.Context, a address.Address) []*types.SignedMessage { + sk, err := mp.api.StateAccountKey(ctx, a, mp.curTs) if err != nil { log.Debugf("mpoolpendingfor failed to resolve sender: %s", err) return nil @@ -1011,7 +1005,7 @@ func (mp *MessagePool) pendingFor(a address.Address) []*types.SignedMessage { return set } -func (mp *MessagePool) HeadChange(revert []*types.TipSet, apply []*types.TipSet) error { +func (mp *MessagePool) HeadChange(ctx context.Context, revert []*types.TipSet, apply []*types.TipSet) error { mp.curTsLk.Lock() defer mp.curTsLk.Unlock() @@ -1028,7 +1022,7 @@ func (mp *MessagePool) HeadChange(revert []*types.TipSet, apply []*types.TipSet) rm := func(from address.Address, nonce uint64) { s, ok := rmsgs[from] if !ok { - mp.Remove(from, nonce, true) + mp.Remove(ctx, from, nonce, true) return } @@ -1037,7 +1031,7 @@ func (mp *MessagePool) HeadChange(revert []*types.TipSet, apply []*types.TipSet) return } - mp.Remove(from, nonce, true) + mp.Remove(ctx, from, nonce, true) } maybeRepub := func(cid cid.Cid) { @@ -1108,7 +1102,7 @@ func (mp *MessagePool) HeadChange(revert []*types.TipSet, apply []*types.TipSet) for _, s := range rmsgs { for _, msg := range s { - if err := mp.addSkipChecks(msg); err != nil { + if err := mp.addSkipChecks(ctx, msg); err != nil { log.Errorf("Failed to readd message from reorg to mpool: %s", err) } } @@ -1116,7 +1110,7 @@ func (mp *MessagePool) HeadChange(revert []*types.TipSet, apply []*types.TipSet) if len(revert) > 0 && futureDebug { mp.lk.Lock() - msgs, ts := mp.allPending() + msgs, ts := mp.allPending(ctx) mp.lk.Unlock() buckets := map[address.Address]*statBucket{} @@ -1323,7 +1317,7 @@ func (mp *MessagePool) Updates(ctx context.Context) (<-chan api.MpoolUpdate, err return out, nil } -func (mp *MessagePool) loadLocal() error { +func (mp *MessagePool) loadLocal(ctx context.Context) error { res, err := mp.localMsgs.Query(query.Query{}) if err != nil { return xerrors.Errorf("query local messages: %w", err) @@ -1339,7 +1333,7 @@ func (mp *MessagePool) loadLocal() error { return xerrors.Errorf("unmarshaling local message: %w", err) } - if err := mp.addLoaded(&sm); err != nil { + if err := mp.addLoaded(ctx, &sm); err != nil { if xerrors.Is(err, ErrNonceTooLow) { continue // todo: drop the message from local cache (if above certain confidence threshold) } @@ -1347,8 +1341,7 @@ func (mp *MessagePool) loadLocal() error { log.Errorf("adding local message: %+v", err) } - // TODO: Is context.TODO() safe here? Idk how Go works. - sk, err := mp.api.StateAccountKey(context.TODO(), sm.Message.From, mp.curTs) + sk, err := mp.api.StateAccountKey(ctx, sm.Message.From, mp.curTs) if err != nil { log.Debugf("mpoolloadLocal failed to resolve sender: %s", err) return err diff --git a/chain/messagepool/messagepool_test.go b/chain/messagepool/messagepool_test.go index e31df936c..3e5bad81f 100644 --- a/chain/messagepool/messagepool_test.go +++ b/chain/messagepool/messagepool_test.go @@ -199,7 +199,7 @@ func (tma *testMpoolAPI) ChainComputeBaseFee(ctx context.Context, ts *types.TipS func assertNonce(t *testing.T, mp *MessagePool, addr address.Address, val uint64) { t.Helper() - n, err := mp.GetNonce(addr) + n, err := mp.GetNonce(context.TODO(), addr) if err != nil { t.Fatal(err) } @@ -211,7 +211,7 @@ func assertNonce(t *testing.T, mp *MessagePool, addr address.Address, val uint64 func mustAdd(t *testing.T, mp *MessagePool, msg *types.SignedMessage) { t.Helper() - if err := mp.Add(msg); err != nil { + if err := mp.Add(context.TODO(), msg); err != nil { t.Fatal(err) } } @@ -226,7 +226,7 @@ func TestMessagePool(t *testing.T) { ds := datastore.NewMapDatastore() - mp, err := New(tma, ds, "mptest", nil) + mp, err := New(context.TODO(), tma, ds, "mptest", nil) if err != nil { t.Fatal(err) } @@ -267,7 +267,7 @@ func TestMessagePoolMessagesInEachBlock(t *testing.T) { ds := datastore.NewMapDatastore() - mp, err := New(tma, ds, "mptest", nil) + mp, err := New(context.TODO(), tma, ds, "mptest", nil) if err != nil { t.Fatal(err) } @@ -293,7 +293,7 @@ func TestMessagePoolMessagesInEachBlock(t *testing.T) { tma.applyBlock(t, a) tsa := mock.TipSet(a) - _, _ = mp.Pending() + _, _ = mp.Pending(context.TODO()) selm, _ := mp.SelectMessages(tsa, 1) if len(selm) == 0 { @@ -316,7 +316,7 @@ func TestRevertMessages(t *testing.T) { ds := datastore.NewMapDatastore() - mp, err := New(tma, ds, "mptest", nil) + mp, err := New(context.TODO(), tma, ds, "mptest", nil) if err != nil { t.Fatal(err) } @@ -355,7 +355,7 @@ func TestRevertMessages(t *testing.T) { assertNonce(t, mp, sender, 4) - p, _ := mp.Pending() + p, _ := mp.Pending(context.TODO()) fmt.Printf("%+v\n", p) if len(p) != 3 { t.Fatal("expected three messages in mempool") @@ -379,7 +379,7 @@ func TestPruningSimple(t *testing.T) { ds := datastore.NewMapDatastore() - mp, err := New(tma, ds, "mptest", nil) + mp, err := New(context.TODO(), tma, ds, "mptest", nil) if err != nil { t.Fatal(err) } @@ -396,14 +396,14 @@ func TestPruningSimple(t *testing.T) { for i := 0; i < 5; i++ { smsg := mock.MkMessage(sender, target, uint64(i), w) - if err := mp.Add(smsg); err != nil { + if err := mp.Add(context.TODO(), smsg); err != nil { t.Fatal(err) } } for i := 10; i < 50; i++ { smsg := mock.MkMessage(sender, target, uint64(i), w) - if err := mp.Add(smsg); err != nil { + if err := mp.Add(context.TODO(), smsg); err != nil { t.Fatal(err) } } @@ -413,7 +413,7 @@ func TestPruningSimple(t *testing.T) { mp.Prune() - msgs, _ := mp.Pending() + msgs, _ := mp.Pending(context.TODO()) if len(msgs) != 5 { t.Fatal("expected only 5 messages in pool, got: ", len(msgs)) } @@ -423,7 +423,7 @@ func TestLoadLocal(t *testing.T) { tma := newTestMpoolAPI() ds := datastore.NewMapDatastore() - mp, err := New(tma, ds, "mptest", nil) + mp, err := New(context.TODO(), tma, ds, "mptest", nil) if err != nil { t.Fatal(err) } @@ -455,7 +455,7 @@ func TestLoadLocal(t *testing.T) { msgs := make(map[cid.Cid]struct{}) for i := 0; i < 10; i++ { m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(i+1)) - cid, err := mp.Push(m) + cid, err := mp.Push(context.TODO(), m) if err != nil { t.Fatal(err) } @@ -466,12 +466,12 @@ func TestLoadLocal(t *testing.T) { t.Fatal(err) } - mp, err = New(tma, ds, "mptest", nil) + mp, err = New(context.TODO(), tma, ds, "mptest", nil) if err != nil { t.Fatal(err) } - pmsgs, _ := mp.Pending() + pmsgs, _ := mp.Pending(context.TODO()) if len(msgs) != len(pmsgs) { t.Fatalf("expected %d messages, but got %d", len(msgs), len(pmsgs)) } @@ -495,7 +495,7 @@ func TestClearAll(t *testing.T) { tma := newTestMpoolAPI() ds := datastore.NewMapDatastore() - mp, err := New(tma, ds, "mptest", nil) + mp, err := New(context.TODO(), tma, ds, "mptest", nil) if err != nil { t.Fatal(err) } @@ -526,7 +526,7 @@ func TestClearAll(t *testing.T) { gasLimit := gasguess.Costs[gasguess.CostKey{Code: builtin2.StorageMarketActorCodeID, M: 2}] for i := 0; i < 10; i++ { m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(i+1)) - _, err := mp.Push(m) + _, err := mp.Push(context.TODO(), m) if err != nil { t.Fatal(err) } @@ -539,7 +539,7 @@ func TestClearAll(t *testing.T) { mp.Clear(true) - pending, _ := mp.Pending() + pending, _ := mp.Pending(context.TODO()) if len(pending) > 0 { t.Fatalf("cleared the mpool, but got %d pending messages", len(pending)) } @@ -549,7 +549,7 @@ func TestClearNonLocal(t *testing.T) { tma := newTestMpoolAPI() ds := datastore.NewMapDatastore() - mp, err := New(tma, ds, "mptest", nil) + mp, err := New(context.TODO(), tma, ds, "mptest", nil) if err != nil { t.Fatal(err) } @@ -581,7 +581,7 @@ func TestClearNonLocal(t *testing.T) { gasLimit := gasguess.Costs[gasguess.CostKey{Code: builtin2.StorageMarketActorCodeID, M: 2}] for i := 0; i < 10; i++ { m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(i+1)) - _, err := mp.Push(m) + _, err := mp.Push(context.TODO(), m) if err != nil { t.Fatal(err) } @@ -594,7 +594,7 @@ func TestClearNonLocal(t *testing.T) { mp.Clear(false) - pending, _ := mp.Pending() + pending, _ := mp.Pending(context.TODO()) if len(pending) != 10 { t.Fatalf("expected 10 pending messages, but got %d instead", len(pending)) } @@ -610,7 +610,7 @@ func TestUpdates(t *testing.T) { tma := newTestMpoolAPI() ds := datastore.NewMapDatastore() - mp, err := New(tma, ds, "mptest", nil) + mp, err := New(context.TODO(), tma, ds, "mptest", nil) if err != nil { t.Fatal(err) } @@ -651,7 +651,7 @@ func TestUpdates(t *testing.T) { for i := 0; i < 10; i++ { m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(i+1)) - _, err := mp.Push(m) + _, err := mp.Push(context.TODO(), m) if err != nil { t.Fatal(err) } diff --git a/chain/messagepool/pruning.go b/chain/messagepool/pruning.go index dc1c69417..ad8f38c50 100644 --- a/chain/messagepool/pruning.go +++ b/chain/messagepool/pruning.go @@ -108,7 +108,7 @@ keepLoop: // and remove all messages that are still in pruneMsgs after processing the chains log.Infof("Pruning %d messages", len(pruneMsgs)) for _, m := range pruneMsgs { - mp.remove(m.Message.From, m.Message.Nonce, false) + mp.remove(ctx, m.Message.From, m.Message.Nonce, false) } return nil diff --git a/chain/messagepool/repub_test.go b/chain/messagepool/repub_test.go index 8da64f974..70e457aaa 100644 --- a/chain/messagepool/repub_test.go +++ b/chain/messagepool/repub_test.go @@ -24,7 +24,7 @@ func TestRepubMessages(t *testing.T) { tma := newTestMpoolAPI() ds := datastore.NewMapDatastore() - mp, err := New(tma, ds, "mptest", nil) + mp, err := New(context.TODO(), tma, ds, "mptest", nil) if err != nil { t.Fatal(err) } @@ -56,7 +56,7 @@ func TestRepubMessages(t *testing.T) { for i := 0; i < 10; i++ { m := makeTestMessage(w1, a1, a2, uint64(i), gasLimit, uint64(i+1)) - _, err := mp.Push(m) + _, err := mp.Push(context.TODO(), m) if err != nil { t.Fatal(err) } diff --git a/chain/messagepool/selection_test.go b/chain/messagepool/selection_test.go index e32d897c4..f254c6706 100644 --- a/chain/messagepool/selection_test.go +++ b/chain/messagepool/selection_test.go @@ -60,7 +60,7 @@ func makeTestMessage(w *wallet.LocalWallet, from, to address.Address, nonce uint func makeTestMpool() (*MessagePool, *testMpoolAPI) { tma := newTestMpoolAPI() ds := datastore.NewMapDatastore() - mp, err := New(tma, ds, "test", nil) + mp, err := New(context.TODO(), tma, ds, "test", nil) if err != nil { panic(err) } @@ -464,7 +464,7 @@ func TestBasicMessageSelection(t *testing.T) { tma.applyBlock(t, block2) // we should have no pending messages in the mpool - pend, _ := mp.Pending() + pend, _ := mp.Pending(context.TODO()) if len(pend) != 0 { t.Fatalf("expected no pending messages, but got %d", len(pend)) } diff --git a/chain/messagesigner/messagesigner.go b/chain/messagesigner/messagesigner.go index ce9d01b3a..c64d00003 100644 --- a/chain/messagesigner/messagesigner.go +++ b/chain/messagesigner/messagesigner.go @@ -23,7 +23,7 @@ const dsKeyActorNonce = "ActorNextNonce" var log = logging.Logger("messagesigner") type MpoolNonceAPI interface { - GetNonce(address.Address) (uint64, error) + GetNonce(context.Context, address.Address) (uint64, error) } // MessageSigner keeps track of nonces per address, and increments the nonce @@ -51,7 +51,7 @@ func (ms *MessageSigner) SignMessage(ctx context.Context, msg *types.Message, cb defer ms.lk.Unlock() // Get the next message nonce - nonce, err := ms.nextNonce(msg.From) + nonce, err := ms.nextNonce(ctx, msg.From) if err != nil { return nil, xerrors.Errorf("failed to create nonce: %w", err) } @@ -92,12 +92,12 @@ func (ms *MessageSigner) SignMessage(ctx context.Context, msg *types.Message, cb // nextNonce gets the next nonce for the given address. // If there is no nonce in the datastore, gets the nonce from the message pool. -func (ms *MessageSigner) nextNonce(addr address.Address) (uint64, error) { +func (ms *MessageSigner) nextNonce(ctx context.Context, addr address.Address) (uint64, error) { // Nonces used to be created by the mempool and we need to support nodes // that have mempool nonces, so first check the mempool for a nonce for // this address. Note that the mempool returns the actor state's nonce // by default. - nonce, err := ms.mpool.GetNonce(addr) + nonce, err := ms.mpool.GetNonce(ctx, addr) if err != nil { return 0, xerrors.Errorf("failed to get nonce from mempool: %w", err) } diff --git a/chain/messagesigner/messagesigner_test.go b/chain/messagesigner/messagesigner_test.go index 5eebd36da..8206b11c0 100644 --- a/chain/messagesigner/messagesigner_test.go +++ b/chain/messagesigner/messagesigner_test.go @@ -24,6 +24,8 @@ type mockMpool struct { nonces map[address.Address]uint64 } +var _ MpoolNonceAPI = (*mockMpool)(nil) + func newMockMpool() *mockMpool { return &mockMpool{nonces: make(map[address.Address]uint64)} } @@ -35,7 +37,7 @@ func (mp *mockMpool) setNonce(addr address.Address, nonce uint64) { mp.nonces[addr] = nonce } -func (mp *mockMpool) GetNonce(addr address.Address) (uint64, error) { +func (mp *mockMpool) GetNonce(ctx context.Context, addr address.Address) (uint64, error) { mp.lk.RLock() defer mp.lk.RUnlock() diff --git a/chain/sub/incoming.go b/chain/sub/incoming.go index d1c6414a1..e262fe271 100644 --- a/chain/sub/incoming.go +++ b/chain/sub/incoming.go @@ -516,7 +516,7 @@ func (mv *MessageValidator) Validate(ctx context.Context, pid peer.ID, msg *pubs return pubsub.ValidationReject } - if err := mv.mpool.Add(m); err != nil { + if err := mv.mpool.Add(ctx, m); err != nil { log.Debugf("failed to add message from network to message pool (From: %s, To: %s, Nonce: %d, Value: %s): %s", m.Message.From, m.Message.To, m.Message.Nonce, types.FIL(m.Message.Value), err) ctx, _ = tag.New( ctx, diff --git a/node/impl/full/gas.go b/node/impl/full/gas.go index 3d9889c10..acd2eccfe 100644 --- a/node/impl/full/gas.go +++ b/node/impl/full/gas.go @@ -265,7 +265,7 @@ func gasEstimateGasLimit( return -1, xerrors.Errorf("getting key address: %w", err) } - pending, ts := mpool.PendingFor(fromA) + pending, ts := mpool.PendingFor(ctx, fromA) priorMsgs := make([]types.ChainMsg, 0, len(pending)) for _, m := range pending { if m.Message.Nonce == msg.Nonce { diff --git a/node/impl/full/mpool.go b/node/impl/full/mpool.go index b1e9f94f9..9aa5371e9 100644 --- a/node/impl/full/mpool.go +++ b/node/impl/full/mpool.go @@ -66,7 +66,7 @@ func (a *MpoolAPI) MpoolPending(ctx context.Context, tsk types.TipSetKey) ([]*ty if err != nil { return nil, xerrors.Errorf("loading tipset %s: %w", tsk, err) } - pending, mpts := a.Mpool.Pending() + pending, mpts := a.Mpool.Pending(ctx) haveCids := map[cid.Cid]struct{}{} for _, m := range pending { @@ -125,11 +125,11 @@ func (a *MpoolAPI) MpoolClear(ctx context.Context, local bool) error { } func (m *MpoolModule) MpoolPush(ctx context.Context, smsg *types.SignedMessage) (cid.Cid, error) { - return m.Mpool.Push(smsg) + return m.Mpool.Push(ctx, smsg) } func (a *MpoolAPI) MpoolPushUntrusted(ctx context.Context, smsg *types.SignedMessage) (cid.Cid, error) { - return a.Mpool.PushUntrusted(smsg) + return a.Mpool.PushUntrusted(ctx, smsg) } func (a *MpoolAPI) MpoolPushMessage(ctx context.Context, msg *types.Message, spec *api.MessageSendSpec) (*types.SignedMessage, error) { @@ -190,7 +190,7 @@ func (a *MpoolAPI) MpoolPushMessage(ctx context.Context, msg *types.Message, spe func (a *MpoolAPI) MpoolBatchPush(ctx context.Context, smsgs []*types.SignedMessage) ([]cid.Cid, error) { var messageCids []cid.Cid for _, smsg := range smsgs { - smsgCid, err := a.Mpool.Push(smsg) + smsgCid, err := a.Mpool.Push(ctx, smsg) if err != nil { return messageCids, err } @@ -202,7 +202,7 @@ func (a *MpoolAPI) MpoolBatchPush(ctx context.Context, smsgs []*types.SignedMess func (a *MpoolAPI) MpoolBatchPushUntrusted(ctx context.Context, smsgs []*types.SignedMessage) ([]cid.Cid, error) { var messageCids []cid.Cid for _, smsg := range smsgs { - smsgCid, err := a.Mpool.PushUntrusted(smsg) + smsgCid, err := a.Mpool.PushUntrusted(ctx, smsg) if err != nil { return messageCids, err } @@ -224,7 +224,7 @@ func (a *MpoolAPI) MpoolBatchPushMessage(ctx context.Context, msgs []*types.Mess } func (a *MpoolAPI) MpoolGetNonce(ctx context.Context, addr address.Address) (uint64, error) { - return a.Mpool.GetNonce(addr) + return a.Mpool.GetNonce(ctx, addr) } func (a *MpoolAPI) MpoolSub(ctx context.Context) (<-chan api.MpoolUpdate, error) { diff --git a/node/modules/chain.go b/node/modules/chain.go index ffdf3aa3a..b0f0543c6 100644 --- a/node/modules/chain.go +++ b/node/modules/chain.go @@ -61,7 +61,8 @@ func ChainBlockService(bs dtypes.ExposedBlockstore, rem dtypes.ChainBitswap) dty 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) - mp, err := messagepool.New(mpp, ds, nn, j) + // TODO: I still don't know how go works -- should this context be part of the builder? + mp, err := messagepool.New(context.TODO(), 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 efcb14037..3d670611b 100644 --- a/node/modules/mpoolnonceapi.go +++ b/node/modules/mpoolnonceapi.go @@ -23,7 +23,7 @@ type MpoolNonceAPI struct { } // GetNonce gets the nonce from current chain head. -func (a *MpoolNonceAPI) GetNonce(addr address.Address) (uint64, error) { +func (a *MpoolNonceAPI) GetNonce(ctx context.Context, addr address.Address) (uint64, error) { ts := a.StateAPI.Chain.GetHeaviestTipSet() // make sure we have a key address so we can compare with messages From ed93d0725ffc17ab06cf690d32683b8f8753df7a Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Fri, 28 May 2021 20:35:50 -0400 Subject: [PATCH 59/88] Protect mp.localAddrs and mp.pending behind helper functions --- chain/messagepool/messagepool.go | 240 +++++++++++++++++++------- chain/messagepool/messagepool_test.go | 4 +- chain/messagepool/pruning.go | 4 +- chain/messagepool/repub.go | 18 +- chain/messagepool/selection.go | 5 +- node/impl/full/mpool.go | 2 +- 6 files changed, 192 insertions(+), 81 deletions(-) diff --git a/chain/messagepool/messagepool.go b/chain/messagepool/messagepool.go index 0c8569a1f..12e8f24c2 100644 --- a/chain/messagepool/messagepool.go +++ b/chain/messagepool/messagepool.go @@ -126,12 +126,14 @@ type MessagePool struct { republished map[cid.Cid]struct{} - // only pubkey addresses + // do NOT access this map directly, use isLocal, setLocal, and forEachLocal respectively localAddrs map[address.Address]struct{} - // only pubkey addresses + // do NOT access this map directly, use getPendingMset, setPendingMset, deletePendingMset, forEachPending, and clearPending respectively pending map[address.Address]*msgSet + keyCache map[address.Address]address.Address + curTsLk sync.Mutex // DO NOT LOCK INSIDE lk curTs *types.TipSet @@ -331,6 +333,20 @@ func (ms *msgSet) getRequiredFunds(nonce uint64) types.BigInt { return types.BigInt{Int: requiredFunds} } +func (ms *msgSet) toSlice() []*types.SignedMessage { + set := make([]*types.SignedMessage, 0, len(ms.msgs)) + + for _, m := range ms.msgs { + set = append(set, m) + } + + sort.Slice(set, func(i, j int) bool { + return set[i].Message.Nonce < set[j].Message.Nonce + }) + + return set +} + func New(ctx context.Context, api Provider, ds dtypes.MetadataDS, netName dtypes.NetworkName, j journal.Journal) (*MessagePool, error) { cache, _ := lru.New2Q(build.BlsSignatureCacheSize) verifcache, _ := lru.New2Q(build.VerifSigCacheSize) @@ -352,6 +368,7 @@ func New(ctx context.Context, api Provider, ds dtypes.MetadataDS, netName dtypes repubTrigger: make(chan struct{}, 1), localAddrs: make(map[address.Address]struct{}), pending: make(map[address.Address]*msgSet), + keyCache: make(map[address.Address]address.Address), minGasPrice: types.NewInt(0), pruneTrigger: make(chan struct{}, 1), pruneCooldown: make(chan struct{}, 1), @@ -397,12 +414,106 @@ func New(ctx context.Context, api Provider, ds dtypes.MetadataDS, netName dtypes log.Info("mpool ready") - mp.runLoop() + mp.runLoop(context.Background()) }() return mp, nil } +func (mp *MessagePool) resolveToKey(ctx context.Context, addr address.Address) (address.Address, error) { + // check the cache + a, f := mp.keyCache[addr] + if f { + return a, nil + } + + // resolve the address + ka, err := mp.api.StateAccountKey(ctx, addr, mp.curTs) + if err != nil { + return address.Undef, err + } + + // place both entries in the cache (may both be key addresses, which is fine) + mp.keyCache[addr] = ka + mp.keyCache[ka] = ka + + return ka, nil +} + +func (mp *MessagePool) getPendingMset(ctx context.Context, addr address.Address) (*msgSet, bool, error) { + ra, err := mp.resolveToKey(ctx, addr) + if err != nil { + return nil, false, err + } + + ms, f := mp.pending[ra] + + return ms, f, nil +} + +func (mp *MessagePool) setPendingMset(ctx context.Context, addr address.Address, ms *msgSet) error { + ra, err := mp.resolveToKey(ctx, addr) + if err != nil { + return err + } + + mp.pending[ra] = ms + + return nil +} + +// This method isn't strictly necessary, since it doesn't resolve any addresses, but it's safer to have +func (mp *MessagePool) forEachPending(f func(address.Address, *msgSet)) { + for la, ms := range mp.pending { + f(la, ms) + } +} + +func (mp *MessagePool) deletePendingMset(ctx context.Context, addr address.Address) error { + ra, err := mp.resolveToKey(ctx, addr) + if err != nil { + return err + } + + delete(mp.pending, ra) + + return nil +} + +// This method isn't strictly necessary, since it doesn't resolve any addresses, but it's safer to have +func (mp *MessagePool) clearPending() { + mp.pending = make(map[address.Address]*msgSet) +} + +func (mp *MessagePool) isLocal(ctx context.Context, addr address.Address) (bool, error) { + ra, err := mp.resolveToKey(ctx, addr) + if err != nil { + return false, err + } + + _, f := mp.localAddrs[ra] + + return f, nil +} + +func (mp *MessagePool) setLocal(ctx context.Context, addr address.Address) error { + ra, err := mp.resolveToKey(ctx, addr) + if err != nil { + return err + } + + mp.localAddrs[ra] = struct{}{} + + return nil +} + +// This method isn't strictly necessary, since it doesn't resolve any addresses, but it's safer to have +func (mp *MessagePool) forEachLocal(ctx context.Context, f func(context.Context, address.Address)) { + for la := range mp.localAddrs { + f(ctx, la) + } +} + func (mp *MessagePool) Close() error { close(mp.closer) return nil @@ -420,15 +531,15 @@ func (mp *MessagePool) Prune() { mp.pruneTrigger <- struct{}{} } -func (mp *MessagePool) runLoop() { +func (mp *MessagePool) runLoop(ctx context.Context) { for { select { case <-mp.repubTk.C: - if err := mp.republishPendingMessages(); err != nil { + if err := mp.republishPendingMessages(ctx); err != nil { log.Errorf("error while republishing messages: %s", err) } case <-mp.repubTrigger: - if err := mp.republishPendingMessages(); err != nil { + if err := mp.republishPendingMessages(ctx); err != nil { log.Errorf("error while republishing messages: %s", err) } @@ -445,14 +556,10 @@ func (mp *MessagePool) runLoop() { } func (mp *MessagePool) addLocal(ctx context.Context, m *types.SignedMessage) error { - sk, err := mp.api.StateAccountKey(ctx, m.Message.From, mp.curTs) - if err != nil { - log.Debugf("mpooladdlocal failed to resolve sender: %s", err) + if err := mp.setLocal(ctx, m.Message.From); err != nil { return err } - mp.localAddrs[sk] = struct{}{} - msgb, err := m.Serialize() if err != nil { return xerrors.Errorf("error serializing message: %w", err) @@ -653,13 +760,12 @@ func (mp *MessagePool) checkBalance(ctx context.Context, m *types.SignedMessage, // add Value for soft failure check //requiredFunds = types.BigAdd(requiredFunds, m.Message.Value) - sk, err := mp.api.StateAccountKey(ctx, m.Message.From, mp.curTs) + mset, ok, err := mp.getPendingMset(ctx, m.Message.From) if err != nil { - log.Debugf("mpoolcheckbalance failed to resolve sender: %s", err) + log.Debugf("mpoolcheckbalance failed to get pending mset: %s", err) return err } - mset, ok := mp.pending[sk] if ok { requiredFunds = types.BigAdd(requiredFunds, mset.getRequiredFunds(m.Message.Nonce)) } @@ -766,21 +872,22 @@ func (mp *MessagePool) addLocked(ctx context.Context, m *types.SignedMessage, st return err } - sk, err := mp.api.StateAccountKey(ctx, m.Message.From, mp.curTs) + mset, ok, err := mp.getPendingMset(ctx, m.Message.From) if err != nil { - log.Debugf("mpooladd failed to resolve sender: %s", err) + log.Debug(err) return err } - mset, ok := mp.pending[sk] if !ok { - nonce, err := mp.getStateNonce(sk, mp.curTs) + nonce, err := mp.getStateNonce(m.Message.From, mp.curTs) if err != nil { return xerrors.Errorf("failed to get initial actor nonce: %w", err) } mset = newMsgSet(nonce) - mp.pending[sk] = mset + if err = mp.setPendingMset(ctx, m.Message.From, mset); err != nil { + return xerrors.Errorf("failed to set pending mset: %w", err) + } } incr, err := mset.add(m, mp, strict, untrusted) @@ -831,13 +938,12 @@ func (mp *MessagePool) getNonceLocked(ctx context.Context, addr address.Address, return 0, err } - sk, err := mp.api.StateAccountKey(ctx, addr, mp.curTs) + mset, ok, err := mp.getPendingMset(ctx, addr) if err != nil { - log.Debugf("mpoolgetnonce failed to resolve sender: %s", err) + log.Debugf("mpoolgetnonce failed to get mset: %s", err) return 0, err } - mset, ok := mp.pending[sk] if ok { if stateNonce > mset.nextNonce { log.Errorf("state nonce was larger than mset.nextNonce (%d > %d)", stateNonce, mset.nextNonce) @@ -917,13 +1023,12 @@ func (mp *MessagePool) Remove(ctx context.Context, from address.Address, nonce u } func (mp *MessagePool) remove(ctx context.Context, from address.Address, nonce uint64, applied bool) { - sk, err := mp.api.StateAccountKey(ctx, from, mp.curTs) + mset, ok, err := mp.getPendingMset(ctx, from) if err != nil { - log.Debugf("mpoolremove failed to resolve sender: %s", err) + log.Debugf("mpoolremove failed to get mset: %s", err) return } - mset, ok := mp.pending[sk] if !ok { return } @@ -948,7 +1053,10 @@ func (mp *MessagePool) remove(ctx context.Context, from address.Address, nonce u mset.rm(nonce, applied) if len(mset.msgs) == 0 { - delete(mp.pending, from) + if err = mp.deletePendingMset(ctx, from); err != nil { + log.Debugf("mpoolremove failed to delete mset: %s", err) + return + } } } @@ -964,9 +1072,10 @@ func (mp *MessagePool) Pending(ctx context.Context) ([]*types.SignedMessage, *ty func (mp *MessagePool) allPending(ctx context.Context) ([]*types.SignedMessage, *types.TipSet) { out := make([]*types.SignedMessage, 0) - for a := range mp.pending { - out = append(out, mp.pendingFor(ctx, a)...) - } + + mp.forEachPending(func(a address.Address, mset *msgSet) { + out = append(out, mset.toSlice()...) + }) return out, mp.curTs } @@ -981,28 +1090,17 @@ func (mp *MessagePool) PendingFor(ctx context.Context, a address.Address) ([]*ty } func (mp *MessagePool) pendingFor(ctx context.Context, a address.Address) []*types.SignedMessage { - sk, err := mp.api.StateAccountKey(ctx, a, mp.curTs) + mset, ok, err := mp.getPendingMset(ctx, a) if err != nil { - log.Debugf("mpoolpendingfor failed to resolve sender: %s", err) + log.Debugf("mpoolpendingfor failed to get mset: %s", err) return nil } - mset := mp.pending[sk] - if mset == nil || len(mset.msgs) == 0 { + if mset == nil || !ok || len(mset.msgs) == 0 { return nil } - set := make([]*types.SignedMessage, 0, len(mset.msgs)) - - for _, m := range mset.msgs { - set = append(set, m) - } - - sort.Slice(set, func(i, j int) bool { - return set[i].Message.Nonce < set[j].Message.Nonce - }) - - return set + return mset.toSlice() } func (mp *MessagePool) HeadChange(ctx context.Context, revert []*types.TipSet, apply []*types.TipSet) error { @@ -1341,53 +1439,61 @@ func (mp *MessagePool) loadLocal(ctx context.Context) error { log.Errorf("adding local message: %+v", err) } - sk, err := mp.api.StateAccountKey(ctx, sm.Message.From, mp.curTs) - if err != nil { - log.Debugf("mpoolloadLocal failed to resolve sender: %s", err) + if err = mp.setLocal(ctx, sm.Message.From); err != nil { + log.Debugf("mpoolloadLocal errored: %s", err) return err } - - mp.localAddrs[sk] = struct{}{} } return nil } -func (mp *MessagePool) Clear(local bool) { +func (mp *MessagePool) Clear(ctx context.Context, local bool) { mp.lk.Lock() defer mp.lk.Unlock() // remove everything if local is true, including removing local messages from // the datastore if local { - for a := range mp.localAddrs { - mset, ok := mp.pending[a] - if !ok { - continue + mp.forEachLocal(ctx, func(ctx context.Context, la address.Address) { + mset, ok, err := mp.getPendingMset(ctx, la) + if err != nil { + log.Warnf("errored while getting pending mset: %w", err) + return } - for _, m := range mset.msgs { - err := mp.localMsgs.Delete(datastore.NewKey(string(m.Cid().Bytes()))) - if err != nil { - log.Warnf("error deleting local message: %s", err) + if ok { + for _, m := range mset.msgs { + err := mp.localMsgs.Delete(datastore.NewKey(string(m.Cid().Bytes()))) + if err != nil { + log.Warnf("error deleting local message: %s", err) + } } } - } + }) - mp.pending = make(map[address.Address]*msgSet) + mp.clearPending() mp.republished = nil return } - // remove everything except the local messages - for a := range mp.pending { - _, isLocal := mp.localAddrs[a] - if isLocal { - continue + mp.forEachPending(func(a address.Address, ms *msgSet) { + isLocal, err := mp.isLocal(ctx, a) + if err != nil { + log.Warnf("errored while determining isLocal: %w", err) + return } - delete(mp.pending, a) - } + + if isLocal { + return + } + + if err = mp.deletePendingMset(ctx, a); err != nil { + log.Warnf("errored while deleting mset: %w", err) + return + } + }) } func getBaseFeeLowerBound(baseFee, factor types.BigInt) types.BigInt { diff --git a/chain/messagepool/messagepool_test.go b/chain/messagepool/messagepool_test.go index 3e5bad81f..aa3331c11 100644 --- a/chain/messagepool/messagepool_test.go +++ b/chain/messagepool/messagepool_test.go @@ -537,7 +537,7 @@ func TestClearAll(t *testing.T) { mustAdd(t, mp, m) } - mp.Clear(true) + mp.Clear(context.Background(), true) pending, _ := mp.Pending(context.TODO()) if len(pending) > 0 { @@ -592,7 +592,7 @@ func TestClearNonLocal(t *testing.T) { mustAdd(t, mp, m) } - mp.Clear(false) + mp.Clear(context.Background(), false) pending, _ := mp.Pending(context.TODO()) if len(pending) != 10 { diff --git a/chain/messagepool/pruning.go b/chain/messagepool/pruning.go index ad8f38c50..6802e23f3 100644 --- a/chain/messagepool/pruning.go +++ b/chain/messagepool/pruning.go @@ -61,9 +61,9 @@ func (mp *MessagePool) pruneMessages(ctx context.Context, ts *types.TipSet) erro } // we also never prune locally published messages - for actor := range mp.localAddrs { + mp.forEachLocal(ctx, func(ctx context.Context, actor address.Address) { protected[actor] = struct{}{} - } + }) // Collect all messages to track which ones to remove and create chains for block inclusion pruneMsgs := make(map[cid.Cid]*types.SignedMessage, mp.currentSize) diff --git a/chain/messagepool/repub.go b/chain/messagepool/repub.go index 5fa68aa53..4323bdee1 100644 --- a/chain/messagepool/repub.go +++ b/chain/messagepool/repub.go @@ -18,7 +18,7 @@ const repubMsgLimit = 30 var RepublishBatchDelay = 100 * time.Millisecond -func (mp *MessagePool) republishPendingMessages() error { +func (mp *MessagePool) republishPendingMessages(ctx context.Context) error { mp.curTsLk.Lock() ts := mp.curTs @@ -32,13 +32,18 @@ func (mp *MessagePool) republishPendingMessages() error { pending := make(map[address.Address]map[uint64]*types.SignedMessage) mp.lk.Lock() mp.republished = nil // clear this to avoid races triggering an early republish - for actor := range mp.localAddrs { - mset, ok := mp.pending[actor] + mp.forEachLocal(ctx, func(ctx context.Context, actor address.Address) { + mset, ok, err := mp.getPendingMset(ctx, actor) + if err != nil { + log.Debugf("failed to get mset: %w", err) + return + } + if !ok { - continue + return } if len(mset.msgs) == 0 { - continue + return } // we need to copy this while holding the lock to avoid races with concurrent modification pend := make(map[uint64]*types.SignedMessage, len(mset.msgs)) @@ -46,7 +51,8 @@ func (mp *MessagePool) republishPendingMessages() error { pend[nonce] = m } pending[actor] = pend - } + }) + mp.lk.Unlock() mp.curTsLk.Unlock() diff --git a/chain/messagepool/selection.go b/chain/messagepool/selection.go index af450645f..dfed2b6b5 100644 --- a/chain/messagepool/selection.go +++ b/chain/messagepool/selection.go @@ -654,8 +654,7 @@ func (mp *MessagePool) getPendingMessages(curTs, ts *types.TipSet) (map[address. inSync = true } - // first add our current pending messages - for a, mset := range mp.pending { + mp.forEachPending(func(a address.Address, mset *msgSet) { if inSync { // no need to copy the map result[a] = mset.msgs @@ -668,7 +667,7 @@ func (mp *MessagePool) getPendingMessages(curTs, ts *types.TipSet) (map[address. result[a] = msetCopy } - } + }) // we are in sync, that's the happy path if inSync { diff --git a/node/impl/full/mpool.go b/node/impl/full/mpool.go index 9aa5371e9..e91fc8b9e 100644 --- a/node/impl/full/mpool.go +++ b/node/impl/full/mpool.go @@ -120,7 +120,7 @@ func (a *MpoolAPI) MpoolPending(ctx context.Context, tsk types.TipSetKey) ([]*ty } func (a *MpoolAPI) MpoolClear(ctx context.Context, local bool) error { - a.Mpool.Clear(local) + a.Mpool.Clear(ctx, local) return nil } From 183c12db2549af28ba83e4c89209d70a533dbe74 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Sun, 30 May 2021 14:54:09 -0400 Subject: [PATCH 60/88] Make mempool reject ID addresses that are not reorg-stable --- chain/messagepool/provider.go | 2 +- chain/stmgr/stmgr.go | 40 +++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/chain/messagepool/provider.go b/chain/messagepool/provider.go index 75a2efe91..b90a8af7d 100644 --- a/chain/messagepool/provider.go +++ b/chain/messagepool/provider.go @@ -75,7 +75,7 @@ func (mpp *mpoolProvider) GetActorAfter(addr address.Address, ts *types.TipSet) } func (mpp *mpoolProvider) StateAccountKey(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) { - return mpp.sm.ResolveToKeyAddress(ctx, addr, ts) + return mpp.sm.ResolveToKeyAddressReorgProof(ctx, addr, ts) } func (mpp *mpoolProvider) MessagesForBlock(h *types.BlockHeader) ([]*types.Message, []*types.SignedMessage, error) { diff --git a/chain/stmgr/stmgr.go b/chain/stmgr/stmgr.go index 93832f185..fb1fb80f0 100644 --- a/chain/stmgr/stmgr.go +++ b/chain/stmgr/stmgr.go @@ -6,6 +6,8 @@ import ( "fmt" "sync" + "github.com/filecoin-project/lotus/chain/actors/policy" + "github.com/ipfs/go-cid" cbor "github.com/ipfs/go-ipld-cbor" logging "github.com/ipfs/go-log/v2" @@ -542,6 +544,44 @@ func (sm *StateManager) ResolveToKeyAddress(ctx context.Context, addr address.Ad return vm.ResolveToKeyAddr(tree, cst, addr) } +// ResolveToKeyAddressReorgProof is similar to stmgr.ResolveToKeyAddress but fails if the ID address being resolved isn't reorg-stable yet. +// It should not be used for consensus-critical subsystems. +func (sm *StateManager) ResolveToKeyAddressReorgProof(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) { + switch addr.Protocol() { + case address.BLS, address.SECP256K1: + return addr, nil + case address.Actor: + return address.Undef, xerrors.New("cannot resolve actor address to key address") + default: + } + + if ts == nil { + ts = sm.cs.GetHeaviestTipSet() + } + + if ts.Height() > policy.ChainFinality { + var err error + ts, err = sm.ChainStore().GetTipsetByHeight(ctx, ts.Height()-policy.ChainFinality, ts, true) + if err != nil { + return address.Undef, xerrors.Errorf("failed to load lookback tipset: %w", err) + } + } + + cst := cbor.NewCborStore(sm.cs.StateBlockstore()) + + tree, err := state.LoadStateTree(cst, ts.ParentState()) + if err != nil { + return address.Undef, xerrors.Errorf("failed to load parent state tree: %w", err) + } + + resolved, err := vm.ResolveToKeyAddr(tree, cst, addr) + if err == nil { + return resolved, nil + } + + return address.Undef, xerrors.New("ID address not found in lookback state") +} + func (sm *StateManager) GetBlsPublicKey(ctx context.Context, addr address.Address, ts *types.TipSet) (pubk []byte, err error) { kaddr, err := sm.ResolveToKeyAddress(ctx, addr, ts) if err != nil { From 9ceee6028bb7f0958db5730d048f4710732be9e3 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Sun, 30 May 2021 15:20:47 -0400 Subject: [PATCH 61/88] More tweaking of context handling in the messagepool --- chain/messagepool/messagepool.go | 7 ++++-- chain/messagepool/messagepool_test.go | 20 +++++++-------- chain/messagepool/pruning.go | 7 +++++- chain/messagepool/repub_test.go | 2 +- chain/messagepool/selection.go | 18 +++++++------- chain/messagepool/selection_test.go | 36 +++++++++++++-------------- node/impl/full/mpool.go | 2 +- node/modules/chain.go | 3 +-- node/modules/mpoolnonceapi.go | 4 +-- 9 files changed, 53 insertions(+), 46 deletions(-) diff --git a/chain/messagepool/messagepool.go b/chain/messagepool/messagepool.go index 12e8f24c2..405e22320 100644 --- a/chain/messagepool/messagepool.go +++ b/chain/messagepool/messagepool.go @@ -347,7 +347,7 @@ func (ms *msgSet) toSlice() []*types.SignedMessage { return set } -func New(ctx context.Context, api Provider, ds dtypes.MetadataDS, netName dtypes.NetworkName, j journal.Journal) (*MessagePool, error) { +func New(api Provider, ds dtypes.MetadataDS, netName dtypes.NetworkName, j journal.Journal) (*MessagePool, error) { cache, _ := lru.New2Q(build.BlsSignatureCacheSize) verifcache, _ := lru.New2Q(build.VerifSigCacheSize) @@ -390,6 +390,8 @@ func New(ctx context.Context, api Provider, ds dtypes.MetadataDS, netName dtypes // enable initial prunes mp.pruneCooldown <- struct{}{} + ctx, cancel := context.WithCancel(context.Background()) + // load the current tipset and subscribe to head changes _before_ loading local messages mp.curTs = api.SubscribeHeadChanges(func(rev, app []*types.TipSet) error { err := mp.HeadChange(ctx, rev, app) @@ -403,6 +405,7 @@ func New(ctx context.Context, api Provider, ds dtypes.MetadataDS, netName dtypes mp.lk.Lock() go func() { + defer cancel() err := mp.loadLocal(ctx) mp.lk.Unlock() @@ -414,7 +417,7 @@ func New(ctx context.Context, api Provider, ds dtypes.MetadataDS, netName dtypes log.Info("mpool ready") - mp.runLoop(context.Background()) + mp.runLoop(ctx) }() return mp, nil diff --git a/chain/messagepool/messagepool_test.go b/chain/messagepool/messagepool_test.go index aa3331c11..307ec20a0 100644 --- a/chain/messagepool/messagepool_test.go +++ b/chain/messagepool/messagepool_test.go @@ -226,7 +226,7 @@ func TestMessagePool(t *testing.T) { ds := datastore.NewMapDatastore() - mp, err := New(context.TODO(), tma, ds, "mptest", nil) + mp, err := New(tma, ds, "mptest", nil) if err != nil { t.Fatal(err) } @@ -267,7 +267,7 @@ func TestMessagePoolMessagesInEachBlock(t *testing.T) { ds := datastore.NewMapDatastore() - mp, err := New(context.TODO(), tma, ds, "mptest", nil) + mp, err := New(tma, ds, "mptest", nil) if err != nil { t.Fatal(err) } @@ -295,7 +295,7 @@ func TestMessagePoolMessagesInEachBlock(t *testing.T) { _, _ = mp.Pending(context.TODO()) - selm, _ := mp.SelectMessages(tsa, 1) + selm, _ := mp.SelectMessages(context.Background(), tsa, 1) if len(selm) == 0 { t.Fatal("should have returned the rest of the messages") } @@ -316,7 +316,7 @@ func TestRevertMessages(t *testing.T) { ds := datastore.NewMapDatastore() - mp, err := New(context.TODO(), tma, ds, "mptest", nil) + mp, err := New(tma, ds, "mptest", nil) if err != nil { t.Fatal(err) } @@ -379,7 +379,7 @@ func TestPruningSimple(t *testing.T) { ds := datastore.NewMapDatastore() - mp, err := New(context.TODO(), tma, ds, "mptest", nil) + mp, err := New(tma, ds, "mptest", nil) if err != nil { t.Fatal(err) } @@ -423,7 +423,7 @@ func TestLoadLocal(t *testing.T) { tma := newTestMpoolAPI() ds := datastore.NewMapDatastore() - mp, err := New(context.TODO(), tma, ds, "mptest", nil) + mp, err := New(tma, ds, "mptest", nil) if err != nil { t.Fatal(err) } @@ -466,7 +466,7 @@ func TestLoadLocal(t *testing.T) { t.Fatal(err) } - mp, err = New(context.TODO(), tma, ds, "mptest", nil) + mp, err = New(tma, ds, "mptest", nil) if err != nil { t.Fatal(err) } @@ -495,7 +495,7 @@ func TestClearAll(t *testing.T) { tma := newTestMpoolAPI() ds := datastore.NewMapDatastore() - mp, err := New(context.TODO(), tma, ds, "mptest", nil) + mp, err := New(tma, ds, "mptest", nil) if err != nil { t.Fatal(err) } @@ -549,7 +549,7 @@ func TestClearNonLocal(t *testing.T) { tma := newTestMpoolAPI() ds := datastore.NewMapDatastore() - mp, err := New(context.TODO(), tma, ds, "mptest", nil) + mp, err := New(tma, ds, "mptest", nil) if err != nil { t.Fatal(err) } @@ -610,7 +610,7 @@ func TestUpdates(t *testing.T) { tma := newTestMpoolAPI() ds := datastore.NewMapDatastore() - mp, err := New(context.TODO(), tma, ds, "mptest", nil) + mp, err := New(tma, ds, "mptest", nil) if err != nil { t.Fatal(err) } diff --git a/chain/messagepool/pruning.go b/chain/messagepool/pruning.go index 6802e23f3..829258d25 100644 --- a/chain/messagepool/pruning.go +++ b/chain/messagepool/pruning.go @@ -57,7 +57,12 @@ func (mp *MessagePool) pruneMessages(ctx context.Context, ts *types.TipSet) erro mpCfg := mp.getConfig() // we never prune priority addresses for _, actor := range mpCfg.PriorityAddrs { - protected[actor] = struct{}{} + pk, err := mp.api.StateAccountKey(ctx, actor, mp.curTs) + if err != nil { + log.Debugf("pruneMessages failed to resolve priority address: %s", err) + } + + protected[pk] = struct{}{} } // we also never prune locally published messages diff --git a/chain/messagepool/repub_test.go b/chain/messagepool/repub_test.go index 70e457aaa..580231f7a 100644 --- a/chain/messagepool/repub_test.go +++ b/chain/messagepool/repub_test.go @@ -24,7 +24,7 @@ func TestRepubMessages(t *testing.T) { tma := newTestMpoolAPI() ds := datastore.NewMapDatastore() - mp, err := New(context.TODO(), tma, ds, "mptest", nil) + mp, err := New(tma, ds, "mptest", nil) if err != nil { t.Fatal(err) } diff --git a/chain/messagepool/selection.go b/chain/messagepool/selection.go index dfed2b6b5..9379cb892 100644 --- a/chain/messagepool/selection.go +++ b/chain/messagepool/selection.go @@ -40,7 +40,7 @@ type msgChain struct { prev *msgChain } -func (mp *MessagePool) SelectMessages(ts *types.TipSet, tq float64) (msgs []*types.SignedMessage, err error) { +func (mp *MessagePool) SelectMessages(ctx context.Context, ts *types.TipSet, tq float64) (msgs []*types.SignedMessage, err error) { mp.curTsLk.Lock() defer mp.curTsLk.Unlock() @@ -51,9 +51,9 @@ func (mp *MessagePool) SelectMessages(ts *types.TipSet, tq float64) (msgs []*typ // than any other block, then we don't bother with optimal selection because the // first block will always have higher effective performance if tq > 0.84 { - msgs, err = mp.selectMessagesGreedy(mp.curTs, ts) + msgs, err = mp.selectMessagesGreedy(ctx, mp.curTs, ts) } else { - msgs, err = mp.selectMessagesOptimal(mp.curTs, ts, tq) + msgs, err = mp.selectMessagesOptimal(ctx, mp.curTs, ts, tq) } if err != nil { @@ -67,7 +67,7 @@ func (mp *MessagePool) SelectMessages(ts *types.TipSet, tq float64) (msgs []*typ return msgs, nil } -func (mp *MessagePool) selectMessagesOptimal(curTs, ts *types.TipSet, tq float64) ([]*types.SignedMessage, error) { +func (mp *MessagePool) selectMessagesOptimal(ctx context.Context, curTs, ts *types.TipSet, tq float64) ([]*types.SignedMessage, error) { start := time.Now() baseFee, err := mp.api.ChainComputeBaseFee(context.TODO(), ts) @@ -93,7 +93,7 @@ func (mp *MessagePool) selectMessagesOptimal(curTs, ts *types.TipSet, tq float64 // 0b. Select all priority messages that fit in the block minGas := int64(gasguess.MinGas) - result, gasLimit := mp.selectPriorityMessages(pending, baseFee, ts) + result, gasLimit := mp.selectPriorityMessages(ctx, pending, baseFee, ts) // have we filled the block? if gasLimit < minGas { @@ -391,7 +391,7 @@ tailLoop: return result, nil } -func (mp *MessagePool) selectMessagesGreedy(curTs, ts *types.TipSet) ([]*types.SignedMessage, error) { +func (mp *MessagePool) selectMessagesGreedy(ctx context.Context, curTs, ts *types.TipSet) ([]*types.SignedMessage, error) { start := time.Now() baseFee, err := mp.api.ChainComputeBaseFee(context.TODO(), ts) @@ -417,7 +417,7 @@ func (mp *MessagePool) selectMessagesGreedy(curTs, ts *types.TipSet) ([]*types.S // 0b. Select all priority messages that fit in the block minGas := int64(gasguess.MinGas) - result, gasLimit := mp.selectPriorityMessages(pending, baseFee, ts) + result, gasLimit := mp.selectPriorityMessages(ctx, pending, baseFee, ts) // have we filled the block? if gasLimit < minGas { @@ -527,7 +527,7 @@ tailLoop: return result, nil } -func (mp *MessagePool) selectPriorityMessages(pending map[address.Address]map[uint64]*types.SignedMessage, baseFee types.BigInt, ts *types.TipSet) ([]*types.SignedMessage, int64) { +func (mp *MessagePool) selectPriorityMessages(ctx context.Context, pending map[address.Address]map[uint64]*types.SignedMessage, baseFee types.BigInt, ts *types.TipSet) ([]*types.SignedMessage, int64) { start := time.Now() defer func() { if dt := time.Since(start); dt > time.Millisecond { @@ -543,7 +543,7 @@ func (mp *MessagePool) selectPriorityMessages(pending map[address.Address]map[ui var chains []*msgChain priority := mpCfg.PriorityAddrs for _, actor := range priority { - pk, err := mp.api.StateAccountKey(context.TODO(), actor, mp.curTs) + pk, err := mp.api.StateAccountKey(ctx, actor, mp.curTs) if err != nil { log.Debugf("mpooladdlocal failed to resolve sender: %s", err) return nil, gasLimit diff --git a/chain/messagepool/selection_test.go b/chain/messagepool/selection_test.go index f254c6706..463473229 100644 --- a/chain/messagepool/selection_test.go +++ b/chain/messagepool/selection_test.go @@ -60,7 +60,7 @@ func makeTestMessage(w *wallet.LocalWallet, from, to address.Address, nonce uint func makeTestMpool() (*MessagePool, *testMpoolAPI) { tma := newTestMpoolAPI() ds := datastore.NewMapDatastore() - mp, err := New(context.TODO(), tma, ds, "test", nil) + mp, err := New(tma, ds, "test", nil) if err != nil { panic(err) } @@ -427,7 +427,7 @@ func TestBasicMessageSelection(t *testing.T) { mustAdd(t, mp, m) } - msgs, err := mp.SelectMessages(ts, 1.0) + msgs, err := mp.SelectMessages(context.Background(), ts, 1.0) if err != nil { t.Fatal(err) } @@ -495,7 +495,7 @@ func TestBasicMessageSelection(t *testing.T) { tma.setStateNonce(a1, 10) tma.setStateNonce(a2, 10) - msgs, err = mp.SelectMessages(ts3, 1.0) + msgs, err = mp.SelectMessages(context.Background(), ts3, 1.0) if err != nil { t.Fatal(err) } @@ -569,7 +569,7 @@ func TestMessageSelectionTrimming(t *testing.T) { mustAdd(t, mp, m) } - msgs, err := mp.SelectMessages(ts, 1.0) + msgs, err := mp.SelectMessages(context.Background(), ts, 1.0) if err != nil { t.Fatal(err) } @@ -633,7 +633,7 @@ func TestPriorityMessageSelection(t *testing.T) { mustAdd(t, mp, m) } - msgs, err := mp.SelectMessages(ts, 1.0) + msgs, err := mp.SelectMessages(context.Background(), ts, 1.0) if err != nil { t.Fatal(err) } @@ -712,7 +712,7 @@ func TestPriorityMessageSelection2(t *testing.T) { mustAdd(t, mp, m) } - msgs, err := mp.SelectMessages(ts, 1.0) + msgs, err := mp.SelectMessages(context.Background(), ts, 1.0) if err != nil { t.Fatal(err) } @@ -782,7 +782,7 @@ func TestPriorityMessageSelection3(t *testing.T) { } // test greedy selection - msgs, err := mp.SelectMessages(ts, 1.0) + msgs, err := mp.SelectMessages(context.Background(), ts, 1.0) if err != nil { t.Fatal(err) } @@ -805,7 +805,7 @@ func TestPriorityMessageSelection3(t *testing.T) { } // test optimal selection - msgs, err = mp.SelectMessages(ts, 0.1) + msgs, err = mp.SelectMessages(context.Background(), ts, 0.1) if err != nil { t.Fatal(err) } @@ -872,7 +872,7 @@ func TestOptimalMessageSelection1(t *testing.T) { mustAdd(t, mp, m) } - msgs, err := mp.SelectMessages(ts, 0.25) + msgs, err := mp.SelectMessages(context.Background(), ts, 0.25) if err != nil { t.Fatal(err) } @@ -941,7 +941,7 @@ func TestOptimalMessageSelection2(t *testing.T) { mustAdd(t, mp, m) } - msgs, err := mp.SelectMessages(ts, 0.1) + msgs, err := mp.SelectMessages(context.Background(), ts, 0.1) if err != nil { t.Fatal(err) } @@ -1020,7 +1020,7 @@ func TestOptimalMessageSelection3(t *testing.T) { } } - msgs, err := mp.SelectMessages(ts, 0.1) + msgs, err := mp.SelectMessages(context.Background(), ts, 0.1) if err != nil { t.Fatal(err) } @@ -1108,7 +1108,7 @@ func testCompetitiveMessageSelection(t *testing.T, rng *rand.Rand, getPremium fu logging.SetLogLevel("messagepool", "error") // 1. greedy selection - greedyMsgs, err := mp.selectMessagesGreedy(ts, ts) + greedyMsgs, err := mp.selectMessagesGreedy(context.Background(), ts, ts) if err != nil { t.Fatal(err) } @@ -1137,7 +1137,7 @@ func testCompetitiveMessageSelection(t *testing.T, rng *rand.Rand, getPremium fu var bestMsgs []*types.SignedMessage for j := 0; j < nMiners; j++ { tq := rng.Float64() - msgs, err := mp.SelectMessages(ts, tq) + msgs, err := mp.SelectMessages(context.Background(), ts, tq) if err != nil { t.Fatal(err) } @@ -1396,7 +1396,7 @@ readLoop: minGasLimit := int64(0.9 * float64(build.BlockGasLimit)) // greedy first - selected, err := mp.SelectMessages(ts, 1.0) + selected, err := mp.SelectMessages(context.Background(), ts, 1.0) if err != nil { t.Fatal(err) } @@ -1410,7 +1410,7 @@ readLoop: } // high quality ticket - selected, err = mp.SelectMessages(ts, .8) + selected, err = mp.SelectMessages(context.Background(), ts, .8) if err != nil { t.Fatal(err) } @@ -1424,7 +1424,7 @@ readLoop: } // mid quality ticket - selected, err = mp.SelectMessages(ts, .4) + selected, err = mp.SelectMessages(context.Background(), ts, .4) if err != nil { t.Fatal(err) } @@ -1438,7 +1438,7 @@ readLoop: } // low quality ticket - selected, err = mp.SelectMessages(ts, .1) + selected, err = mp.SelectMessages(context.Background(), ts, .1) if err != nil { t.Fatal(err) } @@ -1452,7 +1452,7 @@ readLoop: } // very low quality ticket - selected, err = mp.SelectMessages(ts, .01) + selected, err = mp.SelectMessages(context.Background(), ts, .01) if err != nil { t.Fatal(err) } diff --git a/node/impl/full/mpool.go b/node/impl/full/mpool.go index e91fc8b9e..d2552e1d5 100644 --- a/node/impl/full/mpool.go +++ b/node/impl/full/mpool.go @@ -58,7 +58,7 @@ func (a *MpoolAPI) MpoolSelect(ctx context.Context, tsk types.TipSetKey, ticketQ return nil, xerrors.Errorf("loading tipset %s: %w", tsk, err) } - return a.Mpool.SelectMessages(ts, ticketQuality) + return a.Mpool.SelectMessages(ctx, ts, ticketQuality) } func (a *MpoolAPI) MpoolPending(ctx context.Context, tsk types.TipSetKey) ([]*types.SignedMessage, error) { diff --git a/node/modules/chain.go b/node/modules/chain.go index b0f0543c6..ffdf3aa3a 100644 --- a/node/modules/chain.go +++ b/node/modules/chain.go @@ -61,8 +61,7 @@ func ChainBlockService(bs dtypes.ExposedBlockstore, rem dtypes.ChainBitswap) dty 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) - // TODO: I still don't know how go works -- should this context be part of the builder? - mp, err := messagepool.New(context.TODO(), mpp, ds, nn, j) + 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 3d670611b..6d775f010 100644 --- a/node/modules/mpoolnonceapi.go +++ b/node/modules/mpoolnonceapi.go @@ -27,14 +27,14 @@ func (a *MpoolNonceAPI) GetNonce(ctx context.Context, addr address.Address) (uin ts := a.StateAPI.Chain.GetHeaviestTipSet() // make sure we have a key address so we can compare with messages - keyAddr, err := a.StateAPI.StateManager.ResolveToKeyAddress(context.TODO(), addr, ts) + keyAddr, err := a.StateAPI.StateManager.ResolveToKeyAddress(ctx, addr, ts) if err != nil { return 0, err } // Load the last nonce from the state, if it exists. highestNonce := uint64(0) - if baseActor, err := a.StateAPI.StateManager.LoadActorRaw(context.TODO(), addr, ts.ParentState()); err != nil { + if baseActor, err := a.StateAPI.StateManager.LoadActorRaw(ctx, addr, ts.ParentState()); err != nil { if !xerrors.Is(err, types.ErrActorNotFound) { return 0, err } From 621e4eab0dc42354f772225657a9b7ca02dd88b3 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Mon, 31 May 2021 18:12:42 -0400 Subject: [PATCH 62/88] Address review --- chain/messagepool/messagepool.go | 5 ++-- chain/messagepool/messagepool_test.go | 2 +- chain/messagepool/provider.go | 6 ++--- chain/messagepool/pruning.go | 2 +- chain/messagepool/selection.go | 2 +- chain/stmgr/stmgr.go | 33 +++++++++++++++++++++------ 6 files changed, 35 insertions(+), 15 deletions(-) diff --git a/chain/messagepool/messagepool.go b/chain/messagepool/messagepool.go index 405e22320..94d7c68ef 100644 --- a/chain/messagepool/messagepool.go +++ b/chain/messagepool/messagepool.go @@ -390,7 +390,7 @@ func New(api Provider, ds dtypes.MetadataDS, netName dtypes.NetworkName, j journ // enable initial prunes mp.pruneCooldown <- struct{}{} - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(context.TODO()) // load the current tipset and subscribe to head changes _before_ loading local messages mp.curTs = api.SubscribeHeadChanges(func(rev, app []*types.TipSet) error { @@ -431,7 +431,7 @@ func (mp *MessagePool) resolveToKey(ctx context.Context, addr address.Address) ( } // resolve the address - ka, err := mp.api.StateAccountKey(ctx, addr, mp.curTs) + ka, err := mp.api.StateAccountKeyAtFinality(ctx, addr, mp.curTs) if err != nil { return address.Undef, err } @@ -875,6 +875,7 @@ func (mp *MessagePool) addLocked(ctx context.Context, m *types.SignedMessage, st return err } + // Note: If performance becomes an issue, making this getOrCreatePendingMset will save some work mset, ok, err := mp.getPendingMset(ctx, m.Message.From) if err != nil { log.Debug(err) diff --git a/chain/messagepool/messagepool_test.go b/chain/messagepool/messagepool_test.go index 307ec20a0..1210dd2aa 100644 --- a/chain/messagepool/messagepool_test.go +++ b/chain/messagepool/messagepool_test.go @@ -150,7 +150,7 @@ func (tma *testMpoolAPI) GetActorAfter(addr address.Address, ts *types.TipSet) ( }, nil } -func (tma *testMpoolAPI) StateAccountKey(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) { +func (tma *testMpoolAPI) StateAccountKeyAtFinality(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) { if addr.Protocol() != address.BLS && addr.Protocol() != address.SECP256K1 { return address.Undef, fmt.Errorf("given address was not a key addr") } diff --git a/chain/messagepool/provider.go b/chain/messagepool/provider.go index b90a8af7d..b2cc26e58 100644 --- a/chain/messagepool/provider.go +++ b/chain/messagepool/provider.go @@ -25,7 +25,7 @@ type Provider interface { PutMessage(m types.ChainMsg) (cid.Cid, error) PubSubPublish(string, []byte) error GetActorAfter(address.Address, *types.TipSet) (*types.Actor, error) - StateAccountKey(context.Context, address.Address, *types.TipSet) (address.Address, error) + StateAccountKeyAtFinality(context.Context, address.Address, *types.TipSet) (address.Address, error) MessagesForBlock(*types.BlockHeader) ([]*types.Message, []*types.SignedMessage, error) MessagesForTipset(*types.TipSet) ([]types.ChainMsg, error) LoadTipSet(tsk types.TipSetKey) (*types.TipSet, error) @@ -74,8 +74,8 @@ func (mpp *mpoolProvider) GetActorAfter(addr address.Address, ts *types.TipSet) return st.GetActor(addr) } -func (mpp *mpoolProvider) StateAccountKey(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) { - return mpp.sm.ResolveToKeyAddressReorgProof(ctx, addr, ts) +func (mpp *mpoolProvider) StateAccountKeyAtFinality(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) { + return mpp.sm.ResolveToKeyAddressAtFinality(ctx, addr, ts) } func (mpp *mpoolProvider) MessagesForBlock(h *types.BlockHeader) ([]*types.Message, []*types.SignedMessage, error) { diff --git a/chain/messagepool/pruning.go b/chain/messagepool/pruning.go index 829258d25..c10239b8e 100644 --- a/chain/messagepool/pruning.go +++ b/chain/messagepool/pruning.go @@ -57,7 +57,7 @@ func (mp *MessagePool) pruneMessages(ctx context.Context, ts *types.TipSet) erro mpCfg := mp.getConfig() // we never prune priority addresses for _, actor := range mpCfg.PriorityAddrs { - pk, err := mp.api.StateAccountKey(ctx, actor, mp.curTs) + pk, err := mp.resolveToKey(ctx, actor) if err != nil { log.Debugf("pruneMessages failed to resolve priority address: %s", err) } diff --git a/chain/messagepool/selection.go b/chain/messagepool/selection.go index 9379cb892..3684a7b80 100644 --- a/chain/messagepool/selection.go +++ b/chain/messagepool/selection.go @@ -543,7 +543,7 @@ func (mp *MessagePool) selectPriorityMessages(ctx context.Context, pending map[a var chains []*msgChain priority := mpCfg.PriorityAddrs for _, actor := range priority { - pk, err := mp.api.StateAccountKey(ctx, actor, mp.curTs) + pk, err := mp.resolveToKey(ctx, actor) if err != nil { log.Debugf("mpooladdlocal failed to resolve sender: %s", err) return nil, gasLimit diff --git a/chain/stmgr/stmgr.go b/chain/stmgr/stmgr.go index fb1fb80f0..049ac3153 100644 --- a/chain/stmgr/stmgr.go +++ b/chain/stmgr/stmgr.go @@ -89,6 +89,7 @@ type StateManager struct { expensiveUpgrades map[abi.ChainEpoch]struct{} stCache map[string][]cid.Cid + tCache treeCache compWait map[string]chan struct{} stlk sync.Mutex genesisMsigLk sync.Mutex @@ -101,6 +102,12 @@ type StateManager struct { genesisMarketFunds abi.TokenAmount } +// Caches a single state tree +type treeCache struct { + root cid.Cid + tree *state.StateTree +} + func NewStateManager(cs *store.ChainStore) *StateManager { sm, err := NewStateManagerWithUpgradeSchedule(cs, DefaultUpgradeSchedule()) if err != nil { @@ -153,7 +160,11 @@ func NewStateManagerWithUpgradeSchedule(cs *store.ChainStore, us UpgradeSchedule newVM: vm.NewVM, cs: cs, stCache: make(map[string][]cid.Cid), - compWait: make(map[string]chan struct{}), + tCache: treeCache{ + root: cid.Undef, + tree: nil, + }, + compWait: make(map[string]chan struct{}), }, nil } @@ -544,9 +555,9 @@ func (sm *StateManager) ResolveToKeyAddress(ctx context.Context, addr address.Ad return vm.ResolveToKeyAddr(tree, cst, addr) } -// ResolveToKeyAddressReorgProof is similar to stmgr.ResolveToKeyAddress but fails if the ID address being resolved isn't reorg-stable yet. +// ResolveToKeyAddressAtFinality is similar to stmgr.ResolveToKeyAddress but fails if the ID address being resolved isn't reorg-stable yet. // It should not be used for consensus-critical subsystems. -func (sm *StateManager) ResolveToKeyAddressReorgProof(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) { +func (sm *StateManager) ResolveToKeyAddressAtFinality(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) { switch addr.Protocol() { case address.BLS, address.SECP256K1: return addr, nil @@ -559,8 +570,8 @@ func (sm *StateManager) ResolveToKeyAddressReorgProof(ctx context.Context, addr ts = sm.cs.GetHeaviestTipSet() } + var err error if ts.Height() > policy.ChainFinality { - var err error ts, err = sm.ChainStore().GetTipsetByHeight(ctx, ts.Height()-policy.ChainFinality, ts, true) if err != nil { return address.Undef, xerrors.Errorf("failed to load lookback tipset: %w", err) @@ -568,10 +579,18 @@ func (sm *StateManager) ResolveToKeyAddressReorgProof(ctx context.Context, addr } cst := cbor.NewCborStore(sm.cs.StateBlockstore()) + tree := sm.tCache.tree - tree, err := state.LoadStateTree(cst, ts.ParentState()) - if err != nil { - return address.Undef, xerrors.Errorf("failed to load parent state tree: %w", err) + if tree == nil || sm.tCache.root != ts.ParentState() { + tree, err = state.LoadStateTree(cst, ts.ParentState()) + if err != nil { + return address.Undef, xerrors.Errorf("failed to load parent state tree: %w", err) + } + + sm.tCache = treeCache{ + root: ts.ParentState(), + tree: tree, + } } resolved, err := vm.ResolveToKeyAddr(tree, cst, addr) From 2ab24b358d5c63e6af958c7eb922126caa4cd7e7 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Mon, 31 May 2021 18:50:29 -0400 Subject: [PATCH 63/88] Fix supported proof type manipulations for v5 actors --- chain/actors/policy/policy.go | 7 +++++++ chain/actors/policy/policy.go.template | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/chain/actors/policy/policy.go b/chain/actors/policy/policy.go index 4d115f783..bb35025ec 100644 --- a/chain/actors/policy/policy.go +++ b/chain/actors/policy/policy.go @@ -92,6 +92,13 @@ func AddSupportedProofTypes(types ...abi.RegisteredSealProof) { miner4.PreCommitSealProofTypesV8[t+abi.RegisteredSealProof_StackedDrg2KiBV1_1] = struct{}{} miner5.PreCommitSealProofTypesV8[t+abi.RegisteredSealProof_StackedDrg2KiBV1_1] = struct{}{} + wpp, err := t.RegisteredWindowPoStProof() + if err != nil { + // Fine to panic, this is a test-only method + panic(err) + } + + miner5.WindowPoStProofTypes[wpp] = struct{}{} } } diff --git a/chain/actors/policy/policy.go.template b/chain/actors/policy/policy.go.template index aef1081cb..cd2eca4ea 100644 --- a/chain/actors/policy/policy.go.template +++ b/chain/actors/policy/policy.go.template @@ -62,6 +62,13 @@ func AddSupportedProofTypes(types ...abi.RegisteredSealProof) { miner{{.}}.PreCommitSealProofTypesV8[t+abi.RegisteredSealProof_StackedDrg2KiBV1_1] = struct{}{} {{else}} miner{{.}}.PreCommitSealProofTypesV8[t+abi.RegisteredSealProof_StackedDrg2KiBV1_1] = struct{}{} + wpp, err := t.RegisteredWindowPoStProof() + if err != nil { + // Fine to panic, this is a test-only method + panic(err) + } + + miner{{.}}.WindowPoStProofTypes[wpp] = struct{}{} {{end}} {{end}} } From 85873f6057bf64c95742ddd2bd8cd1a7c60035fd Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Mon, 31 May 2021 19:28:49 -0400 Subject: [PATCH 64/88] Wdpost run should respect AddressedPartitionsMax --- storage/wdpost_run.go | 14 ++++++++++++-- storage/wdpost_run_test.go | 20 +++++++++++++------- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/storage/wdpost_run.go b/storage/wdpost_run.go index fd7c4f184..ca149aec3 100644 --- a/storage/wdpost_run.go +++ b/storage/wdpost_run.go @@ -497,9 +497,14 @@ func (s *WindowPoStScheduler) runPost(ctx context.Context, di dline.Info, ts *ty return nil, xerrors.Errorf("getting partitions: %w", err) } + nv, err := s.api.StateNetworkVersion(ctx, ts.Key()) + if err != nil { + return nil, xerrors.Errorf("getting network version: %w", err) + } + // Split partitions into batches, so as not to exceed the number of sectors // allowed in a single message - partitionBatches, err := s.batchPartitions(partitions) + partitionBatches, err := s.batchPartitions(partitions, nv) if err != nil { return nil, err } @@ -679,7 +684,7 @@ func (s *WindowPoStScheduler) runPost(ctx context.Context, di dline.Info, ts *ty return posts, nil } -func (s *WindowPoStScheduler) batchPartitions(partitions []api.Partition) ([][]api.Partition, error) { +func (s *WindowPoStScheduler) batchPartitions(partitions []api.Partition, nv network.Version) ([][]api.Partition, error) { // We don't want to exceed the number of sectors allowed in a message. // So given the number of sectors in a partition, work out the number of // partitions that can be in a message without exceeding sectors per @@ -695,6 +700,11 @@ func (s *WindowPoStScheduler) batchPartitions(partitions []api.Partition) ([][]a return nil, xerrors.Errorf("getting sectors per partition: %w", err) } + // Also respect the AddressedPartitionsMax (which is the same as DeclarationsMax (which is all really just MaxPartitionsPerDeadline)) + if partitionsPerMsg > policy.GetDeclarationsMax(nv) { + partitionsPerMsg = policy.GetDeclarationsMax(nv) + } + // The number of messages will be: // ceiling(number of partitions / partitions per message) batchCount := len(partitions) / partitionsPerMsg diff --git a/storage/wdpost_run_test.go b/storage/wdpost_run_test.go index 5117e718a..f80f6bee2 100644 --- a/storage/wdpost_run_test.go +++ b/storage/wdpost_run_test.go @@ -5,6 +5,9 @@ import ( "context" "testing" + builtin5 "github.com/filecoin-project/specs-actors/v5/actors/builtin" + miner5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/miner" + "github.com/stretchr/testify/require" "golang.org/x/xerrors" @@ -177,13 +180,16 @@ func TestWDPostDoPost(t *testing.T) { mockStgMinerAPI := newMockStorageMinerAPI() // Get the number of sectors allowed in a partition for this proof type - sectorsPerPartition, err := builtin2.PoStProofWindowPoStPartitionSectors(proofType) + sectorsPerPartition, err := builtin5.PoStProofWindowPoStPartitionSectors(proofType) require.NoError(t, err) // Work out the number of partitions that can be included in a message // without exceeding the message sector limit require.NoError(t, err) - partitionsPerMsg := int(miner2.AddressedSectorsMax / sectorsPerPartition) + partitionsPerMsg := int(miner5.AddressedSectorsMax / sectorsPerPartition) + if partitionsPerMsg > miner5.AddressedPartitionsMax { + partitionsPerMsg = miner5.AddressedPartitionsMax + } // Enough partitions to fill expectedMsgCount-1 messages partitionCount := (expectedMsgCount - 1) * partitionsPerMsg @@ -219,11 +225,11 @@ func TestWDPostDoPost(t *testing.T) { } di := &dline.Info{ - WPoStPeriodDeadlines: miner2.WPoStPeriodDeadlines, - WPoStProvingPeriod: miner2.WPoStProvingPeriod, - WPoStChallengeWindow: miner2.WPoStChallengeWindow, - WPoStChallengeLookback: miner2.WPoStChallengeLookback, - FaultDeclarationCutoff: miner2.FaultDeclarationCutoff, + WPoStPeriodDeadlines: miner5.WPoStPeriodDeadlines, + WPoStProvingPeriod: miner5.WPoStProvingPeriod, + WPoStChallengeWindow: miner5.WPoStChallengeWindow, + WPoStChallengeLookback: miner5.WPoStChallengeLookback, + FaultDeclarationCutoff: miner5.FaultDeclarationCutoff, } ts := mockTipSet(t) From 9fcb564bef2aa6d2814d340bf14ac8686b78e7e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 1 Jun 2021 11:56:19 +0200 Subject: [PATCH 65/88] Make commit batcher more robust --- api/api_storage.go | 3 +- api/apistruct/struct.go | 5 +- api/test/pledge.go | 4 +- extern/storage-sealing/commit_batch.go | 201 +++++++++++++++---- extern/storage-sealing/sealiface/batching.go | 16 ++ extern/storage-sealing/sealing.go | 3 +- extern/storage-sealing/states_sealing.go | 16 +- node/impl/storminer.go | 3 +- storage/sealing.go | 3 +- 9 files changed, 201 insertions(+), 53 deletions(-) create mode 100644 extern/storage-sealing/sealiface/batching.go diff --git a/api/api_storage.go b/api/api_storage.go index 202b40f93..c2f3a3d57 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -24,6 +24,7 @@ import ( "github.com/filecoin-project/lotus/extern/sector-storage/fsutil" "github.com/filecoin-project/lotus/extern/sector-storage/stores" "github.com/filecoin-project/lotus/extern/sector-storage/storiface" + "github.com/filecoin-project/lotus/extern/storage-sealing/sealiface" ) // StorageMiner is a low-level interface to the Filecoin network storage miner node @@ -87,7 +88,7 @@ type StorageMiner interface { SectorPreCommitPending(ctx context.Context) ([]abi.SectorID, error) //perm:admin // SectorCommitFlush immediately sends a Commit message with sectors aggregated for Commit. // Returns null if message wasn't sent - SectorCommitFlush(ctx context.Context) (*cid.Cid, error) //perm:admin + SectorCommitFlush(ctx context.Context) ([]sealiface.CommitBatchRes, error) //perm:admin // SectorCommitPending returns a list of pending Commit sectors to be sent in the next aggregate message SectorCommitPending(ctx context.Context) ([]abi.SectorID, error) //perm:admin diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index 13375cf72..d70c6aa0d 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -28,6 +28,7 @@ import ( "github.com/filecoin-project/lotus/extern/sector-storage/sealtasks" "github.com/filecoin-project/lotus/extern/sector-storage/stores" "github.com/filecoin-project/lotus/extern/sector-storage/storiface" + "github.com/filecoin-project/lotus/extern/storage-sealing/sealiface" marketevents "github.com/filecoin-project/lotus/markets/loggers" "github.com/filecoin-project/lotus/node/modules/dtypes" "github.com/filecoin-project/specs-storage/storage" @@ -639,7 +640,7 @@ type StorageMinerStruct struct { SealingSchedDiag func(p0 context.Context, p1 bool) (interface{}, error) `perm:"admin"` - SectorCommitFlush func(p0 context.Context) (*cid.Cid, error) `perm:"admin"` + SectorCommitFlush func(p0 context.Context) ([]sealiface.CommitBatchRes, error) `perm:"admin"` SectorCommitPending func(p0 context.Context) ([]abi.SectorID, error) `perm:"admin"` @@ -1931,7 +1932,7 @@ func (s *StorageMinerStruct) SealingSchedDiag(p0 context.Context, p1 bool) (inte return s.Internal.SealingSchedDiag(p0, p1) } -func (s *StorageMinerStruct) SectorCommitFlush(p0 context.Context) (*cid.Cid, error) { +func (s *StorageMinerStruct) SectorCommitFlush(p0 context.Context) ([]sealiface.CommitBatchRes, error) { return s.Internal.SectorCommitFlush(p0) } diff --git a/api/test/pledge.go b/api/test/pledge.go index 8752ad4ac..b4bf88b59 100644 --- a/api/test/pledge.go +++ b/api/test/pledge.go @@ -178,7 +178,7 @@ func TestPledgeBatching(t *testing.T, b APIBuilder, blocktime time.Duration, nSe cb, err := miner.SectorCommitFlush(ctx) require.NoError(t, err) if cb != nil { - fmt.Printf("COMMIT BATCH: %s\n", *cb) + fmt.Printf("COMMIT BATCH: %+v\n", cb) } } @@ -325,7 +325,7 @@ func flushSealingBatches(t *testing.T, ctx context.Context, miner TestStorageNod cb, err := miner.SectorCommitFlush(ctx) require.NoError(t, err) if cb != nil { - fmt.Printf("COMMIT BATCH: %s\n", *cb) + fmt.Printf("COMMIT BATCH: %+v\n", cb) } } diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go index 845400ccf..c01067872 100644 --- a/extern/storage-sealing/commit_batch.go +++ b/extern/storage-sealing/commit_batch.go @@ -22,6 +22,7 @@ import ( "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" + "github.com/filecoin-project/lotus/extern/storage-sealing/sealiface" ) const arp = abi.RegisteredAggregationProof_SnarkPackV1 @@ -30,6 +31,9 @@ type CommitBatcherApi interface { SendMsg(ctx context.Context, from, to address.Address, method abi.MethodNum, value, maxFee abi.TokenAmount, params []byte) (cid.Cid, error) StateMinerInfo(context.Context, address.Address, TipSetToken) (miner.MinerInfo, error) ChainHead(ctx context.Context) (TipSetToken, abi.ChainEpoch, error) + + StateSectorPreCommitInfo(ctx context.Context, maddr address.Address, sectorNumber abi.SectorNumber, tok TipSetToken) (*miner.SectorPreCommitOnChainInfo, error) + StateMinerInitialPledgeCollateral(context.Context, address.Address, miner.SectorPreCommitInfo, TipSetToken) (big.Int, error) } type AggregateInput struct { @@ -49,10 +53,10 @@ type CommitBatcher struct { deadlines map[abi.SectorNumber]time.Time todo map[abi.SectorNumber]AggregateInput - waiting map[abi.SectorNumber][]chan cid.Cid + waiting map[abi.SectorNumber][]chan sealiface.CommitBatchRes notify, stop, stopped chan struct{} - force chan chan *cid.Cid + force chan chan []sealiface.CommitBatchRes lk sync.Mutex } @@ -68,10 +72,10 @@ func NewCommitBatcher(mctx context.Context, maddr address.Address, api CommitBat deadlines: map[abi.SectorNumber]time.Time{}, todo: map[abi.SectorNumber]AggregateInput{}, - waiting: map[abi.SectorNumber][]chan cid.Cid{}, + waiting: map[abi.SectorNumber][]chan sealiface.CommitBatchRes{}, notify: make(chan struct{}, 1), - force: make(chan chan *cid.Cid), + force: make(chan chan []sealiface.CommitBatchRes), stop: make(chan struct{}), stopped: make(chan struct{}), } @@ -82,8 +86,8 @@ func NewCommitBatcher(mctx context.Context, maddr address.Address, api CommitBat } func (b *CommitBatcher) run() { - var forceRes chan *cid.Cid - var lastMsg *cid.Cid + var forceRes chan []sealiface.CommitBatchRes + var lastMsg []sealiface.CommitBatchRes cfg, err := b.getConfig() if err != nil { @@ -111,7 +115,7 @@ func (b *CommitBatcher) run() { } var err error - lastMsg, err = b.processBatch(sendAboveMax, sendAboveMin) + lastMsg, err = b.maybeStartBatch(sendAboveMax, sendAboveMin) if err != nil { log.Warnw("CommitBatcher processBatch error", "error", err) } @@ -159,12 +163,9 @@ func (b *CommitBatcher) batchWait(maxWait, slack time.Duration) <-chan time.Time return time.After(wait) } -func (b *CommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { +func (b *CommitBatcher) maybeStartBatch(notif, after bool) ([]sealiface.CommitBatchRes, error) { b.lk.Lock() defer b.lk.Unlock() - params := miner5.ProveCommitAggregateParams{ - SectorNumbers: bitfield.New(), - } total := len(b.todo) if total == 0 { @@ -184,6 +185,45 @@ func (b *CommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { return nil, nil } + var res []sealiface.CommitBatchRes + + if total < cfg.MinCommitBatch || total < miner5.PreCommitSectorBatchMaxSize { + res, err = b.processIndividually() + } else { + res, err = b.processBatch(cfg) + } + if err != nil && len(res) == 0 { + return nil, err + } + + for _, r := range res { + if err != nil { + r.Error = err.Error() + } + + for _, sn := range r.Sectors { + for _, ch := range b.waiting[sn] { + ch <- r // buffered + } + + delete(b.waiting, sn) + delete(b.todo, sn) + delete(b.deadlines, sn) + } + } + + return res, nil +} + +func (b *CommitBatcher) processBatch(cfg sealiface.Config) ([]sealiface.CommitBatchRes, error) { + total := len(b.todo) + + var res sealiface.CommitBatchRes + + params := miner5.ProveCommitAggregateParams{ + SectorNumbers: bitfield.New(), + } + proofs := make([][]byte, 0, total) infos := make([]proof5.AggregateSealVerifyInfo, 0, total) @@ -202,12 +242,13 @@ func (b *CommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { }) for _, info := range infos { + res.Sectors = append(res.Sectors, info.Number) proofs = append(proofs, b.todo[info.Number].proof) } mid, err := address.IDFromAddress(b.maddr) if err != nil { - return nil, xerrors.Errorf("getting miner id: %w", err) + return []sealiface.CommitBatchRes{res}, xerrors.Errorf("getting miner id: %w", err) } params.AggregateProof, err = b.prover.AggregateSealProofs(proof5.AggregateSealVerifyProofAndInfos{ @@ -217,55 +258,107 @@ func (b *CommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { Infos: infos, }, proofs) if err != nil { - return nil, xerrors.Errorf("aggregating proofs: %w", err) + return []sealiface.CommitBatchRes{res}, xerrors.Errorf("aggregating proofs: %w", err) } enc := new(bytes.Buffer) if err := params.MarshalCBOR(enc); err != nil { - return nil, xerrors.Errorf("couldn't serialize ProveCommitAggregateParams: %w", err) + return []sealiface.CommitBatchRes{res}, xerrors.Errorf("couldn't serialize ProveCommitAggregateParams: %w", err) } + mi, err := b.api.StateMinerInfo(b.mctx, b.maddr, nil) + if err != nil { + return []sealiface.CommitBatchRes{res}, xerrors.Errorf("couldn't get miner info: %w", err) + } + + from, _, err := b.addrSel(b.mctx, mi, api.CommitAddr, b.feeCfg.MaxCommitGasFee, b.feeCfg.MaxCommitGasFee) + if err != nil { + return []sealiface.CommitBatchRes{res}, xerrors.Errorf("no good address found: %w", err) + } + + // todo: collateral + + mcid, err := b.api.SendMsg(b.mctx, from, b.maddr, miner.Methods.ProveCommitAggregate, big.Zero(), b.feeCfg.MaxCommitGasFee, enc.Bytes()) + if err != nil { + return []sealiface.CommitBatchRes{res}, xerrors.Errorf("sending message failed: %w", err) + } + + res.Msg = &mcid + + log.Infow("Sent ProveCommitAggregate message", "cid", mcid, "from", from, "todo", total, "sectors", len(infos)) + + return []sealiface.CommitBatchRes{res}, nil +} + +func (b *CommitBatcher) processIndividually() ([]sealiface.CommitBatchRes, error) { mi, err := b.api.StateMinerInfo(b.mctx, b.maddr, nil) if err != nil { return nil, xerrors.Errorf("couldn't get miner info: %w", err) } - from, _, err := b.addrSel(b.mctx, mi, api.CommitAddr, b.feeCfg.MaxCommitGasFee, b.feeCfg.MaxCommitGasFee) + tok, _, err := b.api.ChainHead(b.mctx) if err != nil { - return nil, xerrors.Errorf("no good address found: %w", err) + return nil, err } - mcid, err := b.api.SendMsg(b.mctx, from, b.maddr, miner.Methods.ProveCommitAggregate, big.Zero(), b.feeCfg.MaxCommitGasFee, enc.Bytes()) - if err != nil { - return nil, xerrors.Errorf("sending message failed: %w", err) - } + var res []sealiface.CommitBatchRes - log.Infow("Sent ProveCommitAggregate message", "cid", mcid, "from", from, "todo", total, "sectors", len(infos)) - - err = params.SectorNumbers.ForEach(func(us uint64) error { - sn := abi.SectorNumber(us) - - for _, ch := range b.waiting[sn] { - ch <- mcid // buffered + for sn, info := range b.todo { + r := sealiface.CommitBatchRes{ + Sectors: []abi.SectorNumber{sn}, } - delete(b.waiting, sn) - delete(b.todo, sn) - delete(b.deadlines, sn) - return nil - }) - if err != nil { - return nil, xerrors.Errorf("done sectors foreach: %w", err) + + mcid, err := b.processSingle(mi, sn, info, tok) + if err != nil { + log.Errorf("process single error: %+v", err) // todo: return to user + r.FailedSectors[sn] = err.Error() + } else { + r.Msg = &mcid + } + + res = append(res, r) } - return &mcid, nil + return res, nil +} + +func (b *CommitBatcher) processSingle(mi miner.MinerInfo, sn abi.SectorNumber, info AggregateInput, tok TipSetToken) (cid.Cid, error) { + enc := new(bytes.Buffer) + params := &miner.ProveCommitSectorParams{ + SectorNumber: sn, + Proof: info.proof, + } + + if err := params.MarshalCBOR(enc); err != nil { + return cid.Undef, xerrors.Errorf("marshaling commit params: %w", err) + } + + collateral, err := b.getSectorCollateral(sn, tok) + if err != nil { + return cid.Undef, err + } + + goodFunds := big.Add(collateral, b.feeCfg.MaxCommitGasFee) + + from, _, err := b.addrSel(b.mctx, mi, api.CommitAddr, goodFunds, collateral) + if err != nil { + return cid.Undef, xerrors.Errorf("no good address to send commit message from: %w", err) + } + + mcid, err := b.api.SendMsg(b.mctx, from, b.maddr, miner.Methods.ProveCommitSector, collateral, b.feeCfg.MaxCommitGasFee, enc.Bytes()) + if err != nil { + return cid.Undef, xerrors.Errorf("pushing message to mpool: %w", err) + } + + return mcid, nil } // register commit, wait for batch message, return message CID -func (b *CommitBatcher) AddCommit(ctx context.Context, s SectorInfo, in AggregateInput) (mcid cid.Cid, err error) { +func (b *CommitBatcher) AddCommit(ctx context.Context, s SectorInfo, in AggregateInput) (res sealiface.CommitBatchRes, err error) { _, curEpoch, err := b.api.ChainHead(b.mctx) if err != nil { log.Errorf("getting chain head: %s", err) - return cid.Undef, nil + return sealiface.CommitBatchRes{}, nil } sn := s.SectorNumber @@ -274,7 +367,7 @@ func (b *CommitBatcher) AddCommit(ctx context.Context, s SectorInfo, in Aggregat b.deadlines[sn] = getSectorDeadline(curEpoch, s) b.todo[sn] = in - sent := make(chan cid.Cid, 1) + sent := make(chan sealiface.CommitBatchRes, 1) b.waiting[sn] = append(b.waiting[sn], sent) select { @@ -284,15 +377,15 @@ func (b *CommitBatcher) AddCommit(ctx context.Context, s SectorInfo, in Aggregat b.lk.Unlock() select { - case c := <-sent: - return c, nil + case r := <-sent: + return r, nil case <-ctx.Done(): - return cid.Undef, ctx.Err() + return sealiface.CommitBatchRes{}, ctx.Err() } } -func (b *CommitBatcher) Flush(ctx context.Context) (*cid.Cid, error) { - resCh := make(chan *cid.Cid, 1) +func (b *CommitBatcher) Flush(ctx context.Context) ([]sealiface.CommitBatchRes, error) { + resCh := make(chan []sealiface.CommitBatchRes, 1) select { case b.force <- resCh: select { @@ -364,3 +457,25 @@ func getSectorDeadline(curEpoch abi.ChainEpoch, si SectorInfo) time.Time { return time.Now().Add(time.Duration(deadlineEpoch-curEpoch) * time.Duration(build.BlockDelaySecs) * time.Second) } + +func (b *CommitBatcher) getSectorCollateral(sn abi.SectorNumber, tok TipSetToken) (abi.TokenAmount, error) { + pci, err := b.api.StateSectorPreCommitInfo(b.mctx, b.maddr, sn, tok) + if err != nil { + return big.Zero(), xerrors.Errorf("getting precommit info: %w", err) + } + if pci == nil { + return big.Zero(), xerrors.Errorf("precommit info not found on chain") + } + + collateral, err := b.api.StateMinerInitialPledgeCollateral(b.mctx, b.maddr, pci.Info, tok) + if err != nil { + return big.Zero(), xerrors.Errorf("getting initial pledge collateral: %w", err) + } + + collateral = big.Sub(collateral, pci.PreCommitDeposit) + if collateral.LessThan(big.Zero()) { + collateral = big.Zero() + } + + return collateral, nil +} diff --git a/extern/storage-sealing/sealiface/batching.go b/extern/storage-sealing/sealiface/batching.go new file mode 100644 index 000000000..e7c2cadbb --- /dev/null +++ b/extern/storage-sealing/sealiface/batching.go @@ -0,0 +1,16 @@ +package sealiface + +import ( + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-state-types/abi" +) + +type CommitBatchRes struct { + Sectors []abi.SectorNumber + + FailedSectors map[abi.SectorNumber]string + + Msg *cid.Cid + Error string // if set, means that all sectors are failed, implies Msg==nil +} diff --git a/extern/storage-sealing/sealing.go b/extern/storage-sealing/sealing.go index fc452cc6f..61360dc12 100644 --- a/extern/storage-sealing/sealing.go +++ b/extern/storage-sealing/sealing.go @@ -27,6 +27,7 @@ import ( "github.com/filecoin-project/lotus/chain/types" sectorstorage "github.com/filecoin-project/lotus/extern/sector-storage" "github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper" + "github.com/filecoin-project/lotus/extern/storage-sealing/sealiface" ) const SectorStorePrefix = "/sectors" @@ -214,7 +215,7 @@ func (m *Sealing) SectorPreCommitPending(ctx context.Context) ([]abi.SectorID, e return m.precommiter.Pending(ctx) } -func (m *Sealing) CommitFlush(ctx context.Context) (*cid.Cid, error) { +func (m *Sealing) CommitFlush(ctx context.Context) ([]sealiface.CommitBatchRes, error) { return m.commiter.Flush(ctx) } diff --git a/extern/storage-sealing/states_sealing.go b/extern/storage-sealing/states_sealing.go index 391951dea..6f4c57bfd 100644 --- a/extern/storage-sealing/states_sealing.go +++ b/extern/storage-sealing/states_sealing.go @@ -581,7 +581,7 @@ func (m *Sealing) handleSubmitCommitAggregate(ctx statemachine.Context, sector S return ctx.Send(SectorCommitFailed{xerrors.Errorf("sector had nil commR or commD")}) } - mcid, err := m.commiter.AddCommit(ctx.Context(), sector, AggregateInput{ + res, err := m.commiter.AddCommit(ctx.Context(), sector, AggregateInput{ info: proof.AggregateSealVerifyInfo{ Number: sector.SectorNumber, Randomness: sector.TicketValue, @@ -596,7 +596,19 @@ func (m *Sealing) handleSubmitCommitAggregate(ctx statemachine.Context, sector S return ctx.Send(SectorCommitFailed{xerrors.Errorf("queuing commit for aggregation failed: %w", err)}) } - return ctx.Send(SectorCommitAggregateSent{mcid}) + if res.Error != "" { + return ctx.Send(SectorCommitFailed{xerrors.Errorf("aggregate error: %s", res.Error)}) + } + + if e, found := res.FailedSectors[sector.SectorNumber]; found { + return ctx.Send(SectorCommitFailed{xerrors.Errorf("sector failed in aggregate processing: %s", e)}) + } + + if res.Msg == nil { + return ctx.Send(SectorCommitFailed{xerrors.Errorf("aggregate message was nil")}) + } + + return ctx.Send(SectorCommitAggregateSent{*res.Msg}) } func (m *Sealing) handleCommitWait(ctx statemachine.Context, sector SectorInfo) error { diff --git a/node/impl/storminer.go b/node/impl/storminer.go index 8660f1efb..9b6f65207 100644 --- a/node/impl/storminer.go +++ b/node/impl/storminer.go @@ -32,6 +32,7 @@ import ( "github.com/filecoin-project/lotus/extern/sector-storage/stores" "github.com/filecoin-project/lotus/extern/sector-storage/storiface" sealing "github.com/filecoin-project/lotus/extern/storage-sealing" + "github.com/filecoin-project/lotus/extern/storage-sealing/sealiface" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/api/apistruct" @@ -386,7 +387,7 @@ func (sm *StorageMinerAPI) SectorMarkForUpgrade(ctx context.Context, id abi.Sect return sm.Miner.MarkForUpgrade(id) } -func (sm *StorageMinerAPI) SectorCommitFlush(ctx context.Context) (*cid.Cid, error) { +func (sm *StorageMinerAPI) SectorCommitFlush(ctx context.Context) ([]sealiface.CommitBatchRes, error) { return sm.Miner.CommitFlush(ctx) } diff --git a/storage/sealing.go b/storage/sealing.go index cd215f238..bd8241197 100644 --- a/storage/sealing.go +++ b/storage/sealing.go @@ -11,6 +11,7 @@ import ( "github.com/filecoin-project/specs-storage/storage" sealing "github.com/filecoin-project/lotus/extern/storage-sealing" + "github.com/filecoin-project/lotus/extern/storage-sealing/sealiface" ) // TODO: refactor this to be direct somehow @@ -67,7 +68,7 @@ func (m *Miner) SectorPreCommitPending(ctx context.Context) ([]abi.SectorID, err return m.sealing.SectorPreCommitPending(ctx) } -func (m *Miner) CommitFlush(ctx context.Context) (*cid.Cid, error) { +func (m *Miner) CommitFlush(ctx context.Context) ([]sealiface.CommitBatchRes, error) { return m.sealing.CommitFlush(ctx) } From 084b0e7f60fce9d3ae19bec00b6e3bd20f374138 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 1 Jun 2021 12:02:34 +0200 Subject: [PATCH 66/88] Handle collateral when submitting aggregated commits --- build/openrpc/full.json.gz | Bin 22809 -> 22810 bytes build/openrpc/miner.json.gz | Bin 7988 -> 8040 bytes build/openrpc/worker.json.gz | Bin 2577 -> 2578 bytes extern/storage-sealing/commit_batch.go | 24 +++++++++++++++++++----- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index a81cb15650063bac74bd3fb4aa2cd03f77604826..47e55e0c58fcc473991a1e90044d1f138815ffe7 100644 GIT binary patch literal 22810 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 A00000 diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index 1ddc27f12930edce042af42f23c095dc7e9e6964..dd1b12856db99e2cd59453cf86b081faf57ded17 100644 GIT binary patch delta 7157 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 delta 7112 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 yjg%sUaV}DHh-&0RR8~fK;x7jsXBr8N;>! diff --git a/build/openrpc/worker.json.gz b/build/openrpc/worker.json.gz index 5bb69aa4919c86a4700e9fe7a69472082c5d4cc8..4c2337e31a1d9171d1bde64d53ced59a4e4b1f89 100644 GIT binary patch delta 2368 zcmV-G3BUG{6p|E>903iH9bkWFtRj4O#)`4Hxm8!m5VzNS4sr7IzmgbH+~1%oGExzt)cmCA^!wP z#RZu{>k4ee#U)HEa6b8K?gJjYTijTfI8MkGaYustAQaW@ zOo%JN!hWJ?*>VMfh>4XPGQ)_4Rmi~d^J2x~=DLfxgpa?2t#Eo!6&m=qY~f~MVF7;! zRDgpAN(7)^hA+S-N|%I#TUe{N)y=%E0*Nj4&RF2OHx{M}BB6iCj3C{YnJYvkB4}-p z-%nIq7u!H#sA#IE;RwYH34JPOFMFMKe%}qZ?gG z2CmX=N4%|@s9a9hDwMGS#%4Tg2edNd&hO^man6tBKU~X7M8f=3N_00`2ah~nmnm z<6@uTUyO`ha4NbC5mk=IaD?8BId?Y!`Kp0@+^zu7c}jn)S;!12D%`A``V}XPnx+H7 z4nq!3PV(j84^Z~3q7;NQ)x5@)u{7nnf9({6ZsY3Vh^Sdq+)ot0x5L^0$Q`KRJTBQS z=Q%bm;wy9!i^Owx{?itmr>qZ{Risi0g$Of^nj zM7!?Nug!=C1KNAAiduE60IAQ-V06I2b5luvU&-e z3RC-eGLfLl&25-%sW~w<(?JcuH2`<00C$!Ta2IUCE5e#4$r1ZH=4~QwTX)d{jQ}8t{#XVf4Bq* z;$F**E*Or!Nb}d9;a}R|E-GsHinFJsfJd(WVvV^9jT*+vDBzz_&Dg|bcvv?HsW~ds zMQYkg5|~MEtU_!Ry1jg>@R8rcmGKNJ8ixescJf&@Jl`}AjoZ6d___~n@5@O}H6Cf> zksg04_7tq6jfk4bil`IPGAfDUFO( zQjZjIzMcdDby{D(Z1%1)jpb{JA=D;Y5axey59ziMpP+}3H6#tag{Htj=(iD7bm!<{ z1}v&~&3w}zl8OjbZO2)j`o-hS%wh6LiKUcMFu0SQPMd-4uvDTTH-8DF)-KJMt|&mE zy!BO31tJbi*29(L3j1=4*gxkAE|>$4;Zl3<=CK;FOp@7Oy4)U5!Y|u1w$Wv80<3@i z46vSCHqU6s4mVKtJH2jSMXi6Pd%G3mib9)b@ID1@_FzU-n21E;7{YZAL_-Re z4X%K=L+*K0WrgT@HAzrb^Ztx%68AF9TQZO`KC>ID4zU%zhfIxrbmBJ*B+V8X|Ls~A zL+Z#H-eh_x>-i$xPvceY1$^y;SD6*x13bW6sE99mtl13Jnn~Gth!FNPjFx}>qDcuY zCR6Cb0jB7LCM9{b>i2&L7!ou0c7xH{zF>1r3K;7|pDW^e0q6>q>&Kue9-1(8uk>jjP(4@HULLSF@YH|6(}BX%X~Djj zdgr0Ga}a9N38)YB4%UudS!f(66d#RZ(YBk!c_%Hn4wd1Zp3PaOS9jLwv~YKes&~Ka zim=H28guS$*#DJk>UIJ5kC=%Ss zv8glGk+>bTK^#Ybl;9v5x7I=zN>9Lq3lAJLo+E<(Yrdk=LIe%D12%d&^Dv|K3{mD# zJw)Ay7r2Vr`suMx>^pGcUR3Cc6Xqa-KI&RCB4@3%;B4neLG#Ta&B|1>GL>=(&y$o1 zC{?@KIJoBmRgio9N~rozKtWX$shBQTsGd#WBuVq-D6WCVF1PO_3YelUQZo>MhycXQ mI*mjkU@TnS`AN}xtr?ZGGrU;bEdCb&0RR7WReSRRdjJ4x6^SbV delta 2367 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 diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go index c01067872..c540f6507 100644 --- a/extern/storage-sealing/commit_batch.go +++ b/extern/storage-sealing/commit_batch.go @@ -216,6 +216,11 @@ func (b *CommitBatcher) maybeStartBatch(notif, after bool) ([]sealiface.CommitBa } func (b *CommitBatcher) processBatch(cfg sealiface.Config) ([]sealiface.CommitBatchRes, error) { + tok, _, err := b.api.ChainHead(b.mctx) + if err != nil { + return nil, err + } + total := len(b.todo) var res sealiface.CommitBatchRes @@ -226,6 +231,7 @@ func (b *CommitBatcher) processBatch(cfg sealiface.Config) ([]sealiface.CommitBa proofs := make([][]byte, 0, total) infos := make([]proof5.AggregateSealVerifyInfo, 0, total) + collateral := big.Zero() for id, p := range b.todo { if len(infos) >= cfg.MaxCommitBatch { @@ -233,6 +239,15 @@ func (b *CommitBatcher) processBatch(cfg sealiface.Config) ([]sealiface.CommitBa break } + sc, err := b.getSectorCollateral(id, tok) + if err != nil { + res.FailedSectors[id] = err.Error() + continue + } + + collateral = big.Add(collateral, sc) + + res.Sectors = append(res.Sectors, id) params.SectorNumbers.Set(uint64(id)) infos = append(infos, p.info) } @@ -242,7 +257,6 @@ func (b *CommitBatcher) processBatch(cfg sealiface.Config) ([]sealiface.CommitBa }) for _, info := range infos { - res.Sectors = append(res.Sectors, info.Number) proofs = append(proofs, b.todo[info.Number].proof) } @@ -271,14 +285,14 @@ func (b *CommitBatcher) processBatch(cfg sealiface.Config) ([]sealiface.CommitBa return []sealiface.CommitBatchRes{res}, xerrors.Errorf("couldn't get miner info: %w", err) } - from, _, err := b.addrSel(b.mctx, mi, api.CommitAddr, b.feeCfg.MaxCommitGasFee, b.feeCfg.MaxCommitGasFee) + goodFunds := big.Add(b.feeCfg.MaxCommitGasFee, collateral) + + from, _, err := b.addrSel(b.mctx, mi, api.CommitAddr, goodFunds, collateral) if err != nil { return []sealiface.CommitBatchRes{res}, xerrors.Errorf("no good address found: %w", err) } - // todo: collateral - - mcid, err := b.api.SendMsg(b.mctx, from, b.maddr, miner.Methods.ProveCommitAggregate, big.Zero(), b.feeCfg.MaxCommitGasFee, enc.Bytes()) + mcid, err := b.api.SendMsg(b.mctx, from, b.maddr, miner.Methods.ProveCommitAggregate, collateral, b.feeCfg.MaxCommitGasFee, enc.Bytes()) if err != nil { return []sealiface.CommitBatchRes{res}, xerrors.Errorf("sending message failed: %w", err) } From cd0a1c97fac54b8a93b93d1c5b194d82d8e90753 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 1 Jun 2021 12:25:19 +0200 Subject: [PATCH 67/88] Improve cli for flushing commit batches --- cmd/lotus-storage-miner/sectors.go | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/cmd/lotus-storage-miner/sectors.go b/cmd/lotus-storage-miner/sectors.go index 2bb44b4f4..257fb2713 100644 --- a/cmd/lotus-storage-miner/sectors.go +++ b/cmd/lotus-storage-miner/sectors.go @@ -997,15 +997,30 @@ var sectorsBatchingPendingCommit = &cli.Command{ ctx := lcli.ReqContext(cctx) if cctx.Bool("publish-now") { - cid, err := api.SectorCommitFlush(ctx) + res, err := api.SectorCommitFlush(ctx) if err != nil { return xerrors.Errorf("flush: %w", err) } - if cid == nil { + if res == nil { return xerrors.Errorf("no sectors to publish") } - fmt.Println("sector batch published: ", cid) + for i, re := range res { + fmt.Printf("Batch %d:\n", i) + if re.Error != "" { + fmt.Printf("\tError: %s\n", re.Error) + } else { + fmt.Printf("\tMessage: %s\n", re.Msg) + } + fmt.Printf("\tSectors:\n") + for _, sector := range re.Sectors { + if e, found := re.FailedSectors[sector]; found { + fmt.Printf("\t\t%d\tERROR %s\n", sector, e) + } else { + fmt.Printf("\t\t%d\tOK\n", sector) + } + } + } return nil } From cb4eb487f4e9496765f62be8bf703e3e99661e08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 1 Jun 2021 12:27:22 +0200 Subject: [PATCH 68/88] commit batcher: Fix min aggregate size check --- extern/storage-sealing/commit_batch.go | 2 +- node/config/def.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extern/storage-sealing/commit_batch.go b/extern/storage-sealing/commit_batch.go index c540f6507..7d128fe76 100644 --- a/extern/storage-sealing/commit_batch.go +++ b/extern/storage-sealing/commit_batch.go @@ -187,7 +187,7 @@ func (b *CommitBatcher) maybeStartBatch(notif, after bool) ([]sealiface.CommitBa var res []sealiface.CommitBatchRes - if total < cfg.MinCommitBatch || total < miner5.PreCommitSectorBatchMaxSize { + if total < cfg.MinCommitBatch || total < miner5.MinAggregatedSectors { res, err = b.processIndividually() } else { res, err = b.processBatch(cfg) diff --git a/node/config/def.go b/node/config/def.go index 988b0a6c9..636265560 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -271,7 +271,7 @@ func DefaultStorageMiner() *StorageMiner { PreCommitBatchSlack: Duration(3 * time.Hour), AggregateCommits: true, - MinCommitBatch: 1, // we must have at least one proof to aggregate + MinCommitBatch: miner5.MinAggregatedSectors, // we must have at least four proofs to aggregate MaxCommitBatch: miner5.MaxAggregatedSectors, // this is the maximum aggregation per FIP13 CommitBatchWait: Duration(24 * time.Hour), // this can be up to 6 days CommitBatchSlack: Duration(1 * time.Hour), From 5c3ebd424cc706622a8a898694ed7e9b909b2d23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 1 Jun 2021 12:33:24 +0200 Subject: [PATCH 69/88] miner: Improve batching CLI UX --- cmd/lotus-storage-miner/sectors.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/lotus-storage-miner/sectors.go b/cmd/lotus-storage-miner/sectors.go index 257fb2713..c76d4a249 100644 --- a/cmd/lotus-storage-miner/sectors.go +++ b/cmd/lotus-storage-miner/sectors.go @@ -980,7 +980,7 @@ var sectorsBatching = &cli.Command{ } var sectorsBatchingPendingCommit = &cli.Command{ - Name: "pending-commit", + Name: "commit", Usage: "list sectors waiting in commit batch queue", Flags: []cli.Flag{ &cli.BoolFlag{ @@ -1042,7 +1042,7 @@ var sectorsBatchingPendingCommit = &cli.Command{ } var sectorsBatchingPendingPreCommit = &cli.Command{ - Name: "pending-precommit", + Name: "precommit", Usage: "list sectors waiting in precommit batch queue", Flags: []cli.Flag{ &cli.BoolFlag{ From 482e1110c2e94c2dcdd74015d382cad815a7ad94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 1 Jun 2021 14:35:30 +0200 Subject: [PATCH 70/88] precommit batcher: Improve error propagation --- api/api_storage.go | 2 +- api/apistruct/struct.go | 4 +- api/test/pledge.go | 4 +- build/openrpc/miner.json.gz | Bin 8040 -> 8066 bytes cmd/lotus-storage-miner/sectors.go | 17 +++- extern/storage-sealing/precommit_batch.go | 86 ++++++++++++------- extern/storage-sealing/sealiface/batching.go | 7 ++ extern/storage-sealing/sealing.go | 2 +- extern/storage-sealing/states_sealing.go | 16 +++- node/impl/storminer.go | 2 +- storage/sealing.go | 2 +- 11 files changed, 94 insertions(+), 48 deletions(-) diff --git a/api/api_storage.go b/api/api_storage.go index c2f3a3d57..e50fedc19 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -83,7 +83,7 @@ type StorageMiner interface { SectorMarkForUpgrade(ctx context.Context, id abi.SectorNumber) error //perm:admin // SectorPreCommitFlush immediately sends a PreCommit message with sectors batched for PreCommit. // Returns null if message wasn't sent - SectorPreCommitFlush(ctx context.Context) (*cid.Cid, error) //perm:admin + SectorPreCommitFlush(ctx context.Context) ([]sealiface.PreCommitBatchRes, error) //perm:admin // SectorPreCommitPending returns a list of pending PreCommit sectors to be sent in the next batch message SectorPreCommitPending(ctx context.Context) ([]abi.SectorID, error) //perm:admin // SectorCommitFlush immediately sends a Commit message with sectors aggregated for Commit. diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index d70c6aa0d..acdc0f9b5 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -650,7 +650,7 @@ type StorageMinerStruct struct { SectorMarkForUpgrade func(p0 context.Context, p1 abi.SectorNumber) error `perm:"admin"` - SectorPreCommitFlush func(p0 context.Context) (*cid.Cid, error) `perm:"admin"` + SectorPreCommitFlush func(p0 context.Context) ([]sealiface.PreCommitBatchRes, error) `perm:"admin"` SectorPreCommitPending func(p0 context.Context) ([]abi.SectorID, error) `perm:"admin"` @@ -1952,7 +1952,7 @@ func (s *StorageMinerStruct) SectorMarkForUpgrade(p0 context.Context, p1 abi.Sec return s.Internal.SectorMarkForUpgrade(p0, p1) } -func (s *StorageMinerStruct) SectorPreCommitFlush(p0 context.Context) (*cid.Cid, error) { +func (s *StorageMinerStruct) SectorPreCommitFlush(p0 context.Context) ([]sealiface.PreCommitBatchRes, error) { return s.Internal.SectorPreCommitFlush(p0) } diff --git a/api/test/pledge.go b/api/test/pledge.go index b4bf88b59..08548dc60 100644 --- a/api/test/pledge.go +++ b/api/test/pledge.go @@ -169,7 +169,7 @@ func TestPledgeBatching(t *testing.T, b APIBuilder, blocktime time.Duration, nSe pcb, err := miner.SectorPreCommitFlush(ctx) require.NoError(t, err) if pcb != nil { - fmt.Printf("PRECOMMIT BATCH: %s\n", *pcb) + fmt.Printf("PRECOMMIT BATCH: %+v\n", pcb) } } @@ -319,7 +319,7 @@ func flushSealingBatches(t *testing.T, ctx context.Context, miner TestStorageNod pcb, err := miner.SectorPreCommitFlush(ctx) require.NoError(t, err) if pcb != nil { - fmt.Printf("PRECOMMIT BATCH: %s\n", *pcb) + fmt.Printf("PRECOMMIT BATCH: %+v\n", pcb) } cb, err := miner.SectorCommitFlush(ctx) diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index dd1b12856db99e2cd59453cf86b081faf57ded17..f60d44df02aadd18fc0904012e557bc37e6711c0 100644 GIT binary patch delta 7407 zcmV zcL&`gy`v*MC;I4zP7$@BKGH7RhL>aGm>Qi~IK;q)|vU<&VPcP##Vsl5Q=W!Ep%(V*)R_oepZ8~#RJNLMb_ zyzT|2(8a)i>aG!d`3*mQ!+-z%x8Bjcm2Cs}K_BUQM|Xe=Y|)FA%f>W&IB^a9246{lzj)6So#RBEUTkuKD>smjWD2a?fE(Kz|4Z5zGLXV2bMJh z#%;ij{<|;4yw*XNJG=Kkf^suLEC_I{ zgt{(0MZ?q-F&R;s6GlDOfxymxsSeyZVwvh?epwYGw?7!_Bi#V_f2alw z-e1jyz|<^a-0G}@4xmixCkf{0zKYp)euMjJBn^fVgeSJMqA&oU3IHD?Aj9{sH?tM8 zD8l|6U*I|6;LfI<;olX&Cb6+Foubk)O;7;k$xZ}FzEKp<8q~%Ca0+=LGFQViRvemt zV*#5%aXvGcY8NKN6wOh*Y9IxyY9x(MhiPfGlV+J}AH9lBO)1fe1I$9q46e^%IR&Gk zA&!e$QsOvX^z8!`m2GGu-kBM~z8tF6O!K1^NkD7S=TdD6hi5>VeIM$OO_|VT|xX`;O?6z5j+UKV5&G{`>9O z`1j9~f4@DOzWev&?D|&?;ydILY%{RC11@5AzUD!yQKEUohMEKDvZDcP^0vNzVb&OX z7VG5qCB&KmEQ__67$)*~4-n>kWcoNShhzMYfR= z?TtjWefQ%je3D&fFGL(k*Xom$&i^`3u`2EO z_xoAA%9$CTPSP49Q*#+#co0)h1DXe|`GF`iLwF%R6sAF91~fGkQtN6LR70LNC$0@m zZT6r6?N5;#+0lSDCMGnr7vGo2SZWBH$l%c|pxG;nB7?m>aDZ_O&9D{5+Anz=31}Z# zsU`51-Q*(d5B)U{d(Iz!2D5*F0m1OQXD*K(hG2L^XG8B6+RlH-{ePX+1F@a|%HnZy{wx zl{Ak}ZX+6Iy+avGAE^QxUiWZ`Lp)%WOgI=@2x3}3Q%}?z6UzdB6uJt{rYO9_<~xTN zOSN^HOE800#`1SZpCD+!N=t^kCeOrDT`Ia`mQ4Af1~uz^t|3P*{=;FM&rtJ0`=tGGi3EbY;Jc1-zwl`Sb{%&x+qsUIazR z9Wviq#JII;0e^}+luQeRwxa_4XA#|yE{IIUbtO<}M5-+L?~#F-GQajzaPopgQ%EqJkFmipt*J|Y}{OOojK zhJ%j&5!|PriDN5*^L9G=HL{^cf$gvo{a(MLzq@x3>y-a<3jg~D(MN}a$Bxd%0K}L3 zp^Gyq1|6N}A&&HaVXODo^zG8Xzb)ba{_>0dSn`-If2bvD1YK20CP;n_r5cHEs#v31 z)(M};(upQwW>h*Bn`M>aYaCX8u49>*CUM3oF~NehDsA`f=%o)$$xR&j`SJavCkcIo z=ozj_)CT%EB?;oYK@lGy%fS2^`OV;cMhDX%pIu1p07a4T_4a`VDE=9H{1}c1*C*h? zhM4j%a0e`g!>NZC*l;26AP4Dkkeec}lwK!mUM)8-KI*cNm-s*xVQIgAWLkDcp%4{6 z86N_x@@9c7LXGVhkPvUT4Yb<^+HC_*1Sq*q;3e{CJXlk0Nkl>`Z%aYE;CKlzhJ||z zi2a=V^xKcn^S}aTZVUKC zm(ZQSMAg`7VH7N2@d3quYloSImDG+mUfXHG592YJ&s5$*IgihD#&|BtqH@PB$s&`c zpO0Uq!&%PS`mxb}06Tl;HrlRi!@|l_ba|t2ZK?7cyOo?|vND#LT>EVH9h}Gxe~dwI z<#qEl8qMrCOA~E(owd8p+FfVuuCsR6S$Xu&jVCgYy}$-x=tXOP}~ zueDfOi)A+~mVgs~D}mr-HM5YntW@_3&P#HCkW9I8-njzbdBSFi8idgD-3r|D5Q-(P zc4A$;+yV1tO}>~3tH!mkYz)E=2VN9Ib=EB=#qDIp^5R_QIb+8<}GP6pYP1Vib?Hq%u(B%yKSEbHoE#W{iCd*zF^b|7L(Z?&-ExoYs zTZsllm8?Y7kzT?DFdil@p~N5(gEi0MoPY<5fNGbIJVBFM3>pHT=96{|ApyOUm<(Hg zzx1`T`sk<^{;y;TUoEu%Nn>kmXs8Vh?Lufs$@oYLy3)tbz>y#NoVYfiVpkeGpvZltSB~o<@o0iudh~+jhr)ZM)-ktMHYZK{2hn<8~_Bamzu9eNM0Y7A-C? z<;b-5V90M#9xnmH+@k7e&Y`$i8JH$CLyNMbId8I_D*;>B#Wi{I$(lmBYd@zw0%uRNE6gMrr z50H*8&NCNNRJd-PNWq43#)l3QuR@6nyMCo2Zp>(8<2tF`RQ`msgAOGDe^s*FJ0TCb zt8uCJi*v-HM!pdD7P^8&V<}zhV^tK2X}A|gA|7JHnch1V{~ToeC-_G%DNDk=2wg6P zmGNSsv%&p^AlN_Z=r+Q!U+YfJw8}o9%>qFEb9D2vToSCW${?|0?VEPl%P!?s4q>W> zNgMd;>8zbBcu@^}6BFvAe_m{(X7VJ~vLS0uPU(n!>Bi7@k-5HNlwI~HRKj~md2&}( zt;p+_Qi+unV9?Z7PLgglT&v-VG+c|dE!M7Ktt52o0~o@qKiw*2NM@^-Zg9_FCWcb60++{IDa~jI$FFs;G5flP*u9H?0C{70?bU zAlN!Sts;6ZiYRvxJb~DCeV>R>B}F6vX^c#-K`qp}L9G_re@QJAal0aygbS37og6c5 zlwL{3iyAMtsV)8)wsJ;Gt8QC$TSDFD<{J3N+}6xGi&5E5jzO})Hou@%r8}WYTl8(w zH;KLjNqzw{HV|H%&4`;m*jWX5;{;SOw*Xq#NN)VF$re#tL~Y?B2^W$@VP7<-#uA$6 z2rTMAMo2^k3>)H_wik%u0u#?4kCQJFBY&Rm-mlmfP4z1IP5}qy$R)Z|&AQob@fb?h zu>8;kkFUMAR$jGZJ5p3+hZ=GY6^e@&eha9vyn7M7vMHP->UUq|+03y+eLs4O@=>K&>&==QNt5E)+#Y`_yFETGYX04| zwb8?;Bg2m}=RRhWEk+H$tmgXi4m1mSR3mfE@!$C95da1y+6;2}*AV8{pkjDWBOD2k zq5*WsJyA0Cdb237wSy%0RH`J=MKOOLAb)*#??8hxGzHfA$^|}$RFy9Y#cWBV$cEjZ zh?=0u>F0@@q?WdT)Z?NA{kIzH5?9E;7d$3+Oa;~!tHfZ_7i+G)-OM$KN4@ZWm9Ko< zZGM;Cz#Z}dpMPk9hk8sNl4+8&<3tPCIV>9SFlSx51e8ra{BAuQ7%t=nrr_jn_GRcfMA_eb1{Mp z%1W|d94Uw}pdeVl(Vz_!w1I*Q0m}^(TtS=M)v+H1fky4dkYwIcR#cFG#yco{CEvH7 zyTlH&iM0!=nFMnEN1usDISec#}*t z0X8*EB7*Fhh3;S|so#-hX$Dw%P>YzINj9(;XLlQ0R%?ykpGh2L*QAOWoHib}lTefi zY{Gk2l2&;GMdB-|sd*a;-sK6=PZPChWho|0xgnBiGcmzweoU~s)WoSOh)NU~^mZXI z*b39036t#BN}mB4r}zm(b$^5@My64lagA`HJjIGLvyVAd2Cyt>YFq&c6AoeRo$b&E zjkwwufGEEuStLby-!LT_bU7(1oZWJRyxnB_~d`NNmEg zkspuJK#r==!nybyCg6?lUMWNojHHJ_uAqlGsM?+vt615z7s6@QeFv z$Q7?&EKFiz)C4LZnSU)s?4<@Qi#bt?Fizn$%JY9@+iICbih2KQe=zLy2ZuMibL!7- zbFA#5s~CG+;3@Z^>vC>DN9GTGy#Wr20t6J+;} zq{dhA2&;9|q#|Rsg1Os*nH!++uHd|uK(hMTs>BA)SHW910e{B=3z}DOt|)pw>DsA- zSqv)gTW=k#c5FcAc?sV4Ua+js+KGY3aVkk@FudC+KWh`T;MrT zl+Uoa{PSQwKz}NNg}gZPm4LQDcQ6RJGmre@{24|0cHsW3EJ#f9K>E9`zg9mxf+5Ih zM;ErrNT0YomqzdCV`AIq`iPk(;>T6+L!1#k(*K36-e1$VO9TJ5g#Y`?FM3Dk%P~Q@ zYh>ICIQx?y+u}cd=MZDbfVAK1b08DPh2@nAlkRweXn)kD#DgnHT@Iv&gN}Ypf;@cx zJ6{5izH{C6qhvbzB?&2rvkCd&PCELTLD8L9FBKi>=S`Wdn%bms%@2!eP@Ta$IvaQbuRjT=u zRkmvUJAW2v)0FD+*>SUp!Qr&2eD*j8sgz%1nzA1tL6&)kCd74 zzOl$=dMt`|4L_ds_-NigJvo^j!1=IuaDQkX!-Lrn8V9G~XmC6~oPjssMDOUIuP(#j zT41{$4m$craPK!PevbjaW+UKB_LFr_{4RTVazHD~nTRN7z4d+1RE!|jTNO`Aj5_8> zr6{hu|B+v_F*`3$-zFGX^~h`B00Yqn9*jM^1IPk1*7MVj{(hSLeFZ(}-m$)%@_&!X z|NQLn2W5kX5n-j6Vc1EP(usa6oxaV+qEayW0!sYjsg-Qc>}^1!$d=KBz`unaJ+mxg z)KMxSk>aDi1=6!#Tt7HaxErA@KSlpQewR^Hegf$7nkSIH_qA>arFgY6b``S3bR4GR z3V}5lizN7KaAo5OiLP$d&}~k2 zSN))asD9q=R9~8JLVbA-4NDPoa!)*HWP566?MD zx}}?SpT&OEK~H+OlF#3D^95zef+^_}_^2v&KGiMAU6?qSCvYXQ3x8E5O@D0y3SK*a zF7*N_l5JetGfvG9L|+o)wkD(AHWQ(#7mN1$v9&048+v&rp_iX$7;TI?LOtn4SpqGt zphH~xD+Cs;l|UV*qqYR+{}NS1bv1xnYicVSYj(+HFT|R)uc~^=0tHlfz|AE1cJz!> z3`%0WY)5#|*EWN=W`728?Y*)s)W)DTKmO?_*;j^GR$nFlnaJu_^oM-f&p!0Iz9XXD zFR@Q(w_pCO^l~;K(%0@oU6HZ+}6R3RF|Jp@DzClI}Vhx zYxBMk_ZGTif-#eWDma;-P-{o~IwiR!mME!FXj$SK6--R1QGdd^Al?w|J1^>KSCesr z`-j_o@K49dk`T7wiQmYq+R2|$b@@WeYK6@1=g;Bdpa9CSW|8_%G_kQ&mx4}aF2WSo zQPKEteqx@Ug2BPj$HA-kYEFjv$0#0z`16WlYPbRzv-S0{!@? zchaAM-oa6SW`7 zsF92akob%xsEG>Ao^fV_`wKy^f7H=!gk!(flQg3F*?$=mFdh7fEnbJ*pninkZVoPF z<{$c;xHe#HZ2}4pD6(PJNr^&8Djesyr+%jFaz_D?QwnGut;6f9sGW6e<`vb{PSC~{ z^iF0k3-pexPIG$aht5;<5}+!Oo5x(p$~AnMg!W|l;2{H8`e<-`cyfGv+B-TuJnXPn z)9=91M}Pgn$;shquYYoK(&LY#sLh~%czk#=JUTq-=;O&}Paho}cJvSYZ(nurVe(n) z4~NJ44U?637|g6lE&>=Lcsc!acrtjQMYU)O#$@JY{nKe40wf z;|872$^;BMo0%r)&qPz2Ffo5+M$2bKWvn0kk~HcWi3v7RiV()RNU?(8#-{XLoZl2X h5VGqw7hHB-S-jC7A8#K2e*gdg|Nk*s*L0(g0Ra2LTj2lz delta 7383 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 JdLTBB0RXE=QaS(t diff --git a/cmd/lotus-storage-miner/sectors.go b/cmd/lotus-storage-miner/sectors.go index c76d4a249..2476c16e8 100644 --- a/cmd/lotus-storage-miner/sectors.go +++ b/cmd/lotus-storage-miner/sectors.go @@ -1059,15 +1059,26 @@ var sectorsBatchingPendingPreCommit = &cli.Command{ ctx := lcli.ReqContext(cctx) if cctx.Bool("publish-now") { - cid, err := api.SectorPreCommitFlush(ctx) + res, err := api.SectorPreCommitFlush(ctx) if err != nil { return xerrors.Errorf("flush: %w", err) } - if cid == nil { + if res == nil { return xerrors.Errorf("no sectors to publish") } - fmt.Println("sector batch published: ", cid) + for i, re := range res { + fmt.Printf("Batch %d:\n", i) + if re.Error != "" { + fmt.Printf("\tError: %s\n", re.Error) + } else { + fmt.Printf("\tMessage: %s\n", re.Msg) + } + fmt.Printf("\tSectors:\n") + for _, sector := range re.Sectors { + fmt.Printf("\t\t%d\tOK\n", sector) + } + } return nil } diff --git a/extern/storage-sealing/precommit_batch.go b/extern/storage-sealing/precommit_batch.go index bce8e21d5..dd674d331 100644 --- a/extern/storage-sealing/precommit_batch.go +++ b/extern/storage-sealing/precommit_batch.go @@ -18,6 +18,7 @@ import ( "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" + "github.com/filecoin-project/lotus/extern/storage-sealing/sealiface" ) type PreCommitBatcherApi interface { @@ -41,10 +42,10 @@ type PreCommitBatcher struct { deadlines map[abi.SectorNumber]time.Time todo map[abi.SectorNumber]*preCommitEntry - waiting map[abi.SectorNumber][]chan cid.Cid + waiting map[abi.SectorNumber][]chan sealiface.PreCommitBatchRes notify, stop, stopped chan struct{} - force chan chan *cid.Cid + force chan chan []sealiface.PreCommitBatchRes lk sync.Mutex } @@ -59,10 +60,10 @@ func NewPreCommitBatcher(mctx context.Context, maddr address.Address, api PreCom deadlines: map[abi.SectorNumber]time.Time{}, todo: map[abi.SectorNumber]*preCommitEntry{}, - waiting: map[abi.SectorNumber][]chan cid.Cid{}, + waiting: map[abi.SectorNumber][]chan sealiface.PreCommitBatchRes{}, notify: make(chan struct{}, 1), - force: make(chan chan *cid.Cid), + force: make(chan chan []sealiface.PreCommitBatchRes), stop: make(chan struct{}), stopped: make(chan struct{}), } @@ -73,8 +74,8 @@ func NewPreCommitBatcher(mctx context.Context, maddr address.Address, api PreCom } func (b *PreCommitBatcher) run() { - var forceRes chan *cid.Cid - var lastMsg *cid.Cid + var forceRes chan []sealiface.PreCommitBatchRes + var lastRes []sealiface.PreCommitBatchRes cfg, err := b.getConfig() if err != nil { @@ -83,10 +84,10 @@ func (b *PreCommitBatcher) run() { for { if forceRes != nil { - forceRes <- lastMsg + forceRes <- lastRes forceRes = nil } - lastMsg = nil + lastRes = nil var sendAboveMax, sendAboveMin bool select { @@ -102,7 +103,7 @@ func (b *PreCommitBatcher) run() { } var err error - lastMsg, err = b.processBatch(sendAboveMax, sendAboveMin) + lastRes, err = b.maybeStartBatch(sendAboveMax, sendAboveMin) if err != nil { log.Warnw("PreCommitBatcher processBatch error", "error", err) } @@ -150,10 +151,9 @@ func (b *PreCommitBatcher) batchWait(maxWait, slack time.Duration) <-chan time.T return time.After(wait) } -func (b *PreCommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { +func (b *PreCommitBatcher) maybeStartBatch(notif, after bool) ([]sealiface.PreCommitBatchRes, error) { b.lk.Lock() defer b.lk.Unlock() - params := miner5.PreCommitSectorBatchParams{} total := len(b.todo) if total == 0 { @@ -173,7 +173,35 @@ func (b *PreCommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { return nil, nil } + // todo support multiple batches + res, err := b.processBatch(cfg) + if err != nil && len(res) == 0 { + return nil, err + } + + for _, r := range res { + if err != nil { + r.Error = err.Error() + } + + for _, sn := range r.Sectors { + for _, ch := range b.waiting[sn] { + ch <- r // buffered + } + + delete(b.waiting, sn) + delete(b.todo, sn) + delete(b.deadlines, sn) + } + } + + return res, nil +} + +func (b *PreCommitBatcher) processBatch(cfg sealiface.Config) ([]sealiface.PreCommitBatchRes, error) { + params := miner5.PreCommitSectorBatchParams{} deposit := big.Zero() + var res sealiface.PreCommitBatchRes for _, p := range b.todo { if len(params.Sectors) >= cfg.MaxPreCommitBatch { @@ -181,54 +209,46 @@ func (b *PreCommitBatcher) processBatch(notif, after bool) (*cid.Cid, error) { break } + res.Sectors = append(res.Sectors, p.pci.SectorNumber) params.Sectors = append(params.Sectors, *p.pci) deposit = big.Add(deposit, p.deposit) } enc := new(bytes.Buffer) if err := params.MarshalCBOR(enc); err != nil { - return nil, xerrors.Errorf("couldn't serialize PreCommitSectorBatchParams: %w", err) + return []sealiface.PreCommitBatchRes{res}, xerrors.Errorf("couldn't serialize PreCommitSectorBatchParams: %w", err) } mi, err := b.api.StateMinerInfo(b.mctx, b.maddr, nil) if err != nil { - return nil, xerrors.Errorf("couldn't get miner info: %w", err) + return []sealiface.PreCommitBatchRes{res}, xerrors.Errorf("couldn't get miner info: %w", err) } goodFunds := big.Add(deposit, b.feeCfg.MaxPreCommitGasFee) from, _, err := b.addrSel(b.mctx, mi, api.PreCommitAddr, goodFunds, deposit) if err != nil { - return nil, xerrors.Errorf("no good address found: %w", err) + return []sealiface.PreCommitBatchRes{res}, xerrors.Errorf("no good address found: %w", err) } mcid, err := b.api.SendMsg(b.mctx, from, b.maddr, miner.Methods.PreCommitSectorBatch, deposit, b.feeCfg.MaxPreCommitGasFee, enc.Bytes()) if err != nil { - return nil, xerrors.Errorf("sending message failed: %w", err) + return []sealiface.PreCommitBatchRes{res}, xerrors.Errorf("sending message failed: %w", err) } - log.Infow("Sent ProveCommitAggregate message", "cid", mcid, "from", from, "sectors", total) + res.Msg = &mcid - for _, sector := range params.Sectors { - sn := sector.SectorNumber + log.Infow("Sent ProveCommitAggregate message", "cid", mcid, "from", from, "sectors", len(b.todo)) - for _, ch := range b.waiting[sn] { - ch <- mcid // buffered - } - delete(b.waiting, sn) - delete(b.todo, sn) - delete(b.deadlines, sn) - } - - return &mcid, nil + return []sealiface.PreCommitBatchRes{res}, nil } // register PreCommit, wait for batch message, return message CID -func (b *PreCommitBatcher) AddPreCommit(ctx context.Context, s SectorInfo, deposit abi.TokenAmount, in *miner0.SectorPreCommitInfo) (mcid cid.Cid, err error) { +func (b *PreCommitBatcher) AddPreCommit(ctx context.Context, s SectorInfo, deposit abi.TokenAmount, in *miner0.SectorPreCommitInfo) (res sealiface.PreCommitBatchRes, err error) { _, curEpoch, err := b.api.ChainHead(b.mctx) if err != nil { log.Errorf("getting chain head: %s", err) - return cid.Undef, nil + return sealiface.PreCommitBatchRes{}, err } sn := s.SectorNumber @@ -240,7 +260,7 @@ func (b *PreCommitBatcher) AddPreCommit(ctx context.Context, s SectorInfo, depos pci: in, } - sent := make(chan cid.Cid, 1) + sent := make(chan sealiface.PreCommitBatchRes, 1) b.waiting[sn] = append(b.waiting[sn], sent) select { @@ -253,12 +273,12 @@ func (b *PreCommitBatcher) AddPreCommit(ctx context.Context, s SectorInfo, depos case c := <-sent: return c, nil case <-ctx.Done(): - return cid.Undef, ctx.Err() + return sealiface.PreCommitBatchRes{}, ctx.Err() } } -func (b *PreCommitBatcher) Flush(ctx context.Context) (*cid.Cid, error) { - resCh := make(chan *cid.Cid, 1) +func (b *PreCommitBatcher) Flush(ctx context.Context) ([]sealiface.PreCommitBatchRes, error) { + resCh := make(chan []sealiface.PreCommitBatchRes, 1) select { case b.force <- resCh: select { diff --git a/extern/storage-sealing/sealiface/batching.go b/extern/storage-sealing/sealiface/batching.go index e7c2cadbb..d0e6d4178 100644 --- a/extern/storage-sealing/sealiface/batching.go +++ b/extern/storage-sealing/sealiface/batching.go @@ -14,3 +14,10 @@ type CommitBatchRes struct { Msg *cid.Cid Error string // if set, means that all sectors are failed, implies Msg==nil } + +type PreCommitBatchRes struct { + Sectors []abi.SectorNumber + + Msg *cid.Cid + Error string // if set, means that all sectors are failed, implies Msg==nil +} diff --git a/extern/storage-sealing/sealing.go b/extern/storage-sealing/sealing.go index 61360dc12..e69ce5be0 100644 --- a/extern/storage-sealing/sealing.go +++ b/extern/storage-sealing/sealing.go @@ -207,7 +207,7 @@ func (m *Sealing) TerminatePending(ctx context.Context) ([]abi.SectorID, error) return m.terminator.Pending(ctx) } -func (m *Sealing) SectorPreCommitFlush(ctx context.Context) (*cid.Cid, error) { +func (m *Sealing) SectorPreCommitFlush(ctx context.Context) ([]sealiface.PreCommitBatchRes, error) { return m.precommiter.Flush(ctx) } diff --git a/extern/storage-sealing/states_sealing.go b/extern/storage-sealing/states_sealing.go index 6f4c57bfd..815ad6ac0 100644 --- a/extern/storage-sealing/states_sealing.go +++ b/extern/storage-sealing/states_sealing.go @@ -355,7 +355,7 @@ func (m *Sealing) handlePreCommitting(ctx statemachine.Context, sector SectorInf func (m *Sealing) handleSubmitPreCommitBatch(ctx statemachine.Context, sector SectorInfo) error { if sector.CommD == nil || sector.CommR == nil { - return ctx.Send(SectorCommitFailed{xerrors.Errorf("sector had nil commR or commD")}) + return ctx.Send(SectorSealPreCommit1Failed{xerrors.Errorf("sector had nil commR or commD")}) } params, deposit, _, err := m.preCommitParams(ctx, sector) @@ -363,12 +363,20 @@ func (m *Sealing) handleSubmitPreCommitBatch(ctx statemachine.Context, sector Se return err } - mcid, err := m.precommiter.AddPreCommit(ctx.Context(), sector, deposit, params) + res, err := m.precommiter.AddPreCommit(ctx.Context(), sector, deposit, params) if err != nil { - return ctx.Send(SectorCommitFailed{xerrors.Errorf("queuing precommit batch failed: %w", err)}) + return ctx.Send(SectorChainPreCommitFailed{xerrors.Errorf("queuing precommit batch failed: %w", err)}) } - return ctx.Send(SectorPreCommitBatchSent{mcid}) + if res.Error != "" { + return ctx.Send(SectorChainPreCommitFailed{xerrors.Errorf("precommit batch error: %s", res.Error)}) + } + + if res.Msg == nil { + return ctx.Send(SectorChainPreCommitFailed{xerrors.Errorf("batch message was nil")}) + } + + return ctx.Send(SectorPreCommitBatchSent{*res.Msg}) } func (m *Sealing) handlePreCommitWait(ctx statemachine.Context, sector SectorInfo) error { diff --git a/node/impl/storminer.go b/node/impl/storminer.go index 9b6f65207..e10925927 100644 --- a/node/impl/storminer.go +++ b/node/impl/storminer.go @@ -375,7 +375,7 @@ func (sm *StorageMinerAPI) SectorTerminatePending(ctx context.Context) ([]abi.Se return sm.Miner.TerminatePending(ctx) } -func (sm *StorageMinerAPI) SectorPreCommitFlush(ctx context.Context) (*cid.Cid, error) { +func (sm *StorageMinerAPI) SectorPreCommitFlush(ctx context.Context) ([]sealiface.PreCommitBatchRes, error) { return sm.Miner.SectorPreCommitFlush(ctx) } diff --git a/storage/sealing.go b/storage/sealing.go index bd8241197..6a1195826 100644 --- a/storage/sealing.go +++ b/storage/sealing.go @@ -60,7 +60,7 @@ func (m *Miner) TerminatePending(ctx context.Context) ([]abi.SectorID, error) { return m.sealing.TerminatePending(ctx) } -func (m *Miner) SectorPreCommitFlush(ctx context.Context) (*cid.Cid, error) { +func (m *Miner) SectorPreCommitFlush(ctx context.Context) ([]sealiface.PreCommitBatchRes, error) { return m.sealing.SectorPreCommitFlush(ctx) } From 49fce48c3e8de610472bb457b3cfa619c02fdfe3 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Tue, 1 Jun 2021 12:43:16 -0400 Subject: [PATCH 71/88] Tweak CallVM to use current epoch, not future epoch --- chain/stmgr/call.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/chain/stmgr/call.go b/chain/stmgr/call.go index 961bebd9c..cfbf60a95 100644 --- a/chain/stmgr/call.go +++ b/chain/stmgr/call.go @@ -155,11 +155,6 @@ func (sm *StateManager) CallWithGas(ctx context.Context, msg *types.Message, pri return nil, xerrors.Errorf("computing tipset state: %w", err) } - state, err = sm.handleStateForks(ctx, state, ts.Height(), nil, ts) - if err != nil { - return nil, fmt.Errorf("failed to handle fork: %w", err) - } - r := store.NewChainRand(sm.cs, ts.Cids()) if span.IsRecordingEvents() { @@ -172,7 +167,7 @@ func (sm *StateManager) CallWithGas(ctx context.Context, msg *types.Message, pri vmopt := &vm.VMOpts{ StateBase: state, - Epoch: ts.Height() + 1, + Epoch: ts.Height(), Rand: r, Bstore: sm.cs.StateBlockstore(), Syscalls: sm.cs.VMSys(), From 8072573292dfba1a3eaad77500340eb5ab281488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 1 Jun 2021 19:11:32 +0200 Subject: [PATCH 72/88] Fix TestWindowPostDispute --- api/test/window_post.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/test/window_post.go b/api/test/window_post.go index 2d3302d64..04056d56e 100644 --- a/api/test/window_post.go +++ b/api/test/window_post.go @@ -543,7 +543,7 @@ func TestWindowPostDispute(t *testing.T, b APIBuilder, blocktime time.Duration) for { di, err = client.StateMinerProvingDeadline(ctx, evilMinerAddr, types.EmptyTSK) require.NoError(t, err) - if di.Index == evilSectorLoc.Deadline { + if di.Index == evilSectorLoc.Deadline && di.CurrentEpoch - di.PeriodStart > 1 { break } build.Clock.Sleep(blocktime) @@ -640,7 +640,7 @@ func TestWindowPostDispute(t *testing.T, b APIBuilder, blocktime time.Duration) for { di, err = client.StateMinerProvingDeadline(ctx, evilMinerAddr, types.EmptyTSK) require.NoError(t, err) - if di.Index == evilSectorLoc.Deadline { + if di.Index == evilSectorLoc.Deadline && di.CurrentEpoch - di.PeriodStart > 1 { break } build.Clock.Sleep(blocktime) From 5c5b8866c7034de72db2e08254c372f9d782acb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 1 Jun 2021 19:14:08 +0200 Subject: [PATCH 73/88] gofmt --- api/test/window_post.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/test/window_post.go b/api/test/window_post.go index 04056d56e..8a6ca8044 100644 --- a/api/test/window_post.go +++ b/api/test/window_post.go @@ -543,7 +543,7 @@ func TestWindowPostDispute(t *testing.T, b APIBuilder, blocktime time.Duration) for { di, err = client.StateMinerProvingDeadline(ctx, evilMinerAddr, types.EmptyTSK) require.NoError(t, err) - if di.Index == evilSectorLoc.Deadline && di.CurrentEpoch - di.PeriodStart > 1 { + if di.Index == evilSectorLoc.Deadline && di.CurrentEpoch-di.PeriodStart > 1 { break } build.Clock.Sleep(blocktime) @@ -640,7 +640,7 @@ func TestWindowPostDispute(t *testing.T, b APIBuilder, blocktime time.Duration) for { di, err = client.StateMinerProvingDeadline(ctx, evilMinerAddr, types.EmptyTSK) require.NoError(t, err) - if di.Index == evilSectorLoc.Deadline && di.CurrentEpoch - di.PeriodStart > 1 { + if di.Index == evilSectorLoc.Deadline && di.CurrentEpoch-di.PeriodStart > 1 { break } build.Clock.Sleep(blocktime) From 66c1554670b10147f512a7c1f3fd2a6a282897ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 1 Jun 2021 19:23:12 +0200 Subject: [PATCH 74/88] vm syscalls: fix typo --- chain/vm/syscalls.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chain/vm/syscalls.go b/chain/vm/syscalls.go index 568197bc8..bb93fce8d 100644 --- a/chain/vm/syscalls.go +++ b/chain/vm/syscalls.go @@ -287,7 +287,7 @@ func (ss *syscallShim) VerifyAggregateSeals(aggregate proof5.AggregateSealVerify return xerrors.Errorf("failed to verify aggregated PoRep: %w", err) } if !ok { - return fmt.Errorf("invalid aggredate proof") + return fmt.Errorf("invalid aggregate proof") } return nil From c8cef1cb7e072677a424faeceef079757656ac5e Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Tue, 1 Jun 2021 13:13:45 -0400 Subject: [PATCH 75/88] Fix chain/gen randomness getting (test only) --- chain/gen/gen.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/chain/gen/gen.go b/chain/gen/gen.go index 319b7ac29..0cbdb2188 100644 --- a/chain/gen/gen.go +++ b/chain/gen/gen.go @@ -590,6 +590,10 @@ func (mca mca) ChainGetRandomnessFromTickets(ctx context.Context, tsk types.TipS return nil, xerrors.Errorf("loading tipset key: %w", err) } + if randEpoch > build.UpgradeHyperdriveHeight { + return mca.sm.ChainStore().GetChainRandomnessLookingForward(ctx, pts.Cids(), personalization, randEpoch, entropy) + } + return mca.sm.ChainStore().GetChainRandomnessLookingBack(ctx, pts.Cids(), personalization, randEpoch, entropy) } @@ -599,6 +603,10 @@ func (mca mca) ChainGetRandomnessFromBeacon(ctx context.Context, tsk types.TipSe return nil, xerrors.Errorf("loading tipset key: %w", err) } + if randEpoch > build.UpgradeHyperdriveHeight { + return mca.sm.ChainStore().GetBeaconRandomnessLookingForward(ctx, pts.Cids(), personalization, randEpoch, entropy) + } + return mca.sm.ChainStore().GetBeaconRandomnessLookingBack(ctx, pts.Cids(), personalization, randEpoch, entropy) } From b6b56320bf23575499a4ffdae16dc88e1fd5535c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 1 Jun 2021 20:27:09 +0200 Subject: [PATCH 76/88] Fix StateComputeDataCommitment --- storage/adapter_storage_miner.go | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/storage/adapter_storage_miner.go b/storage/adapter_storage_miner.go index f4d1e0038..3ebe874a9 100644 --- a/storage/adapter_storage_miner.go +++ b/storage/adapter_storage_miner.go @@ -147,15 +147,7 @@ func (s SealingAPIAdapter) StateComputeDataCommitment(ctx context.Context, maddr return cid.Undef, xerrors.Errorf("failed to unmarshal TipSetToken to TipSetKey: %w", err) } - ts, err := s.delegate.ChainGetTipSet(ctx, tsk) - if err != nil { - return cid.Cid{}, err - } - - // using parent ts because the migration won't be run on the first nv13 - // tipset we apply StateCall to (because we don't run migrations in StateCall - // and just apply to parent state) - nv, err := s.delegate.StateNetworkVersion(ctx, ts.Parents()) + nv, err := s.delegate.StateNetworkVersion(ctx, tsk) if err != nil { return cid.Cid{}, err } From 8b84e499c5265d2a2a0a49c3d9d2eaa0e2371d83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 1 Jun 2021 20:36:20 +0200 Subject: [PATCH 77/88] storagefsm: Handle preCommitParams errors better --- extern/storage-sealing/states_sealing.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/storage-sealing/states_sealing.go b/extern/storage-sealing/states_sealing.go index 815ad6ac0..b6ebe32c5 100644 --- a/extern/storage-sealing/states_sealing.go +++ b/extern/storage-sealing/states_sealing.go @@ -360,7 +360,7 @@ func (m *Sealing) handleSubmitPreCommitBatch(ctx statemachine.Context, sector Se params, deposit, _, err := m.preCommitParams(ctx, sector) if params == nil || err != nil { - return err + return ctx.Send(SectorChainPreCommitFailed{xerrors.Errorf("preCommitParams: %w", err)}) } res, err := m.precommiter.AddPreCommit(ctx.Context(), sector, deposit, params) From 39f2246ae69fac5d8d4209f00323deaf5659daaa Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Tue, 1 Jun 2021 14:06:13 -0400 Subject: [PATCH 78/88] Use build.UpgradeHyperDriveHeight directly when sourcing randomness in VM --- chain/vm/runtime.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chain/vm/runtime.go b/chain/vm/runtime.go index 00c04ceb8..7c40fed62 100644 --- a/chain/vm/runtime.go +++ b/chain/vm/runtime.go @@ -214,7 +214,7 @@ func (rt *Runtime) GetActorCodeCID(addr address.Address) (ret cid.Cid, ok bool) func (rt *Runtime) GetRandomnessFromTickets(personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) abi.Randomness { var err error var res []byte - if rt.vm.GetNtwkVersion(rt.ctx, randEpoch) >= network.Version13 { + if randEpoch > build.UpgradeHyperdriveHeight { res, err = rt.vm.rand.GetChainRandomnessLookingForward(rt.ctx, personalization, randEpoch, entropy) } else { res, err = rt.vm.rand.GetChainRandomnessLookingBack(rt.ctx, personalization, randEpoch, entropy) From ee21351aa0c80e5303ad6fb5645211a03c6bee1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 1 Jun 2021 21:16:01 +0200 Subject: [PATCH 79/88] statetree: Add missing version defs --- chain/state/statetree.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/chain/state/statetree.go b/chain/state/statetree.go index 81ab82e14..40955c48b 100644 --- a/chain/state/statetree.go +++ b/chain/state/statetree.go @@ -152,6 +152,8 @@ func VersionForNetwork(ver network.Version) (types.StateTreeVersion, error) { return types.StateTreeVersion2, nil case network.Version12: return types.StateTreeVersion3, nil + case network.Version13: + return types.StateTreeVersion4, nil default: panic(fmt.Sprintf("unsupported network version %d", ver)) } @@ -162,7 +164,7 @@ func NewStateTree(cst cbor.IpldStore, ver types.StateTreeVersion) (*StateTree, e switch ver { case types.StateTreeVersion0: // info is undefined - case types.StateTreeVersion1, types.StateTreeVersion2, types.StateTreeVersion3: + case types.StateTreeVersion1, types.StateTreeVersion2, types.StateTreeVersion3, types.StateTreeVersion4: var err error info, err = cst.Put(context.TODO(), new(types.StateInfo0)) if err != nil { From 2f0a9f6c407658499f68d3767fcf166c91924067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 1 Jun 2021 21:28:48 +0200 Subject: [PATCH 80/88] Improve AddVerifiedClient test --- api/test/test.go | 56 +++++++----- api/test/verifreg.go | 213 +++++++++++++++++++++++-------------------- 2 files changed, 148 insertions(+), 121 deletions(-) diff --git a/api/test/test.go b/api/test/test.go index 8f8d95100..11f0f41ea 100644 --- a/api/test/test.go +++ b/api/test/test.go @@ -122,31 +122,45 @@ var OneFull = DefaultFullOpts(1) var TwoFull = DefaultFullOpts(2) var FullNodeWithLatestActorsAt = func(upgradeHeight abi.ChainEpoch) FullNodeOpts { - if upgradeHeight == -1 { - // Attention: Update this when introducing new actor versions or your tests will be sad - upgradeHeight = 4 + // Attention: Update this when introducing new actor versions or your tests will be sad + return FullNodeWithActorsUpgradeAt(network.Version13, upgradeHeight) +} + +var FullNodeWithActorsUpgradeAt = func(version network.Version, upgradeHeight abi.ChainEpoch) FullNodeOpts { + fullSchedule := stmgr.UpgradeSchedule{{ + // prepare for upgrade. + Network: network.Version9, + Height: 1, + Migration: stmgr.UpgradeActorsV2, + }, { + Network: network.Version10, + Height: 2, + Migration: stmgr.UpgradeActorsV3, + }, { + Network: network.Version12, + Height: 3, + Migration: stmgr.UpgradeActorsV4, + }, { + Network: network.Version13, + Height: 4, + Migration: stmgr.UpgradeActorsV5, + }} + + schedule := stmgr.UpgradeSchedule{} + for _, upgrade := range fullSchedule { + if upgrade.Network > version { + break + } + + schedule = append(schedule, upgrade) } + if upgradeHeight > 0 { + schedule[len(schedule)-1].Height = upgradeHeight + } return FullNodeOpts{ Opts: func(nodes []TestNode) node.Option { - return node.Override(new(stmgr.UpgradeSchedule), stmgr.UpgradeSchedule{{ - // prepare for upgrade. - Network: network.Version9, - Height: 1, - Migration: stmgr.UpgradeActorsV2, - }, { - Network: network.Version10, - Height: 2, - Migration: stmgr.UpgradeActorsV3, - }, { - Network: network.Version12, - Height: 3, - Migration: stmgr.UpgradeActorsV4, - }, { - Network: network.Version13, - Height: upgradeHeight, - Migration: stmgr.UpgradeActorsV5, - }}) + return node.Override(new(stmgr.UpgradeSchedule), fullSchedule) }, } } diff --git a/api/test/verifreg.go b/api/test/verifreg.go index b66ca1a36..266bed190 100644 --- a/api/test/verifreg.go +++ b/api/test/verifreg.go @@ -2,6 +2,7 @@ package test import ( "context" + "github.com/filecoin-project/go-state-types/network" "strings" lapi "github.com/filecoin-project/lotus/api" @@ -19,108 +20,120 @@ import ( ) func AddVerifiedClient(t *testing.T, b APIBuilder) { + test := func(nv network.Version, shouldWork bool) func(*testing.T) { + return func(t *testing.T) { - nodes, miners := b(t, []FullNodeOpts{FullNodeWithLatestActorsAt(-1)}, OneMiner) - api := nodes[0].FullNode.(*impl.FullNodeAPI) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + nodes, miners := b(t, []FullNodeOpts{FullNodeWithActorsUpgradeAt(nv, -1)}, OneMiner) + api := nodes[0].FullNode.(*impl.FullNodeAPI) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() - //Get VRH - vrh, err := api.StateVerifiedRegistryRootKey(ctx, types.TipSetKey{}) - if err != nil { - t.Fatal(err) + //Get VRH + vrh, err := api.StateVerifiedRegistryRootKey(ctx, types.TipSetKey{}) + if err != nil { + t.Fatal(err) + } + + //Add verifier + verifier, err := api.WalletDefaultAddress(ctx) + if err != nil { + t.Fatal(err) + } + + params, err := actors.SerializeParams(&verifreg4.AddVerifierParams{Address: verifier, Allowance: big.NewInt(100000000000)}) + if err != nil { + t.Fatal(err) + } + msg := &types.Message{ + To: verifreg.Address, + From: vrh, + Method: verifreg.Methods.AddVerifier, + Params: params, + Value: big.Zero(), + } + + bm := NewBlockMiner(ctx, t, miners[0], 100*time.Millisecond) + bm.MineBlocks() + defer bm.Stop() + + sm, err := api.MpoolPushMessage(ctx, msg, nil) + if err != nil { + t.Fatal("AddVerifier failed: ", err) + } + res, err := api.StateWaitMsg(ctx, sm.Cid(), 1, lapi.LookbackNoLimit, true) + if err != nil { + t.Fatal(err) + } + if res.Receipt.ExitCode != 0 { + t.Fatal("did not successfully send message") + } + + //Assign datacap to a client + datacap := big.NewInt(10000) + clientAddress, err := api.WalletNew(ctx, types.KTBLS) + if err != nil { + t.Fatal(err) + } + + params, err = actors.SerializeParams(&verifreg4.AddVerifiedClientParams{Address: clientAddress, Allowance: datacap}) + if err != nil { + t.Fatal(err) + } + + msg = &types.Message{ + To: verifreg.Address, + From: verifier, + Method: verifreg.Methods.AddVerifiedClient, + Params: params, + Value: big.Zero(), + } + + sm, err = api.MpoolPushMessage(ctx, msg, nil) + if err != nil { + t.Fatal("AddVerifiedClient faield: ", err) + } + res, err = api.StateWaitMsg(ctx, sm.Cid(), 1, lapi.LookbackNoLimit, true) + if err != nil { + t.Fatal(err) + } + if res.Receipt.ExitCode != 0 { + t.Fatal("did not successfully send message") + } + + //check datacap balance + dcap, err := api.StateVerifiedClientStatus(ctx, clientAddress, types.EmptyTSK) + if err != nil { + t.Fatal(err) + } + if !dcap.Equals(datacap) { + t.Fatal("") + } + + //try to assign datacap to the same client should fail for actor v4 and below + params, err = actors.SerializeParams(&verifreg4.AddVerifiedClientParams{Address: clientAddress, Allowance: datacap}) + if err != nil { + t.Fatal(err) + } + + msg = &types.Message{ + To: verifreg.Address, + From: verifier, + Method: verifreg.Methods.AddVerifiedClient, + Params: params, + Value: big.Zero(), + } + + _, err = api.MpoolPushMessage(ctx, msg, nil) + if shouldWork && err != nil { + t.Fatal("expected nil err", err) + } + + if !shouldWork && err == nil || !strings.Contains(err.Error(), "verified client already exists") { + t.Fatal("Add datacap to an existing verified client should fail") + } + } } - //Add verifier - verifier, err := api.WalletDefaultAddress(ctx) - if err != nil { - t.Fatal(err) - } - - params, err := actors.SerializeParams(&verifreg4.AddVerifierParams{Address: verifier, Allowance: big.NewInt(100000000000)}) - if err != nil { - t.Fatal(err) - } - msg := &types.Message{ - To: verifreg.Address, - From: vrh, - Method: verifreg.Methods.AddVerifier, - Params: params, - Value: big.Zero(), - } - - bm := NewBlockMiner(ctx, t, miners[0], 100*time.Millisecond) - bm.MineBlocks() - defer bm.Stop() - - sm, err := api.MpoolPushMessage(ctx, msg, nil) - if err != nil { - t.Fatal("AddVerifier failed: ", err) - } - res, err := api.StateWaitMsg(ctx, sm.Cid(), 1, lapi.LookbackNoLimit, true) - if err != nil { - t.Fatal(err) - } - if res.Receipt.ExitCode != 0 { - t.Fatal("did not successfully send message") - } - - //Assign datacap to a client - datacap := big.NewInt(10000) - clientAddress, err := api.WalletNew(ctx, types.KTBLS) - if err != nil { - t.Fatal(err) - } - - params, err = actors.SerializeParams(&verifreg4.AddVerifiedClientParams{Address: clientAddress, Allowance: datacap}) - if err != nil { - t.Fatal(err) - } - - msg = &types.Message{ - To: verifreg.Address, - From: verifier, - Method: verifreg.Methods.AddVerifiedClient, - Params: params, - Value: big.Zero(), - } - - sm, err = api.MpoolPushMessage(ctx, msg, nil) - if err != nil { - t.Fatal("AddVerifiedClient faield: ", err) - } - res, err = api.StateWaitMsg(ctx, sm.Cid(), 1, lapi.LookbackNoLimit, true) - if err != nil { - t.Fatal(err) - } - if res.Receipt.ExitCode != 0 { - t.Fatal("did not successfully send message") - } - - //check datacap balance - dcap, err := api.StateVerifiedClientStatus(ctx, clientAddress, types.EmptyTSK) - if err != nil { - t.Fatal(err) - } - if !dcap.Equals(datacap) { - t.Fatal("") - } - - //try to assign datacap to the same client should fail for actor v4 and below - params, err = actors.SerializeParams(&verifreg4.AddVerifiedClientParams{Address: clientAddress, Allowance: datacap}) - if err != nil { - t.Fatal(err) - } - - msg = &types.Message{ - To: verifreg.Address, - From: verifier, - Method: verifreg.Methods.AddVerifiedClient, - Params: params, - Value: big.Zero(), - } - - if _, err = api.MpoolPushMessage(ctx, msg, nil); !strings.Contains(err.Error(), "verified client already exists") { - t.Fatal("Add datacap to an exist verified client should fail") - } + t.Run("nv12", test(network.Version12, false)) + t.Run("nv13", test(network.Version13, true)) } From f00cf70df078d594dfa6b0f8024490f003add1fc Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Tue, 1 Jun 2021 17:11:54 -0400 Subject: [PATCH 81/88] Magik shouldn't write code at midnight --- api/test/test.go | 7 ++++--- api/test/verifreg.go | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/api/test/test.go b/api/test/test.go index 11f0f41ea..7609d0702 100644 --- a/api/test/test.go +++ b/api/test/test.go @@ -123,10 +123,10 @@ var TwoFull = DefaultFullOpts(2) var FullNodeWithLatestActorsAt = func(upgradeHeight abi.ChainEpoch) FullNodeOpts { // Attention: Update this when introducing new actor versions or your tests will be sad - return FullNodeWithActorsUpgradeAt(network.Version13, upgradeHeight) + return FullNodeWithNetworkUpgradeAt(network.Version13, upgradeHeight) } -var FullNodeWithActorsUpgradeAt = func(version network.Version, upgradeHeight abi.ChainEpoch) FullNodeOpts { +var FullNodeWithNetworkUpgradeAt = func(version network.Version, upgradeHeight abi.ChainEpoch) FullNodeOpts { fullSchedule := stmgr.UpgradeSchedule{{ // prepare for upgrade. Network: network.Version9, @@ -158,9 +158,10 @@ var FullNodeWithActorsUpgradeAt = func(version network.Version, upgradeHeight ab if upgradeHeight > 0 { schedule[len(schedule)-1].Height = upgradeHeight } + return FullNodeOpts{ Opts: func(nodes []TestNode) node.Option { - return node.Override(new(stmgr.UpgradeSchedule), fullSchedule) + return node.Override(new(stmgr.UpgradeSchedule), schedule) }, } } diff --git a/api/test/verifreg.go b/api/test/verifreg.go index 266bed190..3fc1fb75a 100644 --- a/api/test/verifreg.go +++ b/api/test/verifreg.go @@ -2,9 +2,10 @@ package test import ( "context" - "github.com/filecoin-project/go-state-types/network" "strings" + "github.com/filecoin-project/go-state-types/network" + lapi "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/actors" @@ -23,7 +24,7 @@ func AddVerifiedClient(t *testing.T, b APIBuilder) { test := func(nv network.Version, shouldWork bool) func(*testing.T) { return func(t *testing.T) { - nodes, miners := b(t, []FullNodeOpts{FullNodeWithActorsUpgradeAt(nv, -1)}, OneMiner) + nodes, miners := b(t, []FullNodeOpts{FullNodeWithNetworkUpgradeAt(nv, -1)}, OneMiner) api := nodes[0].FullNode.(*impl.FullNodeAPI) ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -128,7 +129,7 @@ func AddVerifiedClient(t *testing.T, b APIBuilder) { t.Fatal("expected nil err", err) } - if !shouldWork && err == nil || !strings.Contains(err.Error(), "verified client already exists") { + if !shouldWork && (err == nil || !strings.Contains(err.Error(), "verified client already exists")) { t.Fatal("Add datacap to an existing verified client should fail") } } From 964435a78c112a96306a63cceaccd92248103b26 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Tue, 1 Jun 2021 17:39:45 -0400 Subject: [PATCH 82/88] CLI docsgen --- documentation/en/cli-lotus-miner.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/documentation/en/cli-lotus-miner.md b/documentation/en/cli-lotus-miner.md index dfa9072c9..b4b245514 100644 --- a/documentation/en/cli-lotus-miner.md +++ b/documentation/en/cli-lotus-miner.md @@ -1533,9 +1533,9 @@ USAGE: lotus-miner sectors batching command [command options] [arguments...] COMMANDS: - pending-commit list sectors waiting in commit batch queue - pending-precommit list sectors waiting in precommit batch queue - help, h Shows a list of commands or help for one command + commit list sectors waiting in commit batch queue + precommit list sectors waiting in precommit batch queue + help, h Shows a list of commands or help for one command OPTIONS: --help, -h show help (default: false) @@ -1543,13 +1543,13 @@ OPTIONS: ``` -#### lotus-miner sectors batching pending-commit +#### lotus-miner sectors batching commit ``` NAME: - lotus-miner sectors batching pending-commit - list sectors waiting in commit batch queue + lotus-miner sectors batching commit - list sectors waiting in commit batch queue USAGE: - lotus-miner sectors batching pending-commit [command options] [arguments...] + lotus-miner sectors batching commit [command options] [arguments...] OPTIONS: --publish-now send a batch now (default: false) @@ -1557,13 +1557,13 @@ OPTIONS: ``` -#### lotus-miner sectors batching pending-precommit +#### lotus-miner sectors batching precommit ``` NAME: - lotus-miner sectors batching pending-precommit - list sectors waiting in precommit batch queue + lotus-miner sectors batching precommit - list sectors waiting in precommit batch queue USAGE: - lotus-miner sectors batching pending-precommit [command options] [arguments...] + lotus-miner sectors batching precommit [command options] [arguments...] OPTIONS: --publish-now send a batch now (default: false) From cf18709100779900ecbb9528d2e39a71fddf97b5 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Tue, 1 Jun 2021 17:50:06 -0400 Subject: [PATCH 83/88] Fix TestDeadlineToggling --- api/test/deadlines.go | 2 +- api/test/test.go | 25 ------------------------- 2 files changed, 1 insertion(+), 26 deletions(-) diff --git a/api/test/deadlines.go b/api/test/deadlines.go index 43fa731be..987bfb3ae 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{FullNodeWithLatestActorsAt(upgradeH)}, OneMiner) + n, sn := b(t, []FullNodeOpts{FullNodeWithNetworkUpgradeAt(network.Version12, upgradeH)}, OneMiner) client := n[0].FullNode.(*impl.FullNodeAPI) minerA := sn[0] diff --git a/api/test/test.go b/api/test/test.go index 7609d0702..64062e4ff 100644 --- a/api/test/test.go +++ b/api/test/test.go @@ -185,31 +185,6 @@ var FullNodeWithSDRAt = func(calico, persian abi.ChainEpoch) FullNodeOpts { } } -var FullNodeWithV4ActorsAt = func(upgradeHeight abi.ChainEpoch) FullNodeOpts { - if upgradeHeight == -1 { - upgradeHeight = 3 - } - - return FullNodeOpts{ - Opts: func(nodes []TestNode) node.Option { - return node.Override(new(stmgr.UpgradeSchedule), stmgr.UpgradeSchedule{{ - // prepare for upgrade. - Network: network.Version9, - Height: 1, - Migration: stmgr.UpgradeActorsV2, - }, { - Network: network.Version10, - Height: 2, - Migration: stmgr.UpgradeActorsV3, - }, { - Network: network.Version12, - Height: upgradeHeight, - Migration: stmgr.UpgradeActorsV4, - }}) - }, - } -} - var MineNext = miner.MineReq{ InjectNulls: 0, Done: func(bool, abi.ChainEpoch, error) {}, From f6963523f8d6f22e90ae1094ebc547b7f568824e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 2 Jun 2021 11:13:24 +0200 Subject: [PATCH 84/88] Use filecoin-ffi master --- extern/filecoin-ffi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/filecoin-ffi b/extern/filecoin-ffi index 58771ba4d..8b97bd823 160000 --- a/extern/filecoin-ffi +++ b/extern/filecoin-ffi @@ -1 +1 @@ -Subproject commit 58771ba4d942badc306925160a945022ad335161 +Subproject commit 8b97bd8230b77bd32f4f27e4766a6d8a03b4e801 From e1dc7ad6eb9ee914212b5a0e2da784b8d2b3e28a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 2 Jun 2021 15:12:26 +0200 Subject: [PATCH 85/88] build: Use go embed for srs-inner-product.json --- build/parameters.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build/parameters.go b/build/parameters.go index 9f838ea58..9e60f12a6 100644 --- a/build/parameters.go +++ b/build/parameters.go @@ -7,10 +7,13 @@ import ( //go:embed proof-params/parameters.json var params []byte +//go:embed proof-params/srs-inner-product.json +var srs []byte + func ParametersJSON() []byte { return params } func SrsJSON() []byte { - return rice.MustFindBox("proof-params").MustBytes("srs-inner-product.json") + return srs } From 08b7ab90c1afba31164c1c82794e15eb98e37ed5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 2 Jun 2021 15:18:06 +0200 Subject: [PATCH 86/88] mod tidy, fix testground build --- go.sum | 1 - testplans/lotus-soup/go.mod | 4 ++-- testplans/lotus-soup/go.sum | 10 ++++++++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/go.sum b/go.sum index f8daf0c63..b61212a6c 100644 --- a/go.sum +++ b/go.sum @@ -319,7 +319,6 @@ github.com/filecoin-project/specs-actors/v2 v2.3.5/go.mod h1:LljnY2Mn2homxZsmokJ github.com/filecoin-project/specs-actors/v3 v3.1.0/go.mod h1:mpynccOLlIRy0QnR008BwYBwT9fen+sPR13MA1VmMww= github.com/filecoin-project/specs-actors/v3 v3.1.1 h1:BE8fsns1GnEOxt1DTE5LxBK2FThXtWmCChgcJoHTg0E= github.com/filecoin-project/specs-actors/v3 v3.1.1/go.mod h1:mpynccOLlIRy0QnR008BwYBwT9fen+sPR13MA1VmMww= -github.com/filecoin-project/specs-actors/v4 v4.0.0 h1:vMALksY5G3J5rj3q9rbcyB+f4Tk1xrLqSgdB3jOok4s= github.com/filecoin-project/specs-actors/v4 v4.0.0/go.mod h1:TkHXf/l7Wyw4ZejyXIPS2rK8bBO0rdwhTZyQQgaglng= github.com/filecoin-project/specs-actors/v4 v4.0.1 h1:AiWrtvJZ63MHGe6rn7tPu4nSUY8bA1KDNszqJaD5+Fg= github.com/filecoin-project/specs-actors/v4 v4.0.1/go.mod h1:TkHXf/l7Wyw4ZejyXIPS2rK8bBO0rdwhTZyQQgaglng= diff --git a/testplans/lotus-soup/go.mod b/testplans/lotus-soup/go.mod index ae9b4d4b6..6f84a598e 100644 --- a/testplans/lotus-soup/go.mod +++ b/testplans/lotus-soup/go.mod @@ -11,9 +11,9 @@ require ( github.com/filecoin-project/go-data-transfer v1.6.0 github.com/filecoin-project/go-fil-markets v1.4.0 github.com/filecoin-project/go-jsonrpc v0.1.4-0.20210217175800-45ea43ac2bec - github.com/filecoin-project/go-state-types v0.1.0 + github.com/filecoin-project/go-state-types v0.1.1-0.20210506134452-99b279731c48 github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b - github.com/filecoin-project/lotus v1.9.1-0.20210602101339-07b025a54f6d + github.com/filecoin-project/lotus v1.9.1-0.20210602131226-e1dc7ad6eb9e github.com/filecoin-project/specs-actors v0.9.14 github.com/google/uuid v1.1.2 github.com/gorilla/mux v1.7.4 diff --git a/testplans/lotus-soup/go.sum b/testplans/lotus-soup/go.sum index fc88afe6c..caaeffe78 100644 --- a/testplans/lotus-soup/go.sum +++ b/testplans/lotus-soup/go.sum @@ -301,12 +301,16 @@ github.com/filecoin-project/go-padreader v0.0.0-20200903213702-ed5fae088b20 h1:+ github.com/filecoin-project/go-padreader v0.0.0-20200903213702-ed5fae088b20/go.mod h1:mPn+LRRd5gEKNAtc+r3ScpW2JRU/pj4NBKdADYWHiak= github.com/filecoin-project/go-paramfetch v0.0.2-0.20200701152213-3e0f0afdc261 h1:A256QonvzRaknIIAuWhe/M2dpV2otzs3NBhi5TWa/UA= github.com/filecoin-project/go-paramfetch v0.0.2-0.20200701152213-3e0f0afdc261/go.mod h1:fZzmf4tftbwf9S37XRifoJlz7nCjRdIrMGLR07dKLCc= +github.com/filecoin-project/go-paramfetch v0.0.2-0.20210330140417-936748d3f5ec h1:gExwWUiT1TcARkxGneS4nvp9C+wBsKU0bFdg7qFpNco= +github.com/filecoin-project/go-paramfetch v0.0.2-0.20210330140417-936748d3f5ec/go.mod h1:fZzmf4tftbwf9S37XRifoJlz7nCjRdIrMGLR07dKLCc= github.com/filecoin-project/go-state-types v0.0.0-20200903145444-247639ffa6ad/go.mod h1:IQ0MBPnonv35CJHtWSN3YY1Hz2gkPru1Q9qoaYLxx9I= github.com/filecoin-project/go-state-types v0.0.0-20200904021452-1883f36ca2f4/go.mod h1:IQ0MBPnonv35CJHtWSN3YY1Hz2gkPru1Q9qoaYLxx9I= github.com/filecoin-project/go-state-types v0.0.0-20200928172055-2df22083d8ab/go.mod h1:ezYnPf0bNkTsDibL/psSz5dy4B5awOJ/E7P2Saeep8g= github.com/filecoin-project/go-state-types v0.0.0-20201102161440-c8033295a1fc/go.mod h1:ezYnPf0bNkTsDibL/psSz5dy4B5awOJ/E7P2Saeep8g= github.com/filecoin-project/go-state-types v0.1.0 h1:9r2HCSMMCmyMfGyMKxQtv0GKp6VT/m5GgVk8EhYbLJU= github.com/filecoin-project/go-state-types v0.1.0/go.mod h1:ezYnPf0bNkTsDibL/psSz5dy4B5awOJ/E7P2Saeep8g= +github.com/filecoin-project/go-state-types v0.1.1-0.20210506134452-99b279731c48 h1:Jc4OprDp3bRDxbsrXNHPwJabZJM3iDy+ri8/1e0ZnX4= +github.com/filecoin-project/go-state-types v0.1.1-0.20210506134452-99b279731c48/go.mod h1:ezYnPf0bNkTsDibL/psSz5dy4B5awOJ/E7P2Saeep8g= github.com/filecoin-project/go-statemachine v0.0.0-20200925024713-05bd7c71fbfe h1:dF8u+LEWeIcTcfUcCf3WFVlc81Fr2JKg8zPzIbBDKDw= github.com/filecoin-project/go-statemachine v0.0.0-20200925024713-05bd7c71fbfe/go.mod h1:FGwQgZAt2Gh5mjlwJUlVB62JeYdo+if0xWxSEfBD9ig= github.com/filecoin-project/go-statestore v0.1.0/go.mod h1:LFc9hD+fRxPqiHiaqUEZOinUJB4WARkRfNl10O7kTnI= @@ -316,6 +320,8 @@ github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b/go.mod h1:Q0GQOBtKf1oE10eSXSlhN45kDBdGvEcVOqMiffqX+N8= github.com/filecoin-project/lotus v1.9.1-0.20210602101339-07b025a54f6d h1:gMkgi1SssdZWFpCHXcQvqcrsUJW+HHaO10w//HA9eyI= github.com/filecoin-project/lotus v1.9.1-0.20210602101339-07b025a54f6d/go.mod h1:8YWF0BqH6g3O47qB5mI0Pk9zgC2uA6xUlKXYo5VScIk= +github.com/filecoin-project/lotus v1.9.1-0.20210602131226-e1dc7ad6eb9e h1:JvtYGk30nM7K0TD4sTOUKYUePcSzZNj5ZD6g5vdrqMI= +github.com/filecoin-project/lotus v1.9.1-0.20210602131226-e1dc7ad6eb9e/go.mod h1:/ZeMXR8jPxJslaHSIW3ZxO9YPIaxcnsP+niEoBatzo8= github.com/filecoin-project/specs-actors v0.9.4/go.mod h1:BStZQzx5x7TmCkLv0Bpa07U6cPKol6fd3w9KjMPZ6Z4= github.com/filecoin-project/specs-actors v0.9.12/go.mod h1:TS1AW/7LbG+615j4NsjMK1qlpAwaFsG9w0V2tg2gSao= github.com/filecoin-project/specs-actors v0.9.13/go.mod h1:TS1AW/7LbG+615j4NsjMK1qlpAwaFsG9w0V2tg2gSao= @@ -329,8 +335,12 @@ github.com/filecoin-project/specs-actors/v2 v2.3.5/go.mod h1:LljnY2Mn2homxZsmokJ github.com/filecoin-project/specs-actors/v3 v3.1.0/go.mod h1:mpynccOLlIRy0QnR008BwYBwT9fen+sPR13MA1VmMww= github.com/filecoin-project/specs-actors/v3 v3.1.1 h1:BE8fsns1GnEOxt1DTE5LxBK2FThXtWmCChgcJoHTg0E= github.com/filecoin-project/specs-actors/v3 v3.1.1/go.mod h1:mpynccOLlIRy0QnR008BwYBwT9fen+sPR13MA1VmMww= +github.com/filecoin-project/specs-actors/v4 v4.0.0/go.mod h1:TkHXf/l7Wyw4ZejyXIPS2rK8bBO0rdwhTZyQQgaglng= github.com/filecoin-project/specs-actors/v4 v4.0.1 h1:AiWrtvJZ63MHGe6rn7tPu4nSUY8bA1KDNszqJaD5+Fg= github.com/filecoin-project/specs-actors/v4 v4.0.1/go.mod h1:TkHXf/l7Wyw4ZejyXIPS2rK8bBO0rdwhTZyQQgaglng= +github.com/filecoin-project/specs-actors/v5 v5.0.0-20210512015452-4fe3889fff57/go.mod h1:283yBMMUSDB2abcjP/hhrwTkhb9h3sfM6KGrep/ZlBI= +github.com/filecoin-project/specs-actors/v5 v5.0.0-20210528202914-a9f9f95f5e93 h1:PZ5pLy4dZVgL+fXgvSVtPOYhfEYUzEYYVEz7IfG8e5U= +github.com/filecoin-project/specs-actors/v5 v5.0.0-20210528202914-a9f9f95f5e93/go.mod h1:kSDmoQuO8jlhMVzKNoesbhka1e6gHKcLQjKm9mE9Qhw= github.com/filecoin-project/specs-storage v0.1.1-0.20201105051918-5188d9774506 h1:Ur/l2+6qN+lQiqjozWWc5p9UDaAMDZKTlDS98oRnlIw= github.com/filecoin-project/specs-storage v0.1.1-0.20201105051918-5188d9774506/go.mod h1:nJRRM7Aa9XVvygr3W9k6xGF46RWzr2zxF/iGoAIfA/g= github.com/filecoin-project/test-vectors/schema v0.0.5/go.mod h1:iQ9QXLpYWL3m7warwvK1JC/pTri8mnfEmKygNDqqY6E= From 40cc29d723dabc1446805fe48323bee37d4a2e86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Wed, 2 Jun 2021 19:50:17 +0200 Subject: [PATCH 87/88] Skip FD check in TestDownloadParams --- extern/sector-storage/ffiwrapper/sealer_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/sector-storage/ffiwrapper/sealer_test.go b/extern/sector-storage/ffiwrapper/sealer_test.go index c12958d04..5d96f187f 100644 --- a/extern/sector-storage/ffiwrapper/sealer_test.go +++ b/extern/sector-storage/ffiwrapper/sealer_test.go @@ -252,7 +252,7 @@ func getGrothParamFileAndVerifyingKeys(s abi.SectorSize) { // go test -run=^TestDownloadParams // func TestDownloadParams(t *testing.T) { - defer requireFDsClosed(t, openFDs(t)) + // defer requireFDsClosed(t, openFDs(t)) flaky likely cause of how go-embed works with param files getGrothParamFileAndVerifyingKeys(sectorSize) } From cd4505dd63b0268a037427f3d6b7bcd00674cd47 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Wed, 2 Jun 2021 16:23:18 -0400 Subject: [PATCH 88/88] Update to specs-actors v5-rc-2 --- go.mod | 2 +- go.sum | 10 ++++++---- testplans/lotus-soup/go.mod | 1 + testplans/lotus-soup/go.sum | 14 ++++++-------- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index 2583f299e..21421345c 100644 --- a/go.mod +++ b/go.mod @@ -48,7 +48,7 @@ require ( github.com/filecoin-project/specs-actors/v2 v2.3.5 github.com/filecoin-project/specs-actors/v3 v3.1.1 github.com/filecoin-project/specs-actors/v4 v4.0.1 - github.com/filecoin-project/specs-actors/v5 v5.0.0-20210528202914-a9f9f95f5e93 + github.com/filecoin-project/specs-actors/v5 v5.0.0-20210602024058-0c296bb386bf 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 diff --git a/go.sum b/go.sum index b61212a6c..8510e0363 100644 --- a/go.sum +++ b/go.sum @@ -254,8 +254,9 @@ github.com/filecoin-project/go-address v0.0.5/go.mod h1:jr8JxKsYx+lQlQZmF5i2U0Z+ github.com/filecoin-project/go-amt-ipld/v2 v2.1.0/go.mod h1:nfFPoGyX0CU9SkXX8EoCcSuHN1XcbN0c6KBh7yvP5fs= github.com/filecoin-project/go-amt-ipld/v2 v2.1.1-0.20201006184820-924ee87a1349 h1:pIuR0dnMD0i+as8wNnjjHyQrnhP5O5bmba/lmgQeRgU= github.com/filecoin-project/go-amt-ipld/v2 v2.1.1-0.20201006184820-924ee87a1349/go.mod h1:vgmwKBkx+ca5OIeEvstiQgzAZnb7R6QaqE1oEDSqa6g= -github.com/filecoin-project/go-amt-ipld/v3 v3.0.0 h1:Ou/q82QeHGOhpkedvaxxzpBYuqTxLCcj5OChkDNx4qc= github.com/filecoin-project/go-amt-ipld/v3 v3.0.0/go.mod h1:Qa95YNAbtoVCTSVtX38aAC1ptBnJfPma1R/zZsKmx4o= +github.com/filecoin-project/go-amt-ipld/v3 v3.1.0 h1:ZNJ9tEG5bE72vBWYiuh5bkxJVM3ViHNOmQ7qew9n6RE= +github.com/filecoin-project/go-amt-ipld/v3 v3.1.0/go.mod h1:UjM2QhDFrrjD5s1CdnkJkat4ga+LqZBZgTMniypABRo= github.com/filecoin-project/go-bitfield v0.2.0/go.mod h1:CNl9WG8hgR5mttCnUErjcQjGvuiZjRqK9rHVBsQF4oM= github.com/filecoin-project/go-bitfield v0.2.3/go.mod h1:CNl9WG8hgR5mttCnUErjcQjGvuiZjRqK9rHVBsQF4oM= github.com/filecoin-project/go-bitfield v0.2.4 h1:uZ7MeE+XfM5lqrHJZ93OnhQKc/rveW8p9au0C68JPgk= @@ -282,8 +283,9 @@ github.com/filecoin-project/go-hamt-ipld v0.1.5 h1:uoXrKbCQZ49OHpsTCkrThPNelC4W3 github.com/filecoin-project/go-hamt-ipld v0.1.5/go.mod h1:6Is+ONR5Cd5R6XZoCse1CWaXZc0Hdb/JeX+EQCQzX24= github.com/filecoin-project/go-hamt-ipld/v2 v2.0.0 h1:b3UDemBYN2HNfk3KOXNuxgTTxlWi3xVvbQP0IT38fvM= github.com/filecoin-project/go-hamt-ipld/v2 v2.0.0/go.mod h1:7aWZdaQ1b16BVoQUYR+eEvrDCGJoPLxFpDynFjYfBjI= -github.com/filecoin-project/go-hamt-ipld/v3 v3.0.1 h1:zbzs46G7bOctkZ+JUX3xirrj0RaEsi+27dtlsgrTNBg= github.com/filecoin-project/go-hamt-ipld/v3 v3.0.1/go.mod h1:gXpNmr3oQx8l3o7qkGyDjJjYSRX7hp/FGOStdqrWyDI= +github.com/filecoin-project/go-hamt-ipld/v3 v3.1.0 h1:rVVNq0x6RGQIzCo1iiJlGFm9AGIZzeifggxtKMU7zmI= +github.com/filecoin-project/go-hamt-ipld/v3 v3.1.0/go.mod h1:bxmzgT8tmeVQA1/gvBwFmYdT8SOFUwB3ovSUfG1Ux0g= github.com/filecoin-project/go-jsonrpc v0.1.4-0.20210217175800-45ea43ac2bec h1:rGI5I7fdU4viManxmDdbk5deZO7afe6L1Wc04dAmlOM= github.com/filecoin-project/go-jsonrpc v0.1.4-0.20210217175800-45ea43ac2bec/go.mod h1:XBBpuKIMaXIIzeqzO1iucq4GvbF8CxmXRFoezRh+Cx4= github.com/filecoin-project/go-multistore v0.0.3 h1:vaRBY4YiA2UZFPK57RNuewypB8u0DzzQwqsL0XarpnI= @@ -323,8 +325,8 @@ github.com/filecoin-project/specs-actors/v4 v4.0.0/go.mod h1:TkHXf/l7Wyw4ZejyXIP github.com/filecoin-project/specs-actors/v4 v4.0.1 h1:AiWrtvJZ63MHGe6rn7tPu4nSUY8bA1KDNszqJaD5+Fg= github.com/filecoin-project/specs-actors/v4 v4.0.1/go.mod h1:TkHXf/l7Wyw4ZejyXIPS2rK8bBO0rdwhTZyQQgaglng= github.com/filecoin-project/specs-actors/v5 v5.0.0-20210512015452-4fe3889fff57/go.mod h1:283yBMMUSDB2abcjP/hhrwTkhb9h3sfM6KGrep/ZlBI= -github.com/filecoin-project/specs-actors/v5 v5.0.0-20210528202914-a9f9f95f5e93 h1:PZ5pLy4dZVgL+fXgvSVtPOYhfEYUzEYYVEz7IfG8e5U= -github.com/filecoin-project/specs-actors/v5 v5.0.0-20210528202914-a9f9f95f5e93/go.mod h1:kSDmoQuO8jlhMVzKNoesbhka1e6gHKcLQjKm9mE9Qhw= +github.com/filecoin-project/specs-actors/v5 v5.0.0-20210602024058-0c296bb386bf h1:xt9A1omyhSDbQvpVk7Na1J15a/n8y0y4GQDLeiWLpFs= +github.com/filecoin-project/specs-actors/v5 v5.0.0-20210602024058-0c296bb386bf/go.mod h1:b/btpRl84Q9SeDKlyIoORBQwe2OTmq14POrYrVvBWCM= github.com/filecoin-project/specs-storage v0.1.1-0.20201105051918-5188d9774506 h1:Ur/l2+6qN+lQiqjozWWc5p9UDaAMDZKTlDS98oRnlIw= github.com/filecoin-project/specs-storage v0.1.1-0.20201105051918-5188d9774506/go.mod h1:nJRRM7Aa9XVvygr3W9k6xGF46RWzr2zxF/iGoAIfA/g= github.com/filecoin-project/test-vectors/schema v0.0.5 h1:w3zHQhzM4pYxJDl21avXjOKBLF8egrvwUwjpT8TquDg= diff --git a/testplans/lotus-soup/go.mod b/testplans/lotus-soup/go.mod index 6f84a598e..0c8e92a1b 100644 --- a/testplans/lotus-soup/go.mod +++ b/testplans/lotus-soup/go.mod @@ -15,6 +15,7 @@ require ( github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b github.com/filecoin-project/lotus v1.9.1-0.20210602131226-e1dc7ad6eb9e github.com/filecoin-project/specs-actors v0.9.14 + github.com/filecoin-project/specs-actors/v5 v5.0.0-20210602024058-0c296bb386bf // indirect github.com/google/uuid v1.1.2 github.com/gorilla/mux v1.7.4 github.com/hashicorp/go-multierror v1.1.0 diff --git a/testplans/lotus-soup/go.sum b/testplans/lotus-soup/go.sum index caaeffe78..926f625cf 100644 --- a/testplans/lotus-soup/go.sum +++ b/testplans/lotus-soup/go.sum @@ -263,8 +263,9 @@ github.com/filecoin-project/go-address v0.0.5/go.mod h1:jr8JxKsYx+lQlQZmF5i2U0Z+ github.com/filecoin-project/go-amt-ipld/v2 v2.1.0/go.mod h1:nfFPoGyX0CU9SkXX8EoCcSuHN1XcbN0c6KBh7yvP5fs= github.com/filecoin-project/go-amt-ipld/v2 v2.1.1-0.20201006184820-924ee87a1349 h1:pIuR0dnMD0i+as8wNnjjHyQrnhP5O5bmba/lmgQeRgU= github.com/filecoin-project/go-amt-ipld/v2 v2.1.1-0.20201006184820-924ee87a1349/go.mod h1:vgmwKBkx+ca5OIeEvstiQgzAZnb7R6QaqE1oEDSqa6g= -github.com/filecoin-project/go-amt-ipld/v3 v3.0.0 h1:Ou/q82QeHGOhpkedvaxxzpBYuqTxLCcj5OChkDNx4qc= github.com/filecoin-project/go-amt-ipld/v3 v3.0.0/go.mod h1:Qa95YNAbtoVCTSVtX38aAC1ptBnJfPma1R/zZsKmx4o= +github.com/filecoin-project/go-amt-ipld/v3 v3.1.0 h1:ZNJ9tEG5bE72vBWYiuh5bkxJVM3ViHNOmQ7qew9n6RE= +github.com/filecoin-project/go-amt-ipld/v3 v3.1.0/go.mod h1:UjM2QhDFrrjD5s1CdnkJkat4ga+LqZBZgTMniypABRo= github.com/filecoin-project/go-bitfield v0.2.0/go.mod h1:CNl9WG8hgR5mttCnUErjcQjGvuiZjRqK9rHVBsQF4oM= github.com/filecoin-project/go-bitfield v0.2.3/go.mod h1:CNl9WG8hgR5mttCnUErjcQjGvuiZjRqK9rHVBsQF4oM= github.com/filecoin-project/go-bitfield v0.2.4 h1:uZ7MeE+XfM5lqrHJZ93OnhQKc/rveW8p9au0C68JPgk= @@ -291,23 +292,21 @@ github.com/filecoin-project/go-hamt-ipld v0.1.5 h1:uoXrKbCQZ49OHpsTCkrThPNelC4W3 github.com/filecoin-project/go-hamt-ipld v0.1.5/go.mod h1:6Is+ONR5Cd5R6XZoCse1CWaXZc0Hdb/JeX+EQCQzX24= github.com/filecoin-project/go-hamt-ipld/v2 v2.0.0 h1:b3UDemBYN2HNfk3KOXNuxgTTxlWi3xVvbQP0IT38fvM= github.com/filecoin-project/go-hamt-ipld/v2 v2.0.0/go.mod h1:7aWZdaQ1b16BVoQUYR+eEvrDCGJoPLxFpDynFjYfBjI= -github.com/filecoin-project/go-hamt-ipld/v3 v3.0.1 h1:zbzs46G7bOctkZ+JUX3xirrj0RaEsi+27dtlsgrTNBg= github.com/filecoin-project/go-hamt-ipld/v3 v3.0.1/go.mod h1:gXpNmr3oQx8l3o7qkGyDjJjYSRX7hp/FGOStdqrWyDI= +github.com/filecoin-project/go-hamt-ipld/v3 v3.1.0 h1:rVVNq0x6RGQIzCo1iiJlGFm9AGIZzeifggxtKMU7zmI= +github.com/filecoin-project/go-hamt-ipld/v3 v3.1.0/go.mod h1:bxmzgT8tmeVQA1/gvBwFmYdT8SOFUwB3ovSUfG1Ux0g= github.com/filecoin-project/go-jsonrpc v0.1.4-0.20210217175800-45ea43ac2bec h1:rGI5I7fdU4viManxmDdbk5deZO7afe6L1Wc04dAmlOM= github.com/filecoin-project/go-jsonrpc v0.1.4-0.20210217175800-45ea43ac2bec/go.mod h1:XBBpuKIMaXIIzeqzO1iucq4GvbF8CxmXRFoezRh+Cx4= github.com/filecoin-project/go-multistore v0.0.3 h1:vaRBY4YiA2UZFPK57RNuewypB8u0DzzQwqsL0XarpnI= github.com/filecoin-project/go-multistore v0.0.3/go.mod h1:kaNqCC4IhU4B1uyr7YWFHd23TL4KM32aChS0jNkyUvQ= github.com/filecoin-project/go-padreader v0.0.0-20200903213702-ed5fae088b20 h1:+/4aUeUoKr6AKfPE3mBhXA5spIV6UcKdTYDPNU2Tdmg= github.com/filecoin-project/go-padreader v0.0.0-20200903213702-ed5fae088b20/go.mod h1:mPn+LRRd5gEKNAtc+r3ScpW2JRU/pj4NBKdADYWHiak= -github.com/filecoin-project/go-paramfetch v0.0.2-0.20200701152213-3e0f0afdc261 h1:A256QonvzRaknIIAuWhe/M2dpV2otzs3NBhi5TWa/UA= -github.com/filecoin-project/go-paramfetch v0.0.2-0.20200701152213-3e0f0afdc261/go.mod h1:fZzmf4tftbwf9S37XRifoJlz7nCjRdIrMGLR07dKLCc= github.com/filecoin-project/go-paramfetch v0.0.2-0.20210330140417-936748d3f5ec h1:gExwWUiT1TcARkxGneS4nvp9C+wBsKU0bFdg7qFpNco= github.com/filecoin-project/go-paramfetch v0.0.2-0.20210330140417-936748d3f5ec/go.mod h1:fZzmf4tftbwf9S37XRifoJlz7nCjRdIrMGLR07dKLCc= github.com/filecoin-project/go-state-types v0.0.0-20200903145444-247639ffa6ad/go.mod h1:IQ0MBPnonv35CJHtWSN3YY1Hz2gkPru1Q9qoaYLxx9I= github.com/filecoin-project/go-state-types v0.0.0-20200904021452-1883f36ca2f4/go.mod h1:IQ0MBPnonv35CJHtWSN3YY1Hz2gkPru1Q9qoaYLxx9I= github.com/filecoin-project/go-state-types v0.0.0-20200928172055-2df22083d8ab/go.mod h1:ezYnPf0bNkTsDibL/psSz5dy4B5awOJ/E7P2Saeep8g= github.com/filecoin-project/go-state-types v0.0.0-20201102161440-c8033295a1fc/go.mod h1:ezYnPf0bNkTsDibL/psSz5dy4B5awOJ/E7P2Saeep8g= -github.com/filecoin-project/go-state-types v0.1.0 h1:9r2HCSMMCmyMfGyMKxQtv0GKp6VT/m5GgVk8EhYbLJU= github.com/filecoin-project/go-state-types v0.1.0/go.mod h1:ezYnPf0bNkTsDibL/psSz5dy4B5awOJ/E7P2Saeep8g= github.com/filecoin-project/go-state-types v0.1.1-0.20210506134452-99b279731c48 h1:Jc4OprDp3bRDxbsrXNHPwJabZJM3iDy+ri8/1e0ZnX4= github.com/filecoin-project/go-state-types v0.1.1-0.20210506134452-99b279731c48/go.mod h1:ezYnPf0bNkTsDibL/psSz5dy4B5awOJ/E7P2Saeep8g= @@ -318,8 +317,6 @@ github.com/filecoin-project/go-statestore v0.1.1 h1:ufMFq00VqnT2CAuDpcGnwLnCX1I/ github.com/filecoin-project/go-statestore v0.1.1/go.mod h1:LFc9hD+fRxPqiHiaqUEZOinUJB4WARkRfNl10O7kTnI= github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b h1:fkRZSPrYpk42PV3/lIXiL0LHetxde7vyYYvSsttQtfg= github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b/go.mod h1:Q0GQOBtKf1oE10eSXSlhN45kDBdGvEcVOqMiffqX+N8= -github.com/filecoin-project/lotus v1.9.1-0.20210602101339-07b025a54f6d h1:gMkgi1SssdZWFpCHXcQvqcrsUJW+HHaO10w//HA9eyI= -github.com/filecoin-project/lotus v1.9.1-0.20210602101339-07b025a54f6d/go.mod h1:8YWF0BqH6g3O47qB5mI0Pk9zgC2uA6xUlKXYo5VScIk= github.com/filecoin-project/lotus v1.9.1-0.20210602131226-e1dc7ad6eb9e h1:JvtYGk30nM7K0TD4sTOUKYUePcSzZNj5ZD6g5vdrqMI= github.com/filecoin-project/lotus v1.9.1-0.20210602131226-e1dc7ad6eb9e/go.mod h1:/ZeMXR8jPxJslaHSIW3ZxO9YPIaxcnsP+niEoBatzo8= github.com/filecoin-project/specs-actors v0.9.4/go.mod h1:BStZQzx5x7TmCkLv0Bpa07U6cPKol6fd3w9KjMPZ6Z4= @@ -339,8 +336,9 @@ github.com/filecoin-project/specs-actors/v4 v4.0.0/go.mod h1:TkHXf/l7Wyw4ZejyXIP github.com/filecoin-project/specs-actors/v4 v4.0.1 h1:AiWrtvJZ63MHGe6rn7tPu4nSUY8bA1KDNszqJaD5+Fg= github.com/filecoin-project/specs-actors/v4 v4.0.1/go.mod h1:TkHXf/l7Wyw4ZejyXIPS2rK8bBO0rdwhTZyQQgaglng= github.com/filecoin-project/specs-actors/v5 v5.0.0-20210512015452-4fe3889fff57/go.mod h1:283yBMMUSDB2abcjP/hhrwTkhb9h3sfM6KGrep/ZlBI= -github.com/filecoin-project/specs-actors/v5 v5.0.0-20210528202914-a9f9f95f5e93 h1:PZ5pLy4dZVgL+fXgvSVtPOYhfEYUzEYYVEz7IfG8e5U= github.com/filecoin-project/specs-actors/v5 v5.0.0-20210528202914-a9f9f95f5e93/go.mod h1:kSDmoQuO8jlhMVzKNoesbhka1e6gHKcLQjKm9mE9Qhw= +github.com/filecoin-project/specs-actors/v5 v5.0.0-20210602024058-0c296bb386bf h1:xt9A1omyhSDbQvpVk7Na1J15a/n8y0y4GQDLeiWLpFs= +github.com/filecoin-project/specs-actors/v5 v5.0.0-20210602024058-0c296bb386bf/go.mod h1:b/btpRl84Q9SeDKlyIoORBQwe2OTmq14POrYrVvBWCM= github.com/filecoin-project/specs-storage v0.1.1-0.20201105051918-5188d9774506 h1:Ur/l2+6qN+lQiqjozWWc5p9UDaAMDZKTlDS98oRnlIw= github.com/filecoin-project/specs-storage v0.1.1-0.20201105051918-5188d9774506/go.mod h1:nJRRM7Aa9XVvygr3W9k6xGF46RWzr2zxF/iGoAIfA/g= github.com/filecoin-project/test-vectors/schema v0.0.5/go.mod h1:iQ9QXLpYWL3m7warwvK1JC/pTri8mnfEmKygNDqqY6E=